Merge branch 'RED-9142' into 'master'

RED-9142 - Basic component management

Closes RED-9142

See merge request redactmanager/persistence-service!515
This commit is contained in:
Andrei Isvoran 2024-06-05 08:13:25 +02:00
commit fb133a4770
17 changed files with 1503 additions and 154 deletions

View File

@ -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<DossierTemplateModel> 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<DroolsValidationResponse> 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<ComponentDefinition> createComponents(String dossierTemplateId, List<ComponentDefinitionAddRequest> componentDefinitionAddRequests) {
if (componentDefinitionAddRequests.isEmpty()) {
return Collections.emptyList();
}
List<ComponentDefinition> 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<ComponentDefinition> 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<ComponentDefinition> updateComponents(String dossierTemplateId, List<ComponentDefinitionUpdateRequest> componentDefinitionUpdateRequests) {
if (componentDefinitionUpdateRequests.isEmpty()) {
return Collections.emptyList();
}
List<ComponentDefinition> 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<String> componentIds) {
List<ComponentDefinitionEntity> 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<ComponentDefinition> restoreComponents(String dossierTemplateId, List<String> componentIds) {
List<ComponentDefinition> 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<ComponentDefinition> reorderComponents(String dossierTemplateId, List<String> componentIds) {
List<ComponentDefinition> 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<DroolsValidationResponse> 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);
}
}

View File

@ -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 {

View File

@ -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<ComponentDefinition> createComponents(@Valid @PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId,
@RequestBody List<ComponentDefinitionAddRequest> 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<ComponentDefinition> 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<ComponentDefinition> updateComponents(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId,
@Valid @RequestBody List<ComponentDefinitionUpdateRequest> 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<String> 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<ComponentDefinition> restoreComponents(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId, @RequestParam(name = COMPONENT_IDS) List<String> 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<ComponentDefinition> reorderComponents(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId, @RequestParam(name = COMPONENT_IDS) List<String> componentIds);
}

View File

@ -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

View File

@ -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;
}

View File

@ -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<ComponentDefinition> createComponents(List<ComponentDefinitionAddRequest> componentDefinitionAddRequests) {
validateComponentRequest(componentDefinitionAddRequests);
List<ComponentDefinitionEntity> 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<ComponentDefinitionAddRequest> 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<ComponentDefinition> getComponentsByDossierTemplateId(String dossierTemplateId, boolean includeSoftDeleted) {
List<ComponentDefinitionEntity> 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<ComponentDefinition> updateComponents(String dossierTemplateId, List<ComponentDefinitionUpdateRequest> componentDefinitionUpdateRequests) {
List<ComponentDefinitionEntity> 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<ComponentDefinitionEntity> deleteComponents(String dossierTemplateId, List<String> componentIds) {
OffsetDateTime now = OffsetDateTime.now();
return componentDefinitionPersistenceService.softDeleteComponents(dossierTemplateId, componentIds, now);
}
@PreAuthorize("hasAuthority('" + ADD_UPDATE_DICTIONARY_TYPE + "')")
@Transactional
public List<ComponentDefinition> restoreComponents(String dossierTemplateId, List<String> componentIds) {
List<ComponentDefinitionEntity> 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<ComponentDefinition> reorderComponents(String dossierTemplateId, List<String> componentIds) {
List<ComponentDefinition> orderedComponents = new ArrayList<>();
List<ComponentDefinitionEntity> existingComponents = componentDefinitionPersistenceService.findComponentsByDossierTemplateIdExcludeSoftDeleted(dossierTemplateId);
Map<String, ComponentDefinitionEntity> 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;
}
}

View File

@ -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<String> 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<ComponentDefinitionEntity> orderedEntities = componentDefinitionPersistenceService.findByDossierTemplateIdAndNotSoftDeleted(dossierId);
List<String> 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<String> orderedNames) {
List<ComponentLogEntry> componentLogEntries = componentLog.getComponentLogEntries();
List<ComponentLogEntry> sortedLogEntries = new ArrayList<>();
List<ComponentLogEntry> 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<ComponentLogEntry> componentOverrides) {
componentLog.getComponentLogEntries()

View File

@ -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<ComponentDefinitionEntity> softDeleteComponents(String dossierTemplateId, List<String> componentIds, OffsetDateTime softDeleteTime) {
componentDefinitionRepository.updateSoftDeleteForIds(componentIds, dossierTemplateId, softDeleteTime);
return componentDefinitionRepository.findByIdsAndDossierTemplateId(componentIds, dossierTemplateId);
}
@Transactional
public List<ComponentDefinitionEntity> restoreComponents(String dossierTemplateId, List<String> 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<ComponentDefinitionEntity> findComponentsByDossierTemplateId(String dossierTemplateId) {
return componentDefinitionRepository.findByDossierTemplateId(dossierTemplateId);
}
public List<ComponentDefinitionEntity> findComponentsByDossierTemplateIdExcludeSoftDeleted(String dossierTemplateId) {
return componentDefinitionRepository.findByDossierTemplateIdAndSoftDeletedTimeIsNull(dossierTemplateId);
}
public List<ComponentDefinitionEntity> findByDossierTemplateIdAndNotSoftDeleted(String dossierTemplateId) {
return componentDefinitionRepository.findByDossierTemplateIdAndSoftDeletedTimeIsNull(dossierTemplateId);
}
}

View File

@ -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<ComponentDefinitionEntity, Long> {
List<ComponentDefinitionEntity> findByDossierTemplateId(String dossierTemplateId);
Optional<ComponentDefinitionEntity> findByIdAndDossierTemplateId(String componentId, String dossierTemplateId);
@Query("SELECT c FROM ComponentDefinitionEntity c WHERE c.dossierTemplateId = :dossierTemplateId AND c.softDeleteTime IS NULL")
List<ComponentDefinitionEntity> 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<String> 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<ComponentDefinitionEntity> findByIdsAndDossierTemplateId(@Param("ids") List<String> 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);
}

View File

@ -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());
}
}

View File

@ -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

View File

@ -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

View File

@ -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 {
}

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}