[FINERACT-1678] API for job business step configuration

This commit is contained in:
taskain7
2022-08-22 13:36:38 +02:00
committed by Arnold Gálovics
parent 7a2af51e6c
commit 675dda2b42
22 changed files with 1020 additions and 0 deletions
@@ -0,0 +1,122 @@
/**
* 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.cob.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.Arrays;
import java.util.HashSet;
import java.util.Set;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
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.cob.data.JobBusinessStepConfigData;
import org.apache.fineract.cob.data.JobBusinessStepDetail;
import org.apache.fineract.cob.service.ConfigJobParameterService;
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.springframework.stereotype.Component;
@Path("/jobs")
@Component
@Tag(name = "Business Step Configuration", description = "")
@RequiredArgsConstructor
public class ConfigureBusinessStepResource {
private static final Set<String> BUSINESS_STEP_CONFIG_RESPONSE_DATA_PARAMETERS = new HashSet<>(
Arrays.asList("jobName", "businessSteps"));
private static final Set<String> BUSINESS_STEP_DETAIL_RESPONSE_DATA_PARAMETERS = new HashSet<>(
Arrays.asList("jobName", "availableBusinessSteps"));
private final DefaultToApiJsonSerializer<JobBusinessStepConfigData> businessStepConfigSerializeService;
private final DefaultToApiJsonSerializer<JobBusinessStepDetail> businessStepDetailSerializeService;
private final ApiRequestParameterHelper apiRequestParameterHelper;
private final ConfigJobParameterService configJobParameterService;
private final PortfolioCommandSourceWritePlatformService commandWritePlatformService;
@GET
@Path("{jobName}/steps")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "List Business Step Configurations for a Job", description = "Returns the configured Business Steps for a job")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = ConfigureBusinessStepResourceSwagger.GetBusinessStepConfigResponse.class)))) })
public String retrieveAllConfiguredBusinessStep(@Context final UriInfo uriInfo,
@PathParam("jobName") @Parameter(description = "jobName") final String jobName) {
JobBusinessStepConfigData jobBusinessStepConfigData = configJobParameterService.getBusinessStepConfigByJobName(jobName);
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
return businessStepConfigSerializeService.serialize(settings, jobBusinessStepConfigData,
BUSINESS_STEP_CONFIG_RESPONSE_DATA_PARAMETERS);
}
@PUT
@Path("{jobName}/steps")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "List Business Step Configurations for a Job", description = "Updates the Business steps execution order for a job")
@RequestBody(content = @Content(schema = @Schema(implementation = ConfigureBusinessStepResourceSwagger.UpdateBusinessStepConfigRequest.class)))
@ApiResponses({
@ApiResponse(responseCode = "204", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = ConfigureBusinessStepResourceSwagger.GetBusinessStepConfigResponse.class)))) })
public String updateJobBusinessStepConfig(@PathParam("jobName") @Parameter(description = "jobName") final String jobName,
@Parameter(hidden = true) final String jsonRequestBody) {
final CommandWrapper commandRequest = new CommandWrapperBuilder().updateBusinessStepConfig(jobName).withJson(jsonRequestBody)
.build();
CommandProcessingResult result = commandWritePlatformService.logCommandSource(commandRequest);
return businessStepConfigSerializeService.serialize(result);
}
@GET
@Path("{jobName}/available-steps")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "List Business Step Configurations for a Job", description = "Returns the available Business Steps for a job")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = ConfigureBusinessStepResourceSwagger.GetBusinessStepConfigResponse.class)))) })
public String retrieveAllAvailableBusinessStep(@Context final UriInfo uriInfo,
@PathParam("jobName") @Parameter(description = "jobName") final String jobName) {
JobBusinessStepDetail availableBusinessStepsByJobName = configJobParameterService.getAvailableBusinessStepsByJobName(jobName);
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
return businessStepDetailSerializeService.serialize(settings, availableBusinessStepsByJobName,
BUSINESS_STEP_DETAIL_RESPONSE_DATA_PARAMETERS);
}
}
@@ -0,0 +1,61 @@
/**
* 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.cob.api;
import io.swagger.v3.oas.annotations.media.Schema;
import java.util.List;
final class ConfigureBusinessStepResourceSwagger {
private ConfigureBusinessStepResourceSwagger() {
}
@Schema(description = "GetBusinessStepConfigResponse")
public static final class GetBusinessStepConfigResponse {
private GetBusinessStepConfigResponse() {}
@Schema(example = "CLOSE_OF_BUSINESS")
public String jobName;
public List<BusinessStep> businessSteps;
}
@Schema(description = "BusinessStep")
public static final class BusinessStep {
private BusinessStep() {}
@Schema(example = "APPLY_PENALTY_FOR_OVERDUE_LOANS")
public String stepName;
@Schema(example = "1")
public Long order;
}
@Schema(description = "UpdateBusinessStepConfigRequest")
public static final class UpdateBusinessStepConfigRequest {
private UpdateBusinessStepConfigRequest() {}
public List<BusinessStep> businessSteps;
}
}
@@ -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.cob.data;
import lombok.Data;
@Data
public class BusinessStep {
private String stepName;
private Long order;
}
@@ -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.cob.data;
import lombok.Data;
@Data
public class BusinessStepDetail {
private String stepName;
private String stepDescription;
}
@@ -0,0 +1,29 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.cob.data;
import java.util.List;
import lombok.Data;
@Data
public class JobBusinessStepConfigData {
private String jobName;
private List<BusinessStep> businessSteps;
}
@@ -0,0 +1,29 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.cob.data;
import java.util.List;
import lombok.Data;
@Data
public class JobBusinessStepDetail {
private String jobName;
private List<BusinessStepDetail> availableBusinessSteps;
}
@@ -23,6 +23,7 @@ import javax.persistence.Entity;
import javax.persistence.Table;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
@Entity
@@ -34,9 +35,11 @@ public class BatchBusinessStep extends AbstractPersistableCustom {
@Column(name = "job_name", nullable = false)
private String jobName;
@Setter
@Column(name = "step_name", nullable = false)
private String stepName;
@Setter
@Column(name = "step_order", nullable = false)
private Long stepOrder;
@@ -0,0 +1,21 @@
/**
* 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.cob.exceptions;
public class BusinessStepNotBelongsToJobException extends RuntimeException {}
@@ -0,0 +1,44 @@
/**
* 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.cob.service;
import java.util.Arrays;
import java.util.Optional;
public enum BusinessStepCategory {
LOAN("LOAN");
private final String name;
BusinessStepCategory(String name) {
this.name = name;
}
public static BusinessStepCategory getCategoryName(String categoryName) {
Optional<BusinessStepCategory> optionalCategory = Arrays.stream(BusinessStepCategory.values())
.filter(jn -> categoryName.equals(jn.name)).findAny();
return optionalCategory.orElseThrow(() -> new IllegalArgumentException("Category not found by name: " + categoryName));
}
@Override
public String toString() {
return this.name;
}
}
@@ -0,0 +1,26 @@
/**
* 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.cob.service;
import org.apache.fineract.cob.COBBusinessStep;
public interface BusinessStepCategoryService {
Class<? extends COBBusinessStep> getBusinessStepByCategory(String category);
}
@@ -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.cob.service;
import java.util.Map;
import org.apache.fineract.cob.COBBusinessStep;
import org.apache.fineract.cob.loan.LoanCOBBusinessStep;
import org.springframework.stereotype.Service;
@Service
public class BusinessStepCategoryServiceImpl implements BusinessStepCategoryService {
private final Map<BusinessStepCategory, Class<? extends COBBusinessStep>> businessSteps = Map.of(BusinessStepCategory.LOAN,
LoanCOBBusinessStep.class);
@Override
public Class<? extends COBBusinessStep> getBusinessStepByCategory(String category) {
Map.Entry<BusinessStepCategory, Class<? extends COBBusinessStep>> businessStepCategoryClassEntry = businessSteps.entrySet().stream()
.filter(businessStep -> businessStep.getKey().name().equals(category.toUpperCase())).findFirst().orElse(null);
return businessStepCategoryClassEntry != null ? businessStepCategoryClassEntry.getValue() : null;
}
}
@@ -0,0 +1,64 @@
/**
* 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.cob.service;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import java.util.ArrayList;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.cob.data.BusinessStep;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
import org.springframework.stereotype.Component;
@Component
@RequiredArgsConstructor
public class BusinessStepConfigDataParser {
private final FromJsonHelper jsonHelper;
public List<BusinessStep> parseUpdate(JsonCommand command) {
JsonObject element = extractJsonObject(command);
List<BusinessStep> businessSteps = new ArrayList<>();
JsonArray jsonArray = jsonHelper.extractJsonArrayNamed("businessSteps", element);
for (final JsonElement businessStepConfig : jsonArray) {
final String stepName = jsonHelper.extractStringNamed("stepName", businessStepConfig);
final Long order = jsonHelper.extractLongNamed("order", businessStepConfig);
BusinessStep businessStep = new BusinessStep();
businessStep.setStepName(stepName);
businessStep.setOrder(order);
businessSteps.add(businessStep);
}
return businessSteps;
}
private JsonObject extractJsonObject(JsonCommand command) {
String json = command.json();
if (StringUtils.isBlank(json)) {
throw new InvalidJsonException();
}
final JsonElement element = jsonHelper.parse(json);
return element.getAsJsonObject();
}
}
@@ -0,0 +1,48 @@
/**
* 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.cob.service;
import com.google.common.base.Splitter;
import java.util.List;
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.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@RequiredArgsConstructor
@Service
@CommandType(entity = "BATCH_BUSINESS_STEP", action = "UPDATE")
public class BusinessStepConfigUpdateHandler implements NewCommandSourceHandler {
private final ConfigJobParameterService configJobParameterService;
@Override
@Transactional
public CommandProcessingResult processCommand(JsonCommand command) {
List<String> split = Splitter.on("/").splitToList(command.getUrl());
String jobName = split.get(split.size() - 2);
if ("null".equals(jobName)) {
jobName = null;
}
return configJobParameterService.updateStepConfigByJobName(command, jobName);
}
}
@@ -0,0 +1,35 @@
/**
* 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.cob.service;
import java.util.List;
import org.apache.fineract.cob.data.BusinessStep;
import org.apache.fineract.cob.domain.BatchBusinessStep;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
@Mapper(componentModel = "spring")
public interface BusinessStepMapper {
@Mappings({ @Mapping(target = "order", source = "source.stepOrder") })
BusinessStep map(BatchBusinessStep source);
List<BusinessStep> map(List<BatchBusinessStep> source);
}
@@ -0,0 +1,33 @@
/**
* 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.cob.service;
import org.apache.fineract.cob.data.JobBusinessStepConfigData;
import org.apache.fineract.cob.data.JobBusinessStepDetail;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
public interface ConfigJobParameterService {
JobBusinessStepConfigData getBusinessStepConfigByJobName(String jobName);
CommandProcessingResult updateStepConfigByJobName(JsonCommand command, String jobName);
JobBusinessStepDetail getAvailableBusinessStepsByJobName(String jobName);
}
@@ -0,0 +1,94 @@
/**
* 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.cob.service;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.cob.COBBusinessStep;
import org.apache.fineract.cob.data.BusinessStep;
import org.apache.fineract.cob.data.BusinessStepDetail;
import org.apache.fineract.cob.data.JobBusinessStepConfigData;
import org.apache.fineract.cob.data.JobBusinessStepDetail;
import org.apache.fineract.cob.domain.BatchBusinessStep;
import org.apache.fineract.cob.domain.BatchBusinessStepRepository;
import org.apache.fineract.cob.exceptions.BusinessStepNotBelongsToJobException;
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.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class ConfigJobParameterServiceImpl implements ConfigJobParameterService {
private final BatchBusinessStepRepository batchBusinessStepRepository;
private final BusinessStepConfigDataParser dataParser;
private final BusinessStepCategoryService businessStepCategoryService;
private final ApplicationContext applicationContext;
private final BusinessStepMapper mapper;
@Override
public JobBusinessStepConfigData getBusinessStepConfigByJobName(String jobName) {
List<BatchBusinessStep> batchBusinessSteps = batchBusinessStepRepository.findAllByJobName(jobName);
JobBusinessStepConfigData jobBusinessStepConfigData = new JobBusinessStepConfigData();
jobBusinessStepConfigData.setJobName(jobName);
jobBusinessStepConfigData.setBusinessSteps(mapper.map(batchBusinessSteps));
return jobBusinessStepConfigData;
}
@Override
public CommandProcessingResult updateStepConfigByJobName(JsonCommand command, String jobName)
throws BusinessStepNotBelongsToJobException {
List<BusinessStep> businessSteps = dataParser.parseUpdate(command);
List<BatchBusinessStep> batchBusinessSteps = batchBusinessStepRepository.findAllByJobName(jobName);
businessSteps.forEach(newBusinessStepConfig -> {
BatchBusinessStep batchBusinessStep = batchBusinessSteps.stream()
.filter(oldBusinessStepConfig -> oldBusinessStepConfig.getStepName().equals(newBusinessStepConfig.getStepName()))
.findFirst().orElseThrow(BusinessStepNotBelongsToJobException::new);
batchBusinessStep.setStepName(newBusinessStepConfig.getStepName());
batchBusinessStep.setStepOrder(newBusinessStepConfig.getOrder());
batchBusinessStepRepository.save(batchBusinessStep);
});
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).build();
}
@Override
public JobBusinessStepDetail getAvailableBusinessStepsByJobName(String jobName) {
Class<? extends COBBusinessStep> businessStepClass = businessStepCategoryService.getBusinessStepByCategory(jobName);
if (businessStepClass == null) {
return null;
}
List<String> businessStepBeanNames = Arrays.stream(applicationContext.getBeanNamesForType(businessStepClass)).toList();
JobBusinessStepDetail jobBusinessStepDetail = new JobBusinessStepDetail();
List<BusinessStepDetail> availableBusinessSteps = new ArrayList<>();
for (String businessStepBean : businessStepBeanNames) {
COBBusinessStep businessStep = (COBBusinessStep) applicationContext.getBean(businessStepBean);
BusinessStepDetail businessStepDetail = new BusinessStepDetail();
businessStepDetail.setStepName(businessStep.getEnumStyledName());
businessStepDetail.setStepDescription(businessStep.getHumanReadableName());
availableBusinessSteps.add(businessStepDetail);
}
jobBusinessStepDetail.setJobName(jobName);
jobBusinessStepDetail.setAvailableBusinessSteps(availableBusinessSteps);
return jobBusinessStepDetail;
}
}
@@ -3549,4 +3549,11 @@ public class CommandWrapperBuilder {
return this;
}
public CommandWrapperBuilder updateBusinessStepConfig(String jobName) {
this.actionName = "UPDATE";
this.entityName = "BATCH_BUSINESS_STEP";
this.href = "/jobs/" + jobName + "/steps";
return this;
}
}
@@ -60,4 +60,5 @@
<include file="parts/0038_add_reversal_data_to_loan_transaction.xml" relativeToChangelogFile="true"/>
<include file="parts/0039_add_loan_account_locks.xml" relativeToChangelogFile="true"/>
<include file="parts/0040_add_delinquency_tags_job.xml" relativeToChangelogFile="true"/>
<include file="parts/0041_add_update_business_step_permission.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>
@@ -0,0 +1,32 @@
<?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">
<insert tableName="m_permission">
<column name="grouping" value="organisation"/>
<column name="code" value="UPDATE_BATCH_BUSINESS_STEP"/>
<column name="entity_name" value="BATCH_BUSINESS_STEP"/>
<column name="action_name" value="UPDATE"/>
<column name="can_maker_checker" valueBoolean="false"/>
</insert>
</changeSet>
</databaseChangeLog>
@@ -0,0 +1,80 @@
/**
* 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.cob.service;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;
@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
class BusinessStepConfigUpdateHandlerTest {
@InjectMocks
private BusinessStepConfigUpdateHandler testObj;
@Mock
private ConfigJobParameterService configJobParameterService;
@Captor
private ArgumentCaptor<String> argumentCaptor;
@Test
void shouldProcessCommandGetsJobName() {
JsonCommand command = mock(JsonCommand.class);
given(command.getUrl()).willReturn("/jobs/jobName/steps");
testObj.processCommand(command);
verify(configJobParameterService).updateStepConfigByJobName(eq(command), argumentCaptor.capture());
assertEquals("jobName", argumentCaptor.getValue());
}
@Test
void shouldProcessCommandGetsJobNameEmptyString() {
JsonCommand command = mock(JsonCommand.class);
given(command.getUrl()).willReturn("/jobs//steps");
testObj.processCommand(command);
verify(configJobParameterService).updateStepConfigByJobName(eq(command), argumentCaptor.capture());
assertEquals("", argumentCaptor.getValue());
}
@Test
void shouldProcessCommandGetsJobNameNull() {
JsonCommand command = mock(JsonCommand.class);
given(command.getUrl()).willReturn("/jobs/" + null + "/steps");
testObj.processCommand(command);
verify(configJobParameterService).updateStepConfigByJobName(eq(command), argumentCaptor.capture());
assertEquals(null, argumentCaptor.getValue());
}
}
@@ -0,0 +1,112 @@
/**
* 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.assertTrue;
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 java.util.List;
import org.apache.fineract.cob.data.BusinessStep;
import org.apache.fineract.cob.data.JobBusinessStepConfigData;
import org.apache.fineract.cob.data.JobBusinessStepDetail;
import org.apache.fineract.integrationtests.common.BusinessStepConfigurationHelper;
import org.apache.fineract.integrationtests.common.Utils;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
public class BusinessConfigurationApiTest {
private ResponseSpecification responseSpec;
private RequestSpecification requestSpec;
private static final String LOAN_JOB_NAME = "LOAN_CLOSE_OF_BUSINESS";
private static final String LOAN_CATEGORY_NAME = "loan";
private static final String APPLY_CHARGE_TO_OVERDUE_LOANS = "APPLY_CHARGE_TO_OVERDUE_LOANS";
@BeforeEach
public void setup() {
Utils.initializeRESTAssured();
this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
}
@Test
public void shouldReturnApplyChargeToOverdueLoanStepConfig() {
JobBusinessStepConfigData response = BusinessStepConfigurationHelper.getConfiguredBusinessStepsByJobName(requestSpec, responseSpec,
LOAN_JOB_NAME);
Assertions.assertNotNull(response);
assertEquals(LOAN_JOB_NAME, response.getJobName());
assertTrue(response.getBusinessSteps().size() > 0);
assertTrue(response.getBusinessSteps().stream()
.anyMatch(businessStep -> APPLY_CHARGE_TO_OVERDUE_LOANS.equals(businessStep.getStepName())));
}
@Test
public void shouldReturnApplyChargeToOverdueLoanStepConfigByJobCategory() {
JobBusinessStepDetail response = BusinessStepConfigurationHelper.getAvailableBusinessStepsByJobName(requestSpec, responseSpec,
LOAN_CATEGORY_NAME);
Assertions.assertNotNull(response);
assertEquals(LOAN_CATEGORY_NAME, response.getJobName());
assertTrue(response.getAvailableBusinessSteps().size() > 0);
assertTrue(response.getAvailableBusinessSteps().stream()
.anyMatch(businessStep -> APPLY_CHARGE_TO_OVERDUE_LOANS.equals(businessStep.getStepName())));
}
@Test
public void shouldUpdateStepOrder() {
JobBusinessStepConfigData originalStepConfig = BusinessStepConfigurationHelper.getConfiguredBusinessStepsByJobName(requestSpec,
responseSpec, LOAN_JOB_NAME);
Long originalOrder = originalStepConfig.getBusinessSteps().stream() //
.filter(businessStep -> APPLY_CHARGE_TO_OVERDUE_LOANS.equals(businessStep.getStepName())) //
.findFirst().get().getOrder();
List<BusinessStep> requestBody = getBusinessSteps(originalOrder + 1);
BusinessStepConfigurationHelper.updateBusinessStepOrder(requestSpec, responseSpec, LOAN_JOB_NAME,
BusinessStepConfigurationHelper.toJsonString(requestBody));
JobBusinessStepConfigData newStepConfig = BusinessStepConfigurationHelper.getConfiguredBusinessStepsByJobName(requestSpec,
responseSpec, LOAN_JOB_NAME);
Long newOrder = newStepConfig.getBusinessSteps().stream() //
.filter(businessStep -> APPLY_CHARGE_TO_OVERDUE_LOANS.equals(businessStep.getStepName())) //
.findFirst().get().getOrder();
assertEquals(originalOrder + 1, newOrder);
List<BusinessStep> originalBusinessSteps = getBusinessSteps(originalOrder);
BusinessStepConfigurationHelper.updateBusinessStepOrder(requestSpec, responseSpec, LOAN_JOB_NAME,
BusinessStepConfigurationHelper.toJsonString(originalBusinessSteps));
}
private List<BusinessStep> getBusinessSteps(Long order) {
List<BusinessStep> businessSteps = new ArrayList<>();
BusinessStep businessStep = new BusinessStep();
businessStep.setStepName(APPLY_CHARGE_TO_OVERDUE_LOANS);
businessStep.setOrder(order);
businessSteps.add(businessStep);
return businessSteps;
}
}
@@ -0,0 +1,85 @@
/**
* 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;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import io.restassured.specification.RequestSpecification;
import io.restassured.specification.ResponseSpecification;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.cob.data.BusinessStep;
import org.apache.fineract.cob.data.JobBusinessStepConfigData;
import org.apache.fineract.cob.data.JobBusinessStepDetail;
@Slf4j
public final class BusinessStepConfigurationHelper {
private static final String BUSINESS_STEPS_API_URL_START = "/fineract-provider/api/v1/jobs/";
private static final String BUSINESS_STEPS_API_URL_END = "/steps?" + Utils.TENANT_IDENTIFIER;
private static final String GET_AVAILABLE_BUSINESS_STEPS_API_URL_END = "/available-steps?" + Utils.TENANT_IDENTIFIER;
private BusinessStepConfigurationHelper() {
}
public static String toJsonString(final List<BusinessStep> batchRequests) {
return new Gson().toJson(new BusinessStepWrapper(batchRequests));
}
private static JobBusinessStepConfigData configuredBusinessStepFromJsonString(final String json) {
return new Gson().fromJson(json, new TypeToken<JobBusinessStepConfigData>() {}.getType());
}
private static JobBusinessStepDetail availableBusinessStepFromJsonString(final String json) {
return new Gson().fromJson(json, new TypeToken<JobBusinessStepDetail>() {}.getType());
}
public static JobBusinessStepConfigData getConfiguredBusinessStepsByJobName(final RequestSpecification requestSpec,
final ResponseSpecification responseSpec, String jobName) {
final String response = Utils.performServerGet(requestSpec, responseSpec,
BUSINESS_STEPS_API_URL_START + jobName + BUSINESS_STEPS_API_URL_END);
log.info("BusinessStepConfigurationHelper Response: {}", response);
return BusinessStepConfigurationHelper.configuredBusinessStepFromJsonString(response);
}
public static JobBusinessStepDetail getAvailableBusinessStepsByJobName(final RequestSpecification requestSpec,
final ResponseSpecification responseSpec, String jobName) {
final String response = Utils.performServerGet(requestSpec, responseSpec,
BUSINESS_STEPS_API_URL_START + jobName + GET_AVAILABLE_BUSINESS_STEPS_API_URL_END);
log.info("BusinessStepConfigurationHelper Response: {}", response);
return BusinessStepConfigurationHelper.availableBusinessStepFromJsonString(response);
}
public static void updateBusinessStepOrder(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
String jobName, String jsonBodyToSend) {
String response = Utils.performServerPut(requestSpec, responseSpec,
BUSINESS_STEPS_API_URL_START + jobName + BUSINESS_STEPS_API_URL_END, jsonBodyToSend);
log.info("BusinessStepConfigurationHelper Response: {}", response);
}
private static final class BusinessStepWrapper {
private List<BusinessStep> businessSteps;
private BusinessStepWrapper(List<BusinessStep> businessSteps) {
this.businessSteps = businessSteps;
}
}
}