diff --git a/persistence-service-v1/persistence-service-external-api-impl-v2/src/main/java/com/iqser/red/persistence/service/v2/external/api/impl/controller/DossierTemplateControllerV2.java b/persistence-service-v1/persistence-service-external-api-impl-v2/src/main/java/com/iqser/red/persistence/service/v2/external/api/impl/controller/DossierTemplateControllerV2.java index d7cd0d692..a2f50cb55 100644 --- a/persistence-service-v1/persistence-service-external-api-impl-v2/src/main/java/com/iqser/red/persistence/service/v2/external/api/impl/controller/DossierTemplateControllerV2.java +++ b/persistence-service-v1/persistence-service-external-api-impl-v2/src/main/java/com/iqser/red/persistence/service/v2/external/api/impl/controller/DossierTemplateControllerV2.java @@ -13,8 +13,10 @@ import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Collections; import java.util.List; import java.util.Locale; +import java.util.Map; import org.springframework.core.io.InputStreamResource; import org.springframework.http.HttpHeaders; @@ -33,14 +35,16 @@ import com.iqser.red.persistence.service.v1.external.api.impl.controller.Dossier import com.iqser.red.persistence.service.v1.external.api.impl.controller.FileAttributesController; import com.iqser.red.persistence.service.v2.external.api.impl.mapper.ComponentMappingMapper; import com.iqser.red.service.persistence.management.v1.processor.entity.configuration.RuleSetEntity; +import com.iqser.red.service.persistence.management.v1.processor.entity.dossier.ComponentDefinitionEntity; import com.iqser.red.service.persistence.management.v1.processor.exception.BadRequestException; import com.iqser.red.service.persistence.management.v1.processor.model.ComponentMappingDownloadModel; +import com.iqser.red.service.persistence.management.v1.processor.service.ComponentDefinitionService; import com.iqser.red.service.persistence.management.v1.processor.service.ComponentMappingService; import com.iqser.red.service.persistence.management.v1.processor.service.RulesValidationService; import com.iqser.red.service.persistence.management.v1.processor.service.persistence.AuditPersistenceService; -import com.iqser.red.service.persistence.management.v1.processor.service.persistence.DossierTemplatePersistenceService; import com.iqser.red.service.persistence.management.v1.processor.service.persistence.DossierAttributeConfigPersistenceService; import com.iqser.red.service.persistence.management.v1.processor.service.persistence.DossierStatusPersistenceService; +import com.iqser.red.service.persistence.management.v1.processor.service.persistence.DossierTemplatePersistenceService; import com.iqser.red.service.persistence.management.v1.processor.service.persistence.RulesPersistenceService; import com.iqser.red.service.persistence.management.v1.processor.utils.RulesValidationMapper; import com.iqser.red.service.persistence.management.v1.processor.utils.StringEncodingUtils; @@ -48,6 +52,9 @@ import com.iqser.red.service.persistence.service.v1.api.shared.model.AuditCatego import com.iqser.red.service.persistence.service.v1.api.shared.model.DossierTemplateModel; import com.iqser.red.service.persistence.service.v1.api.shared.model.RuleFileType; import com.iqser.red.service.persistence.service.v1.api.shared.model.audit.AuditRequest; +import com.iqser.red.service.persistence.service.v1.api.shared.model.component.ComponentDefinition; +import com.iqser.red.service.persistence.service.v1.api.shared.model.component.ComponentDefinitionAddRequest; +import com.iqser.red.service.persistence.service.v1.api.shared.model.component.ComponentDefinitionUpdateRequest; import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.rules.DroolsValidationResponse; import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.rules.RulesUploadRequest; import com.iqser.red.service.persistence.service.v2.api.external.model.ComponentMappingMetadataModel; @@ -88,6 +95,7 @@ public class DossierTemplateControllerV2 implements DossierTemplateResource { DossierTemplatePersistenceService dossierTemplatePersistenceService; DossierStatusPersistenceService dossierStatusPersistenceService; DossierAttributeConfigPersistenceService dossierAttributeConfigPersistenceService; + ComponentDefinitionService componentDefinitionService; public List getAllDossierTemplates() { @@ -181,100 +189,6 @@ public class DossierTemplateControllerV2 implements DossierTemplateResource { } - @PreAuthorize("hasAuthority('" + READ_DOSSIER_STATUS + "')") - public DossierStatusDefinitionList getDossierStatusDefinitions(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId) { - - getDossierTemplate(dossierTemplateId); - - return new DossierStatusDefinitionList(dossierStatusPersistenceService.getAllDossierStatusForTemplate(dossierTemplateId) - .stream() - .map(dossierStatusInfo -> DossierStatusDefinition.builder() - .id(dossierStatusInfo.getId()) - .name(dossierStatusInfo.getName()) - .description(dossierStatusInfo.getDescription()) - .rank(dossierStatusInfo.getRank()) - .color(dossierStatusInfo.getColor()) - .dossierCount(dossierStatusInfo.getDossierCount() != null ? dossierStatusInfo.getDossierCount() : 0) - .build()) - .toList()); - } - - - @PreAuthorize("hasAuthority('" + READ_DOSSIER_ATTRIBUTES_CONFIG + "')") - public DossierAttributeDefinitionList getDossierAttributeDefinitions(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId) { - - getDossierTemplate(dossierTemplateId); - - return new DossierAttributeDefinitionList(dossierAttributeConfigPersistenceService.getDossierAttributes(dossierTemplateId) - .stream() - .map(config -> DossierAttributeDefinition.builder() - - .id(config.getId()) - .name(config.getLabel()) - .type(config.getType()) - .reportingPlaceholder(config.getPlaceholder()) - .build()) - .toList()); - } - - - @SneakyThrows - private ResponseEntity uploadRules(String dossierTemplateId, RuleFileType ruleFileType, MultipartFile file, boolean dryRun) { - - var rulesUploadRequest = RulesUploadRequest.builder() - .rules(new String(file.getBytes(), StandardCharsets.UTF_8)) - .dossierTemplateId(dossierTemplateId) - .ruleFileType(ruleFileType) - .build(); - - DroolsValidationResponse rulesValidationResponse = new DroolsValidationResponse(); - - try { - var droolsValidation = rulesValidationService.validateRules(rulesUploadRequest.getRuleFileType(), rulesUploadRequest.getRules()); - rulesValidationResponse = RulesValidationMapper.createFromDroolsValidation(droolsValidation); - if (!droolsValidation.isCompiled()) { - - return new ResponseEntity<>(rulesValidationResponse, !dryRun ? HttpStatus.UNPROCESSABLE_ENTITY : HttpStatus.OK); - } - } catch (FeignException e) { - if (e.status() == HttpStatus.BAD_REQUEST.value()) { - throw new BadRequestException("The provided rule string is not a valid drools rule file!"); - } - } - - if (!dryRun) { - rulesPersistenceService.setRules(rulesUploadRequest.getRules(), rulesUploadRequest.getDossierTemplateId(), rulesUploadRequest.getRuleFileType()); - } - - auditPersistenceService.audit(AuditRequest.builder() - .userId(KeycloakSecurity.getUserId()) - .objectId(rulesUploadRequest.getDossierTemplateId()) - .category(AuditCategory.DOSSIER_TEMPLATE.name()) - .message(String.format("%s rules have been %s", rulesUploadRequest.getRuleFileType(), dryRun ? "validated" : "updated")) - .build()); - - return new ResponseEntity<>(rulesValidationResponse, HttpStatus.OK); - } - - - private ResponseEntity downloadRules(String dossierTemplateId, RuleFileType ruleFileType) { - - RuleSetEntity ruleEntity = rulesPersistenceService.getRules(dossierTemplateId, ruleFileType); - - var data = ruleEntity.getValue().getBytes(StandardCharsets.UTF_8); - - HttpHeaders httpHeaders = new HttpHeaders(); - httpHeaders.setContentType(MediaType.TEXT_PLAIN); - - httpHeaders.add("Content-Disposition", - "attachment" + "; filename*=utf-8''" + StringEncodingUtils.urlEncode(ruleFileType.name().toLowerCase(Locale.ROOT) + RULES_DOWNLOAD_FILE_NAME_SUFFIX)); - - InputStream is = new ByteArrayInputStream(data); - - return new ResponseEntity<>(new InputStreamResource(is), httpHeaders, HttpStatus.OK); - } - - @Override @PreAuthorize("hasAuthority('" + READ_RULES + "')") public ComponentMappingSummary getComponentMappingSummaries(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId) { @@ -371,6 +285,145 @@ public class DossierTemplateControllerV2 implements DossierTemplateResource { } + @PreAuthorize("hasAuthority('" + READ_DOSSIER_STATUS + "')") + public DossierStatusDefinitionList getDossierStatusDefinitions(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId) { + + getDossierTemplate(dossierTemplateId); + + return new DossierStatusDefinitionList(dossierStatusPersistenceService.getAllDossierStatusForTemplate(dossierTemplateId) + .stream() + .map(dossierStatusInfo -> DossierStatusDefinition.builder() + .id(dossierStatusInfo.getId()) + .name(dossierStatusInfo.getName()) + .description(dossierStatusInfo.getDescription()) + .rank(dossierStatusInfo.getRank()) + .color(dossierStatusInfo.getColor()) + .dossierCount(dossierStatusInfo.getDossierCount() != null ? dossierStatusInfo.getDossierCount() : 0) + .build()) + .toList()); + } + + + @PreAuthorize("hasAuthority('" + READ_DOSSIER_ATTRIBUTES_CONFIG + "')") + public DossierAttributeDefinitionList getDossierAttributeDefinitions(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId) { + + getDossierTemplate(dossierTemplateId); + + return new DossierAttributeDefinitionList(dossierAttributeConfigPersistenceService.getDossierAttributes(dossierTemplateId) + .stream() + .map(config -> DossierAttributeDefinition.builder() + + .id(config.getId()) + .name(config.getLabel()) + .type(config.getType()) + .reportingPlaceholder(config.getPlaceholder()) + .build()) + .toList()); + } + + + @Override + public List createComponents(String dossierTemplateId, List componentDefinitionAddRequests) { + + if (componentDefinitionAddRequests.isEmpty()) { + return Collections.emptyList(); + } + + List componentDefinitions = componentDefinitionService.createComponents(componentDefinitionAddRequests); + auditPersistenceService.audit(AuditRequest.builder() + .userId(KeycloakSecurity.getUserId()) + .objectId(dossierTemplateId) + .category(AuditCategory.DOSSIER_TEMPLATE.name()) + .message("Components added.") + .details(Map.of("Number of added components", componentDefinitions.size())) + .build()); + return componentDefinitions; + } + + + @Override + public List getComponents(String dossierTemplateId, boolean includeSoftDeleted) { + + return componentDefinitionService.getComponentsByDossierTemplateId(dossierTemplateId, includeSoftDeleted); + } + + + @Override + public ComponentDefinition getComponent(String dossierTemplateId, String componentId) { + + return componentDefinitionService.getComponentByDossierTemplateIdAndComponentId(dossierTemplateId, componentId); + } + + + @Override + public List updateComponents(String dossierTemplateId, List componentDefinitionUpdateRequests) { + + if (componentDefinitionUpdateRequests.isEmpty()) { + return Collections.emptyList(); + } + + List componentDefinitions = componentDefinitionService.updateComponents(dossierTemplateId, componentDefinitionUpdateRequests); + auditPersistenceService.audit(AuditRequest.builder() + .userId(KeycloakSecurity.getUserId()) + .objectId(dossierTemplateId) + .category(AuditCategory.DOSSIER_TEMPLATE.name()) + .message("Components updated.") + .details(Map.of("Number of updated components", componentDefinitions.size())) + .build()); + return componentDefinitions; + } + + + @Override + public void deleteComponents(String dossierTemplateId, List componentIds) { + + List components = componentDefinitionService.deleteComponents(dossierTemplateId, componentIds); + if (!components.isEmpty()) { + auditPersistenceService.audit(AuditRequest.builder() + .userId(KeycloakSecurity.getUserId()) + .objectId(dossierTemplateId) + .category(AuditCategory.DOSSIER_TEMPLATE.name()) + .message("Components deleted.") + .details(Map.of("Number of deleted components", components.size())) + .build()); + } + } + + + @Override + public List restoreComponents(String dossierTemplateId, List componentIds) { + + List components = componentDefinitionService.restoreComponents(dossierTemplateId, componentIds); + if (!components.isEmpty()) { + auditPersistenceService.audit(AuditRequest.builder() + .userId(KeycloakSecurity.getUserId()) + .objectId(dossierTemplateId) + .category(AuditCategory.DOSSIER_TEMPLATE.name()) + .message("Components restored.") + .details(Map.of("Number of restored components", components.size())) + .build()); + } + return components; + } + + + @Override + public List reorderComponents(String dossierTemplateId, List componentIds) { + + List orderedComponents = componentDefinitionService.reorderComponents(dossierTemplateId, componentIds); + if (!orderedComponents.isEmpty()) { + auditPersistenceService.audit(AuditRequest.builder() + .userId(KeycloakSecurity.getUserId()) + .objectId(dossierTemplateId) + .category(AuditCategory.DOSSIER_TEMPLATE.name()) + .message("Components reordered.") + .details(Map.of("Number of reordered components", orderedComponents.size())) + .build()); + } + return orderedComponents; + } + + @SneakyThrows private static Path saveToFile(MultipartFile file) { @@ -382,4 +435,61 @@ public class DossierTemplateControllerV2 implements DossierTemplateResource { return mappingFile; } + + private ResponseEntity downloadRules(String dossierTemplateId, RuleFileType ruleFileType) { + + RuleSetEntity ruleEntity = rulesPersistenceService.getRules(dossierTemplateId, ruleFileType); + + var data = ruleEntity.getValue().getBytes(StandardCharsets.UTF_8); + + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.setContentType(MediaType.TEXT_PLAIN); + + httpHeaders.add("Content-Disposition", + "attachment" + "; filename*=utf-8''" + StringEncodingUtils.urlEncode(ruleFileType.name().toLowerCase(Locale.ROOT) + RULES_DOWNLOAD_FILE_NAME_SUFFIX)); + + InputStream is = new ByteArrayInputStream(data); + + return new ResponseEntity<>(new InputStreamResource(is), httpHeaders, HttpStatus.OK); + } + + + @SneakyThrows + private ResponseEntity uploadRules(String dossierTemplateId, RuleFileType ruleFileType, MultipartFile file, boolean dryRun) { + + var rulesUploadRequest = RulesUploadRequest.builder() + .rules(new String(file.getBytes(), StandardCharsets.UTF_8)) + .dossierTemplateId(dossierTemplateId) + .ruleFileType(ruleFileType) + .build(); + + DroolsValidationResponse rulesValidationResponse = new DroolsValidationResponse(); + + try { + var droolsValidation = rulesValidationService.validateRules(rulesUploadRequest.getRuleFileType(), rulesUploadRequest.getRules()); + rulesValidationResponse = RulesValidationMapper.createFromDroolsValidation(droolsValidation); + if (!droolsValidation.isCompiled()) { + + return new ResponseEntity<>(rulesValidationResponse, !dryRun ? HttpStatus.UNPROCESSABLE_ENTITY : HttpStatus.OK); + } + } catch (FeignException e) { + if (e.status() == HttpStatus.BAD_REQUEST.value()) { + throw new BadRequestException("The provided rule string is not a valid drools rule file!"); + } + } + + if (!dryRun) { + rulesPersistenceService.setRules(rulesUploadRequest.getRules(), rulesUploadRequest.getDossierTemplateId(), rulesUploadRequest.getRuleFileType()); + } + + auditPersistenceService.audit(AuditRequest.builder() + .userId(KeycloakSecurity.getUserId()) + .objectId(rulesUploadRequest.getDossierTemplateId()) + .category(AuditCategory.DOSSIER_TEMPLATE.name()) + .message(String.format("%s rules have been %s", rulesUploadRequest.getRuleFileType(), dryRun ? "validated" : "updated")) + .build()); + + return new ResponseEntity<>(rulesValidationResponse, HttpStatus.OK); + } + } diff --git a/persistence-service-v1/persistence-service-external-api-impl-v2/src/main/java/com/iqser/red/persistence/service/v2/external/api/impl/controller/ExternalControllerAdviceV2.java b/persistence-service-v1/persistence-service-external-api-impl-v2/src/main/java/com/iqser/red/persistence/service/v2/external/api/impl/controller/ExternalControllerAdviceV2.java index b22db8eb7..fcd0314e8 100644 --- a/persistence-service-v1/persistence-service-external-api-impl-v2/src/main/java/com/iqser/red/persistence/service/v2/external/api/impl/controller/ExternalControllerAdviceV2.java +++ b/persistence-service-v1/persistence-service-external-api-impl-v2/src/main/java/com/iqser/red/persistence/service/v2/external/api/impl/controller/ExternalControllerAdviceV2.java @@ -2,6 +2,7 @@ package com.iqser.red.persistence.service.v2.external.api.impl.controller; import java.time.OffsetDateTime; import java.util.Map; +import java.util.Objects; import java.util.stream.Collectors; import org.quartz.JobDataMap; @@ -10,6 +11,7 @@ import org.quartz.Scheduler; import org.quartz.SchedulerException; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; +import org.springframework.dao.DataIntegrityViolationException; import org.springframework.http.HttpStatus; import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.security.access.AccessDeniedException; @@ -167,6 +169,23 @@ public class ExternalControllerAdviceV2 { } + @Hidden + @ResponseBody + @ResponseStatus(value = HttpStatus.BAD_REQUEST) + @ExceptionHandler(value = DataIntegrityViolationException.class) + public ErrorMessage handleDataIntegrityViolationException(DataIntegrityViolationException exception) { + + String message = Objects.requireNonNull(exception.getRootCause()).getMessage(); + if (message.contains("uq_component_definition_technical_name_template")) { + message = "A component with the same technical name already exists in the given dossier template."; + } else { + message = "Database error occurred."; + } + + return new ErrorMessage(OffsetDateTime.now(), message); + } + + @Order(10000) public static class BinderControllerAdvice { diff --git a/persistence-service-v1/persistence-service-external-api-v2/src/main/java/com/iqser/red/service/persistence/service/v2/api/external/resource/DossierTemplateResource.java b/persistence-service-v1/persistence-service-external-api-v2/src/main/java/com/iqser/red/service/persistence/service/v2/api/external/resource/DossierTemplateResource.java index 92eb6f61c..994e3da34 100644 --- a/persistence-service-v1/persistence-service-external-api-v2/src/main/java/com/iqser/red/service/persistence/service/v2/api/external/resource/DossierTemplateResource.java +++ b/persistence-service-v1/persistence-service-external-api-v2/src/main/java/com/iqser/red/service/persistence/service/v2/api/external/resource/DossierTemplateResource.java @@ -1,18 +1,6 @@ package com.iqser.red.service.persistence.service.v2.api.external.resource; -import com.iqser.red.service.persistence.service.v1.api.shared.model.DossierTemplateModel; -import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.rules.DroolsValidationResponse; -import com.iqser.red.service.persistence.service.v2.api.external.model.ComponentMappingMetadataModel; -import com.iqser.red.service.persistence.service.v2.api.external.model.ComponentMappingSummary; -import com.iqser.red.service.persistence.service.v2.api.external.model.FileAttributeDefinitionList; -import com.iqser.red.service.persistence.service.v2.api.external.model.DossierAttributeDefinitionList; -import com.iqser.red.service.persistence.service.v2.api.external.model.DossierStatusDefinitionList; - -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.responses.ApiResponses; +import java.util.List; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; @@ -22,13 +10,30 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.multipart.MultipartFile; -import java.util.List; +import com.iqser.red.service.persistence.service.v1.api.shared.model.DossierTemplateModel; +import com.iqser.red.service.persistence.service.v1.api.shared.model.component.ComponentDefinition; +import com.iqser.red.service.persistence.service.v1.api.shared.model.component.ComponentDefinitionAddRequest; +import com.iqser.red.service.persistence.service.v1.api.shared.model.component.ComponentDefinitionUpdateRequest; +import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.rules.DroolsValidationResponse; +import com.iqser.red.service.persistence.service.v2.api.external.model.ComponentMappingMetadataModel; +import com.iqser.red.service.persistence.service.v2.api.external.model.ComponentMappingSummary; +import com.iqser.red.service.persistence.service.v2.api.external.model.DossierAttributeDefinitionList; +import com.iqser.red.service.persistence.service.v2.api.external.model.DossierStatusDefinitionList; +import com.iqser.red.service.persistence.service.v2.api.external.model.FileAttributeDefinitionList; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import jakarta.validation.Valid; @ApiResponses(value = {@ApiResponse(responseCode = "429", description = "Too many requests.")}) @ResponseStatus(HttpStatus.OK) @@ -47,7 +52,6 @@ public interface DossierTemplateResource { String DOSSIER_TEMPLATE_ID_PARAM = "dossierTemplateId"; String DOSSIER_TEMPLATE_ID_PATH_VARIABLE = "/{" + DOSSIER_TEMPLATE_ID_PARAM + "}"; - String COMPONENT_MAPPING_ID_PARAM = "componentMappingId"; String COMPONENT_MAPPING_ID_PATH_VARIABLE = "/{" + COMPONENT_MAPPING_ID_PARAM + "}"; @@ -56,6 +60,15 @@ public interface DossierTemplateResource { String DELIMITER_PARAM = "delimiter"; String MAPPING_NAME_PARAM = "name"; + String INCLUDE_SOFT_DELETED = "includeSoftDeleted"; + String COMPONENT_ID_PARAM = "componentId"; + String COMPONENT_ID_PATH = "/{" + COMPONENT_ID_PARAM + "}"; + String COMPONENT_IDS = "componentIds"; + String COMPONENT_PATH = "/component-definitions"; + String REORDER_PATH = "/reorder"; + String RESTORE_PATH = "/restore"; + + @GetMapping(value = PATH, produces = MediaType.APPLICATION_JSON_VALUE) @Operation(summary = "Lists all existing DossierTemplates.", description = "None") @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "List of all existing DossierTemplates.")}) @@ -156,6 +169,7 @@ public interface DossierTemplateResource { @DeleteMapping(value = PATH + DOSSIER_TEMPLATE_ID_PATH_VARIABLE + COMPONENT_MAPPINGS_PATH + COMPONENT_MAPPING_ID_PATH_VARIABLE) ResponseEntity deleteMapping(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId, @PathVariable(COMPONENT_MAPPING_ID_PARAM) String componentMappingId); + @Operation(summary = "Returns the list of all existing dossier status definitions.", description = "None") @GetMapping(value = PATH + DOSSIER_TEMPLATE_ID_PATH_VARIABLE + DOSSIER_STATUS_DEFINITIONS_PATH, produces = MediaType.APPLICATION_JSON_VALUE) @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Successfully returned the dossier status definitions for the specified dossier template."), @ApiResponse(responseCode = "404", description = "The DossierTemplate is not found.")}) @@ -167,4 +181,63 @@ public interface DossierTemplateResource { @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Successfully returned the dossier attribute definitions for the specified dossier template."), @ApiResponse(responseCode = "404", description = "The DossierTemplate is not found.")}) DossierAttributeDefinitionList getDossierAttributeDefinitions(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId); + + @ResponseBody + @ResponseStatus(HttpStatus.CREATED) + @Operation(summary = "Create new components", description = "Create new components for a given dossier template.") + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Created")}) + @PostMapping(value = PATH + DOSSIER_TEMPLATE_ID_PATH_VARIABLE + COMPONENT_PATH, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) + List createComponents(@Valid @PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId, + @RequestBody List componentDefinitionUpdateRequests); + + + @ResponseBody + @ResponseStatus(HttpStatus.OK) + @Operation(summary = "Get all components", description = "Get all components for a given dossier template.") + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")}) + @GetMapping(value = PATH + DOSSIER_TEMPLATE_ID_PATH_VARIABLE + COMPONENT_PATH, produces = MediaType.APPLICATION_JSON_VALUE) + List getComponents(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId, + @Parameter(name = INCLUDE_SOFT_DELETED, description = "Include files which are soft deleted. Default is false.") @RequestParam(value = INCLUDE_SOFT_DELETED, defaultValue = "false") boolean includeSoftDeleted); + + + @ResponseBody + @ResponseStatus(HttpStatus.OK) + @Operation(summary = "Get a component by ID", description = "Get specific details for a component by ID.") + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "404", description = "The DossierTemplate or the specified component is not found.")}) + @GetMapping(value = PATH + DOSSIER_TEMPLATE_ID_PATH_VARIABLE + COMPONENT_PATH + COMPONENT_ID_PATH, produces = MediaType.APPLICATION_JSON_VALUE) + ComponentDefinition getComponent(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId, @PathVariable(COMPONENT_ID_PARAM) String componentId); + + + @ResponseBody + @ResponseStatus(HttpStatus.OK) + @Operation(summary = "Update components", description = "Update existing components.") + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")}) + @PutMapping(value = PATH + DOSSIER_TEMPLATE_ID_PATH_VARIABLE + COMPONENT_PATH, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) + List updateComponents(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId, + @Valid @RequestBody List componentDefinitionUpdateRequests); + + + @ResponseBody + @ResponseStatus(HttpStatus.NO_CONTENT) + @Operation(summary = "Delete components", description = "Delete existing components by their IDs.") + @ApiResponses(value = {@ApiResponse(responseCode = "204", description = "No Content")}) + @DeleteMapping(value = PATH + DOSSIER_TEMPLATE_ID_PATH_VARIABLE + COMPONENT_PATH) + void deleteComponents(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId, @RequestParam(name = COMPONENT_IDS) List componentIds); + + + @ResponseBody + @ResponseStatus(HttpStatus.OK) + @Operation(summary = "Restore components", description = "Restore previously soft deleted components based on their IDs.") + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")}) + @PutMapping(value = PATH + DOSSIER_TEMPLATE_ID_PATH_VARIABLE + COMPONENT_PATH + RESTORE_PATH) + List restoreComponents(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId, @RequestParam(name = COMPONENT_IDS) List componentIds); + + + @ResponseBody + @ResponseStatus(HttpStatus.OK) + @Operation(summary = "Reorder components", description = "Reorder components based on their rank.") + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")}) + @GetMapping(value = PATH + DOSSIER_TEMPLATE_ID_PATH_VARIABLE + COMPONENT_PATH + REORDER_PATH, produces = MediaType.APPLICATION_JSON_VALUE) + List reorderComponents(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId, @RequestParam(name = COMPONENT_IDS) List componentIds); + } diff --git a/persistence-service-v1/persistence-service-external-api-v2/src/main/resources/api/documine.yaml b/persistence-service-v1/persistence-service-external-api-v2/src/main/resources/api/documine.yaml index 91bdfd2b7..184937f44 100644 --- a/persistence-service-v1/persistence-service-external-api-v2/src/main/resources/api/documine.yaml +++ b/persistence-service-v1/persistence-service-external-api-v2/src/main/resources/api/documine.yaml @@ -353,7 +353,7 @@ paths: tags: - 1. Dossier Templates description: | - Use this endpoint to retrieves a collection of file attribute definitions associated with a specific dossier template. Each file + Use this endpoint to retrieve a collection of file attribute definitions associated with a specific dossier template. Each file attribute definition includes details such as attribute type, name, and other relevant metadata. This endpoint is useful for clients needing to understand what attributes are expected or allowed for files associated with a specific dossier template. @@ -587,6 +587,269 @@ paths: $ref: '#/components/responses/429' "500": $ref: '#/components/responses/500' + /dossier-templates/{dossierTemplateId}/component-definitions: + post: + operationId: createComponents + tags: + - Component Definitions + summary: Create new component definitions + description: | + Create new component definitions for a given dossier template. The component will have a technical name which is automatically converted to snake case + that can't be updated after the creation. The rank is used to determine the order in which the components are displayed. The component's rank will + automatically be appended at the end based on the current number of components of this dossier template. + parameters: + - $ref: '#/components/parameters/dossierTemplateId' + requestBody: + required: true + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/ComponentDefinitionAddRequest' + responses: + "201": + description: Created + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/ComponentDefinition' + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "403": + $ref: '#/components/responses/403' + "404": + $ref: '#/components/responses/404-dossier-template' + "429": + $ref: '#/components/responses/429' + "500": + $ref: '#/components/responses/500' + get: + operationId: getComponents + tags: + - Component Definitions + summary: Get all component definitions + description: | + Get all the component definitions in a given dossier template. `includeSoftDeleted` is false by default and will not return soft deleted components. If set to true, + this endpoint will also return components that have been soft deleted. + parameters: + - $ref: '#/components/parameters/dossierTemplateId' + - name: includeSoftDeleted + in: query + required: true + schema: + default: false + type: boolean + description: | + A toggle to include or exclude soft-deleted components: If `false` (default), soft-deleted components + are not included. If `true`, they are included. + responses: + "200": + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/ComponentDefinition' + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "403": + $ref: '#/components/responses/403' + "404": + $ref: '#/components/responses/404-component-definition' + "429": + $ref: '#/components/responses/429' + "500": + $ref: '#/components/responses/500' + put: + operationId: updateComponents + tags: + - Component Definitions + summary: Update existing component definitions + description: | + Update specific existing component definitions. The rank and technical name cannot be updated via this endpoint. The technical name cannot be updated + at all after creation. To update the rank, use the [reorder endpoint](#/paths/~1dossier-templates~1{dossierTemplateId}~1component-definitions~1reorder/post). + parameters: + - $ref: '#/components/parameters/dossierTemplateId' + requestBody: + required: true + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/ComponentDefinitionUpdateRequest' + responses: + "200": + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/ComponentDefinition' + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "403": + $ref: '#/components/responses/403' + "404": + $ref: '#/components/responses/404-component-definition' + "429": + $ref: '#/components/responses/429' + "500": + $ref: '#/components/responses/500' + delete: + operationId: deleteComponents + tags: + - Component Definitions + summary: Soft delete component definitions + description: | + Soft delete existing component definitions by their IDs. Soft deletion is done by setting the `softDeletedTime` value to the current time of the request. If a component + does not have `softDeletedTime` set, it means the component is not soft deleted. + parameters: + - $ref: '#/components/parameters/dossierTemplateId' + - name: componentIds + in: query + required: true + schema: + type: array + items: + type: string + description: The IDs of the component definitions to soft delete + responses: + "204": + description: No Content + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "403": + $ref: '#/components/responses/403' + "404": + $ref: '#/components/responses/404-component-definition' + "429": + $ref: '#/components/responses/429' + "500": + $ref: '#/components/responses/500' + /dossier-templates/{dossierTemplateId}/component-definitions/reorder: + post: + operationId: reorderComponents + tags: + - Component Definitions + summary: Reorder component definitions + description: Reorder the component definitions based on the provided list. Their ranks will be updated based on the order provided in the request, starting from rank 1. + parameters: + - $ref: '#/components/parameters/dossierTemplateId' + - name: componentIds + in: query + required: true + schema: + type: array + items: + type: string + description: The IDs of the component definitions in the order in which they will be reordered. + responses: + "201": + description: Created + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/ComponentDefinition' + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "403": + $ref: '#/components/responses/403' + "404": + $ref: '#/components/responses/404-component-definition' + "429": + $ref: '#/components/responses/429' + "500": + $ref: '#/components/responses/500' + /api/dossier-templates/{dossierTemplateId}/component-definitions/{componentId}: + get: + operationId: getComponent + tags: + - Component Definitions + summary: Get specific component by ID + description: Get details about a specific component based on its ID for a given dossier template. + parameters: + - $ref: '#/components/parameters/dossierTemplateId' + - $ref: '#/components/parameters/componentId' + responses: + "200": + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/ComponentDefinition' + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "403": + $ref: '#/components/responses/403' + "404": + $ref: '#/components/responses/404-component-definition' + "429": + $ref: '#/components/responses/429' + "500": + $ref: '#/components/responses/500' + /api/dossier-templates/{dossierTemplateId}/component-definitions/restore: + post: + operationId: restoreComponents + tags: + - Component Definitions + summary: Restore soft deleted components by ID + description: | + Restore a previously soft-deleted component. The restored component will be placed at the end of the rank order. + For example, if there are 5 components ranked 1, 2, 3, 4, and 5, and the third component is soft-deleted, the ranks will be updated to 1, 2, 4, and 5. + If the soft-deleted component is then restored, it will be appended to the end, resulting in ranks 1, 2, 4, 5, and 6. + parameters: + - $ref: '#/components/parameters/dossierTemplateId' + - name: componentIds + in: query + required: true + schema: + type: array + items: + type: string + description: The IDs of the component definitions to restore. + responses: + "200": + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/ComponentDefinition' + "400": + $ref: '#/components/responses/400' + "401": + $ref: '#/components/responses/401' + "403": + $ref: '#/components/responses/403' + "404": + $ref: '#/components/responses/404-component-definition' + "429": + $ref: '#/components/responses/429' + "500": + $ref: '#/components/responses/500' /api/dossier-templates/{dossierTemplateId}/dossiers: get: operationId: getDossiers @@ -1602,6 +1865,13 @@ components: $ref: '#/components/schemas/ErrorMessage' description: | Dossier template or component mapping not found. This happens if the requested dossier template or component mapping does not exist. + "404-component-definition": + content: + 'application/json': + schema: + $ref: '#/components/schemas/ErrorMessage' + description: | + Dossier template or component definition not found. This happens if the requested dossier template or component definition does not exist. "404-dossier": content: 'application/json': @@ -1682,6 +1952,15 @@ components: style: simple explode: false description: The identifier of a component mapping + componentId: + name: componentId + in: path + required: true + schema: + type: string + style: simple + explode: false + description: The identifier of a component definition. dryRun: name: dryRun in: query @@ -3062,6 +3341,98 @@ components: required: - dossierTemplateId - componentMappingList + ComponentDefinition: + description: The ComponentDefinition object represents a component definition within a dossier template. + type: object + properties: + id: + description: A unique identifier for the component definition. + type: string + format: uuid + dossierTemplateId: + description: The ID of the dossier template to which this component belongs. + type: string + technicalName: + description: The technical name of the component. + type: string + displayName: + description: The display name of the component. + type: string + description: + description: A brief description of the component. + type: string + rank: + description: The rank of the component within the template, used for ordering. + type: integer + softDeleteTime: + description: The timestamp indicating when the component was soft deleted. If null, the component is not soft deleted. + type: string + format: date-time + required: + - id + - dossierTemplateId + - technicalName + - displayName + - rank + example: + id: 300b9406-59c0-4473-9c8a-1f5f0b4a88c3 + dossierTemplateId: 8cd4b482-fb49-4315-9b51-789b4ae46c57 + technicalName: study_conclusion + displayName: Study Conclusion + description: The drawn conclusion from the performed study + rank: 1 + softDeleteTime: null + ComponentDefinitionAddRequest: + description: | + The ComponentDefinitionAddRequest object represents a request to create a component definition within a dossier template. + The rank will be automatically generated and does not need to be provided at creation. + example: + dossierTemplateId: 8cd4b482-fb49-4315-9b51-789b4ae46c57 + technicalName: study_conclusion + displayName: Study conclusion + description: The conclusion of the study + type: object + properties: + dossierTemplateId: + description: The ID of the dossier template to which this component belongs. + type: string + technicalName: + description: The technical name of the component. + type: string + displayName: + description: The display name of the component. + type: string + description: + description: A brief description of the component. + type: string + required: + - dossierTemplateId + - technicalName + - displayName + ComponentDefinitionUpdateRequest: + description: | + The ComponentDefinitionUpdateRequest object represents a request to update a component definition within a dossier template. + Only the display name and description can be updated. + example: + id: aa7107f7-cc5b-490f-b91c-35c9d86749f7 + displayName: Study conclusion + description: The conclusion of the study + type: object + properties: + id: + description: The unique identifier for the component to be updated. + type: string + format: uuid + displayName: + description: The new display name of the component. + type: string + description: + description: The new description of the component. + type: string + required: + - id + - displayName + - description DossierTemplate: description: | The `DossierTemplate` object represents the blueprint for creating and diff --git a/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/entity/dossier/ComponentDefinitionEntity.java b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/entity/dossier/ComponentDefinitionEntity.java new file mode 100644 index 000000000..31ad16c90 --- /dev/null +++ b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/entity/dossier/ComponentDefinitionEntity.java @@ -0,0 +1,45 @@ +package com.iqser.red.service.persistence.management.v1.processor.entity.dossier; + +import java.time.OffsetDateTime; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.UniqueConstraint; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +@Entity +@Table(name = "component_definition", uniqueConstraints = {@UniqueConstraint(columnNames = {"technical_name", "dossier_template_id"})}) +@Data +@NoArgsConstructor +@EqualsAndHashCode +public class ComponentDefinitionEntity { + + @Id + @Column(name = "id", nullable = false, updatable = false) + private String id; + + @Column(name = "dossier_template_id", nullable = false) + private String dossierTemplateId; + + @Column(name = "technical_name", nullable = false) + private String technicalName; + + @Column(name = "display_name", nullable = false) + private String displayName; + + @Column(name = "description") + private String description; + + @Column(name = "rank") + private Integer rank; + + @Column(name = "soft_delete_time") + private OffsetDateTime softDeleteTime; + +} diff --git a/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/ComponentDefinitionService.java b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/ComponentDefinitionService.java new file mode 100644 index 000000000..57f442da6 --- /dev/null +++ b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/ComponentDefinitionService.java @@ -0,0 +1,152 @@ +package com.iqser.red.service.persistence.management.v1.processor.service; + +import static com.iqser.red.service.persistence.management.v1.processor.roles.ActionRoles.ADD_UPDATE_DICTIONARY_TYPE; +import static com.iqser.red.service.persistence.management.v1.processor.roles.ActionRoles.DELETE_DICTIONARY_TYPE; +import static com.iqser.red.service.persistence.management.v1.processor.roles.ActionRoles.READ_DICTIONARY_TYPES; + +import java.time.OffsetDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.iqser.red.service.persistence.management.v1.processor.entity.dossier.ComponentDefinitionEntity; +import com.iqser.red.service.persistence.management.v1.processor.exception.BadRequestException; +import com.iqser.red.service.persistence.management.v1.processor.service.persistence.ComponentDefinitionPersistenceService; +import com.iqser.red.service.persistence.service.v1.api.shared.model.component.ComponentDefinition; +import com.iqser.red.service.persistence.service.v1.api.shared.model.component.ComponentDefinitionAddRequest; +import com.iqser.red.service.persistence.service.v1.api.shared.model.component.ComponentDefinitionUpdateRequest; +import com.knecon.fforesight.databasetenantcommons.providers.utils.MagicConverter; + +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; +import lombok.experimental.FieldDefaults; + +@Service +@RequiredArgsConstructor +@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) +public class ComponentDefinitionService { + + ComponentDefinitionPersistenceService componentDefinitionPersistenceService; + + + @PreAuthorize("hasAuthority('" + ADD_UPDATE_DICTIONARY_TYPE + "')") + @Transactional + public List createComponents(List componentDefinitionAddRequests) { + + validateComponentRequest(componentDefinitionAddRequests); + List componentEntities = new ArrayList<>(); + componentDefinitionAddRequests.forEach(componentDefinitionAddRequest -> componentEntities.add(componentDefinitionPersistenceService.insert(componentDefinitionAddRequest))); + return componentEntities.stream() + .map(componentDefinitionEntity -> MagicConverter.convert(componentDefinitionEntity, ComponentDefinition.class)) + .toList(); + } + + + private void validateComponentRequest(List componentDefinitionAddRequests) { + + String firstDossierTemplateId = componentDefinitionAddRequests.get(0).getDossierTemplateId(); + boolean allMatch = componentDefinitionAddRequests.stream() + .allMatch(request -> request.getDossierTemplateId().equals(firstDossierTemplateId)); + if (!allMatch) { + throw new BadRequestException("All components must have the same dossierTemplateId."); + } + } + + + @PreAuthorize("hasAuthority('" + READ_DICTIONARY_TYPES + "')") + public ComponentDefinition getComponentByDossierTemplateIdAndComponentId(String dossierTemplateId, String componentId) { + + return MagicConverter.convert(componentDefinitionPersistenceService.findComponentByDossierTemplateIdAndComponentId(dossierTemplateId, componentId), + ComponentDefinition.class); + } + + + @PreAuthorize("hasAuthority('" + READ_DICTIONARY_TYPES + "')") + public List getComponentsByDossierTemplateId(String dossierTemplateId, boolean includeSoftDeleted) { + + List entities; + + if (includeSoftDeleted) { + entities = componentDefinitionPersistenceService.findComponentsByDossierTemplateId(dossierTemplateId); + } else { + entities = componentDefinitionPersistenceService.findComponentsByDossierTemplateIdExcludeSoftDeleted(dossierTemplateId); + } + + return entities.stream() + .map(entity -> MagicConverter.convert(entity, ComponentDefinition.class)) + .collect(Collectors.toList()); + } + + + @PreAuthorize("hasAuthority('" + ADD_UPDATE_DICTIONARY_TYPE + "')") + @Transactional + public List updateComponents(String dossierTemplateId, List componentDefinitionUpdateRequests) { + + List componentEntities = new ArrayList<>(); + componentDefinitionUpdateRequests.forEach(componentDefinitionUpdateRequest -> { + ComponentDefinitionEntity componentDefinitionEntity = componentDefinitionPersistenceService.findComponent(dossierTemplateId, componentDefinitionUpdateRequest.getId()); + componentDefinitionEntity.setDescription(componentDefinitionUpdateRequest.getDescription()); + componentDefinitionEntity.setDisplayName(componentDefinitionUpdateRequest.getDisplayName()); + componentEntities.add(componentDefinitionPersistenceService.update(componentDefinitionEntity)); + }); + return componentEntities.stream() + .map(componentDefinitionEntity -> MagicConverter.convert(componentDefinitionEntity, ComponentDefinition.class)) + .toList(); + } + + + @PreAuthorize("hasAuthority('" + DELETE_DICTIONARY_TYPE + "')") + @Transactional + public List deleteComponents(String dossierTemplateId, List componentIds) { + + OffsetDateTime now = OffsetDateTime.now(); + return componentDefinitionPersistenceService.softDeleteComponents(dossierTemplateId, componentIds, now); + } + + + @PreAuthorize("hasAuthority('" + ADD_UPDATE_DICTIONARY_TYPE + "')") + @Transactional + public List restoreComponents(String dossierTemplateId, List componentIds) { + + + List componentDefinitionEntities = componentDefinitionPersistenceService.restoreComponents(dossierTemplateId, componentIds); + int rank = componentDefinitionPersistenceService.countByDossierTemplateId(dossierTemplateId); + for (ComponentDefinitionEntity componentDefinitionEntity : componentDefinitionEntities) { + componentDefinitionEntity.setRank(++rank); + componentDefinitionPersistenceService.update(componentDefinitionEntity); + } + return componentDefinitionEntities.stream() + .map(componentDefinitionEntity -> MagicConverter.convert(componentDefinitionEntity, ComponentDefinition.class)) + .toList(); + } + + + @PreAuthorize("hasAuthority('" + ADD_UPDATE_DICTIONARY_TYPE + "')") + @Transactional + public List reorderComponents(String dossierTemplateId, List componentIds) { + + List orderedComponents = new ArrayList<>(); + List existingComponents = componentDefinitionPersistenceService.findComponentsByDossierTemplateIdExcludeSoftDeleted(dossierTemplateId); + + Map componentMap = existingComponents.stream() + .collect(Collectors.toMap(ComponentDefinitionEntity::getId, component -> component)); + + int rank = 1; + for (String componentId : componentIds) { + ComponentDefinitionEntity component = componentMap.get(componentId); + if (component != null) { + component.setRank(rank++); + componentDefinitionPersistenceService.update(component); + orderedComponents.add(MagicConverter.convert(component, ComponentDefinition.class)); + } + } + + return orderedComponents; + } + +} diff --git a/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/ComponentLogService.java b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/ComponentLogService.java index c4e01f056..6e4800d01 100644 --- a/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/ComponentLogService.java +++ b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/ComponentLogService.java @@ -1,13 +1,17 @@ package com.iqser.red.service.persistence.management.v1.processor.service; +import java.util.ArrayList; import java.util.Comparator; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import com.iqser.red.service.persistence.management.v1.processor.entity.dossier.ComponentDefinitionEntity; import com.iqser.red.service.persistence.management.v1.processor.service.persistence.AuditPersistenceService; +import com.iqser.red.service.persistence.management.v1.processor.service.persistence.ComponentDefinitionPersistenceService; import com.iqser.red.service.persistence.service.v1.api.shared.model.AuditCategory; import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.componentlog.ComponentLog; import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.componentlog.ComponentLogEntry; @@ -27,55 +31,20 @@ public class ComponentLogService { private final FileManagementStorageService fileManagementStorageService; private final ComponentLogMongoService componentLogMongoService; private final AuditPersistenceService auditPersistenceService; - - // TODO: make this DB changeable! - private static final List ORDER = List.of("Study_Title", - "Performing_Laboratory", - "Report_Number", - "GLP_Study", - "Test_Guidelines_1", - "Test_Guidelines_2", - "Experimental_Starting_Date", - "Experimental_Completion_Date", - "Certificate_of_Analysis_Batch_Identification", - "Species", - "Strain", - "Was_the_definitive_study_conducted_with_positive_control", - "Study_Design_Main_Study", - "Results_Main_Study", - "Preliminary_Test_Results", - "What_was_the_approach_used", - "Sex", - "Number_of_Animals", - "Study_Design", - "Test_Results", - "Results_and_Conclusions", - "Conducted_with_4_Hours_of_Exposure", - "Dosages", - "Doses_mg_per_kg_bw", - "Mortality", - "Dose_Mortality", - "Mortality_Statement", - "Weight_Behavior_Changes", - "Clinical_Observations", - "Clinical_Signs", - "Body_Weight_Changes", - "Necropsy_Findings", - "Detailing_of_Reported_Changes", - "Deviation_from_the_Guideline", - "Conclusion_LD50_Greater_than", - "Conclusion_LD50_mg_per_kg", - "Conclusion_Minimum_Confidence", - "Conclusion_Maximum_Confidence", - "Study_Conclusion"); + private final ComponentDefinitionPersistenceService componentDefinitionPersistenceService; public ComponentLog getComponentLog(String dossierId, String fileId, boolean includeOverrides) { + List orderedEntities = componentDefinitionPersistenceService.findByDossierTemplateIdAndNotSoftDeleted(dossierId); + List orderedNames = orderedEntities.stream() + .map(ComponentDefinitionEntity::getTechnicalName) + .collect(Collectors.toList()); + ComponentLog componentLog = fileManagementStorageService.getComponentLog(dossierId, fileId); if (!includeOverrides) { - componentLog = sortComponentLogEntriesByOrderList(componentLog, ORDER); + componentLog = sortComponentLogEntries(componentLog, orderedNames); return componentLog; } @@ -83,12 +52,34 @@ public class ComponentLogService { replaceOverriddenComponentLogEntries(componentLog, componentOverrides); - componentLog = sortComponentLogEntriesByOrderList(componentLog, ORDER); + componentLog = sortComponentLogEntries(componentLog, orderedNames); return componentLog; } + private ComponentLog sortComponentLogEntries(ComponentLog componentLog, List orderedNames) { + + List componentLogEntries = componentLog.getComponentLogEntries(); + + List sortedLogEntries = new ArrayList<>(); + List nonOrderedLogEntries = new ArrayList<>(); + + for (ComponentLogEntry entry : componentLogEntries) { + if (orderedNames.contains(entry.getName())) { + sortedLogEntries.add(entry); + } else { + nonOrderedLogEntries.add(entry); + } + } + + nonOrderedLogEntries.sort(new ComponentOrderComparator(orderedNames)); + sortedLogEntries.addAll(nonOrderedLogEntries); + + return new ComponentLog(componentLog.getAnalysisNumber(), componentLog.getComponentRulesVersion(), sortedLogEntries); + } + + private void replaceOverriddenComponentLogEntries(ComponentLog componentLog, List componentOverrides) { componentLog.getComponentLogEntries() diff --git a/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/persistence/ComponentDefinitionPersistenceService.java b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/persistence/ComponentDefinitionPersistenceService.java new file mode 100644 index 000000000..1626486f6 --- /dev/null +++ b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/persistence/ComponentDefinitionPersistenceService.java @@ -0,0 +1,99 @@ +package com.iqser.red.service.persistence.management.v1.processor.service.persistence; + +import java.time.OffsetDateTime; +import java.util.List; +import java.util.UUID; + +import org.springframework.stereotype.Service; + +import com.iqser.red.service.persistence.management.v1.processor.entity.dossier.ComponentDefinitionEntity; +import com.iqser.red.service.persistence.management.v1.processor.exception.NotFoundException; +import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.ComponentDefinitionRepository; +import com.iqser.red.service.persistence.management.v1.processor.utils.SnakeCaseUtils; +import com.iqser.red.service.persistence.service.v1.api.shared.model.component.ComponentDefinitionAddRequest; + +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Service +@RequiredArgsConstructor +public class ComponentDefinitionPersistenceService { + + private final ComponentDefinitionRepository componentDefinitionRepository; + + + public ComponentDefinitionEntity insert(ComponentDefinitionAddRequest component) { + + ComponentDefinitionEntity componentDefinitionEntity = new ComponentDefinitionEntity(); + componentDefinitionEntity.setId(UUID.randomUUID().toString()); + componentDefinitionEntity.setDossierTemplateId(component.getDossierTemplateId()); + componentDefinitionEntity.setTechnicalName(SnakeCaseUtils.toSnakeCase(component.getTechnicalName())); + componentDefinitionEntity.setDisplayName(component.getDisplayName()); + componentDefinitionEntity.setDescription(component.getDescription()); + componentDefinitionEntity.setRank(countByDossierTemplateId(component.getDossierTemplateId()) + 1); + return componentDefinitionRepository.saveAndFlush(componentDefinitionEntity); + } + + + public int countByDossierTemplateId(String dossierTemplateId) { + + return componentDefinitionRepository.countByDossierTemplateId(dossierTemplateId); + } + + + public ComponentDefinitionEntity update(ComponentDefinitionEntity componentDefinitionEntity) { + + return componentDefinitionRepository.saveAndFlush(componentDefinitionEntity); + } + + + public ComponentDefinitionEntity findComponent(String dossierTemplateId, String componentId) { + + return componentDefinitionRepository.findByIdAndDossierTemplateId(componentId, dossierTemplateId) + .orElseThrow(() -> new NotFoundException("Component with id: " + componentId + " not found")); + } + + + @Transactional + public List softDeleteComponents(String dossierTemplateId, List componentIds, OffsetDateTime softDeleteTime) { + + componentDefinitionRepository.updateSoftDeleteForIds(componentIds, dossierTemplateId, softDeleteTime); + return componentDefinitionRepository.findByIdsAndDossierTemplateId(componentIds, dossierTemplateId); + } + + + @Transactional + public List restoreComponents(String dossierTemplateId, List componentIds) { + + componentDefinitionRepository.updateSoftDeleteForIds(componentIds, dossierTemplateId, null); + return componentDefinitionRepository.findByIdsAndDossierTemplateId(componentIds, dossierTemplateId); + } + + + public ComponentDefinitionEntity findComponentByDossierTemplateIdAndComponentId(String dossierTemplateId, String componentId) { + + return componentDefinitionRepository.findByIdAndDossierTemplateId(componentId, dossierTemplateId) + .orElseThrow(() -> new NotFoundException(String.format("Component with id %s not found in dossier template %s", componentId, dossierTemplateId))); + } + + + public List findComponentsByDossierTemplateId(String dossierTemplateId) { + + return componentDefinitionRepository.findByDossierTemplateId(dossierTemplateId); + } + + + public List findComponentsByDossierTemplateIdExcludeSoftDeleted(String dossierTemplateId) { + + return componentDefinitionRepository.findByDossierTemplateIdAndSoftDeletedTimeIsNull(dossierTemplateId); + } + + + public List findByDossierTemplateIdAndNotSoftDeleted(String dossierTemplateId) { + + return componentDefinitionRepository.findByDossierTemplateIdAndSoftDeletedTimeIsNull(dossierTemplateId); + } + +} diff --git a/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/persistence/repository/ComponentDefinitionRepository.java b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/persistence/repository/ComponentDefinitionRepository.java new file mode 100644 index 000000000..453f2e654 --- /dev/null +++ b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/persistence/repository/ComponentDefinitionRepository.java @@ -0,0 +1,40 @@ +package com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository; + +import java.time.OffsetDateTime; +import java.util.List; +import java.util.Optional; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +import com.iqser.red.service.persistence.management.v1.processor.entity.dossier.ComponentDefinitionEntity; + +@Repository +public interface ComponentDefinitionRepository extends JpaRepository { + + List findByDossierTemplateId(String dossierTemplateId); + + + Optional findByIdAndDossierTemplateId(String componentId, String dossierTemplateId); + + + @Query("SELECT c FROM ComponentDefinitionEntity c WHERE c.dossierTemplateId = :dossierTemplateId AND c.softDeleteTime IS NULL") + List findByDossierTemplateIdAndSoftDeletedTimeIsNull(@Param("dossierTemplateId") String dossierTemplateId); + + + @Modifying + @Query("UPDATE ComponentDefinitionEntity c SET c.softDeleteTime = :softDeleteTime WHERE c.id IN :ids AND c.dossierTemplateId = :dossierTemplateId") + void updateSoftDeleteForIds(@Param("ids") List ids, @Param("dossierTemplateId") String dossierTemplateId, @Param("softDeleteTime") OffsetDateTime softDeleteTime); + + + @Query("SELECT c FROM ComponentDefinitionEntity c WHERE c.id in :ids AND c.dossierTemplateId = :dossierTemplateId") + List findByIdsAndDossierTemplateId(@Param("ids") List ids, @Param("dossierTemplateId") String dossierTemplateId); + + + @Query("SELECT COUNT(c) from ComponentDefinitionEntity c WHERE c.dossierTemplateId = :dossierTemplateId AND c.softDeleteTime IS NULL") + int countByDossierTemplateId(@Param("dossierTemplateId") String dossierTemplateId); + +} diff --git a/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/utils/SnakeCaseUtils.java b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/utils/SnakeCaseUtils.java new file mode 100644 index 000000000..90d68fb0a --- /dev/null +++ b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/utils/SnakeCaseUtils.java @@ -0,0 +1,12 @@ +package com.iqser.red.service.persistence.management.v1.processor.utils; + +import java.util.Locale; + +public class SnakeCaseUtils { + + public static String toSnakeCase(String input) { + + return input.replaceAll("([a-z])([A-Z])", "$1_$2").replaceAll("([A-Z]+)([A-Z][a-z])", "$1_$2").replaceAll("[\\s-]+", "_").toLowerCase(Locale.getDefault()); + } + +} diff --git a/persistence-service-v1/persistence-service-processor-v1/src/main/resources/db/changelog/db.changelog-tenant.yaml b/persistence-service-v1/persistence-service-processor-v1/src/main/resources/db/changelog/db.changelog-tenant.yaml index d79a21be9..ecc6424e1 100644 --- a/persistence-service-v1/persistence-service-processor-v1/src/main/resources/db/changelog/db.changelog-tenant.yaml +++ b/persistence-service-v1/persistence-service-processor-v1/src/main/resources/db/changelog/db.changelog-tenant.yaml @@ -209,3 +209,5 @@ databaseChangeLog: file: db/changelog/tenant/128-add-component-mapping-versions-to-file.yaml - include: file: db/changelog/tenant/126-add-uuid-to-download-status.yaml + - include: + file: db/changelog/tenant/129-add-component-table.yaml \ No newline at end of file diff --git a/persistence-service-v1/persistence-service-processor-v1/src/main/resources/db/changelog/tenant/129-add-component-table.yaml b/persistence-service-v1/persistence-service-processor-v1/src/main/resources/db/changelog/tenant/129-add-component-table.yaml new file mode 100644 index 000000000..0f72e721b --- /dev/null +++ b/persistence-service-v1/persistence-service-processor-v1/src/main/resources/db/changelog/tenant/129-add-component-table.yaml @@ -0,0 +1,51 @@ +databaseChangeLog: + - changeSet: + id: 129-add-component-table + author: aisvoran + changes: + - createTable: + tableName: component_definition + columns: + - column: + name: id + type: VARCHAR(255) + constraints: + primaryKey: true + primaryKeyName: component_definition_pkey + nullable: false + - column: + name: dossier_template_id + type: VARCHAR(255) + constraints: + nullable: false + - column: + name: technical_name + type: VARCHAR(255) + constraints: + nullable: false + - column: + name: display_name + type: VARCHAR(255) + constraints: + nullable: false + - column: + name: description + type: TEXT + - column: + name: rank + type: INTEGER + - column: + name: soft_delete_time + type: TIMESTAMP + - addUniqueConstraint: + columnNames: technical_name, dossier_template_id + tableName: component_definition + constraintName: uq_component_definition_technical_name_template + - addForeignKeyConstraint: + baseTableName: component_definition + baseColumnNames: dossier_template_id + constraintName: fk_component_definition_dossier_template + referencedTableName: dossier_template + referencedColumnNames: id + onDelete: CASCADE + onUpdate: CASCADE \ No newline at end of file diff --git a/persistence-service-v1/persistence-service-server-v1/src/test/java/com/iqser/red/service/peristence/v1/server/integration/client/DossierTemplateExternalClient.java b/persistence-service-v1/persistence-service-server-v1/src/test/java/com/iqser/red/service/peristence/v1/server/integration/client/DossierTemplateExternalClient.java new file mode 100644 index 000000000..e54797ed1 --- /dev/null +++ b/persistence-service-v1/persistence-service-server-v1/src/test/java/com/iqser/red/service/peristence/v1/server/integration/client/DossierTemplateExternalClient.java @@ -0,0 +1,10 @@ +package com.iqser.red.service.peristence.v1.server.integration.client; + +import org.springframework.cloud.openfeign.FeignClient; + +import com.iqser.red.service.persistence.service.v2.api.external.resource.DossierTemplateResource; + +@FeignClient(name = "DossierTemplateExternalClient", url = "http://localhost:${server.port}", configuration = FeignSupportConfig.class) +public interface DossierTemplateExternalClient extends DossierTemplateResource { + +} diff --git a/persistence-service-v1/persistence-service-server-v1/src/test/java/com/iqser/red/service/peristence/v1/server/integration/tests/ComponentDefinitionTests.java b/persistence-service-v1/persistence-service-server-v1/src/test/java/com/iqser/red/service/peristence/v1/server/integration/tests/ComponentDefinitionTests.java new file mode 100644 index 000000000..63b29ed65 --- /dev/null +++ b/persistence-service-v1/persistence-service-server-v1/src/test/java/com/iqser/red/service/peristence/v1/server/integration/tests/ComponentDefinitionTests.java @@ -0,0 +1,289 @@ +package com.iqser.red.service.peristence.v1.server.integration.tests; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import com.iqser.red.service.peristence.v1.server.integration.client.DossierTemplateExternalClient; +import com.iqser.red.service.peristence.v1.server.integration.service.DossierTemplateTesterAndProvider; +import com.iqser.red.service.peristence.v1.server.integration.utils.AbstractPersistenceServerServiceTest; +import com.iqser.red.service.persistence.service.v1.api.shared.model.DossierTemplateModel; +import com.iqser.red.service.persistence.service.v1.api.shared.model.component.ComponentDefinitionAddRequest; +import com.iqser.red.service.persistence.service.v1.api.shared.model.component.ComponentDefinitionUpdateRequest; + +import feign.FeignException; + +public class ComponentDefinitionTests extends AbstractPersistenceServerServiceTest { + + @Autowired + private DossierTemplateTesterAndProvider dossierTemplateTesterAndProvider; + + @Autowired + private DossierTemplateExternalClient dossierTemplateExternalClient; + + + @Test + public void testCreateComponentDefinition() { + + var dossierTemplate = dossierTemplateTesterAndProvider.provideTestTemplate(); + var componentDefinitionAddRequest = buildComponentDefinitionAddRequest(dossierTemplate); + + var response = dossierTemplateExternalClient.createComponents(dossierTemplate.getId(), List.of(componentDefinitionAddRequest)); + + assertEquals(response.size(), 1); + assertEquals(response.get(0).getRank(), 1); + assertEquals(response.get(0).getDescription(), componentDefinitionAddRequest.getDescription()); + assertEquals(response.get(0).getDisplayName(), componentDefinitionAddRequest.getDisplayName()); + assertEquals(response.get(0).getTechnicalName(), componentDefinitionAddRequest.getTechnicalName()); + assertNull(response.get(0).getSoftDeleteTime()); + } + + + private ComponentDefinitionAddRequest buildComponentDefinitionAddRequest(DossierTemplateModel dossierTemplate) { + + return ComponentDefinitionAddRequest.builder() + .dossierTemplateId(dossierTemplate.getId()) + .technicalName("component_1") + .displayName("Component 1") + .description("Description") + .build(); + } + + + @Test + public void testGetComponentDefinition() { + + var dossierTemplate = dossierTemplateTesterAndProvider.provideTestTemplate(); + var componentDefinitionAddRequest = buildComponentDefinitionAddRequest(dossierTemplate); + + var response = dossierTemplateExternalClient.createComponents(dossierTemplate.getId(), List.of(componentDefinitionAddRequest)); + assertEquals(response.size(), 1); + + var component = dossierTemplateExternalClient.getComponent(dossierTemplate.getId(), response.get(0).getId()); + assertEquals(component.getRank(), 1); + assertEquals(component.getDescription(), componentDefinitionAddRequest.getDescription()); + assertEquals(component.getDisplayName(), componentDefinitionAddRequest.getDisplayName()); + assertEquals(component.getTechnicalName(), componentDefinitionAddRequest.getTechnicalName()); + assertNull(component.getSoftDeleteTime()); + + var componentDefinitionAddRequest2 = ComponentDefinitionAddRequest.builder() + .dossierTemplateId(dossierTemplate.getId()) + .technicalName("component_2") + .displayName("Component 2") + .description("Description") + .build(); + + response = dossierTemplateExternalClient.createComponents(dossierTemplate.getId(), List.of(componentDefinitionAddRequest2)); + assertEquals(response.size(), 1); + + var components = dossierTemplateExternalClient.getComponents(dossierTemplate.getId(), false); + assertEquals(components.size(), 2); + + dossierTemplateExternalClient.deleteComponents(dossierTemplate.getId(), List.of(response.get(0).getId())); + component = dossierTemplateExternalClient.getComponent(dossierTemplate.getId(), response.get(0).getId()); + assertEquals(component.getRank(), 2); + assertNotNull(component.getSoftDeleteTime()); + + components = dossierTemplateExternalClient.getComponents(dossierTemplate.getId(), false); + assertEquals(components.size(), 1); + } + + + @Test + public void testUpdateComponentDefinition() { + + var dossierTemplate = dossierTemplateTesterAndProvider.provideTestTemplate(); + var componentDefinitionAddRequest = buildComponentDefinitionAddRequest(dossierTemplate); + + var response = dossierTemplateExternalClient.createComponents(dossierTemplate.getId(), List.of(componentDefinitionAddRequest)); + assertEquals(response.size(), 1); + + var componentDefinitionUpdateRequest = ComponentDefinitionUpdateRequest.builder() + .id(response.get(0).getId()) + .description("updated description") + .displayName("updated display name") + .build(); + var updatedComponents = dossierTemplateExternalClient.updateComponents(dossierTemplate.getId(), List.of(componentDefinitionUpdateRequest)); + assertEquals(updatedComponents.size(), 1); + assertEquals(updatedComponents.get(0).getRank(), 1); + assertEquals(updatedComponents.get(0).getDescription(), componentDefinitionUpdateRequest.getDescription()); + assertEquals(updatedComponents.get(0).getDisplayName(), componentDefinitionUpdateRequest.getDisplayName()); + assertEquals(updatedComponents.get(0).getTechnicalName(), componentDefinitionAddRequest.getTechnicalName()); + assertNull(updatedComponents.get(0).getSoftDeleteTime()); + } + + + @Test + public void testSoftDeleteComponentDefinition() { + + var dossierTemplate = dossierTemplateTesterAndProvider.provideTestTemplate(); + var componentDefinitionAddRequest = buildComponentDefinitionAddRequest(dossierTemplate); + + var response = dossierTemplateExternalClient.createComponents(dossierTemplate.getId(), List.of(componentDefinitionAddRequest)); + assertEquals(response.size(), 1); + + dossierTemplateExternalClient.deleteComponents(dossierTemplate.getId(), List.of(response.get(0).getId())); + + var softDeletedComponent = dossierTemplateExternalClient.getComponent(dossierTemplate.getId(), response.get(0).getId()); + assertNotNull(softDeletedComponent.getSoftDeleteTime()); + } + + + @Test + public void testRestoreDeletedComponentDefinitions() { + + var dossierTemplate = dossierTemplateTesterAndProvider.provideTestTemplate(); + var componentDefinitionAddRequest = ComponentDefinitionAddRequest.builder() + .dossierTemplateId(dossierTemplate.getId()) + .technicalName("Component 1") + .displayName("Component 1") + .description("Description") + .build(); + var componentDefinitionAddRequest2 = ComponentDefinitionAddRequest.builder() + .dossierTemplateId(dossierTemplate.getId()) + .technicalName("Component 2") + .displayName("Component 2") + .description("Description") + .build(); + var componentDefinitionAddRequest3 = ComponentDefinitionAddRequest.builder() + .dossierTemplateId(dossierTemplate.getId()) + .technicalName("Component 3") + .displayName("Component 3") + .description("Description") + .build(); + + var response = dossierTemplateExternalClient.createComponents(dossierTemplate.getId(), + List.of(componentDefinitionAddRequest, componentDefinitionAddRequest2, componentDefinitionAddRequest3)); + assertEquals(response.size(), 3); + + dossierTemplateExternalClient.deleteComponents(dossierTemplate.getId(), List.of(response.get(0).getId())); + + var softDeletedComponent = dossierTemplateExternalClient.getComponent(dossierTemplate.getId(), response.get(0).getId()); + assertNotNull(softDeletedComponent.getSoftDeleteTime()); + + var restoredComponents = dossierTemplateExternalClient.restoreComponents(dossierTemplate.getId(), List.of(response.get(0).getId())); + assertEquals(restoredComponents.size(), 1); + assertEquals(restoredComponents.get(0).getId(), response.get(0).getId()); + assertEquals(restoredComponents.get(0).getRank(), 4); + assertNull(restoredComponents.get(0).getSoftDeleteTime()); + } + + + @Test + public void createComponentDefinitionsWithDifferentDossierTemplateIds() { + + var dossierTemplate = dossierTemplateTesterAndProvider.provideTestTemplate("dossier1"); + var dossierTemplate2 = dossierTemplateTesterAndProvider.provideTestTemplate("dossier2"); + var componentDefinitionAddRequest = buildComponentDefinitionAddRequest(dossierTemplate); + var componentDefinitionAddRequest2 = buildComponentDefinitionAddRequest(dossierTemplate2); + + var error = assertThrows(FeignException.class, + () -> dossierTemplateExternalClient.createComponents(dossierTemplate.getId(), + List.of(componentDefinitionAddRequest, componentDefinitionAddRequest2))); + assertTrue(error.getMessage().contains("All components must have the same dossierTemplateId.")); + } + + + @Test + public void testSnakeCaseTechnicalName() { + + var dossierTemplate = dossierTemplateTesterAndProvider.provideTestTemplate("dossier1"); + var componentDefinitionAddRequest = ComponentDefinitionAddRequest.builder() + .dossierTemplateId(dossierTemplate.getId()) + .technicalName("Component 1") + .displayName("Component 1") + .description("Description") + .build(); + + var response = dossierTemplateExternalClient.createComponents(dossierTemplate.getId(), List.of(componentDefinitionAddRequest)); + assertEquals(response.size(), 1); + assertEquals(response.get(0).getTechnicalName(), "component_1"); + } + + + @Test + public void testGetAllComponentsForADossierTemplate() { + + var dossierTemplate = dossierTemplateTesterAndProvider.provideTestTemplate("dossier1"); + var componentDefinitionAddRequest = ComponentDefinitionAddRequest.builder() + .dossierTemplateId(dossierTemplate.getId()) + .technicalName("Component 1") + .displayName("Component 1") + .description("Description") + .build(); + var componentDefinitionAddRequest2 = ComponentDefinitionAddRequest.builder() + .dossierTemplateId(dossierTemplate.getId()) + .technicalName("Component 2") + .displayName("Component 2") + .description("Description") + .build(); + var componentDefinitionAddRequest3 = ComponentDefinitionAddRequest.builder() + .dossierTemplateId(dossierTemplate.getId()) + .technicalName("Component 3") + .displayName("Component 3") + .description("Description") + .build(); + + var response = dossierTemplateExternalClient.createComponents(dossierTemplate.getId(), + List.of(componentDefinitionAddRequest, componentDefinitionAddRequest2, componentDefinitionAddRequest3)); + assertEquals(response.size(), 3); + + var components = dossierTemplateExternalClient.getComponents(dossierTemplate.getId(), false); + assertEquals(components.size(), 3); + assertEquals(components.get(0).getId(), response.get(0).getId()); + assertEquals(components.get(0).getRank(), response.get(0).getRank()); + assertEquals(components.get(1).getId(), response.get(1).getId()); + assertEquals(components.get(1).getRank(), response.get(1).getRank()); + assertEquals(components.get(2).getId(), response.get(2).getId()); + assertEquals(components.get(2).getRank(), response.get(2).getRank()); + } + + + @Test + public void testReorderComponentDefinitions() { + + var dossierTemplate = dossierTemplateTesterAndProvider.provideTestTemplate("dossier1"); + var componentDefinitionAddRequest = ComponentDefinitionAddRequest.builder() + .dossierTemplateId(dossierTemplate.getId()) + .technicalName("Component 1") + .displayName("Component 1") + .description("Description") + .build(); + var componentDefinitionAddRequest2 = ComponentDefinitionAddRequest.builder() + .dossierTemplateId(dossierTemplate.getId()) + .technicalName("Component 2") + .displayName("Component 2") + .description("Description") + .build(); + var componentDefinitionAddRequest3 = ComponentDefinitionAddRequest.builder() + .dossierTemplateId(dossierTemplate.getId()) + .technicalName("Component 3") + .displayName("Component 3") + .description("Description") + .build(); + + var response = dossierTemplateExternalClient.createComponents(dossierTemplate.getId(), + List.of(componentDefinitionAddRequest, componentDefinitionAddRequest2, componentDefinitionAddRequest3)); + assertEquals(response.size(), 3); + String firstComponentId = response.get(0).getId(); + String secondComponentId = response.get(1).getId(); + String thirdComponentId = response.get(2).getId(); + + var newOrder = dossierTemplateExternalClient.reorderComponents(dossierTemplate.getId(), List.of(secondComponentId, thirdComponentId, firstComponentId)); + assertEquals(newOrder.size(), 3); + assertEquals(newOrder.get(0).getId(), secondComponentId); + assertEquals(newOrder.get(0).getRank(), 1); + assertEquals(newOrder.get(1).getId(), thirdComponentId); + assertEquals(newOrder.get(1).getRank(), 2); + assertEquals(newOrder.get(2).getId(), firstComponentId); + assertEquals(newOrder.get(2).getRank(), 3); + } + +} diff --git a/persistence-service-v1/persistence-service-shared-api-v1/src/main/java/com/iqser/red/service/persistence/service/v1/api/shared/model/component/ComponentDefinition.java b/persistence-service-v1/persistence-service-shared-api-v1/src/main/java/com/iqser/red/service/persistence/service/v1/api/shared/model/component/ComponentDefinition.java new file mode 100644 index 000000000..08b623b5f --- /dev/null +++ b/persistence-service-v1/persistence-service-shared-api-v1/src/main/java/com/iqser/red/service/persistence/service/v1/api/shared/model/component/ComponentDefinition.java @@ -0,0 +1,25 @@ +package com.iqser.red.service.persistence.service.v1.api.shared.model.component; + +import java.time.OffsetDateTime; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class ComponentDefinition { + + private String id; + private String dossierTemplateId; + private String technicalName; + private String displayName; + private String description; + private Integer rank; + private OffsetDateTime softDeleteTime; + +} + diff --git a/persistence-service-v1/persistence-service-shared-api-v1/src/main/java/com/iqser/red/service/persistence/service/v1/api/shared/model/component/ComponentDefinitionAddRequest.java b/persistence-service-v1/persistence-service-shared-api-v1/src/main/java/com/iqser/red/service/persistence/service/v1/api/shared/model/component/ComponentDefinitionAddRequest.java new file mode 100644 index 000000000..aff76eda2 --- /dev/null +++ b/persistence-service-v1/persistence-service-shared-api-v1/src/main/java/com/iqser/red/service/persistence/service/v1/api/shared/model/component/ComponentDefinitionAddRequest.java @@ -0,0 +1,32 @@ +package com.iqser.red.service.persistence.service.v1.api.shared.model.component; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.NonNull; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +@Schema(description = "Object containing information about a component.") +public class ComponentDefinitionAddRequest { + + @NonNull + @Schema(description = "The dossierTemplateId for this component.") + private String dossierTemplateId; + + @NonNull + @Schema(description = "The technical name of the component in snake case. Must be unique within the same dossier template.") + private String technicalName; + + @NonNull + @Schema(description = "The display name of the component.") + private String displayName; + + @Schema(description = "The component's description (optional).") + private String description; + +} diff --git a/persistence-service-v1/persistence-service-shared-api-v1/src/main/java/com/iqser/red/service/persistence/service/v1/api/shared/model/component/ComponentDefinitionUpdateRequest.java b/persistence-service-v1/persistence-service-shared-api-v1/src/main/java/com/iqser/red/service/persistence/service/v1/api/shared/model/component/ComponentDefinitionUpdateRequest.java new file mode 100644 index 000000000..89f6fe8f6 --- /dev/null +++ b/persistence-service-v1/persistence-service-shared-api-v1/src/main/java/com/iqser/red/service/persistence/service/v1/api/shared/model/component/ComponentDefinitionUpdateRequest.java @@ -0,0 +1,28 @@ +package com.iqser.red.service.persistence.service.v1.api.shared.model.component; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.NonNull; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +@Schema(description = "Object containing information about a component.") +public class ComponentDefinitionUpdateRequest { + + @Schema(description = "The id of the component.") + private String id; + + @NonNull + @Schema(description = "The display name of the component.") + private String displayName; + + @Schema(description = "The component's description.") + private String description; + +} +