diff --git a/CLAUDE.md b/CLAUDE.md index 77fadf1..aed1ec8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -106,10 +106,10 @@ Application Form │ ├── processingPurpose │ └── employeeDataCategory └── visibilityCondition (FormElementVisibilityCondition) - ├── conditionType (SHOW, HIDE) + ├── formElementConditionType (SHOW, HIDE) ├── sourceFormElementReference (string - reference key of source element) - ├── expectedValue (string - value to compare against) - └── operator (EQUALS, NOT_EQUALS, IS_EMPTY, IS_NOT_EMPTY) + ├── formElementExpectedValue (string - value to compare against) + └── formElementOperator (EQUALS, NOT_EQUALS, IS_EMPTY, IS_NOT_EMPTY) ``` **Dynamic Addition**: Users can add new form elements to any subsection at runtime via the API endpoint: @@ -237,10 +237,10 @@ Form elements can be conditionally shown or hidden based on the values of other } ], "visibilityCondition": { - "conditionType": "SHOW", + "formElementConditionType": "SHOW", "sourceFormElementReference": "testphase_findet_statt", - "expectedValue": "Ja", - "operator": "EQUALS" + "formElementExpectedValue": "Ja", + "formElementOperator": "EQUALS" } } ``` @@ -269,6 +269,106 @@ In this example, the "Testphase Zeitraum" field is only visible when the form el - For checkboxes, checked = "true", unchecked = "false" - Test visibility conditions thoroughly before deployment +### 11. Dynamic Section Spawning + +Form elements can trigger the dynamic creation of full sections based on user input. This enables complex workflows where additional form sections appear when certain conditions are met. + +**Key Concepts**: + +- **Section Templates**: Pre-defined section blueprints marked with `isTemplate: true` +- **Spawn Triggers**: Rules on form elements that define when to spawn a section template +- **Clonable Elements**: Form elements that can be duplicated by users (e.g., adding multiple modules) +- **Title Interpolation**: Section titles can include placeholders like `{{triggerValue}}` + +**Section Template Properties**: + +- `isTemplate: boolean` - If true, section is a template (excluded from display/PDF) +- `templateReference: string` - Unique identifier for the template +- `titleTemplate: string` - Title with placeholder (e.g., "Modul: {{triggerValue}}") +- `spawnedFromElementReference: string` - Links spawned instance to trigger element + +**Form Element Properties for Spawning**: + +- `sectionSpawnTrigger`: Defines spawn conditions + - `templateReference`: Which template to spawn + - `sectionSpawnConditionType`: SHOW or HIDE + - `sectionSpawnExpectedValue`: Value to trigger spawning + - `sectionSpawnOperator`: EQUALS, NOT_EQUALS, IS_EMPTY, IS_NOT_EMPTY +- `isClonable: boolean` - Shows "Add another" button when true + +**Example - Clonable Module with Dynamic Section**: + +```json +{ + "reference": "modul_1", + "title": "Modulname", + "type": "TEXTAREA", + "isClonable": true, + "sectionSpawnTrigger": { + "templateReference": "module_details_template", + "sectionSpawnConditionType": "SHOW", + "sectionSpawnOperator": "IS_NOT_EMPTY" + } +} +``` + +**Example - Template Section**: + +```json +{ + "title": "Moduldetails", + "isTemplate": true, + "templateReference": "module_details_template", + "titleTemplate": "Modul: {{triggerValue}}", + "formElementSubSections": [ + { + "title": "Modulinformationen", + "formElements": [...] + } + ] +} +``` + +**Client-Side Spawning Flow**: + +1. User fills trigger element (e.g., types "SAP Finance") +2. Frontend clones template into CreateFormElementSectionDto (no ID) +3. Title interpolated: "Modul: SAP Finance" +4. User clicks "Add another" → new element clone created +5. User saves → backend generates IDs + +**Element Cloning Implementation**: + +- **Deep Cloning**: Elements are deep-cloned using `JSON.parse(JSON.stringify())` to avoid shared references between cloned elements +- **Reference Generation**: Cloned elements get auto-generated references (e.g., `modul_1` → `modul_2` → `modul_3`) +- **Value Reset**: TEXTAREA and TEXTFIELD elements have their values reset to empty string when cloned +- **ID Removal**: Cloned elements don't have IDs (they're `CreateFormElementDto`), IDs are generated by the backend on save +- **Independent Instances**: Each cloned element is completely independent - updating one doesn't affect others + +**Spawn Trigger Evaluation**: + +- Spawn triggers are evaluated when form element values change +- Each element with a unique reference can spawn its own section independently +- Multiple spawns are handled sequentially using `resultSections` to ensure correct state +- Spawned sections are linked to their trigger element via `spawnedFromElementReference` + +**Element Update Matching**: + +- Form element updates use unique identifiers (`id` or `reference`) instead of array indices +- This prevents cross-element updates when elements are filtered by visibility +- Matching priority: `id` (if present) → `reference` (fallback) + +**Backend Behavior**: + +- Template sections (`isTemplate=true`) are filtered out from PDF/HTML exports +- Spawned sections are included in exports with their interpolated titles +- Versioning captures all template and spawn trigger fields + +**Frontend Composables**: + +- `useSectionSpawning`: Handles spawn trigger evaluation and template cloning +- `useClonableElements`: Handles element cloning with reference generation and deep cloning + --- ## Project Structure @@ -303,9 +403,12 @@ legalconsenthub/ │ │ ├── complianceMap.ts │ │ ├── useApplicationFormNavigation.ts │ │ ├── useApplicationFormValidator.ts +│ │ ├── useClonableElements.ts │ │ ├── useFormElementManagement.ts +│ │ ├── useFormElementVisibility.ts │ │ ├── useFormStepper.ts │ │ ├── usePermissions.ts +│ │ ├── useSectionSpawning.ts │ │ └── useServerHealth.ts │ ├── layouts/ # Layout components │ │ ├── auth.vue diff --git a/api/legalconsenthub.yml b/api/legalconsenthub.yml index 0ac9366..25bfe81 100644 --- a/api/legalconsenthub.yml +++ b/api/legalconsenthub.yml @@ -46,7 +46,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/CreateApplicationFormDto" + $ref: "#/components/schemas/ApplicationFormDto" responses: "201": description: Successfully created application form @@ -256,7 +256,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/CreateFormElementDto" + $ref: "#/components/schemas/FormElementDto" responses: "201": description: Form element successfully added @@ -406,7 +406,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/CreateApplicationFormDto" + $ref: "#/components/schemas/ApplicationFormDto" responses: "201": description: Successfully created application form template @@ -1088,63 +1088,44 @@ components: ApplicationFormDto: type: object required: - - id - name - formElementSections - isTemplate - - organizationId - - createdBy - - lastModifiedBy - - createdAt - - modifiedAt - - status properties: id: type: string format: uuid + nullable: true name: type: string formElementSections: type: array items: $ref: "#/components/schemas/FormElementSectionDto" - isTemplate: - type: boolean - organizationId: - type: string - createdBy: - $ref: "#/components/schemas/UserDto" - lastModifiedBy: - $ref: "#/components/schemas/UserDto" - createdAt: - type: string - format: date-time - modifiedAt: - type: string - format: date-time - status: - $ref: "#/components/schemas/ApplicationFormStatus" - - CreateApplicationFormDto: - required: - - name - - formElementSections - - isTemplate - type: object - properties: - name: - type: string - formElementSections: - type: array - items: - $ref: "#/components/schemas/CreateFormElementSectionDto" isTemplate: type: boolean default: false organizationId: type: string + nullable: true + createdBy: + $ref: "#/components/schemas/UserDto" + nullable: true + lastModifiedBy: + $ref: "#/components/schemas/UserDto" + nullable: true + createdAt: + type: string + format: date-time + nullable: true + modifiedAt: + type: string + format: date-time + nullable: true status: - $ref: "#/components/schemas/ApplicationFormStatus" + allOf: + - $ref: "#/components/schemas/ApplicationFormStatus" + nullable: true PagedApplicationFormDto: type: object @@ -1249,14 +1230,13 @@ components: FormElementSectionDto: type: object required: - - id - title - formElementSubSections - - applicationFormId properties: id: type: string format: uuid + nullable: true title: type: string shortTitle: @@ -1270,6 +1250,20 @@ components: applicationFormId: type: string format: uuid + nullable: true + isTemplate: + type: boolean + default: false + description: If true, this section is a template for spawning + templateReference: + type: string + description: Unique reference key for this template section + titleTemplate: + type: string + description: Title template with placeholder (e.g., "Modul{{triggerValue}}") + spawnedFromElementReference: + type: string + description: Reference of the form element that triggered this section FormElementSectionSnapshotDto: type: object @@ -1287,38 +1281,26 @@ components: type: array items: $ref: "#/components/schemas/FormElementSubSectionSnapshotDto" - - CreateFormElementSectionDto: - type: object - required: - - title - - formElementSubSections - properties: - id: + isTemplate: + type: boolean + default: false + templateReference: type: string - format: uuid - title: + titleTemplate: type: string - shortTitle: + spawnedFromElementReference: type: string - description: - type: string - formElementSubSections: - type: array - items: - $ref: "#/components/schemas/CreateFormElementSubSectionDto" FormElementSubSectionDto: type: object required: - - id - title - formElements - - formElementSectionId properties: id: type: string format: uuid + nullable: true title: type: string subtitle: @@ -1330,6 +1312,7 @@ components: formElementSectionId: type: string format: uuid + nullable: true FormElementSubSectionSnapshotDto: type: object @@ -1346,32 +1329,16 @@ components: items: $ref: "#/components/schemas/FormElementSnapshotDto" - CreateFormElementSubSectionDto: - type: object - required: - - title - - formElements - properties: - title: - type: string - subtitle: - type: string - formElements: - type: array - items: - $ref: "#/components/schemas/CreateFormElementDto" - FormElementDto: type: object required: - - id - options - type - - formElementSubSectionId properties: id: type: string format: uuid + nullable: true reference: type: string description: Unique reference key for this form element (e.g., "art_der_massnahme") @@ -1388,8 +1355,15 @@ components: formElementSubSectionId: type: string format: uuid + nullable: true visibilityCondition: $ref: "#/components/schemas/FormElementVisibilityCondition" + sectionSpawnTrigger: + $ref: "#/components/schemas/SectionSpawnTriggerDto" + isClonable: + type: boolean + default: false + description: If true, user can add more instances of this element FormElementSnapshotDto: type: object @@ -1411,27 +1385,11 @@ components: $ref: "#/components/schemas/FormOptionDto" visibilityCondition: $ref: "#/components/schemas/FormElementVisibilityCondition" - - CreateFormElementDto: - type: object - required: - - options - - type - properties: - reference: - type: string - title: - type: string - description: - type: string - options: - type: array - items: - $ref: "#/components/schemas/FormOptionDto" - type: - $ref: "#/components/schemas/FormElementType" - visibilityCondition: - $ref: "#/components/schemas/FormElementVisibilityCondition" + sectionSpawnTrigger: + $ref: "#/components/schemas/SectionSpawnTriggerDto" + isClonable: + type: boolean + default: false FormOptionDto: type: object @@ -1465,19 +1423,19 @@ components: FormElementVisibilityCondition: type: object required: - - conditionType + - formElementConditionType - sourceFormElementReference - - expectedValue + - formElementExpectedValue properties: - conditionType: + formElementConditionType: $ref: "#/components/schemas/VisibilityConditionType" sourceFormElementReference: type: string description: Reference key of the source form element to check - expectedValue: + formElementExpectedValue: type: string description: Expected value to compare against the source element's value property - operator: + formElementOperator: $ref: "#/components/schemas/VisibilityConditionOperator" default: EQUALS @@ -1495,6 +1453,24 @@ components: - IS_EMPTY - IS_NOT_EMPTY + SectionSpawnTriggerDto: + type: object + required: + - templateReference + - sectionSpawnConditionType + - sectionSpawnOperator + properties: + templateReference: + type: string + description: Reference key of the section template to spawn + sectionSpawnConditionType: + $ref: "#/components/schemas/VisibilityConditionType" + sectionSpawnExpectedValue: + type: string + description: Expected value to trigger spawning + sectionSpawnOperator: + $ref: "#/components/schemas/VisibilityConditionOperator" + ####### UserDto ####### UserDto: type: object diff --git a/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/application_form/ApplicationFormController.kt b/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/application_form/ApplicationFormController.kt index c0f97ba..4f3e0b2 100644 --- a/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/application_form/ApplicationFormController.kt +++ b/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/application_form/ApplicationFormController.kt @@ -2,8 +2,7 @@ package com.betriebsratkanzlei.legalconsenthub.application_form import com.betriebsratkanzlei.legalconsenthub_api.api.ApplicationFormApi import com.betriebsratkanzlei.legalconsenthub_api.model.ApplicationFormDto -import com.betriebsratkanzlei.legalconsenthub_api.model.CreateApplicationFormDto -import com.betriebsratkanzlei.legalconsenthub_api.model.CreateFormElementDto +import com.betriebsratkanzlei.legalconsenthub_api.model.FormElementDto import com.betriebsratkanzlei.legalconsenthub_api.model.PagedApplicationFormDto import org.springframework.core.io.ByteArrayResource import org.springframework.core.io.Resource @@ -24,13 +23,11 @@ class ApplicationFormController( @PreAuthorize( "hasAnyRole('CHIEF_EXECUTIVE_OFFICER', 'BUSINESS_DEPARTMENT', 'IT_DEPARTMENT', 'HUMAN_RESOURCES', 'HEAD_OF_WORKS_COUNCIL', 'WORKS_COUNCIL')", ) - override fun createApplicationForm( - createApplicationFormDto: CreateApplicationFormDto, - ): ResponseEntity { - val updatedCreateApplicationFormDto = createApplicationFormDto.copy(isTemplate = false) + override fun createApplicationForm(applicationFormDto: ApplicationFormDto): ResponseEntity { + val updatedApplicationFormDto = applicationFormDto.copy(isTemplate = false) return ResponseEntity.ok( applicationFormMapper.toApplicationFormDto( - applicationFormService.createApplicationForm(updatedCreateApplicationFormDto), + applicationFormService.createApplicationForm(updatedApplicationFormDto), ), ) } @@ -88,7 +85,7 @@ class ApplicationFormController( ): ResponseEntity = ResponseEntity.ok( applicationFormMapper.toApplicationFormDto( - applicationFormService.updateApplicationForm(applicationFormDto), + applicationFormService.updateApplicationForm(id, applicationFormDto), ), ) @@ -117,14 +114,14 @@ class ApplicationFormController( applicationFormId: UUID, subsectionId: UUID, position: Int, - createFormElementDto: CreateFormElementDto, + formElementDto: FormElementDto, ): ResponseEntity = ResponseEntity.status(201).body( applicationFormMapper.toApplicationFormDto( applicationFormService.addFormElementToSubSection( applicationFormId, subsectionId, - createFormElementDto, + formElementDto, position, ), ), diff --git a/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/application_form/ApplicationFormFormatService.kt b/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/application_form/ApplicationFormFormatService.kt index 180a2f8..aec106c 100644 --- a/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/application_form/ApplicationFormFormatService.kt +++ b/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/application_form/ApplicationFormFormatService.kt @@ -43,38 +43,45 @@ class ApplicationFormFormatService( val visibilityMap = evaluateVisibility(formElementsByRef) val filteredSections = - applicationForm.formElementSections.mapNotNull { section -> - val filteredSubSections = - section.formElementSubSections.mapNotNull { subsection -> - val filteredElements = - subsection.formElements.filter { element -> - visibilityMap[element.id] == true + applicationForm.formElementSections + .filter { !it.isTemplate } + .mapNotNull { section -> + val filteredSubSections = + section.formElementSubSections + .mapNotNull { subsection -> + val filteredElements = + subsection.formElements.filter { element -> + visibilityMap[element.id] == true + } + if (filteredElements.isEmpty()) { + null + } else { + FormElementSubSection( + id = subsection.id, + title = subsection.title, + subtitle = subsection.subtitle, + formElements = filteredElements.toMutableList(), + formElementSection = null, + ) + } } - if (filteredElements.isEmpty()) { - null - } else { - FormElementSubSection( - id = subsection.id, - title = subsection.title, - subtitle = subsection.subtitle, - formElements = filteredElements.toMutableList(), - formElementSection = null, - ) - } + if (filteredSubSections.isEmpty()) { + null + } else { + FormElementSection( + id = section.id, + title = section.title, + shortTitle = section.shortTitle, + description = section.description, + isTemplate = section.isTemplate, + templateReference = section.templateReference, + titleTemplate = section.titleTemplate, + spawnedFromElementReference = section.spawnedFromElementReference, + formElementSubSections = filteredSubSections.toMutableList(), + applicationForm = null, + ) } - if (filteredSubSections.isEmpty()) { - null - } else { - FormElementSection( - id = section.id, - title = section.title, - shortTitle = section.shortTitle, - description = section.description, - formElementSubSections = filteredSubSections.toMutableList(), - applicationForm = null, - ) } - } return ApplicationForm( id = applicationForm.id, @@ -122,9 +129,10 @@ class ApplicationFormFormatService( val sourceElement = formElementsByRef[condition.sourceFormElementReference] ?: return false val sourceValue = getFormElementValue(sourceElement) - val conditionMet = evaluateCondition(sourceValue, condition.expectedValue, condition.operator) + val conditionMet = + evaluateCondition(sourceValue, condition.formElementExpectedValue, condition.formElementOperator) - return when (condition.conditionType) { + return when (condition.formElementConditionType) { VisibilityConditionType.SHOW -> conditionMet VisibilityConditionType.HIDE -> !conditionMet } diff --git a/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/application_form/ApplicationFormMapper.kt b/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/application_form/ApplicationFormMapper.kt index d032eb4..bb342f1 100644 --- a/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/application_form/ApplicationFormMapper.kt +++ b/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/application_form/ApplicationFormMapper.kt @@ -4,9 +4,9 @@ import com.betriebsratkanzlei.legalconsenthub.form_element.FormElementSectionMap import com.betriebsratkanzlei.legalconsenthub.user.UserMapper import com.betriebsratkanzlei.legalconsenthub.user.UserService import com.betriebsratkanzlei.legalconsenthub_api.model.ApplicationFormDto -import com.betriebsratkanzlei.legalconsenthub_api.model.CreateApplicationFormDto import org.springframework.stereotype.Component import java.time.LocalDateTime +import java.util.UUID @Component class ApplicationFormMapper( @@ -16,7 +16,7 @@ class ApplicationFormMapper( ) { fun toApplicationFormDto(applicationForm: ApplicationForm): ApplicationFormDto = ApplicationFormDto( - id = applicationForm.id ?: throw IllegalStateException("ApplicationForm ID must not be null!"), + id = applicationForm.id, name = applicationForm.name, formElementSections = applicationForm.formElementSections.map { @@ -34,45 +34,53 @@ class ApplicationFormMapper( status = applicationForm.status, ) - fun toApplicationForm(applicationForm: ApplicationFormDto): ApplicationForm { - val form = - ApplicationForm( - id = applicationForm.id, - name = applicationForm.name, - isTemplate = applicationForm.isTemplate, - organizationId = applicationForm.organizationId, - status = applicationForm.status, - createdBy = userMapper.toUser(applicationForm.createdBy), - lastModifiedBy = userMapper.toUser(applicationForm.lastModifiedBy), - createdAt = applicationForm.createdAt, - modifiedAt = applicationForm.modifiedAt, - ) - form.formElementSections = - applicationForm.formElementSections - .map { - formElementSectionMapper.toFormElementSection(it, form) - }.toMutableList() - return form - } - - fun toApplicationForm(createApplicationFormDto: CreateApplicationFormDto): ApplicationForm { + fun toNewApplicationForm(applicationFormDto: ApplicationFormDto): ApplicationForm { val currentUser = userService.getCurrentUser() val applicationForm = ApplicationForm( - name = createApplicationFormDto.name, - isTemplate = createApplicationFormDto.isTemplate, - organizationId = createApplicationFormDto.organizationId ?: "", + id = null, + name = applicationFormDto.name, + isTemplate = applicationFormDto.isTemplate, + organizationId = applicationFormDto.organizationId ?: "", status = - createApplicationFormDto.status + applicationFormDto.status ?: com.betriebsratkanzlei.legalconsenthub_api.model.ApplicationFormStatus.DRAFT, createdBy = currentUser, lastModifiedBy = currentUser, ) applicationForm.formElementSections = - createApplicationFormDto.formElementSections - .map { formElementSectionMapper.toFormElementSection(it, applicationForm) } + applicationFormDto.formElementSections + .map { formElementSectionMapper.toNewFormElementSection(it, applicationForm) } .toMutableList() return applicationForm } + + fun toUpdatedApplicationForm( + id: UUID, + applicationFormDto: ApplicationFormDto, + existingApplicationForm: ApplicationForm, + ): ApplicationForm { + val currentUser = userService.getCurrentUser() + + val form = + ApplicationForm( + id = id, + name = applicationFormDto.name, + isTemplate = applicationFormDto.isTemplate, + organizationId = applicationFormDto.organizationId ?: existingApplicationForm.organizationId, + status = applicationFormDto.status ?: existingApplicationForm.status, + createdBy = existingApplicationForm.createdBy, + lastModifiedBy = currentUser, + createdAt = existingApplicationForm.createdAt, + modifiedAt = existingApplicationForm.modifiedAt, + ) + + form.formElementSections = + applicationFormDto.formElementSections + .map { formElementSectionMapper.toFormElementSection(it, form) } + .toMutableList() + + return form + } } diff --git a/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/application_form/ApplicationFormService.kt b/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/application_form/ApplicationFormService.kt index 1c3246d..f5b9a76 100644 --- a/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/application_form/ApplicationFormService.kt +++ b/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/application_form/ApplicationFormService.kt @@ -13,9 +13,8 @@ import com.betriebsratkanzlei.legalconsenthub.notification.NotificationService import com.betriebsratkanzlei.legalconsenthub.user.UserService import com.betriebsratkanzlei.legalconsenthub_api.model.ApplicationFormDto import com.betriebsratkanzlei.legalconsenthub_api.model.ApplicationFormStatus -import com.betriebsratkanzlei.legalconsenthub_api.model.CreateApplicationFormDto -import com.betriebsratkanzlei.legalconsenthub_api.model.CreateFormElementDto import com.betriebsratkanzlei.legalconsenthub_api.model.CreateNotificationDto +import com.betriebsratkanzlei.legalconsenthub_api.model.FormElementDto import com.betriebsratkanzlei.legalconsenthub_api.model.NotificationType import org.springframework.context.ApplicationEventPublisher import org.springframework.data.domain.Page @@ -33,8 +32,8 @@ class ApplicationFormService( private val userService: UserService, private val eventPublisher: ApplicationEventPublisher, ) { - fun createApplicationForm(createApplicationFormDto: CreateApplicationFormDto): ApplicationForm { - val applicationForm = applicationFormMapper.toApplicationForm(createApplicationFormDto) + fun createApplicationForm(applicationFormDto: ApplicationFormDto): ApplicationForm { + val applicationForm = applicationFormMapper.toNewApplicationForm(applicationFormDto) val savedApplicationForm: ApplicationForm try { savedApplicationForm = applicationFormRepository.save(applicationForm) @@ -67,17 +66,26 @@ class ApplicationFormService( return applicationFormRepository.findAllByIsTemplateFalseAndOrganizationId(organizationId, pageable) } - fun updateApplicationForm(applicationFormDto: ApplicationFormDto): ApplicationForm { - val existingApplicationForm = getApplicationFormById(applicationFormDto.id) + fun updateApplicationForm( + id: UUID, + applicationFormDto: ApplicationFormDto, + ): ApplicationForm { + println("Updating ApplicationForm: $applicationFormDto") + val existingApplicationForm = getApplicationFormById(id) val existingSnapshot = versionService.createSnapshot(existingApplicationForm) - val applicationForm = applicationFormMapper.toApplicationForm(applicationFormDto) + val applicationForm = + applicationFormMapper.toUpdatedApplicationForm( + id, + applicationFormDto, + existingApplicationForm, + ) val updatedApplicationForm: ApplicationForm try { updatedApplicationForm = applicationFormRepository.save(applicationForm) } catch (e: Exception) { - throw ApplicationFormNotUpdatedException(e, applicationFormDto.id) + throw ApplicationFormNotUpdatedException(e, id) } val currentUser = userService.getCurrentUser() @@ -160,7 +168,7 @@ class ApplicationFormService( fun addFormElementToSubSection( applicationFormId: UUID, subsectionId: UUID, - createFormElementDto: CreateFormElementDto, + formElementDto: FormElementDto, position: Int, ): ApplicationForm { val applicationForm = getApplicationFormById(applicationFormId) @@ -171,7 +179,7 @@ class ApplicationFormService( .find { it.id == subsectionId } ?: throw IllegalArgumentException("FormElementSubSection with id $subsectionId not found") - val newFormElement = formElementMapper.toFormElement(createFormElementDto, subsection) + val newFormElement = formElementMapper.toNewFormElement(formElementDto, subsection) if (position >= 0 && position < subsection.formElements.size) { subsection.formElements.add(position, newFormElement) diff --git a/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/application_form_template/ApplicationFormTemplateController.kt b/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/application_form_template/ApplicationFormTemplateController.kt index 3934e7f..b86386c 100644 --- a/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/application_form_template/ApplicationFormTemplateController.kt +++ b/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/application_form_template/ApplicationFormTemplateController.kt @@ -4,7 +4,6 @@ import com.betriebsratkanzlei.legalconsenthub.application_form.ApplicationFormMa import com.betriebsratkanzlei.legalconsenthub.application_form.PagedApplicationFormMapper import com.betriebsratkanzlei.legalconsenthub_api.api.ApplicationFormTemplateApi import com.betriebsratkanzlei.legalconsenthub_api.model.ApplicationFormDto -import com.betriebsratkanzlei.legalconsenthub_api.model.CreateApplicationFormDto import com.betriebsratkanzlei.legalconsenthub_api.model.PagedApplicationFormDto import org.springframework.http.ResponseEntity import org.springframework.security.access.prepost.PreAuthorize @@ -21,11 +20,13 @@ class ApplicationFormTemplateController( "hasAnyRole('CHIEF_EXECUTIVE_OFFICER', 'BUSINESS_DEPARTMENT', 'IT_DEPARTMENT', 'HUMAN_RESOURCES', 'HEAD_OF_WORKS_COUNCIL', 'WORKS_COUNCIL')", ) override fun createApplicationFormTemplate( - createApplicationFormDto: CreateApplicationFormDto, + applicationFormDto: ApplicationFormDto, ): ResponseEntity = ResponseEntity.ok( applicationFormMapper.toApplicationFormDto( - applicationFormTemplateService.createApplicationFormTemplate(createApplicationFormDto), + applicationFormTemplateService.createApplicationFormTemplate( + applicationFormDto.copy(isTemplate = true), + ), ), ) @@ -58,7 +59,7 @@ class ApplicationFormTemplateController( ): ResponseEntity = ResponseEntity.ok( applicationFormMapper.toApplicationFormDto( - applicationFormTemplateService.updateApplicationFormTemplate(applicationFormDto), + applicationFormTemplateService.updateApplicationFormTemplate(id, applicationFormDto), ), ) diff --git a/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/application_form_template/ApplicationFormTemplateService.kt b/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/application_form_template/ApplicationFormTemplateService.kt index d732574..631157e 100644 --- a/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/application_form_template/ApplicationFormTemplateService.kt +++ b/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/application_form_template/ApplicationFormTemplateService.kt @@ -8,7 +8,6 @@ import com.betriebsratkanzlei.legalconsenthub.error.ApplicationFormNotDeletedExc import com.betriebsratkanzlei.legalconsenthub.error.ApplicationFormNotFoundException import com.betriebsratkanzlei.legalconsenthub.error.ApplicationFormNotUpdatedException import com.betriebsratkanzlei.legalconsenthub_api.model.ApplicationFormDto -import com.betriebsratkanzlei.legalconsenthub_api.model.CreateApplicationFormDto import org.springframework.data.domain.Page import org.springframework.data.domain.PageRequest import org.springframework.stereotype.Service @@ -19,8 +18,8 @@ class ApplicationFormTemplateService( private val applicationFormRepository: ApplicationFormRepository, private val applicationFormMapper: ApplicationFormMapper, ) { - fun createApplicationFormTemplate(createApplicationFormDto: CreateApplicationFormDto): ApplicationForm { - val applicationForm = applicationFormMapper.toApplicationForm(createApplicationFormDto) + fun createApplicationFormTemplate(applicationFormDto: ApplicationFormDto): ApplicationForm { + val applicationForm = applicationFormMapper.toNewApplicationForm(applicationFormDto) val savedApplicationForm: ApplicationForm try { savedApplicationForm = applicationFormRepository.save(applicationForm) @@ -41,14 +40,23 @@ class ApplicationFormTemplateService( return applicationFormRepository.findAllByIsTemplateTrue(pageable) } - fun updateApplicationFormTemplate(applicationFormDto: ApplicationFormDto): ApplicationForm { - val applicationForm = applicationFormMapper.toApplicationForm(applicationFormDto) + fun updateApplicationFormTemplate( + id: UUID, + applicationFormDto: ApplicationFormDto, + ): ApplicationForm { + val existingApplicationForm = getApplicationFormTemplateById(id) + val applicationForm = + applicationFormMapper.toUpdatedApplicationForm( + id, + applicationFormDto, + existingApplicationForm, + ) val updatedApplicationForm: ApplicationForm try { updatedApplicationForm = applicationFormRepository.save(applicationForm) } catch (e: Exception) { - throw ApplicationFormNotUpdatedException(e, applicationFormDto.id) + throw ApplicationFormNotUpdatedException(e, id) } return updatedApplicationForm diff --git a/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/application_form_version/ApplicationFormVersionService.kt b/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/application_form_version/ApplicationFormVersionService.kt index 2388461..3f62d38 100644 --- a/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/application_form_version/ApplicationFormVersionService.kt +++ b/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/application_form_version/ApplicationFormVersionService.kt @@ -8,6 +8,7 @@ import com.betriebsratkanzlei.legalconsenthub.form_element.FormElement import com.betriebsratkanzlei.legalconsenthub.form_element.FormElementSection import com.betriebsratkanzlei.legalconsenthub.form_element.FormElementSubSection import com.betriebsratkanzlei.legalconsenthub.form_element.FormOption +import com.betriebsratkanzlei.legalconsenthub.form_element.SectionSpawnTriggerMapper import com.betriebsratkanzlei.legalconsenthub.user.User import com.betriebsratkanzlei.legalconsenthub_api.model.ApplicationFormSnapshotDto import com.betriebsratkanzlei.legalconsenthub_api.model.FormElementSectionSnapshotDto @@ -24,6 +25,7 @@ class ApplicationFormVersionService( private val versionRepository: ApplicationFormVersionRepository, private val applicationFormRepository: ApplicationFormRepository, private val objectMapper: ObjectMapper, + private val spawnTriggerMapper: SectionSpawnTriggerMapper, ) { @Transactional fun createVersion( @@ -103,6 +105,10 @@ class ApplicationFormVersionService( title = section.title, shortTitle = section.shortTitle, description = section.description, + isTemplate = section.isTemplate, + templateReference = section.templateReference, + titleTemplate = section.titleTemplate, + spawnedFromElementReference = section.spawnedFromElementReference, subsections = section.formElementSubSections.map { subsection -> FormElementSubSectionSnapshotDto( @@ -111,6 +117,7 @@ class ApplicationFormVersionService( elements = subsection.formElements.map { element -> FormElementSnapshotDto( + reference = element.reference, title = element.title, description = element.description, type = element.type, @@ -123,6 +130,11 @@ class ApplicationFormVersionService( employeeDataCategory = option.employeeDataCategory, ) }, + sectionSpawnTrigger = + element.sectionSpawnTrigger?.let { + spawnTriggerMapper.toSectionSpawnTriggerDto(it) + }, + isClonable = element.isClonable, ) }, ) @@ -140,6 +152,10 @@ class ApplicationFormVersionService( title = sectionSnapshot.title, shortTitle = sectionSnapshot.shortTitle, description = sectionSnapshot.description, + isTemplate = sectionSnapshot.isTemplate ?: false, + templateReference = sectionSnapshot.templateReference, + titleTemplate = sectionSnapshot.titleTemplate, + spawnedFromElementReference = sectionSnapshot.spawnedFromElementReference, applicationForm = applicationForm, ) @@ -154,6 +170,7 @@ class ApplicationFormVersionService( subsectionSnapshot.elements.forEach { elementSnapshot -> val element = FormElement( + reference = elementSnapshot.reference, title = elementSnapshot.title, description = elementSnapshot.description, type = elementSnapshot.type, @@ -168,6 +185,11 @@ class ApplicationFormVersionService( employeeDataCategory = optionDto.employeeDataCategory, ) }.toMutableList(), + sectionSpawnTrigger = + elementSnapshot.sectionSpawnTrigger?.let { + spawnTriggerMapper.toSectionSpawnTrigger(it) + }, + isClonable = elementSnapshot.isClonable ?: false, ) subsection.formElements.add(element) } diff --git a/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/form_element/FormElement.kt b/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/form_element/FormElement.kt index 691ab88..a574fd6 100644 --- a/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/form_element/FormElement.kt +++ b/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/form_element/FormElement.kt @@ -4,6 +4,7 @@ import com.betriebsratkanzlei.legalconsenthub_api.model.FormElementType import jakarta.persistence.CollectionTable import jakarta.persistence.Column import jakarta.persistence.ElementCollection +import jakarta.persistence.Embedded import jakarta.persistence.Entity import jakarta.persistence.GeneratedValue import jakarta.persistence.Id @@ -27,5 +28,9 @@ class FormElement( @ManyToOne @JoinColumn(name = "form_element_sub_section_id", nullable = false) var formElementSubSection: FormElementSubSection? = null, + @Embedded var visibilityCondition: FormElementVisibilityCondition? = null, + @Embedded + var sectionSpawnTrigger: SectionSpawnTrigger? = null, + var isClonable: Boolean = false, ) diff --git a/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/form_element/FormElementMapper.kt b/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/form_element/FormElementMapper.kt index 82d9581..1167083 100644 --- a/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/form_element/FormElementMapper.kt +++ b/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/form_element/FormElementMapper.kt @@ -1,6 +1,5 @@ package com.betriebsratkanzlei.legalconsenthub.form_element -import com.betriebsratkanzlei.legalconsenthub_api.model.CreateFormElementDto import com.betriebsratkanzlei.legalconsenthub_api.model.FormElementDto import org.springframework.stereotype.Component @@ -8,10 +7,11 @@ import org.springframework.stereotype.Component class FormElementMapper( private val formOptionMapper: FormOptionMapper, private val visibilityConditionMapper: FormElementVisibilityConditionMapper, + private val spawnTriggerMapper: SectionSpawnTriggerMapper, ) { fun toFormElementDto(formElement: FormElement): FormElementDto = FormElementDto( - id = formElement.id ?: throw IllegalStateException("FormElement ID must not be null!"), + id = formElement.id, reference = formElement.reference, title = formElement.title, description = formElement.description, @@ -24,6 +24,11 @@ class FormElementMapper( formElement.visibilityCondition?.let { visibilityConditionMapper.toFormElementVisibilityConditionDto(it) }, + sectionSpawnTrigger = + formElement.sectionSpawnTrigger?.let { + spawnTriggerMapper.toSectionSpawnTriggerDto(it) + }, + isClonable = formElement.isClonable, ) fun toFormElement( @@ -42,10 +47,15 @@ class FormElementMapper( formElement.visibilityCondition?.let { visibilityConditionMapper.toFormElementVisibilityCondition(it) }, + sectionSpawnTrigger = + formElement.sectionSpawnTrigger?.let { + spawnTriggerMapper.toSectionSpawnTrigger(it) + }, + isClonable = formElement.isClonable ?: false, ) - fun toFormElement( - formElement: CreateFormElementDto, + fun toNewFormElement( + formElement: FormElementDto, formElementSubSection: FormElementSubSection, ): FormElement = FormElement( @@ -60,5 +70,10 @@ class FormElementMapper( formElement.visibilityCondition?.let { visibilityConditionMapper.toFormElementVisibilityCondition(it) }, + sectionSpawnTrigger = + formElement.sectionSpawnTrigger?.let { + spawnTriggerMapper.toSectionSpawnTrigger(it) + }, + isClonable = formElement.isClonable ?: false, ) } diff --git a/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/form_element/FormElementSection.kt b/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/form_element/FormElementSection.kt index 97010fc..0b94fb9 100644 --- a/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/form_element/FormElementSection.kt +++ b/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/form_element/FormElementSection.kt @@ -21,6 +21,10 @@ class FormElementSection( var title: String, var shortTitle: String? = null, var description: String? = null, + var isTemplate: Boolean = false, + var templateReference: String? = null, + var titleTemplate: String? = null, + var spawnedFromElementReference: String? = null, @OneToMany(mappedBy = "formElementSection", cascade = [CascadeType.ALL], orphanRemoval = true) @OrderColumn(name = "form_element_sub_section_order") var formElementSubSections: MutableList = mutableListOf(), diff --git a/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/form_element/FormElementSectionMapper.kt b/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/form_element/FormElementSectionMapper.kt index 98895a0..486fc75 100644 --- a/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/form_element/FormElementSectionMapper.kt +++ b/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/form_element/FormElementSectionMapper.kt @@ -1,7 +1,6 @@ package com.betriebsratkanzlei.legalconsenthub.form_element import com.betriebsratkanzlei.legalconsenthub.application_form.ApplicationForm -import com.betriebsratkanzlei.legalconsenthub_api.model.CreateFormElementSectionDto import com.betriebsratkanzlei.legalconsenthub_api.model.FormElementSectionDto import org.springframework.stereotype.Component @@ -11,7 +10,7 @@ class FormElementSectionMapper( ) { fun toFormElementSectionDto(formElementSection: FormElementSection): FormElementSectionDto = FormElementSectionDto( - id = formElementSection.id ?: throw IllegalStateException("FormElementSection ID must not be null!"), + id = formElementSection.id, title = formElementSection.title, description = formElementSection.description, shortTitle = formElementSection.shortTitle, @@ -22,6 +21,10 @@ class FormElementSectionMapper( applicationFormId = formElementSection.applicationForm?.id ?: throw IllegalStateException("ApplicationForm ID must not be null!"), + isTemplate = formElementSection.isTemplate, + templateReference = formElementSection.templateReference, + titleTemplate = formElementSection.titleTemplate, + spawnedFromElementReference = formElementSection.spawnedFromElementReference, ) fun toFormElementSection( @@ -34,6 +37,10 @@ class FormElementSectionMapper( title = formElementSection.title, description = formElementSection.description, shortTitle = formElementSection.shortTitle, + isTemplate = formElementSection.isTemplate ?: false, + templateReference = formElementSection.templateReference, + titleTemplate = formElementSection.titleTemplate, + spawnedFromElementReference = formElementSection.spawnedFromElementReference, applicationForm = applicationForm, ) section.formElementSubSections = @@ -43,21 +50,26 @@ class FormElementSectionMapper( return section } - fun toFormElementSection( - createFormElementSection: CreateFormElementSectionDto, + fun toNewFormElementSection( + formElementSection: FormElementSectionDto, applicationForm: ApplicationForm, ): FormElementSection { - val formElementSection = + val section = FormElementSection( - title = createFormElementSection.title, - description = createFormElementSection.description, - shortTitle = createFormElementSection.shortTitle, + id = null, + title = formElementSection.title, + description = formElementSection.description, + shortTitle = formElementSection.shortTitle, + isTemplate = formElementSection.isTemplate ?: false, + templateReference = formElementSection.templateReference, + titleTemplate = formElementSection.titleTemplate, + spawnedFromElementReference = formElementSection.spawnedFromElementReference, applicationForm = applicationForm, ) - formElementSection.formElementSubSections = - createFormElementSection.formElementSubSections - .map { formElementSubSectionMapper.toFormElementSubSection(it, formElementSection) } + section.formElementSubSections = + formElementSection.formElementSubSections + .map { formElementSubSectionMapper.toNewFormElementSubSection(it, section) } .toMutableList() - return formElementSection + return section } } diff --git a/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/form_element/FormElementSubSectionMapper.kt b/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/form_element/FormElementSubSectionMapper.kt index 5673c9d..7301a4e 100644 --- a/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/form_element/FormElementSubSectionMapper.kt +++ b/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/form_element/FormElementSubSectionMapper.kt @@ -1,6 +1,5 @@ package com.betriebsratkanzlei.legalconsenthub.form_element -import com.betriebsratkanzlei.legalconsenthub_api.model.CreateFormElementSubSectionDto import com.betriebsratkanzlei.legalconsenthub_api.model.FormElementSubSectionDto import org.springframework.stereotype.Component @@ -10,7 +9,7 @@ class FormElementSubSectionMapper( ) { fun toFormElementSubSectionDto(formElementSubSection: FormElementSubSection): FormElementSubSectionDto = FormElementSubSectionDto( - id = formElementSubSection.id ?: throw IllegalStateException("FormElementSubSection ID must not be null!"), + id = formElementSubSection.id, title = formElementSubSection.title, subtitle = formElementSubSection.subtitle, formElements = formElementSubSection.formElements.map { formElementMapper.toFormElementDto(it) }, @@ -37,20 +36,21 @@ class FormElementSubSectionMapper( return subsection } - fun toFormElementSubSection( - createFormElementSubSection: CreateFormElementSubSectionDto, + fun toNewFormElementSubSection( + formElementSubSection: FormElementSubSectionDto, formElementSection: FormElementSection, ): FormElementSubSection { - val formElementSubSection = + val subsection = FormElementSubSection( - title = createFormElementSubSection.title, - subtitle = createFormElementSubSection.subtitle, + id = null, + title = formElementSubSection.title, + subtitle = formElementSubSection.subtitle, formElementSection = formElementSection, ) - formElementSubSection.formElements = - createFormElementSubSection.formElements - .map { formElementMapper.toFormElement(it, formElementSubSection) } + subsection.formElements = + formElementSubSection.formElements + .map { formElementMapper.toNewFormElement(it, subsection) } .toMutableList() - return formElementSubSection + return subsection } } diff --git a/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/form_element/FormElementVisibilityCondition.kt b/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/form_element/FormElementVisibilityCondition.kt index 40ee912..8827d3a 100644 --- a/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/form_element/FormElementVisibilityCondition.kt +++ b/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/form_element/FormElementVisibilityCondition.kt @@ -7,9 +7,9 @@ import jakarta.persistence.Enumerated @Embeddable data class FormElementVisibilityCondition( @Enumerated(EnumType.STRING) - val conditionType: VisibilityConditionType, + val formElementConditionType: VisibilityConditionType, val sourceFormElementReference: String, - val expectedValue: String, + val formElementExpectedValue: String, @Enumerated(EnumType.STRING) - val operator: VisibilityConditionOperator = VisibilityConditionOperator.EQUALS, + val formElementOperator: VisibilityConditionOperator = VisibilityConditionOperator.EQUALS, ) diff --git a/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/form_element/FormElementVisibilityConditionMapper.kt b/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/form_element/FormElementVisibilityConditionMapper.kt index fb090ea..cc1fb4f 100644 --- a/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/form_element/FormElementVisibilityConditionMapper.kt +++ b/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/form_element/FormElementVisibilityConditionMapper.kt @@ -11,21 +11,21 @@ class FormElementVisibilityConditionMapper { condition: FormElementVisibilityCondition, ): FormElementVisibilityConditionDto = FormElementVisibilityConditionDto( - conditionType = toVisibilityConditionTypeDto(condition.conditionType), + formElementConditionType = toVisibilityConditionTypeDto(condition.formElementConditionType), sourceFormElementReference = condition.sourceFormElementReference, - expectedValue = condition.expectedValue, - operator = toVisibilityConditionOperatorDto(condition.operator), + formElementExpectedValue = condition.formElementExpectedValue, + formElementOperator = toVisibilityConditionOperatorDto(condition.formElementOperator), ) fun toFormElementVisibilityCondition( conditionDto: FormElementVisibilityConditionDto, ): FormElementVisibilityCondition = FormElementVisibilityCondition( - conditionType = toVisibilityConditionType(conditionDto.conditionType), + formElementConditionType = toVisibilityConditionType(conditionDto.formElementConditionType), sourceFormElementReference = conditionDto.sourceFormElementReference, - expectedValue = conditionDto.expectedValue, - operator = - conditionDto.operator?.let { toVisibilityConditionOperator(it) } + formElementExpectedValue = conditionDto.formElementExpectedValue, + formElementOperator = + conditionDto.formElementOperator?.let { toVisibilityConditionOperator(it) } ?: VisibilityConditionOperator.EQUALS, ) diff --git a/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/form_element/SectionSpawnTrigger.kt b/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/form_element/SectionSpawnTrigger.kt new file mode 100644 index 0000000..22d2bc5 --- /dev/null +++ b/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/form_element/SectionSpawnTrigger.kt @@ -0,0 +1,15 @@ +package com.betriebsratkanzlei.legalconsenthub.form_element + +import jakarta.persistence.Embeddable +import jakarta.persistence.EnumType +import jakarta.persistence.Enumerated + +@Embeddable +data class SectionSpawnTrigger( + val templateReference: String, + @Enumerated(EnumType.STRING) + val sectionSpawnConditionType: VisibilityConditionType, + val sectionSpawnExpectedValue: String? = null, + @Enumerated(EnumType.STRING) + val sectionSpawnOperator: VisibilityConditionOperator = VisibilityConditionOperator.EQUALS, +) diff --git a/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/form_element/SectionSpawnTriggerMapper.kt b/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/form_element/SectionSpawnTriggerMapper.kt new file mode 100644 index 0000000..67b1be0 --- /dev/null +++ b/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/form_element/SectionSpawnTriggerMapper.kt @@ -0,0 +1,57 @@ +package com.betriebsratkanzlei.legalconsenthub.form_element + +import com.betriebsratkanzlei.legalconsenthub_api.model.SectionSpawnTriggerDto +import org.springframework.stereotype.Component +import com.betriebsratkanzlei.legalconsenthub_api.model.VisibilityConditionOperator as VisibilityConditionOperatorDto +import com.betriebsratkanzlei.legalconsenthub_api.model.VisibilityConditionType as VisibilityConditionTypeDto + +@Component +class SectionSpawnTriggerMapper { + fun toSectionSpawnTriggerDto(trigger: SectionSpawnTrigger): SectionSpawnTriggerDto = + SectionSpawnTriggerDto( + templateReference = trigger.templateReference, + sectionSpawnConditionType = toVisibilityConditionTypeDto(trigger.sectionSpawnConditionType), + sectionSpawnExpectedValue = trigger.sectionSpawnExpectedValue, + sectionSpawnOperator = toVisibilityConditionOperatorDto(trigger.sectionSpawnOperator), + ) + + fun toSectionSpawnTrigger(triggerDto: SectionSpawnTriggerDto): SectionSpawnTrigger = + SectionSpawnTrigger( + templateReference = triggerDto.templateReference, + sectionSpawnConditionType = toVisibilityConditionType(triggerDto.sectionSpawnConditionType), + sectionSpawnExpectedValue = triggerDto.sectionSpawnExpectedValue, + sectionSpawnOperator = toVisibilityConditionOperator(triggerDto.sectionSpawnOperator), + ) + + private fun toVisibilityConditionTypeDto(type: VisibilityConditionType): VisibilityConditionTypeDto = + when (type) { + VisibilityConditionType.SHOW -> VisibilityConditionTypeDto.SHOW + VisibilityConditionType.HIDE -> VisibilityConditionTypeDto.HIDE + } + + private fun toVisibilityConditionType(typeDto: VisibilityConditionTypeDto): VisibilityConditionType = + when (typeDto) { + VisibilityConditionTypeDto.SHOW -> VisibilityConditionType.SHOW + VisibilityConditionTypeDto.HIDE -> VisibilityConditionType.HIDE + } + + private fun toVisibilityConditionOperatorDto( + operator: VisibilityConditionOperator, + ): VisibilityConditionOperatorDto = + when (operator) { + VisibilityConditionOperator.EQUALS -> VisibilityConditionOperatorDto.EQUALS + VisibilityConditionOperator.NOT_EQUALS -> VisibilityConditionOperatorDto.NOT_EQUALS + VisibilityConditionOperator.IS_EMPTY -> VisibilityConditionOperatorDto.IS_EMPTY + VisibilityConditionOperator.IS_NOT_EMPTY -> VisibilityConditionOperatorDto.IS_NOT_EMPTY + } + + private fun toVisibilityConditionOperator( + operatorDto: VisibilityConditionOperatorDto, + ): VisibilityConditionOperator = + when (operatorDto) { + VisibilityConditionOperatorDto.EQUALS -> VisibilityConditionOperator.EQUALS + VisibilityConditionOperatorDto.NOT_EQUALS -> VisibilityConditionOperator.NOT_EQUALS + VisibilityConditionOperatorDto.IS_EMPTY -> VisibilityConditionOperator.IS_EMPTY + VisibilityConditionOperatorDto.IS_NOT_EMPTY -> VisibilityConditionOperator.IS_NOT_EMPTY + } +} diff --git a/legalconsenthub-backend/src/main/resources/application-testcontainers.yaml b/legalconsenthub-backend/src/main/resources/application-testcontainers.yaml index 3875810..2c80384 100644 --- a/legalconsenthub-backend/src/main/resources/application-testcontainers.yaml +++ b/legalconsenthub-backend/src/main/resources/application-testcontainers.yaml @@ -45,7 +45,7 @@ logging: springframework: security: TRACE oauth2: TRACE - web: DEBUG + web: TRACE org.testcontainers: INFO com.github.dockerjava: WARN diff --git a/legalconsenthub-backend/src/main/resources/db/migrations/001-schema.sql b/legalconsenthub-backend/src/main/resources/db/migrations/001-schema.sql index dad7427..54843fe 100644 --- a/legalconsenthub-backend/src/main/resources/db/migrations/001-schema.sql +++ b/legalconsenthub-backend/src/main/resources/db/migrations/001-schema.sql @@ -63,26 +63,37 @@ create table form_element_options create table form_element ( form_element_order integer, - type smallint not null check (type between 0 and 6), + is_clonable boolean not null, + type smallint not null check (type between 0 and 7), form_element_sub_section_id uuid not null, id uuid not null, - condition_type varchar(255) check (condition_type in ('SHOW', 'HIDE')), description varchar(255), - expected_value varchar(255), - operator varchar(255) check (operator in ('EQUALS', 'NOT_EQUALS', 'IS_EMPTY', 'IS_NOT_EMPTY')), + form_element_condition_type varchar(255) check (form_element_condition_type in ('SHOW', 'HIDE')), + form_element_expected_value varchar(255), + form_element_operator varchar(255) check (form_element_operator in + ('EQUALS', 'NOT_EQUALS', 'IS_EMPTY', 'IS_NOT_EMPTY')), reference varchar(255), + section_spawn_condition_type varchar(255) check (section_spawn_condition_type in ('SHOW', 'HIDE')), + section_spawn_expected_value varchar(255), + section_spawn_operator varchar(255) check (section_spawn_operator in + ('EQUALS', 'NOT_EQUALS', 'IS_EMPTY', 'IS_NOT_EMPTY')), source_form_element_reference varchar(255), + template_reference varchar(255), title varchar(255), primary key (id) ); create table form_element_section ( - application_form_id uuid not null, - id uuid not null, - description varchar(255), - short_title varchar(255), - title varchar(255) not null, + is_template boolean not null, + application_form_id uuid not null, + id uuid not null, + description varchar(255), + short_title varchar(255), + spawned_from_element_reference varchar(255), + template_reference varchar(255), + title varchar(255) not null, + title_template varchar(255), primary key (id) ); diff --git a/legalconsenthub/.prettierignore b/legalconsenthub/.prettierignore index e26b9cb..82e86b1 100644 --- a/legalconsenthub/.prettierignore +++ b/legalconsenthub/.prettierignore @@ -4,3 +4,4 @@ coverage .nuxt .output .api-client +pnpm-lock.yaml diff --git a/legalconsenthub/app/components/DeleteModal.vue b/legalconsenthub/app/components/DeleteModal.vue index e8658c0..2121d15 100644 --- a/legalconsenthub/app/components/DeleteModal.vue +++ b/legalconsenthub/app/components/DeleteModal.vue @@ -5,7 +5,7 @@ @@ -13,13 +13,20 @@ diff --git a/legalconsenthub/app/components/FormEngine.vue b/legalconsenthub/app/components/FormEngine.vue index e1bb078..ac96d96 100644 --- a/legalconsenthub/app/components/FormEngine.vue +++ b/legalconsenthub/app/components/FormEngine.vue @@ -1,5 +1,5 @@