feat(fullstack): Add test application form creation
This commit is contained in:
@@ -63,6 +63,31 @@ paths:
|
|||||||
"503":
|
"503":
|
||||||
$ref: "https://api.swaggerhub.com/domains/smartbear-public/ProblemDetails/1.0.0#/components/responses/ServiceUnavailable"
|
$ref: "https://api.swaggerhub.com/domains/smartbear-public/ProblemDetails/1.0.0#/components/responses/ServiceUnavailable"
|
||||||
|
|
||||||
|
/test-data/application-form:
|
||||||
|
post:
|
||||||
|
summary: Creates a test application form based on seeded data
|
||||||
|
operationId: createTestDataApplicationForm
|
||||||
|
tags:
|
||||||
|
- test-data
|
||||||
|
parameters:
|
||||||
|
- in: query
|
||||||
|
name: organizationId
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
description: The organization ID to create the test form in
|
||||||
|
responses:
|
||||||
|
"201":
|
||||||
|
description: Successfully created test application form
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/ApplicationFormDto"
|
||||||
|
"401":
|
||||||
|
$ref: "https://api.swaggerhub.com/domains/smartbear-public/ProblemDetails/1.0.0#/components/responses/Unauthorized"
|
||||||
|
"500":
|
||||||
|
$ref: "https://api.swaggerhub.com/domains/smartbear-public/ProblemDetails/1.0.0#/components/responses/ServerError"
|
||||||
|
|
||||||
/application-forms/{id}:
|
/application-forms/{id}:
|
||||||
parameters:
|
parameters:
|
||||||
- name: id
|
- name: id
|
||||||
|
|||||||
@@ -0,0 +1,107 @@
|
|||||||
|
package com.betriebsratkanzlei.legalconsenthub.test_data
|
||||||
|
|
||||||
|
import com.betriebsratkanzlei.legalconsenthub.application_form.ApplicationForm
|
||||||
|
import com.betriebsratkanzlei.legalconsenthub.application_form.ApplicationFormService
|
||||||
|
import com.betriebsratkanzlei.legalconsenthub_api.model.ApplicationFormDto
|
||||||
|
import com.betriebsratkanzlei.legalconsenthub_api.model.ApplicationFormStatus
|
||||||
|
import com.betriebsratkanzlei.legalconsenthub_api.model.FormElementSectionDto
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
|
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import org.springframework.core.io.ClassPathResource
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
|
|
||||||
|
@Service
|
||||||
|
class TestDataApplicationFormService(
|
||||||
|
private val applicationFormService: ApplicationFormService,
|
||||||
|
) {
|
||||||
|
private val logger = LoggerFactory.getLogger(TestDataApplicationFormService::class.java)
|
||||||
|
private val yamlMapper = ObjectMapper(YAMLFactory()).findAndRegisterModules()
|
||||||
|
|
||||||
|
fun createTestDataApplicationForm(organizationId: String): ApplicationForm {
|
||||||
|
val seededDto = loadInitialFormDto()
|
||||||
|
|
||||||
|
// Load template sections from seed template YAML (deterministic, no DB dependency)
|
||||||
|
val templateDto = loadInitialFormTemplateDto()
|
||||||
|
val templateSections =
|
||||||
|
templateDto.formElementSections.filter { it.isTemplate == true && !it.templateReference.isNullOrBlank() }
|
||||||
|
|
||||||
|
// Merge seeded sections with template sections
|
||||||
|
// Add template sections that don't already exist as templates in the seeded form
|
||||||
|
// (templates are needed for reactive section spawning even if spawned instances exist)
|
||||||
|
val finalSections =
|
||||||
|
seededDto.formElementSections +
|
||||||
|
templateSections.filter { ts ->
|
||||||
|
seededDto.formElementSections.none { seeded ->
|
||||||
|
seeded.isTemplate == true && seeded.templateReference == ts.templateReference
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val newFormDto =
|
||||||
|
seededDto.copy(
|
||||||
|
id = null,
|
||||||
|
organizationId = organizationId,
|
||||||
|
status = ApplicationFormStatus.DRAFT,
|
||||||
|
name = "SAP S/4HANA (Copy ${System.currentTimeMillis()})",
|
||||||
|
isTemplate = false,
|
||||||
|
formElementSections = finalSections,
|
||||||
|
)
|
||||||
|
|
||||||
|
validateSectionSpawningInvariants(newFormDto.formElementSections)
|
||||||
|
|
||||||
|
return applicationFormService.createApplicationForm(newFormDto)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadInitialFormDto(): ApplicationFormDto =
|
||||||
|
ClassPathResource(INITIAL_FORM_RESOURCE_PATH).inputStream.use { inputStream ->
|
||||||
|
yamlMapper.readValue(inputStream, ApplicationFormDto::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadInitialFormTemplateDto(): ApplicationFormDto =
|
||||||
|
ClassPathResource(INITIAL_FORM_TEMPLATE_RESOURCE_PATH).inputStream.use { inputStream ->
|
||||||
|
yamlMapper.readValue(inputStream, ApplicationFormDto::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun validateSectionSpawningInvariants(sections: List<FormElementSectionDto>) {
|
||||||
|
val templateRefs = sections.filter { it.isTemplate == true }.mapNotNull { it.templateReference }.toSet()
|
||||||
|
|
||||||
|
val spawnedWithoutTemplateRef =
|
||||||
|
sections
|
||||||
|
.filter {
|
||||||
|
it.isTemplate != true &&
|
||||||
|
!it.spawnedFromElementReference.isNullOrBlank() &&
|
||||||
|
it.templateReference.isNullOrBlank()
|
||||||
|
}.mapNotNull { it.title }
|
||||||
|
|
||||||
|
if (spawnedWithoutTemplateRef.isNotEmpty()) {
|
||||||
|
val msg =
|
||||||
|
"Invalid seeded form: spawned sections missing templateReference: " +
|
||||||
|
spawnedWithoutTemplateRef.joinToString(", ")
|
||||||
|
logger.error(msg)
|
||||||
|
throw IllegalStateException(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
val triggerTemplateRefs =
|
||||||
|
sections
|
||||||
|
.flatMap { it.formElementSubSections }
|
||||||
|
.flatMap { it.formElements }
|
||||||
|
.flatMap { it.sectionSpawnTriggers ?: emptyList() }
|
||||||
|
.map { it.templateReference }
|
||||||
|
.filter { it.isNotBlank() }
|
||||||
|
.toSet()
|
||||||
|
|
||||||
|
val missingTemplateSections = triggerTemplateRefs.minus(templateRefs)
|
||||||
|
if (missingTemplateSections.isNotEmpty()) {
|
||||||
|
val msg =
|
||||||
|
"Invalid seeded form: missing template sections for triggers: " +
|
||||||
|
missingTemplateSections.joinToString(", ")
|
||||||
|
logger.error(msg)
|
||||||
|
throw IllegalStateException(msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private companion object {
|
||||||
|
private const val INITIAL_FORM_RESOURCE_PATH = "seed/initial_application_form.yaml"
|
||||||
|
private const val INITIAL_FORM_TEMPLATE_RESOURCE_PATH = "seed/initial_application_form_template.yaml"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package com.betriebsratkanzlei.legalconsenthub.test_data
|
||||||
|
|
||||||
|
import com.betriebsratkanzlei.legalconsenthub.application_form.ApplicationFormMapper
|
||||||
|
import com.betriebsratkanzlei.legalconsenthub_api.api.TestDataApi
|
||||||
|
import com.betriebsratkanzlei.legalconsenthub_api.model.ApplicationFormDto
|
||||||
|
import org.springframework.http.ResponseEntity
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize
|
||||||
|
import org.springframework.web.bind.annotation.RestController
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
class TestDataController(
|
||||||
|
private val testDataApplicationFormService: TestDataApplicationFormService,
|
||||||
|
private val applicationFormMapper: ApplicationFormMapper,
|
||||||
|
) : TestDataApi {
|
||||||
|
@PreAuthorize(
|
||||||
|
"hasAnyRole('CHIEF_EXECUTIVE_OFFICER', 'BUSINESS_DEPARTMENT', 'IT_DEPARTMENT', 'HUMAN_RESOURCES', 'HEAD_OF_WORKS_COUNCIL', 'WORKS_COUNCIL')",
|
||||||
|
)
|
||||||
|
override fun createTestDataApplicationForm(organizationId: String): ResponseEntity<ApplicationFormDto> =
|
||||||
|
ResponseEntity.status(201).body(
|
||||||
|
applicationFormMapper.toApplicationFormDto(
|
||||||
|
testDataApplicationFormService.createTestDataApplicationForm(organizationId),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -403,7 +403,7 @@ formElementSections:
|
|||||||
formElementExpectedValue: Einführung
|
formElementExpectedValue: Einführung
|
||||||
formElementOperator: EQUALS
|
formElementOperator: EQUALS
|
||||||
sectionSpawnTriggers:
|
sectionSpawnTriggers:
|
||||||
- templateReference: ki_details_template
|
- templateReference: ki_informationen_template
|
||||||
sectionSpawnConditionType: SHOW
|
sectionSpawnConditionType: SHOW
|
||||||
sectionSpawnExpectedValue: Ja
|
sectionSpawnExpectedValue: Ja
|
||||||
sectionSpawnOperator: EQUALS
|
sectionSpawnOperator: EQUALS
|
||||||
@@ -444,6 +444,8 @@ formElementSections:
|
|||||||
- title: Rollen und Berechtigungen
|
- title: Rollen und Berechtigungen
|
||||||
shortTitle: Rollen/Berechtigungen
|
shortTitle: Rollen/Berechtigungen
|
||||||
description: Vollständiges Rollen- und Berechtigungskonzept für das IT-System
|
description: Vollständiges Rollen- und Berechtigungskonzept für das IT-System
|
||||||
|
templateReference: rollen_berechtigungen_template
|
||||||
|
titleTemplate: Rollen und Berechtigungen
|
||||||
spawnedFromElementReference: art_der_massnahme
|
spawnedFromElementReference: art_der_massnahme
|
||||||
formElementSubSections:
|
formElementSubSections:
|
||||||
|
|
||||||
@@ -642,6 +644,8 @@ formElementSections:
|
|||||||
- title: Verarbeitung von Mitarbeiterdaten
|
- title: Verarbeitung von Mitarbeiterdaten
|
||||||
shortTitle: Mitarbeiterdaten
|
shortTitle: Mitarbeiterdaten
|
||||||
description: Angaben zur Verarbeitung von personenbezogenen Arbeitnehmerdaten
|
description: Angaben zur Verarbeitung von personenbezogenen Arbeitnehmerdaten
|
||||||
|
templateReference: verarbeitung_mitarbeiterdaten_template
|
||||||
|
titleTemplate: Verarbeitung von Mitarbeiterdaten
|
||||||
spawnedFromElementReference: art_der_massnahme
|
spawnedFromElementReference: art_der_massnahme
|
||||||
formElementSubSections:
|
formElementSubSections:
|
||||||
|
|
||||||
@@ -727,7 +731,7 @@ formElementSections:
|
|||||||
label: Export/Weitergabe (Ja/Nein + Ziel)
|
label: Export/Weitergabe (Ja/Nein + Ziel)
|
||||||
processingPurpose: DATA_ANALYSIS
|
processingPurpose: DATA_ANALYSIS
|
||||||
employeeDataCategory: REVIEW_REQUIRED
|
employeeDataCategory: REVIEW_REQUIRED
|
||||||
- value: '["false", "true", "false", "true", "true"]'
|
- value: '[false, true, false, true, true]'
|
||||||
label: Leistungs-/Verhaltenskontrolle beabsichtigt?
|
label: Leistungs-/Verhaltenskontrolle beabsichtigt?
|
||||||
processingPurpose: DATA_ANALYSIS
|
processingPurpose: DATA_ANALYSIS
|
||||||
employeeDataCategory: SENSITIVE
|
employeeDataCategory: SENSITIVE
|
||||||
@@ -805,7 +809,7 @@ formElementSections:
|
|||||||
label: Schutzmaßnahmen/Governance
|
label: Schutzmaßnahmen/Governance
|
||||||
processingPurpose: SYSTEM_OPERATION
|
processingPurpose: SYSTEM_OPERATION
|
||||||
employeeDataCategory: REVIEW_REQUIRED
|
employeeDataCategory: REVIEW_REQUIRED
|
||||||
- value: '["true", "true", "true"]'
|
- value: '[true, true, true]'
|
||||||
label: Audit-Logging erforderlich
|
label: Audit-Logging erforderlich
|
||||||
processingPurpose: SYSTEM_OPERATION
|
processingPurpose: SYSTEM_OPERATION
|
||||||
employeeDataCategory: REVIEW_REQUIRED
|
employeeDataCategory: REVIEW_REQUIRED
|
||||||
@@ -843,7 +847,7 @@ formElementSections:
|
|||||||
columnConfig:
|
columnConfig:
|
||||||
sourceTableReference: rollenstamm_tabelle
|
sourceTableReference: rollenstamm_tabelle
|
||||||
sourceColumnIndex: 0
|
sourceColumnIndex: 0
|
||||||
- value: '["true", "true", "true", "true", "true"]'
|
- value: '[true, true, true, true, true]'
|
||||||
label: Sichtbar (Ja/Nein)
|
label: Sichtbar (Ja/Nein)
|
||||||
processingPurpose: SYSTEM_OPERATION
|
processingPurpose: SYSTEM_OPERATION
|
||||||
employeeDataCategory: NON_CRITICAL
|
employeeDataCategory: NON_CRITICAL
|
||||||
@@ -918,6 +922,8 @@ formElementSections:
|
|||||||
- title: Löschkonzept
|
- title: Löschkonzept
|
||||||
shortTitle: Löschkonzept
|
shortTitle: Löschkonzept
|
||||||
description: Angaben zum Löschkonzept für Verarbeitungsvorgänge, Datenkategorien und Arbeitnehmerdaten
|
description: Angaben zum Löschkonzept für Verarbeitungsvorgänge, Datenkategorien und Arbeitnehmerdaten
|
||||||
|
templateReference: loeschkonzept_template
|
||||||
|
titleTemplate: Löschkonzept
|
||||||
spawnedFromElementReference: art_der_massnahme
|
spawnedFromElementReference: art_der_massnahme
|
||||||
formElementSubSections:
|
formElementSubSections:
|
||||||
|
|
||||||
@@ -1080,6 +1086,8 @@ formElementSections:
|
|||||||
- title: Schnittstellen
|
- title: Schnittstellen
|
||||||
shortTitle: Schnittstellen
|
shortTitle: Schnittstellen
|
||||||
description: Angaben zu Schnittstellen zwischen IT-Systemen
|
description: Angaben zu Schnittstellen zwischen IT-Systemen
|
||||||
|
templateReference: schnittstellen_template
|
||||||
|
titleTemplate: Schnittstellen
|
||||||
spawnedFromElementReference: art_der_massnahme
|
spawnedFromElementReference: art_der_massnahme
|
||||||
formElementSubSections:
|
formElementSubSections:
|
||||||
|
|
||||||
@@ -1152,6 +1160,8 @@ formElementSections:
|
|||||||
- title: Datenschutz
|
- title: Datenschutz
|
||||||
shortTitle: Datenschutz
|
shortTitle: Datenschutz
|
||||||
description: Datenschutzrechtliche Angaben zur Datenverarbeitung
|
description: Datenschutzrechtliche Angaben zur Datenverarbeitung
|
||||||
|
templateReference: datenschutz_template
|
||||||
|
titleTemplate: Datenschutz
|
||||||
spawnedFromElementReference: art_der_massnahme
|
spawnedFromElementReference: art_der_massnahme
|
||||||
formElementSubSections:
|
formElementSubSections:
|
||||||
|
|
||||||
@@ -1488,6 +1498,8 @@ formElementSections:
|
|||||||
- title: Modul SAP Finance and Controlling (FI/CO)
|
- title: Modul SAP Finance and Controlling (FI/CO)
|
||||||
shortTitle: SAP Finance and Controlling (FI/CO)
|
shortTitle: SAP Finance and Controlling (FI/CO)
|
||||||
description: Detaillierte Informationen zum Modul
|
description: Detaillierte Informationen zum Modul
|
||||||
|
templateReference: module_details_template
|
||||||
|
titleTemplate: 'Modul: {{triggerValue}}'
|
||||||
spawnedFromElementReference: modul_1
|
spawnedFromElementReference: modul_1
|
||||||
formElementSubSections:
|
formElementSubSections:
|
||||||
|
|
||||||
@@ -1532,7 +1544,7 @@ formElementSections:
|
|||||||
label: Analytische Funktionen
|
label: Analytische Funktionen
|
||||||
processingPurpose: DATA_ANALYSIS
|
processingPurpose: DATA_ANALYSIS
|
||||||
employeeDataCategory: REVIEW_REQUIRED
|
employeeDataCategory: REVIEW_REQUIRED
|
||||||
- value: '["true", "true", "true", "false"]'
|
- value: '[true, true, true, false]'
|
||||||
label: In Nutzung
|
label: In Nutzung
|
||||||
processingPurpose: DATA_ANALYSIS
|
processingPurpose: DATA_ANALYSIS
|
||||||
employeeDataCategory: REVIEW_REQUIRED
|
employeeDataCategory: REVIEW_REQUIRED
|
||||||
@@ -1626,6 +1638,8 @@ formElementSections:
|
|||||||
- title: Modul SAP Human Capital Management (HCM)
|
- title: Modul SAP Human Capital Management (HCM)
|
||||||
shortTitle: SAP Human Capital Management (HCM)
|
shortTitle: SAP Human Capital Management (HCM)
|
||||||
description: Detaillierte Informationen zum Modul
|
description: Detaillierte Informationen zum Modul
|
||||||
|
templateReference: module_details_template
|
||||||
|
titleTemplate: 'Modul: {{triggerValue}}'
|
||||||
spawnedFromElementReference: modul_2
|
spawnedFromElementReference: modul_2
|
||||||
formElementSubSections:
|
formElementSubSections:
|
||||||
|
|
||||||
@@ -1670,7 +1684,7 @@ formElementSections:
|
|||||||
label: Analytische Funktionen
|
label: Analytische Funktionen
|
||||||
processingPurpose: DATA_ANALYSIS
|
processingPurpose: DATA_ANALYSIS
|
||||||
employeeDataCategory: REVIEW_REQUIRED
|
employeeDataCategory: REVIEW_REQUIRED
|
||||||
- value: '["true", "true", "true", "true", "true", "false"]'
|
- value: '[true, true, true, true, true, false]'
|
||||||
label: In Nutzung
|
label: In Nutzung
|
||||||
processingPurpose: DATA_ANALYSIS
|
processingPurpose: DATA_ANALYSIS
|
||||||
employeeDataCategory: REVIEW_REQUIRED
|
employeeDataCategory: REVIEW_REQUIRED
|
||||||
@@ -1764,6 +1778,8 @@ formElementSections:
|
|||||||
- title: Modul SAP Supply Chain Management (SCM)
|
- title: Modul SAP Supply Chain Management (SCM)
|
||||||
shortTitle: SAP Supply Chain Management (SCM)
|
shortTitle: SAP Supply Chain Management (SCM)
|
||||||
description: Detaillierte Informationen zum Modul
|
description: Detaillierte Informationen zum Modul
|
||||||
|
templateReference: module_details_template
|
||||||
|
titleTemplate: 'Modul: {{triggerValue}}'
|
||||||
spawnedFromElementReference: modul_3
|
spawnedFromElementReference: modul_3
|
||||||
formElementSubSections:
|
formElementSubSections:
|
||||||
|
|
||||||
@@ -1808,7 +1824,7 @@ formElementSections:
|
|||||||
label: Analytische Funktionen
|
label: Analytische Funktionen
|
||||||
processingPurpose: DATA_ANALYSIS
|
processingPurpose: DATA_ANALYSIS
|
||||||
employeeDataCategory: REVIEW_REQUIRED
|
employeeDataCategory: REVIEW_REQUIRED
|
||||||
- value: '["true", "true", "true", "false", "true"]'
|
- value: '[true, true, true, false, true]'
|
||||||
label: In Nutzung
|
label: In Nutzung
|
||||||
processingPurpose: DATA_ANALYSIS
|
processingPurpose: DATA_ANALYSIS
|
||||||
employeeDataCategory: REVIEW_REQUIRED
|
employeeDataCategory: REVIEW_REQUIRED
|
||||||
@@ -1902,6 +1918,8 @@ formElementSections:
|
|||||||
- title: Auswirkungen auf Arbeitnehmer
|
- title: Auswirkungen auf Arbeitnehmer
|
||||||
shortTitle: Auswirkungen auf AN
|
shortTitle: Auswirkungen auf AN
|
||||||
description: Auswirkungen des IT-Systems auf Arbeitnehmer, Arbeitsabläufe und Arbeitsbedingungen
|
description: Auswirkungen des IT-Systems auf Arbeitnehmer, Arbeitsabläufe und Arbeitsbedingungen
|
||||||
|
templateReference: auswirkungen_arbeitnehmer_template
|
||||||
|
titleTemplate: Auswirkungen auf Arbeitnehmer
|
||||||
spawnedFromElementReference: art_der_massnahme
|
spawnedFromElementReference: art_der_massnahme
|
||||||
formElementSubSections:
|
formElementSubSections:
|
||||||
|
|
||||||
@@ -2187,6 +2205,9 @@ formElementSections:
|
|||||||
- title: Informationen zur Künstlichen Intelligenz
|
- title: Informationen zur Künstlichen Intelligenz
|
||||||
shortTitle: KI-Informationen
|
shortTitle: KI-Informationen
|
||||||
description: Detaillierte Angaben zum Einsatz von Künstlicher Intelligenz gemäß EU-KI-Verordnung
|
description: Detaillierte Angaben zum Einsatz von Künstlicher Intelligenz gemäß EU-KI-Verordnung
|
||||||
|
templateReference: ki_informationen_template
|
||||||
|
titleTemplate: Informationen zur Künstlichen Intelligenz
|
||||||
|
spawnedFromElementReference: ki_einsatz
|
||||||
formElementSubSections:
|
formElementSubSections:
|
||||||
|
|
||||||
# Risk class selection
|
# Risk class selection
|
||||||
|
|||||||
@@ -203,9 +203,10 @@ const { isSwiping } = usePointerSwipe(stepperScrollEl, {
|
|||||||
const previousVisibilityMap = ref<Map<string, boolean>>(new Map())
|
const previousVisibilityMap = ref<Map<string, boolean>>(new Map())
|
||||||
|
|
||||||
const allFormElements = computed(() => {
|
const allFormElements = computed(() => {
|
||||||
return props.formElementSections
|
const nonTemplateSections = props.formElementSections.filter((section) => section.isTemplate !== true)
|
||||||
.filter((section) => section.isTemplate !== true)
|
return nonTemplateSections.flatMap(
|
||||||
.flatMap((section) => section.formElementSubSections?.flatMap((subsection) => subsection.formElements) ?? [])
|
(section) => section.formElementSubSections?.flatMap((subsection) => subsection.formElements) ?? []
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
const visibilityMap = computed(() => {
|
const visibilityMap = computed(() => {
|
||||||
@@ -407,15 +408,34 @@ function handleFormElementUpdate(updatedFormElements: FormElementDto[], subsecti
|
|||||||
}
|
}
|
||||||
|
|
||||||
function clearNewlyHiddenFormElements(sections: FormElementSectionDto[]): FormElementSectionDto[] {
|
function clearNewlyHiddenFormElements(sections: FormElementSectionDto[]): FormElementSectionDto[] {
|
||||||
const allElements = sections.flatMap(
|
// Only evaluate visibility for non-template sections to avoid duplicate reference issues
|
||||||
|
const nonTemplateSections = sections.filter((section) => section.isTemplate !== true)
|
||||||
|
const allElements = nonTemplateSections.flatMap(
|
||||||
(section) => section.formElementSubSections?.flatMap((subsection) => subsection.formElements) ?? []
|
(section) => section.formElementSubSections?.flatMap((subsection) => subsection.formElements) ?? []
|
||||||
)
|
)
|
||||||
const newVisibilityMap = evaluateFormElementVisibility(allElements)
|
const newVisibilityMap = evaluateFormElementVisibility(allElements)
|
||||||
|
|
||||||
const clearedSections = clearHiddenFormElementValues(sections, previousVisibilityMap.value, newVisibilityMap)
|
// Only clear values in non-template sections, preserve template sections unchanged
|
||||||
|
const clearedNonTemplateSections = clearHiddenFormElementValues(
|
||||||
|
nonTemplateSections,
|
||||||
|
previousVisibilityMap.value,
|
||||||
|
newVisibilityMap
|
||||||
|
)
|
||||||
previousVisibilityMap.value = newVisibilityMap
|
previousVisibilityMap.value = newVisibilityMap
|
||||||
|
|
||||||
return clearedSections
|
// Create a map of cleared sections by their identity for lookup
|
||||||
|
const clearedSectionMap = new Map<FormElementSectionDto, FormElementSectionDto>()
|
||||||
|
nonTemplateSections.forEach((original, index) => {
|
||||||
|
clearedSectionMap.set(original, clearedNonTemplateSections[index]!)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Preserve original order: replace non-template sections with cleared versions, keep templates unchanged
|
||||||
|
return sections.map((section) => {
|
||||||
|
if (section.isTemplate === true) {
|
||||||
|
return section
|
||||||
|
}
|
||||||
|
return clearedSectionMap.get(section) ?? section
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSubsectionKey(
|
function getSubsectionKey(
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
import { useUserStore } from '~~/stores/useUserStore'
|
||||||
|
import { useTestDataApi } from '~/composables/testing/useTestDataApi'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TESTING-ONLY FEATURE
|
||||||
|
*
|
||||||
|
* One-click duplicator for the seeded demo application form "SAP S/4HANA".
|
||||||
|
*
|
||||||
|
* This version uses a dedicated backend endpoint to ensure reliability:
|
||||||
|
* 1. Loads the seeded YAML on the backend.
|
||||||
|
* 2. Creates a fresh ApplicationForm entity with new IDs.
|
||||||
|
* 3. Sets current user as creator.
|
||||||
|
* 4. Assigns the form to the currently selected organization.
|
||||||
|
*/
|
||||||
|
export function useSeededSapS4HanaDuplicator() {
|
||||||
|
const { t } = useI18n()
|
||||||
|
const logger = useLogger().withTag('seeded-sap-duplicator')
|
||||||
|
const toast = useToast()
|
||||||
|
|
||||||
|
const { canWriteApplicationForms } = usePermissions()
|
||||||
|
const userStore = useUserStore()
|
||||||
|
const { selectedOrganization } = storeToRefs(userStore)
|
||||||
|
|
||||||
|
const isDuplicating = ref(false)
|
||||||
|
|
||||||
|
const showButton = computed(() => canWriteApplicationForms.value)
|
||||||
|
|
||||||
|
async function duplicateSapS4HanaForTesting() {
|
||||||
|
if (isDuplicating.value) return
|
||||||
|
|
||||||
|
const organizationId = selectedOrganization.value?.id
|
||||||
|
if (!organizationId) {
|
||||||
|
toast.add({
|
||||||
|
title: t('common.error'),
|
||||||
|
description: 'Please select an organization first.',
|
||||||
|
color: 'error'
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
isDuplicating.value = true
|
||||||
|
try {
|
||||||
|
const { createTestDataApplicationForm } = useTestDataApi()
|
||||||
|
|
||||||
|
const created = await createTestDataApplicationForm(organizationId)
|
||||||
|
|
||||||
|
toast.add({
|
||||||
|
title: t('common.success'),
|
||||||
|
description: 'Created a new test application form.',
|
||||||
|
color: 'success'
|
||||||
|
})
|
||||||
|
|
||||||
|
if (created?.id) {
|
||||||
|
await navigateTo(`/application-forms/${created.id}/0`)
|
||||||
|
}
|
||||||
|
} catch (e: unknown) {
|
||||||
|
logger.error('Failed creating test application form via backend:', e)
|
||||||
|
toast.add({
|
||||||
|
title: t('common.error'),
|
||||||
|
description: 'Failed to create test application form. Check backend logs.',
|
||||||
|
color: 'error'
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
isDuplicating.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
showButton,
|
||||||
|
isDuplicating,
|
||||||
|
duplicateSapS4HanaForTesting
|
||||||
|
}
|
||||||
|
}
|
||||||
24
legalconsenthub/app/composables/testing/useTestDataApi.ts
Normal file
24
legalconsenthub/app/composables/testing/useTestDataApi.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { TestDataApi, Configuration, type ApplicationFormDto } from '~~/.api-client'
|
||||||
|
import { cleanDoubleSlashes, withoutTrailingSlash } from 'ufo'
|
||||||
|
import { wrappedFetchWrap } from '~/utils/wrappedFetch'
|
||||||
|
|
||||||
|
export function useTestDataApi() {
|
||||||
|
const appBaseUrl = useRuntimeConfig().app.baseURL
|
||||||
|
const { serverApiBasePath, clientProxyBasePath } = useRuntimeConfig().public
|
||||||
|
|
||||||
|
const basePath = withoutTrailingSlash(
|
||||||
|
cleanDoubleSlashes(import.meta.client ? appBaseUrl + clientProxyBasePath : clientProxyBasePath + serverApiBasePath)
|
||||||
|
)
|
||||||
|
|
||||||
|
const testDataApiClient = new TestDataApi(
|
||||||
|
new Configuration({ basePath, fetchApi: wrappedFetchWrap(useRequestFetch()) })
|
||||||
|
)
|
||||||
|
|
||||||
|
async function createTestDataApplicationForm(organizationId: string): Promise<ApplicationFormDto> {
|
||||||
|
return testDataApiClient.createTestDataApplicationForm({ organizationId })
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
createTestDataApplicationForm
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,7 +25,18 @@
|
|||||||
|
|
||||||
<template #footer="{ collapsed }">
|
<template #footer="{ collapsed }">
|
||||||
<UserMenu :collapsed="collapsed" />
|
<UserMenu :collapsed="collapsed" />
|
||||||
<UButton @click="copyAccessTokenToClipboard">📋</UButton>
|
<UButton
|
||||||
|
v-if="showSapCopyButton"
|
||||||
|
:loading="isDuplicatingSapForm"
|
||||||
|
:disabled="isDuplicatingSapForm"
|
||||||
|
icon="i-lucide-copy"
|
||||||
|
size="xs"
|
||||||
|
variant="soft"
|
||||||
|
@click="duplicateSapS4HanaForTesting"
|
||||||
|
>
|
||||||
|
🖨
|
||||||
|
</UButton>
|
||||||
|
<UButton size="xs" variant="soft" @click="copyAccessTokenToClipboard">📋</UButton>
|
||||||
</template>
|
</template>
|
||||||
</UDashboardSidebar>
|
</UDashboardSidebar>
|
||||||
|
|
||||||
@@ -37,6 +48,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useNotificationStore } from '~~/stores/useNotificationStore'
|
import { useNotificationStore } from '~~/stores/useNotificationStore'
|
||||||
|
import { useSeededSapS4HanaDuplicator } from '~/composables/testing/useSeededSapS4HanaDuplicator'
|
||||||
|
|
||||||
const { t: $t } = useI18n()
|
const { t: $t } = useI18n()
|
||||||
|
|
||||||
@@ -65,6 +77,12 @@ const isNotificationsSlideoverOpen = ref(false)
|
|||||||
const notificationStore = useNotificationStore()
|
const notificationStore = useNotificationStore()
|
||||||
const { hasUnread } = storeToRefs(notificationStore)
|
const { hasUnread } = storeToRefs(notificationStore)
|
||||||
|
|
||||||
|
const {
|
||||||
|
showButton: showSapCopyButton,
|
||||||
|
isDuplicating: isDuplicatingSapForm,
|
||||||
|
duplicateSapS4HanaForTesting
|
||||||
|
} = useSeededSapS4HanaDuplicator()
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await notificationStore.fetchUnreadCount()
|
await notificationStore.fetchUnreadCount()
|
||||||
notificationStore.startPeriodicRefresh()
|
notificationStore.startPeriodicRefresh()
|
||||||
|
|||||||
Reference in New Issue
Block a user