Compare commits
13 Commits
65194d49a9
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| d4c6def398 | |||
| 097631adc1 | |||
| 5cc0635630 | |||
| a51875c3f1 | |||
| e56feac831 | |||
| 46eb0f5a60 | |||
| 803316c4cc | |||
| 7d18824c32 | |||
| fea2ca8a3b | |||
| f72923f945 | |||
| dbb23cc7be | |||
| b3f4647867 | |||
| 150c542a3a |
446
.github/workflows/pipeline.yaml
vendored
446
.github/workflows/pipeline.yaml
vendored
@@ -1,221 +1,225 @@
|
||||
#name: CI/CD Pipeline
|
||||
#run-name: ${{ github.actor }} triggered pipeline on ${{ github.ref_name }}
|
||||
#
|
||||
#on:
|
||||
# pull_request:
|
||||
# paths:
|
||||
# - 'legalconsenthub/**'
|
||||
# - 'legalconsenthub-backend/**'
|
||||
# - 'api/**'
|
||||
# - '.github/workflows/pipeline.yaml'
|
||||
# push:
|
||||
# branches:
|
||||
# - main
|
||||
# paths:
|
||||
# - 'legalconsenthub/**'
|
||||
# - 'legalconsenthub-backend/**'
|
||||
# - 'api/**'
|
||||
# - '.github/workflows/pipeline.yaml'
|
||||
#
|
||||
#concurrency:
|
||||
# group: ci-${{ github.ref }}
|
||||
# cancel-in-progress: true
|
||||
#
|
||||
#jobs:
|
||||
# frontend:
|
||||
# runs-on: ubuntu-latest
|
||||
# defaults:
|
||||
# run:
|
||||
# working-directory: ./legalconsenthub
|
||||
#
|
||||
# steps:
|
||||
# - name: Checkout code
|
||||
# uses: actions/checkout@v4
|
||||
#
|
||||
# - name: Setup Node.js
|
||||
# uses: actions/setup-node@v4
|
||||
# with:
|
||||
# node-version: '22.16.0'
|
||||
#
|
||||
# - name: Setup Java
|
||||
# uses: actions/setup-java@v4
|
||||
# with:
|
||||
# distribution: 'temurin'
|
||||
# java-version: '21'
|
||||
#
|
||||
# - name: Setup pnpm
|
||||
# uses: pnpm/action-setup@v4
|
||||
# with:
|
||||
# version: 10.13.1
|
||||
# run_install: false
|
||||
#
|
||||
# - name: Get pnpm store directory
|
||||
# id: pnpm-cache
|
||||
# run: |
|
||||
# echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
|
||||
#
|
||||
# - name: Setup pnpm cache
|
||||
# uses: actions/cache@v4
|
||||
# with:
|
||||
# path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
|
||||
# key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
# restore-keys: |
|
||||
# ${{ runner.os }}-pnpm-store-
|
||||
#
|
||||
# - name: Install dependencies
|
||||
# run: pnpm install --frozen-lockfile
|
||||
#
|
||||
# - name: Build application
|
||||
# run: pnpm build
|
||||
#
|
||||
# - name: Run linting
|
||||
# run: pnpm lint
|
||||
#
|
||||
# - name: Run type checking
|
||||
# run: pnpm type-check
|
||||
#
|
||||
# - name: Set up Docker Buildx
|
||||
# uses: docker/setup-buildx-action@v3
|
||||
#
|
||||
# - name: Log in to Gitea Container Registry
|
||||
# if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
# uses: docker/login-action@v3
|
||||
# with:
|
||||
# registry: gitea.lugnas.de
|
||||
# username: ${{ github.actor }}
|
||||
# password: ${{ secrets.DOCKER_PUSH_TOKEN }}
|
||||
#
|
||||
# - name: Extract metadata for Docker
|
||||
# id: meta
|
||||
# uses: docker/metadata-action@v5
|
||||
# with:
|
||||
# images: gitea.lugnas.de/${{ github.repository_owner }}/legalconsenthub
|
||||
# tags: |
|
||||
# type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }}
|
||||
# type=sha,prefix=,format=long
|
||||
#
|
||||
# - name: Build and push Docker image
|
||||
# uses: docker/build-push-action@v5
|
||||
# with:
|
||||
# context: .
|
||||
# file: ./legalconsenthub/Dockerfile
|
||||
# push: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
|
||||
# platforms: linux/amd64
|
||||
# tags: ${{ steps.meta.outputs.tags }}
|
||||
# labels: ${{ steps.meta.outputs.labels }}
|
||||
# cache-from: type=local,src=/tmp/.buildx-cache
|
||||
# cache-to: type=local,dest=/tmp/.buildx-cache
|
||||
#
|
||||
# - name: Image built successfully
|
||||
# if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
# run: |
|
||||
# echo "✅ Docker image built and pushed successfully"
|
||||
# echo "📦 Image: gitea.lugnas.de/${{ github.repository_owner }}/legalconsenthub:latest"
|
||||
# echo "📦 Image: gitea.lugnas.de/${{ github.repository_owner }}/legalconsenthub:${{ github.sha }}"
|
||||
#
|
||||
# - name: Dry-run completed
|
||||
# if: github.event_name == 'pull_request'
|
||||
# run: |
|
||||
# echo "✅ Dry-run build completed successfully (image not pushed)"
|
||||
#
|
||||
# backend:
|
||||
# runs-on: ubuntu-latest
|
||||
#
|
||||
# defaults:
|
||||
# run:
|
||||
# working-directory: ./legalconsenthub-backend
|
||||
#
|
||||
# steps:
|
||||
# - name: Checkout code
|
||||
# uses: actions/checkout@v4
|
||||
#
|
||||
# - name: Setup Java
|
||||
# uses: actions/setup-java@v4
|
||||
# with:
|
||||
# distribution: 'temurin'
|
||||
# java-version: '21'
|
||||
#
|
||||
# - name: Setup Gradle cache
|
||||
# uses: actions/cache@v4
|
||||
# with:
|
||||
# path: |
|
||||
# ~/.gradle/caches
|
||||
# ~/.gradle/wrapper
|
||||
# legalconsenthub-backend/.gradle
|
||||
# key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
|
||||
# restore-keys: |
|
||||
# ${{ runner.os }}-gradle-
|
||||
#
|
||||
# - name: Build application
|
||||
# run: ./gradlew build -x test
|
||||
#
|
||||
# - name: Run ktlint check
|
||||
# run: ./gradlew ktlintCheck
|
||||
#
|
||||
# - name: Run tests
|
||||
# run: ./gradlew test
|
||||
# env:
|
||||
# # Fixes Ryuk testcontainers error during test (https://github.com/testcontainers/testcontainers-java/issues/7036)
|
||||
# TESTCONTAINERS_HOST_OVERRIDE: host.docker.internal
|
||||
#
|
||||
# - name: Set up Docker Buildx
|
||||
# uses: docker/setup-buildx-action@v3
|
||||
#
|
||||
# - name: Log in to Gitea Container Registry
|
||||
# if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
# uses: docker/login-action@v3
|
||||
# with:
|
||||
# registry: gitea.lugnas.de
|
||||
# username: ${{ github.actor }}
|
||||
# password: ${{ secrets.DOCKER_PUSH_TOKEN }}
|
||||
#
|
||||
# - name: Extract metadata for Docker
|
||||
# id: meta
|
||||
# uses: docker/metadata-action@v5
|
||||
# with:
|
||||
# images: gitea.lugnas.de/${{ github.repository_owner }}/legalconsenthub-backend
|
||||
# tags: |
|
||||
# type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }}
|
||||
# type=sha,prefix=,format=long
|
||||
#
|
||||
# - name: Build and push Docker image
|
||||
# uses: docker/build-push-action@v5
|
||||
# with:
|
||||
# context: .
|
||||
# file: ./legalconsenthub-backend/Dockerfile
|
||||
# push: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
|
||||
# platforms: linux/amd64
|
||||
# tags: ${{ steps.meta.outputs.tags }}
|
||||
# labels: ${{ steps.meta.outputs.labels }}
|
||||
# cache-from: type=local,src=/tmp/.buildx-cache
|
||||
# cache-to: type=local,dest=/tmp/.buildx-cache
|
||||
#
|
||||
# - name: Image built successfully
|
||||
# if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
# run: |
|
||||
# echo "✅ Docker image built and pushed successfully"
|
||||
# echo "📦 Image: gitea.lugnas.de/${{ github.repository_owner }}/legalconsenthub-backend:latest"
|
||||
# echo "📦 Image: gitea.lugnas.de/${{ github.repository_owner }}/legalconsenthub-backend:${{ github.sha }}"
|
||||
#
|
||||
# - name: Dry-run completed
|
||||
# if: github.event_name == 'pull_request'
|
||||
# run: |
|
||||
# echo "✅ Dry-run build completed successfully (image not pushed)"
|
||||
#
|
||||
# deploy:
|
||||
# runs-on: ubuntu-latest
|
||||
# needs: [frontend, backend]
|
||||
# if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
|
||||
#
|
||||
# steps:
|
||||
# - name: Checkout code
|
||||
# uses: actions/checkout@v4
|
||||
#
|
||||
# - name: Deploy to server
|
||||
# run: |
|
||||
# ssh -i ~/.ssh/id_rsa -p 32766 -o StrictHostKeyChecking=accept-new deploy@ds218 "sudo /usr/local/bin/deployLegalconsenthub.sh"
|
||||
#
|
||||
# - name: Deployment successful
|
||||
# run: |
|
||||
# echo "✅ Deployment triggered successfully"
|
||||
# echo "🚀 Application is being deployed to production"
|
||||
name: CI/CD Pipeline
|
||||
run-name: ${{ github.actor }} triggered pipeline on ${{ github.ref_name }}
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'legalconsenthub/**'
|
||||
- 'legalconsenthub-backend/**'
|
||||
- 'api/**'
|
||||
- '.github/workflows/pipeline.yaml'
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- 'legalconsenthub/**'
|
||||
- 'legalconsenthub-backend/**'
|
||||
- 'api/**'
|
||||
- '.github/workflows/pipeline.yaml'
|
||||
|
||||
concurrency:
|
||||
group: ci-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
frontend:
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./legalconsenthub
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '22.16.0'
|
||||
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '21'
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10.13.1
|
||||
run_install: false
|
||||
|
||||
- name: Get pnpm store directory
|
||||
id: pnpm-cache
|
||||
run: |
|
||||
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Setup pnpm cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
|
||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-store-
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build application
|
||||
run: pnpm build
|
||||
|
||||
- name: Run linting
|
||||
run: pnpm lint
|
||||
|
||||
- name: Run type checking
|
||||
run: pnpm type-check
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log in to Gitea Container Registry
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: git.gremiumhub.de
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.DOCKER_PUSH_TOKEN }}
|
||||
|
||||
- name: Extract metadata for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: git.gremiumhub.de/${{ github.repository_owner }}/legalconsenthub
|
||||
tags: |
|
||||
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }}
|
||||
type=sha,prefix=,format=long
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: ./legalconsenthub/Dockerfile
|
||||
push: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
|
||||
platforms: linux/amd64
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=local,src=/tmp/.buildx-cache-frontend
|
||||
cache-to: type=local,dest=/tmp/.buildx-cache-frontend,mode=max
|
||||
|
||||
- name: Image built successfully
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
run: |
|
||||
echo "✅ Docker image built and pushed successfully"
|
||||
echo "📦 Image: git.gremiumhub.de/${{ github.repository_owner }}/legalconsenthub:latest"
|
||||
echo "📦 Image: git.gremiumhub.de/${{ github.repository_owner }}/legalconsenthub:${{ github.sha }}"
|
||||
|
||||
- name: Dry-run completed
|
||||
if: github.event_name == 'pull_request'
|
||||
run: |
|
||||
echo "✅ Dry-run build completed successfully (image not pushed)"
|
||||
|
||||
backend:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./legalconsenthub-backend
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '21'
|
||||
|
||||
- name: Setup Gradle cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
~/.gradle/wrapper
|
||||
legalconsenthub-backend/.gradle
|
||||
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-gradle-
|
||||
|
||||
- name: Build application
|
||||
run: ./gradlew build -x test
|
||||
|
||||
- name: Run ktlint check
|
||||
run: ./gradlew ktlintCheck
|
||||
|
||||
- name: Run tests
|
||||
run: ./gradlew test
|
||||
env:
|
||||
TESTCONTAINERS_RYUK_DISABLED: "true"
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log in to Gitea Container Registry
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: git.gremiumhub.de
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.DOCKER_PUSH_TOKEN }}
|
||||
|
||||
- name: Extract metadata for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: git.gremiumhub.de/${{ github.repository_owner }}/legalconsenthub-backend
|
||||
tags: |
|
||||
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }}
|
||||
type=sha,prefix=,format=long
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: ./legalconsenthub-backend/Dockerfile
|
||||
push: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
|
||||
platforms: linux/amd64
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=local,src=/tmp/.buildx-cache-backend
|
||||
cache-to: type=local,dest=/tmp/.buildx-cache-backend,mode=max
|
||||
|
||||
- name: Image built successfully
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
run: |
|
||||
echo "✅ Docker image built and pushed successfully"
|
||||
echo "📦 Image: git.gremiumhub.de/${{ github.repository_owner }}/legalconsenthub-backend:latest"
|
||||
echo "📦 Image: git.gremiumhub.de/${{ github.repository_owner }}/legalconsenthub-backend:${{ github.sha }}"
|
||||
|
||||
- name: Dry-run completed
|
||||
if: github.event_name == 'pull_request'
|
||||
run: |
|
||||
echo "✅ Dry-run build completed successfully (image not pushed)"
|
||||
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [frontend, backend]
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
|
||||
steps:
|
||||
- name: Trigger Coolify redeploy (frontend)
|
||||
run: |
|
||||
curl -s -X POST \
|
||||
-H "Authorization: Bearer ${{ secrets.COOLIFY_DEPLOY_TOKEN }}" \
|
||||
"https://coolify.gremiumhub.de/api/v1/deploy?uuid=${{ secrets.COOLIFY_FRONTEND_UUID }}&force=false"
|
||||
|
||||
- name: Trigger Coolify redeploy (backend)
|
||||
run: |
|
||||
curl -s -X POST \
|
||||
-H "Authorization: Bearer ${{ secrets.COOLIFY_DEPLOY_TOKEN }}" \
|
||||
"https://coolify.gremiumhub.de/api/v1/deploy?uuid=${{ secrets.COOLIFY_BACKEND_UUID }}&force=false"
|
||||
|
||||
- name: Deployment triggered
|
||||
run: |
|
||||
echo "✅ Coolify redeployment triggered successfully"
|
||||
echo "🚀 Frontend and backend are being redeployed"
|
||||
|
||||
@@ -1681,6 +1681,30 @@ components:
|
||||
type: boolean
|
||||
default: false
|
||||
description: If true, renders a checkbox instead of text input
|
||||
readOnlyConditions:
|
||||
$ref: "#/components/schemas/VisibilityConditionGroup"
|
||||
description: If set, the column is read-only when conditions evaluate to true.
|
||||
readOnlyDefaultValue:
|
||||
type: string
|
||||
description: Value to write into each cell when the column transitions to read-only via readOnlyConditions.
|
||||
rowVisibilityCondition:
|
||||
$ref: "#/components/schemas/TableRowVisibilityConditionDto"
|
||||
description: If set, individual cells are hidden/shown based on the row's value in the specified column
|
||||
|
||||
TableRowVisibilityConditionDto:
|
||||
type: object
|
||||
description: Per-row cell visibility condition referencing another column in the same table
|
||||
properties:
|
||||
sourceColumnIndex:
|
||||
type: integer
|
||||
description: Index of the column in the same table to evaluate (0-based)
|
||||
expectedValues:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
description: Cell is visible if the source column's value matches any of these with the given operator
|
||||
operator:
|
||||
$ref: "#/components/schemas/VisibilityConditionOperator"
|
||||
|
||||
TableRowConstraintDto:
|
||||
type: object
|
||||
|
||||
@@ -35,6 +35,18 @@ class FormOption(
|
||||
AttributeOverride(name = "filterCondition.operator", column = Column(name = "col_config_filter_operator")),
|
||||
AttributeOverride(name = "isReadOnly", column = Column(name = "col_config_is_read_only")),
|
||||
AttributeOverride(name = "isCheckbox", column = Column(name = "col_config_is_checkbox")),
|
||||
AttributeOverride(
|
||||
name = "readOnlyDefaultValue",
|
||||
column = Column(name = "col_config_read_only_default_value"),
|
||||
),
|
||||
AttributeOverride(
|
||||
name = "readOnlyConditions",
|
||||
column = Column(name = "col_config_read_only_conditions", columnDefinition = "jsonb"),
|
||||
),
|
||||
AttributeOverride(
|
||||
name = "rowVisibilityCondition",
|
||||
column = Column(name = "col_config_row_visibility_condition", columnDefinition = "jsonb"),
|
||||
),
|
||||
)
|
||||
var columnConfig: TableColumnConfig? = null,
|
||||
@JdbcTypeCode(SqlTypes.JSON)
|
||||
|
||||
@@ -5,6 +5,8 @@ import jakarta.persistence.AttributeOverrides
|
||||
import jakarta.persistence.Column
|
||||
import jakarta.persistence.Embeddable
|
||||
import jakarta.persistence.Embedded
|
||||
import org.hibernate.annotations.JdbcTypeCode
|
||||
import org.hibernate.type.SqlTypes
|
||||
|
||||
@Embeddable
|
||||
data class TableColumnConfig(
|
||||
@@ -29,4 +31,11 @@ data class TableColumnConfig(
|
||||
val isReadOnly: Boolean = false,
|
||||
val isMultipleAllowed: Boolean = false,
|
||||
val isCheckbox: Boolean = false,
|
||||
@JdbcTypeCode(SqlTypes.JSON)
|
||||
@Column(columnDefinition = "jsonb")
|
||||
val readOnlyConditions: GroupCondition? = null,
|
||||
val readOnlyDefaultValue: String? = null,
|
||||
@JdbcTypeCode(SqlTypes.JSON)
|
||||
@Column(columnDefinition = "jsonb")
|
||||
val rowVisibilityCondition: RowVisibilityCondition? = null,
|
||||
)
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
package com.betriebsratkanzlei.legalconsenthub.form_element
|
||||
|
||||
import com.betriebsratkanzlei.legalconsenthub_api.model.TableColumnConfigDto
|
||||
import com.betriebsratkanzlei.legalconsenthub_api.model.TableRowVisibilityConditionDto
|
||||
import org.springframework.stereotype.Component
|
||||
import com.betriebsratkanzlei.legalconsenthub_api.model.VisibilityConditionOperator as VisibilityConditionOperatorDto
|
||||
|
||||
@Component
|
||||
class TableColumnConfigMapper(
|
||||
private val filterMapper: TableColumnFilterMapper,
|
||||
private val rowConstraintMapper: TableRowConstraintMapper,
|
||||
private val visibilityConditionMapper: FormElementVisibilityConditionMapper,
|
||||
) {
|
||||
fun toTableColumnConfigDto(config: TableColumnConfig): TableColumnConfigDto =
|
||||
TableColumnConfigDto(
|
||||
@@ -17,6 +20,9 @@ class TableColumnConfigMapper(
|
||||
isReadOnly = config.isReadOnly,
|
||||
isMultipleAllowed = config.isMultipleAllowed,
|
||||
isCheckbox = config.isCheckbox,
|
||||
readOnlyConditions = config.readOnlyConditions?.let { visibilityConditionMapper.toGroupConditionDto(it) },
|
||||
readOnlyDefaultValue = config.readOnlyDefaultValue,
|
||||
rowVisibilityCondition = config.rowVisibilityCondition?.let { toRowVisibilityConditionDto(it) },
|
||||
)
|
||||
|
||||
fun toTableColumnConfig(dto: TableColumnConfigDto): TableColumnConfig =
|
||||
@@ -28,5 +34,42 @@ class TableColumnConfigMapper(
|
||||
isReadOnly = dto.isReadOnly ?: false,
|
||||
isMultipleAllowed = dto.isMultipleAllowed ?: false,
|
||||
isCheckbox = dto.isCheckbox ?: false,
|
||||
readOnlyConditions = dto.readOnlyConditions?.let { visibilityConditionMapper.toGroupCondition(it) },
|
||||
readOnlyDefaultValue = dto.readOnlyDefaultValue,
|
||||
rowVisibilityCondition = dto.rowVisibilityCondition?.let { toRowVisibilityCondition(it) },
|
||||
)
|
||||
|
||||
private fun toRowVisibilityConditionDto(entity: RowVisibilityCondition): TableRowVisibilityConditionDto =
|
||||
TableRowVisibilityConditionDto(
|
||||
sourceColumnIndex = entity.sourceColumnIndex,
|
||||
expectedValues = entity.expectedValues,
|
||||
operator = entity.operator.toDto(),
|
||||
)
|
||||
|
||||
private fun toRowVisibilityCondition(dto: TableRowVisibilityConditionDto): RowVisibilityCondition =
|
||||
RowVisibilityCondition(
|
||||
sourceColumnIndex = dto.sourceColumnIndex ?: 0,
|
||||
expectedValues = dto.expectedValues ?: emptyList(),
|
||||
operator = dto.operator?.toEntity() ?: VisibilityConditionOperator.CONTAINS,
|
||||
)
|
||||
|
||||
private fun VisibilityConditionOperator.toDto(): VisibilityConditionOperatorDto =
|
||||
when (this) {
|
||||
VisibilityConditionOperator.EQUALS -> VisibilityConditionOperatorDto.EQUALS
|
||||
VisibilityConditionOperator.NOT_EQUALS -> VisibilityConditionOperatorDto.NOT_EQUALS
|
||||
VisibilityConditionOperator.IS_EMPTY -> VisibilityConditionOperatorDto.IS_EMPTY
|
||||
VisibilityConditionOperator.IS_NOT_EMPTY -> VisibilityConditionOperatorDto.IS_NOT_EMPTY
|
||||
VisibilityConditionOperator.CONTAINS -> VisibilityConditionOperatorDto.CONTAINS
|
||||
VisibilityConditionOperator.NOT_CONTAINS -> VisibilityConditionOperatorDto.NOT_CONTAINS
|
||||
}
|
||||
|
||||
private fun VisibilityConditionOperatorDto.toEntity(): VisibilityConditionOperator =
|
||||
when (this) {
|
||||
VisibilityConditionOperatorDto.EQUALS -> VisibilityConditionOperator.EQUALS
|
||||
VisibilityConditionOperatorDto.NOT_EQUALS -> VisibilityConditionOperator.NOT_EQUALS
|
||||
VisibilityConditionOperatorDto.IS_EMPTY -> VisibilityConditionOperator.IS_EMPTY
|
||||
VisibilityConditionOperatorDto.IS_NOT_EMPTY -> VisibilityConditionOperator.IS_NOT_EMPTY
|
||||
VisibilityConditionOperatorDto.CONTAINS -> VisibilityConditionOperator.CONTAINS
|
||||
VisibilityConditionOperatorDto.NOT_CONTAINS -> VisibilityConditionOperator.NOT_CONTAINS
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,12 @@ data class LeafCondition(
|
||||
val formElementOperator: VisibilityConditionOperator = VisibilityConditionOperator.EQUALS,
|
||||
) : VisibilityConditionNode
|
||||
|
||||
data class RowVisibilityCondition(
|
||||
val sourceColumnIndex: Int,
|
||||
val expectedValues: List<String>,
|
||||
val operator: VisibilityConditionOperator = VisibilityConditionOperator.CONTAINS,
|
||||
)
|
||||
|
||||
enum class GroupOperator {
|
||||
AND,
|
||||
OR,
|
||||
|
||||
@@ -70,10 +70,13 @@ create table form_element_options
|
||||
col_config_filter_operator varchar(255) check (col_config_filter_operator in
|
||||
('EQUALS', 'NOT_EQUALS', 'IS_EMPTY', 'IS_NOT_EMPTY',
|
||||
'CONTAINS', 'NOT_CONTAINS')),
|
||||
col_config_read_only_default_value varchar(255),
|
||||
col_config_source_table_ref varchar(255),
|
||||
label varchar(255) not null,
|
||||
option_value TEXT not null,
|
||||
row_constraint_table_reference varchar(255),
|
||||
col_config_read_only_conditions jsonb,
|
||||
col_config_row_visibility_condition jsonb,
|
||||
visibility_conditions jsonb
|
||||
);
|
||||
|
||||
|
||||
@@ -31,14 +31,6 @@ formElementSubSections:
|
||||
sectionSpawnConditionType: SHOW
|
||||
sectionSpawnExpectedValue: Einführung
|
||||
sectionSpawnOperator: EQUALS
|
||||
- templateReference: loeschkonzept_template
|
||||
sectionSpawnConditionType: SHOW
|
||||
sectionSpawnExpectedValue: Einführung
|
||||
sectionSpawnOperator: EQUALS
|
||||
- templateReference: datenschutz_template
|
||||
sectionSpawnConditionType: SHOW
|
||||
sectionSpawnExpectedValue: Einführung
|
||||
sectionSpawnOperator: EQUALS
|
||||
- templateReference: auswirkungen_arbeitnehmer_template
|
||||
sectionSpawnConditionType: SHOW
|
||||
sectionSpawnExpectedValue: Einführung
|
||||
@@ -520,6 +512,14 @@ formElementSubSections:
|
||||
sectionSpawnConditionType: SHOW
|
||||
sectionSpawnExpectedValue: Personenbeziehbar
|
||||
sectionSpawnOperator: EQUALS
|
||||
- templateReference: loeschkonzept_template
|
||||
sectionSpawnConditionType: SHOW
|
||||
sectionSpawnExpectedValue: Personenbeziehbar
|
||||
sectionSpawnOperator: EQUALS
|
||||
- templateReference: datenschutz_template
|
||||
sectionSpawnConditionType: SHOW
|
||||
sectionSpawnExpectedValue: Personenbeziehbar
|
||||
sectionSpawnOperator: EQUALS
|
||||
visibilityConditions:
|
||||
operator: OR
|
||||
conditions:
|
||||
@@ -669,6 +669,23 @@ formElementSubSections:
|
||||
sourceFormElementReference: sens_auswertung
|
||||
formElementExpectedValue: Funktionen vorhanden
|
||||
formElementOperator: EQUALS
|
||||
- reference: sens_art_analytische_funktionen_sonstiges
|
||||
title: Beschreibung der sonstigen analytischen Funktionen
|
||||
description: ''
|
||||
options:
|
||||
- value: ''
|
||||
label: Beschreibung
|
||||
processingPurpose: DATA_ANALYSIS
|
||||
employeeDataCategory: REVIEW_REQUIRED
|
||||
type: TEXTAREA
|
||||
visibilityConditions:
|
||||
operator: AND
|
||||
conditions:
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_art_analytische_funktionen
|
||||
formElementExpectedValue: Sonstiges
|
||||
formElementOperator: CONTAINS
|
||||
- reference: sens_luv
|
||||
title: Werden analytischen Funktionen für Leistungs-/Verhaltenskontrolle genutzt?
|
||||
description: ''
|
||||
@@ -724,7 +741,7 @@ formElementSubSections:
|
||||
title: Werden Ereignisse, Nutzungen und Logs erfasst?
|
||||
description: ''
|
||||
options:
|
||||
- value: ''
|
||||
- value: 'false'
|
||||
label: Nein
|
||||
processingPurpose: SYSTEM_OPERATION
|
||||
employeeDataCategory: NON_CRITICAL
|
||||
@@ -732,15 +749,23 @@ formElementSubSections:
|
||||
label: Technisch (Betrieb, Sicherheit)
|
||||
processingPurpose: SYSTEM_OPERATION
|
||||
employeeDataCategory: NON_CRITICAL
|
||||
- value: ''
|
||||
- value: 'false'
|
||||
label: Ja (Nutzer-/Aktivitätsbezug)
|
||||
processingPurpose: DATA_ANALYSIS
|
||||
employeeDataCategory: SENSITIVE
|
||||
- value: ''
|
||||
visibilityConditions:
|
||||
operator: AND
|
||||
conditions:
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_sichtbarkeit
|
||||
formElementOperator: NOT_EQUALS
|
||||
formElementExpectedValue: "Für Administratoren"
|
||||
- value: 'false'
|
||||
label: Audit-Logs
|
||||
processingPurpose: DATA_ANALYSIS
|
||||
employeeDataCategory: REVIEW_REQUIRED
|
||||
type: RADIOBUTTON
|
||||
type: CHECKBOX
|
||||
visibilityConditions:
|
||||
operator: AND
|
||||
conditions:
|
||||
@@ -791,6 +816,14 @@ formElementSubSections:
|
||||
label: Fachlich
|
||||
processingPurpose: DATA_ANALYSIS
|
||||
employeeDataCategory: REVIEW_REQUIRED
|
||||
visibilityConditions:
|
||||
operator: AND
|
||||
conditions:
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_sichtbarkeit
|
||||
formElementOperator: NOT_EQUALS
|
||||
formElementExpectedValue: "Für Administratoren"
|
||||
type: CHECKBOX
|
||||
visibilityConditions:
|
||||
operator: AND
|
||||
@@ -830,31 +863,41 @@ formElementSubSections:
|
||||
title: Bewertet / empfiehlt das System Maßnahmen über Beschäftigte oder bereitet Entscheidungen maßgeblich vor?
|
||||
description: ''
|
||||
options:
|
||||
- value: 'true'
|
||||
label: Ja
|
||||
processingPurpose: DATA_ANALYSIS
|
||||
employeeDataCategory: REVIEW_REQUIRED
|
||||
- value: ''
|
||||
label: Nein
|
||||
processingPurpose: SYSTEM_OPERATION
|
||||
employeeDataCategory: NON_CRITICAL
|
||||
- value: 'true'
|
||||
label: Unterstützend (Empfehlung)
|
||||
processingPurpose: DATA_ANALYSIS
|
||||
employeeDataCategory: REVIEW_REQUIRED
|
||||
- value: ''
|
||||
label: Auto-Entscheidungen
|
||||
processingPurpose: DATA_ANALYSIS
|
||||
employeeDataCategory: SENSITIVE
|
||||
type: CHECKBOX
|
||||
type: RADIOBUTTON
|
||||
visibilityConditions:
|
||||
operator: OR
|
||||
operator: AND
|
||||
conditions:
|
||||
- sourceFormElementReference: art_der_massnahme
|
||||
formElementExpectedValue: Einführung
|
||||
formElementOperator: EQUALS
|
||||
- sourceFormElementReference: art_der_massnahme
|
||||
formElementExpectedValue: Einführung mit einhergehender Ablösung
|
||||
formElementOperator: EQUALS
|
||||
- sourceFormElementReference: art_der_massnahme
|
||||
formElementExpectedValue: Änderung IT-System
|
||||
formElementOperator: EQUALS
|
||||
- nodeType: GROUP
|
||||
groupOperator: OR
|
||||
conditions:
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: art_der_massnahme
|
||||
formElementExpectedValue: Einführung
|
||||
formElementOperator: EQUALS
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: art_der_massnahme
|
||||
formElementExpectedValue: Einführung mit einhergehender Ablösung
|
||||
formElementOperator: EQUALS
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: art_der_massnahme
|
||||
formElementExpectedValue: Änderung IT-System
|
||||
formElementOperator: EQUALS
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_sichtbarkeit
|
||||
formElementExpectedValue: Für Administratoren
|
||||
formElementOperator: NOT_EQUALS
|
||||
- reference: sens_ki
|
||||
title: Kommt im System Künstliche Intelligenz zum Einsatz?
|
||||
description: ''
|
||||
@@ -894,7 +937,7 @@ formElementSubSections:
|
||||
label: Schnittstellen vorhanden
|
||||
processingPurpose: DATA_ANALYSIS
|
||||
employeeDataCategory: REVIEW_REQUIRED
|
||||
- value: ''
|
||||
- value: 'true'
|
||||
label: Exporte möglich
|
||||
processingPurpose: DATA_ANALYSIS
|
||||
employeeDataCategory: REVIEW_REQUIRED
|
||||
|
||||
@@ -14,39 +14,26 @@ formElementSubSections:
|
||||
description: ''
|
||||
type: TABLE
|
||||
visibilityConditions:
|
||||
operator: AND
|
||||
operator: OR
|
||||
conditions:
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_sichtbarkeit
|
||||
formElementExpectedValue: Für Administratoren
|
||||
formElementOperator: NOT_EQUALS
|
||||
sourceFormElementReference: sens_luv
|
||||
formElementExpectedValue: Aggregiert (Team)
|
||||
formElementOperator: CONTAINS
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_auswertung
|
||||
formElementExpectedValue: Funktionen vorhanden
|
||||
formElementOperator: EQUALS
|
||||
- nodeType: GROUP
|
||||
groupOperator: OR
|
||||
conditions:
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_luv
|
||||
formElementExpectedValue: Aggregiert (Team)
|
||||
formElementOperator: CONTAINS
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_luv
|
||||
formElementExpectedValue: Aggregiert (Abteilung)
|
||||
formElementOperator: CONTAINS
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_luv
|
||||
formElementExpectedValue: Individuell/vergleichend
|
||||
formElementOperator: CONTAINS
|
||||
sourceFormElementReference: sens_luv
|
||||
formElementExpectedValue: Aggregiert (Abteilung)
|
||||
formElementOperator: CONTAINS
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_luv
|
||||
formElementExpectedValue: Individuell/vergleichend
|
||||
formElementOperator: CONTAINS
|
||||
options:
|
||||
- value: '["V001", "V002", "V003", "V004", "V005"]'
|
||||
label: Verarbeitungsvorgang-ID
|
||||
label: Verarbeitungs-ID
|
||||
processingPurpose: SYSTEM_OPERATION
|
||||
employeeDataCategory: NON_CRITICAL
|
||||
- value: '["Personalstammdatenpflege", "Zeiterfassung", "Gehaltsabrechnung", "Leistungsbeurteilung", "Produktionsauswertung"]'
|
||||
@@ -54,13 +41,13 @@ formElementSubSections:
|
||||
processingPurpose: SYSTEM_OPERATION
|
||||
employeeDataCategory: NON_CRITICAL
|
||||
- value: '["HCM Master Data", "CATS Zeiterfassung", "Payroll Processing", "Performance Management", "Shop Floor Control"]'
|
||||
label: Systemfunktion/Verarbeitungsform
|
||||
processingPurpose: SYSTEM_OPERATION
|
||||
employeeDataCategory: NON_CRITICAL
|
||||
- value: '["Anlage und Pflege von Mitarbeiterstammdaten", "Erfassung von Arbeitszeiten und Abwesenheiten", "Berechnung und Auszahlung von Gehältern", "Erfassung und Auswertung von Leistungsdaten", "Analyse von Produktionskennzahlen pro Schicht"]'
|
||||
label: Kurzbeschreibung
|
||||
label: Verarbeitungsform
|
||||
processingPurpose: SYSTEM_OPERATION
|
||||
employeeDataCategory: NON_CRITICAL
|
||||
- value: '["Anlage und Pflege von Mitarbeiterstammdaten zur Personalverwaltung", "Erfassung von Arbeitszeiten und Abwesenheiten zur Arbeitszeitdokumentation", "Berechnung und Auszahlung von Gehältern zur Entgeltabrechnung", "Erfassung und Auswertung von Leistungsdaten zur Personalentwicklung", "Analyse von Produktionskennzahlen pro Schicht zur Produktionssteuerung"]'
|
||||
label: Verarbeitungszweck
|
||||
processingPurpose: DATA_ANALYSIS
|
||||
employeeDataCategory: REVIEW_REQUIRED
|
||||
- value: '["Stammdaten", "Arbeitszeitdaten", "Gehaltsdaten", "Leistungsdaten", "Produktionsdaten"]'
|
||||
label: Datenkategorien
|
||||
processingPurpose: DATA_ANALYSIS
|
||||
@@ -73,14 +60,6 @@ formElementSubSections:
|
||||
label: Betroffene Mitarbeiter
|
||||
processingPurpose: DATA_ANALYSIS
|
||||
employeeDataCategory: SENSITIVE
|
||||
- value: '["Personalverwaltung", "Arbeitszeitdokumentation", "Entgeltabrechnung", "Personalentwicklung", "Produktionssteuerung"]'
|
||||
label: Allgemeiner Zweck
|
||||
processingPurpose: DATA_ANALYSIS
|
||||
employeeDataCategory: REVIEW_REQUIRED
|
||||
- value: '["Fortlaufend", "Täglich", "Monatlich", "Jährlich/Halbjährlich", "Täglich/Schichtweise"]'
|
||||
label: Häufigkeit/Anlass
|
||||
processingPurpose: SYSTEM_OPERATION
|
||||
employeeDataCategory: NON_CRITICAL
|
||||
- value: '["R002", "R002,R004", "R001,R002", "R002,R004", "R003,R004"]'
|
||||
label: Rollen-Sichtbarkeit (grob)
|
||||
processingPurpose: DATA_ANALYSIS
|
||||
@@ -103,9 +82,17 @@ formElementSubSections:
|
||||
formElementExpectedValue: Für Administratoren
|
||||
formElementOperator: NOT_EQUALS
|
||||
- value: '["Nein", "Ja - an Vorgesetzte", "Nein", "Ja - an Management", "Ja - an Produktionsleitung"]'
|
||||
label: Export/Weitergabe (Ja/Nein + Ziel)
|
||||
label: Export/Weitergabe
|
||||
processingPurpose: DATA_ANALYSIS
|
||||
employeeDataCategory: REVIEW_REQUIRED
|
||||
visibilityConditions:
|
||||
operator: AND
|
||||
conditions:
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_schnittstellen_export
|
||||
formElementExpectedValue: Exporte möglich
|
||||
formElementOperator: CONTAINS
|
||||
- value: '[false, true, false, true, true]'
|
||||
label: Leistungs-/Verhaltenskontrolle beabsichtigt?
|
||||
processingPurpose: DATA_ANALYSIS
|
||||
@@ -113,6 +100,79 @@ formElementSubSections:
|
||||
columnConfig:
|
||||
isCheckbox: true
|
||||
|
||||
# Rollen-Sichtbarkeit (Umfassende Darstellung - shown when LuV contains Team, Abteilung, or Individuell)
|
||||
- title: Rollen-Sichtbarkeit (Umfassende Darstellung)
|
||||
formElements:
|
||||
- reference: rollen_sichtbarkeit_umfassend_tabelle
|
||||
title: Welche Rollen können welche Verarbeitungsvorgänge sehen? (Umfassende Darstellung)
|
||||
description: ''
|
||||
type: TABLE
|
||||
visibilityConditions:
|
||||
operator: OR
|
||||
conditions:
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_luv
|
||||
formElementExpectedValue: Aggregiert (Team)
|
||||
formElementOperator: CONTAINS
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_luv
|
||||
formElementExpectedValue: Aggregiert (Abteilung)
|
||||
formElementOperator: CONTAINS
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_luv
|
||||
formElementExpectedValue: Individuell/vergleichend
|
||||
formElementOperator: CONTAINS
|
||||
options:
|
||||
- value: '["V001", "V002", "V003", "V004", "V005"]'
|
||||
label: Verarbeitungs-ID
|
||||
processingPurpose: SYSTEM_OPERATION
|
||||
employeeDataCategory: NON_CRITICAL
|
||||
columnConfig:
|
||||
sourceTableReference: umfassende_datenverarbeitung_tabelle
|
||||
sourceColumnIndex: 0
|
||||
- value: '["R002", "R002", "R001", "R004", "R003"]'
|
||||
label: Rollen-ID
|
||||
processingPurpose: SYSTEM_OPERATION
|
||||
employeeDataCategory: NON_CRITICAL
|
||||
columnConfig:
|
||||
sourceTableReference: rollenstamm_tabelle
|
||||
sourceColumnIndex: 0
|
||||
- value: '["Nein", "Ja - an Vorgesetzte", "Nein", "Ja - an Management", "Ja - an Produktionsleitung"]'
|
||||
label: Export/Weitergabe
|
||||
processingPurpose: DATA_ANALYSIS
|
||||
employeeDataCategory: REVIEW_REQUIRED
|
||||
visibilityConditions:
|
||||
operator: AND
|
||||
conditions:
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_schnittstellen_export
|
||||
formElementExpectedValue: Exporte möglich
|
||||
formElementOperator: CONTAINS
|
||||
- value: '["", "Direkter Vorgesetzter (Team Lead)", "", "HR-Management und Geschäftsführung", "Produktionsleitung und Schichtführer"]'
|
||||
label: Empfänger
|
||||
processingPurpose: DATA_ANALYSIS
|
||||
employeeDataCategory: REVIEW_REQUIRED
|
||||
visibilityConditions:
|
||||
operator: AND
|
||||
conditions:
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_schnittstellen_export
|
||||
formElementExpectedValue: Exporte möglich
|
||||
formElementOperator: CONTAINS
|
||||
- value: '[true, true, false, true, true]'
|
||||
label: Personenbezug möglich
|
||||
processingPurpose: DATA_ANALYSIS
|
||||
employeeDataCategory: SENSITIVE
|
||||
- value: '["Aggregierte Stammdaten sichtbar", "Nur eigene Teamdaten", "Nur Finanzkennzahlen ohne Personenbezug", "Leistungsberichte mit Namensnennung", "Schichtauswertungen mit Mitarbeiterliste"]'
|
||||
label: Hinweise
|
||||
processingPurpose: SYSTEM_OPERATION
|
||||
employeeDataCategory: NON_CRITICAL
|
||||
|
||||
# Angaben zur Leistungs-/Verhaltenskontrolle
|
||||
- title: Angaben zur Leistungs-/Verhaltenskontrolle
|
||||
formElements:
|
||||
@@ -123,7 +183,7 @@ formElementSubSections:
|
||||
tableRowPreset:
|
||||
sourceTableReference: umfassende_datenverarbeitung_tabelle
|
||||
filterCondition:
|
||||
sourceColumnIndex: 11
|
||||
sourceColumnIndex: 9
|
||||
expectedValue: 'true'
|
||||
operator: EQUALS
|
||||
columnMappings:
|
||||
@@ -131,39 +191,26 @@ formElementSubSections:
|
||||
targetColumnIndex: 0
|
||||
canAddRows: false
|
||||
visibilityConditions:
|
||||
operator: AND
|
||||
operator: OR
|
||||
conditions:
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_sichtbarkeit
|
||||
formElementExpectedValue: Für Administratoren
|
||||
formElementOperator: NOT_EQUALS
|
||||
sourceFormElementReference: sens_luv
|
||||
formElementExpectedValue: Aggregiert (Team)
|
||||
formElementOperator: CONTAINS
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_auswertung
|
||||
formElementExpectedValue: Funktionen vorhanden
|
||||
formElementOperator: EQUALS
|
||||
- nodeType: GROUP
|
||||
groupOperator: OR
|
||||
conditions:
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_luv
|
||||
formElementExpectedValue: Aggregiert (Team)
|
||||
formElementOperator: CONTAINS
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_luv
|
||||
formElementExpectedValue: Aggregiert (Abteilung)
|
||||
formElementOperator: CONTAINS
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_luv
|
||||
formElementExpectedValue: Individuell/vergleichend
|
||||
formElementOperator: CONTAINS
|
||||
sourceFormElementReference: sens_luv
|
||||
formElementExpectedValue: Aggregiert (Abteilung)
|
||||
formElementOperator: CONTAINS
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_luv
|
||||
formElementExpectedValue: Individuell/vergleichend
|
||||
formElementOperator: CONTAINS
|
||||
options:
|
||||
- value: '["V002", "V004", "V005"]'
|
||||
label: Verarbeitungsvorgang-ID
|
||||
label: Verarbeitungs-ID
|
||||
processingPurpose: SYSTEM_OPERATION
|
||||
employeeDataCategory: NON_CRITICAL
|
||||
columnConfig:
|
||||
@@ -171,7 +218,11 @@ formElementSubSections:
|
||||
sourceColumnIndex: 0
|
||||
isReadOnly: true
|
||||
- value: '["Überwachung der Einhaltung von Arbeitszeiten", "Bewertung der individuellen Zielerreichung", "Auswertung der Produktivität pro Mitarbeiter/Schicht"]'
|
||||
label: Konkreter Kontrollzweck
|
||||
label: Kontrollzweck
|
||||
processingPurpose: DATA_ANALYSIS
|
||||
employeeDataCategory: SENSITIVE
|
||||
- value: '["Sicherstellung korrekter Arbeitszeiterfassung und -vergütung", "Leistungsorientierte Vergütung und Personalentwicklung", "Optimierung der Produktionseffizienz und Ressourcenplanung"]'
|
||||
label: Berechtigtes Kontrollinteresse
|
||||
processingPurpose: DATA_ANALYSIS
|
||||
employeeDataCategory: SENSITIVE
|
||||
- value: '["Berichte", "Dashboards", "Berichte,Dashboards"]'
|
||||
@@ -182,10 +233,10 @@ formElementSubSections:
|
||||
sourceTableReference: sens_art_analytische_funktionen
|
||||
sourceColumnIndex: 0
|
||||
isMultipleAllowed: true
|
||||
- value: '["Hinweis bei Abweichungen, keine automatischen Konsequenzen", "Einfluss auf Bonuszahlungen und Beförderungen", "Grundlage für Schichtplanung und Personalentscheidungen"]'
|
||||
label: Entscheidungswirkung
|
||||
processingPurpose: DATA_ANALYSIS
|
||||
employeeDataCategory: SENSITIVE
|
||||
- value: '["Täglich", "Jährlich/Halbjährlich", "Täglich/Schichtweise"]'
|
||||
label: Häufigkeit/Anlass
|
||||
processingPurpose: SYSTEM_OPERATION
|
||||
employeeDataCategory: NON_CRITICAL
|
||||
- value: '["Aggregiert (Team)", "Individuell/vergleichend", "Aggregiert (Team),Individuell/vergleichend"]'
|
||||
label: Granularität/Bezugsebene
|
||||
processingPurpose: DATA_ANALYSIS
|
||||
@@ -194,70 +245,24 @@ formElementSubSections:
|
||||
sourceTableReference: sens_luv
|
||||
sourceColumnIndex: 0
|
||||
isMultipleAllowed: true
|
||||
- value: '["Ja", "Ja", "Ja, bei begründetem Verdacht"]'
|
||||
label: Drilldown bis Person möglich?
|
||||
- value: '[true, true, true]'
|
||||
label: Drilldown auf Individualebene
|
||||
processingPurpose: DATA_ANALYSIS
|
||||
employeeDataCategory: SENSITIVE
|
||||
- value: '["Nein", "Ja - Ranking im Team", "Ja - Vergleich mit Durchschnitt"]'
|
||||
label: Ranking/Scoring
|
||||
processingPurpose: DATA_ANALYSIS
|
||||
employeeDataCategory: SENSITIVE
|
||||
visibilityConditions:
|
||||
operator: OR
|
||||
conditions:
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_art_analytische_funktionen
|
||||
formElementExpectedValue: Rankings
|
||||
formElementOperator: CONTAINS
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_art_analytische_funktionen
|
||||
formElementExpectedValue: Scores
|
||||
formElementOperator: CONTAINS
|
||||
columnConfig:
|
||||
isCheckbox: true
|
||||
rowVisibilityCondition:
|
||||
sourceColumnIndex: 5
|
||||
expectedValues:
|
||||
- "Aggregiert (Team)"
|
||||
- "Aggregiert (Abteilung)"
|
||||
operator: CONTAINS
|
||||
- value: '["N/A", "Min. 5 Personen im Vergleich", "Min. 10 Personen pro Auswertung"]'
|
||||
label: Mindestgruppe/Schwelle
|
||||
processingPurpose: DATA_ANALYSIS
|
||||
employeeDataCategory: SENSITIVE
|
||||
- value: '["Automatischer Hinweis bei > 10h Arbeitszeit", "Nein, manuelle Bewertung", "Alert bei Produktivität < 80% des Durchschnitts"]'
|
||||
label: Automatisierte Alerts/Entscheidungen
|
||||
processingPurpose: DATA_ANALYSIS
|
||||
employeeDataCategory: SENSITIVE
|
||||
visibilityConditions:
|
||||
operator: AND
|
||||
conditions:
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_alarme
|
||||
formElementExpectedValue: Nein
|
||||
formElementOperator: NOT_CONTAINS
|
||||
- value: '["Benachrichtigung an Mitarbeiter und Vorgesetzten", "4-Augen-Prinzip bei Bewertungen", "Prüfung durch BR vor Einzelauswertungen"]'
|
||||
label: Schutzmaßnahmen/Governance
|
||||
processingPurpose: SYSTEM_OPERATION
|
||||
employeeDataCategory: REVIEW_REQUIRED
|
||||
|
||||
# Access rules table
|
||||
- title: Zugriffsregeln hinsichtlich der Verarbeitungsvorgänge
|
||||
formElements:
|
||||
- reference: zugriffsregeln_tabelle
|
||||
title: Zugriffsregeln hinsichtlich der Verarbeitungsvorgänge
|
||||
description: ''
|
||||
type: TABLE
|
||||
visibilityConditions:
|
||||
operator: AND
|
||||
conditions:
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_sichtbarkeit
|
||||
formElementExpectedValue: Für Administratoren
|
||||
formElementOperator: NOT_EQUALS
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_auswertung
|
||||
formElementExpectedValue: Funktionen vorhanden
|
||||
formElementOperator: EQUALS
|
||||
- nodeType: GROUP
|
||||
groupOperator: OR
|
||||
operator: OR
|
||||
conditions:
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
@@ -269,14 +274,71 @@ formElementSubSections:
|
||||
sourceFormElementReference: sens_luv
|
||||
formElementExpectedValue: Aggregiert (Abteilung)
|
||||
formElementOperator: CONTAINS
|
||||
- value: '["Automatischer Hinweis bei > 10h Arbeitszeit", "Nein", "Alert bei Produktivität < 80% des Durchschnitts"]'
|
||||
label: Automatisierte Alerts
|
||||
processingPurpose: DATA_ANALYSIS
|
||||
employeeDataCategory: SENSITIVE
|
||||
visibilityConditions:
|
||||
operator: AND
|
||||
conditions:
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_luv
|
||||
formElementExpectedValue: Individuell/vergleichend
|
||||
formElementOperator: CONTAINS
|
||||
sourceFormElementReference: sens_alarme
|
||||
formElementExpectedValue: Nein
|
||||
formElementOperator: NOT_CONTAINS
|
||||
- value: '["Nein", "Empfehlung für Bonushöhe", "Nein"]'
|
||||
label: Automatisierte Entscheidungen
|
||||
processingPurpose: DATA_ANALYSIS
|
||||
employeeDataCategory: SENSITIVE
|
||||
visibilityConditions:
|
||||
operator: AND
|
||||
conditions:
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_automatisierte_entscheidungen
|
||||
formElementExpectedValue: Ja
|
||||
formElementOperator: EQUALS
|
||||
- value: '["Unterstützend", "Empfehlung", "Unterstützend"]'
|
||||
label: Art der Entscheidungsunterstützung
|
||||
processingPurpose: DATA_ANALYSIS
|
||||
employeeDataCategory: SENSITIVE
|
||||
visibilityConditions:
|
||||
operator: AND
|
||||
conditions:
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_automatisierte_entscheidungen
|
||||
formElementExpectedValue: Ja
|
||||
formElementOperator: EQUALS
|
||||
|
||||
# Access rules table
|
||||
- title: Zugriffsregeln hinsichtlich der Verarbeitungsvorgänge
|
||||
formElements:
|
||||
- reference: zugriffsregeln_tabelle
|
||||
title: Zugriffsregeln hinsichtlich der Verarbeitungsvorgänge
|
||||
description: ''
|
||||
type: TABLE
|
||||
visibilityConditions:
|
||||
operator: OR
|
||||
conditions:
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_luv
|
||||
formElementExpectedValue: Aggregiert (Team)
|
||||
formElementOperator: CONTAINS
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_luv
|
||||
formElementExpectedValue: Aggregiert (Abteilung)
|
||||
formElementOperator: CONTAINS
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_luv
|
||||
formElementExpectedValue: Individuell/vergleichend
|
||||
formElementOperator: CONTAINS
|
||||
options:
|
||||
- value: '["V001", "V002", "V003", "V004", "V005"]'
|
||||
label: Verarbeitungsvorgang-ID
|
||||
label: Verarbeitungs-ID
|
||||
processingPurpose: SYSTEM_OPERATION
|
||||
employeeDataCategory: NON_CRITICAL
|
||||
columnConfig:
|
||||
@@ -316,39 +378,26 @@ formElementSubSections:
|
||||
description: ''
|
||||
type: TABLE
|
||||
visibilityConditions:
|
||||
operator: AND
|
||||
operator: OR
|
||||
conditions:
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_sichtbarkeit
|
||||
formElementExpectedValue: Für Administratoren
|
||||
formElementOperator: NOT_EQUALS
|
||||
sourceFormElementReference: sens_luv
|
||||
formElementExpectedValue: Aggregiert (Team)
|
||||
formElementOperator: CONTAINS
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_auswertung
|
||||
formElementExpectedValue: Funktionen vorhanden
|
||||
formElementOperator: EQUALS
|
||||
- nodeType: GROUP
|
||||
groupOperator: OR
|
||||
conditions:
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_luv
|
||||
formElementExpectedValue: Aggregiert (Team)
|
||||
formElementOperator: CONTAINS
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_luv
|
||||
formElementExpectedValue: Aggregiert (Abteilung)
|
||||
formElementOperator: CONTAINS
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_luv
|
||||
formElementExpectedValue: Individuell/vergleichend
|
||||
formElementOperator: CONTAINS
|
||||
sourceFormElementReference: sens_luv
|
||||
formElementExpectedValue: Aggregiert (Abteilung)
|
||||
formElementOperator: CONTAINS
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_luv
|
||||
formElementExpectedValue: Individuell/vergleichend
|
||||
formElementOperator: CONTAINS
|
||||
options:
|
||||
- value: '["V001", "V002", "V003", "V004", "V005"]'
|
||||
label: Verarbeitungsvorgang-ID
|
||||
label: Verarbeitungs-ID
|
||||
processingPurpose: SYSTEM_OPERATION
|
||||
employeeDataCategory: NON_CRITICAL
|
||||
columnConfig:
|
||||
|
||||
@@ -116,16 +116,6 @@ formElementSubSections:
|
||||
sourceFormElementReference: loeschkonzept_hinterlegen
|
||||
formElementExpectedValue: 'true'
|
||||
formElementOperator: EQUALS
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_sichtbarkeit
|
||||
formElementExpectedValue: Für Administratoren
|
||||
formElementOperator: NOT_EQUALS
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_auswertung
|
||||
formElementExpectedValue: Funktionen vorhanden
|
||||
formElementOperator: EQUALS
|
||||
- nodeType: GROUP
|
||||
groupOperator: OR
|
||||
conditions:
|
||||
|
||||
@@ -17,36 +17,23 @@ formElementSubSections:
|
||||
description: ''
|
||||
type: TABLE
|
||||
visibilityConditions:
|
||||
operator: AND
|
||||
operator: OR
|
||||
conditions:
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_sichtbarkeit
|
||||
formElementExpectedValue: Für Administratoren
|
||||
formElementOperator: NOT_EQUALS
|
||||
sourceFormElementReference: sens_luv
|
||||
formElementExpectedValue: Aggregiert (Team)
|
||||
formElementOperator: CONTAINS
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_auswertung
|
||||
formElementExpectedValue: Funktionen vorhanden
|
||||
formElementOperator: EQUALS
|
||||
- nodeType: GROUP
|
||||
groupOperator: OR
|
||||
conditions:
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_luv
|
||||
formElementExpectedValue: Aggregiert (Team)
|
||||
formElementOperator: CONTAINS
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_luv
|
||||
formElementExpectedValue: Aggregiert (Abteilung)
|
||||
formElementOperator: CONTAINS
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_luv
|
||||
formElementExpectedValue: Individuell/vergleichend
|
||||
formElementOperator: CONTAINS
|
||||
sourceFormElementReference: sens_luv
|
||||
formElementExpectedValue: Aggregiert (Abteilung)
|
||||
formElementOperator: CONTAINS
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_luv
|
||||
formElementExpectedValue: Individuell/vergleichend
|
||||
formElementOperator: CONTAINS
|
||||
options:
|
||||
# Column 0: Schnittstellen-ID
|
||||
- value: '["IF001", "IF002", "IF003", "IF004", "IF005"]'
|
||||
|
||||
@@ -118,11 +118,6 @@ formElementSubSections:
|
||||
sourceFormElementReference: datenschutz_verantwortlichkeit_art
|
||||
formElementExpectedValue: Auftragsdatenverarbeitung
|
||||
formElementOperator: EQUALS
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_luv
|
||||
formElementExpectedValue: Individuell/vergleichend
|
||||
formElementOperator: CONTAINS
|
||||
options:
|
||||
- value: '["SAP SE (Cloud Services)", "Amazon Web Services EMEA SARL"]'
|
||||
label: Auftragsdatenverarbeiter
|
||||
@@ -186,11 +181,6 @@ formElementSubSections:
|
||||
sourceFormElementReference: datenschutz_verantwortlichkeit_art
|
||||
formElementExpectedValue: Gemeinsame Verantwortlichkeit
|
||||
formElementOperator: EQUALS
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_luv
|
||||
formElementExpectedValue: Individuell/vergleichend
|
||||
formElementOperator: CONTAINS
|
||||
options:
|
||||
- value: '["Betriebsrat", "Konzern-IT"]'
|
||||
label: Gemeinsame Verantwortlichkeit mit
|
||||
|
||||
@@ -29,14 +29,6 @@ formElementSubSections:
|
||||
sectionSpawnConditionType: SHOW
|
||||
sectionSpawnExpectedValue: Einführung
|
||||
sectionSpawnOperator: EQUALS
|
||||
- templateReference: loeschkonzept_template
|
||||
sectionSpawnConditionType: SHOW
|
||||
sectionSpawnExpectedValue: Einführung
|
||||
sectionSpawnOperator: EQUALS
|
||||
- templateReference: datenschutz_template
|
||||
sectionSpawnConditionType: SHOW
|
||||
sectionSpawnExpectedValue: Einführung
|
||||
sectionSpawnOperator: EQUALS
|
||||
- templateReference: auswirkungen_arbeitnehmer_template
|
||||
sectionSpawnConditionType: SHOW
|
||||
sectionSpawnExpectedValue: Einführung
|
||||
@@ -488,6 +480,14 @@ formElementSubSections:
|
||||
sectionSpawnConditionType: SHOW
|
||||
sectionSpawnExpectedValue: Personenbeziehbar
|
||||
sectionSpawnOperator: EQUALS
|
||||
- templateReference: loeschkonzept_template
|
||||
sectionSpawnConditionType: SHOW
|
||||
sectionSpawnExpectedValue: Personenbeziehbar
|
||||
sectionSpawnOperator: EQUALS
|
||||
- templateReference: datenschutz_template
|
||||
sectionSpawnConditionType: SHOW
|
||||
sectionSpawnExpectedValue: Personenbeziehbar
|
||||
sectionSpawnOperator: EQUALS
|
||||
visibilityConditions:
|
||||
operator: OR
|
||||
conditions:
|
||||
@@ -637,6 +637,23 @@ formElementSubSections:
|
||||
sourceFormElementReference: sens_auswertung
|
||||
formElementExpectedValue: Funktionen vorhanden
|
||||
formElementOperator: EQUALS
|
||||
- reference: sens_art_analytische_funktionen_sonstiges
|
||||
title: Beschreibung der sonstigen analytischen Funktionen
|
||||
description: ''
|
||||
options:
|
||||
- value: ''
|
||||
label: Beschreibung
|
||||
processingPurpose: DATA_ANALYSIS
|
||||
employeeDataCategory: REVIEW_REQUIRED
|
||||
type: TEXTAREA
|
||||
visibilityConditions:
|
||||
operator: AND
|
||||
conditions:
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_art_analytische_funktionen
|
||||
formElementExpectedValue: Sonstiges
|
||||
formElementOperator: CONTAINS
|
||||
- reference: sens_luv
|
||||
title: Werden analytischen Funktionen für Leistungs-/Verhaltenskontrolle genutzt?
|
||||
description: ''
|
||||
@@ -692,23 +709,31 @@ formElementSubSections:
|
||||
title: Werden Ereignisse, Nutzungen und Logs erfasst?
|
||||
description: ''
|
||||
options:
|
||||
- value: ''
|
||||
- value: 'false'
|
||||
label: Nein
|
||||
processingPurpose: SYSTEM_OPERATION
|
||||
employeeDataCategory: NON_CRITICAL
|
||||
- value: ''
|
||||
- value: 'false'
|
||||
label: Technisch (Betrieb, Sicherheit)
|
||||
processingPurpose: SYSTEM_OPERATION
|
||||
employeeDataCategory: NON_CRITICAL
|
||||
- value: ''
|
||||
- value: 'false'
|
||||
label: Ja (Nutzer-/Aktivitätsbezug)
|
||||
processingPurpose: DATA_ANALYSIS
|
||||
employeeDataCategory: SENSITIVE
|
||||
- value: ''
|
||||
visibilityConditions:
|
||||
operator: AND
|
||||
conditions:
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_sichtbarkeit
|
||||
formElementOperator: NOT_EQUALS
|
||||
formElementExpectedValue: "Für Administratoren"
|
||||
- value: 'false'
|
||||
label: Audit-Logs
|
||||
processingPurpose: DATA_ANALYSIS
|
||||
employeeDataCategory: REVIEW_REQUIRED
|
||||
type: RADIOBUTTON
|
||||
type: CHECKBOX
|
||||
visibilityConditions:
|
||||
operator: AND
|
||||
conditions:
|
||||
@@ -759,6 +784,14 @@ formElementSubSections:
|
||||
label: Fachlich
|
||||
processingPurpose: DATA_ANALYSIS
|
||||
employeeDataCategory: REVIEW_REQUIRED
|
||||
visibilityConditions:
|
||||
operator: AND
|
||||
conditions:
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_sichtbarkeit
|
||||
formElementOperator: NOT_EQUALS
|
||||
formElementExpectedValue: "Für Administratoren"
|
||||
type: CHECKBOX
|
||||
visibilityConditions:
|
||||
operator: AND
|
||||
@@ -799,30 +832,40 @@ formElementSubSections:
|
||||
description: ''
|
||||
options:
|
||||
- value: ''
|
||||
label: Nein
|
||||
processingPurpose: SYSTEM_OPERATION
|
||||
employeeDataCategory: NON_CRITICAL
|
||||
- value: ''
|
||||
label: Unterstützend (Empfehlung)
|
||||
label: Ja
|
||||
processingPurpose: DATA_ANALYSIS
|
||||
employeeDataCategory: REVIEW_REQUIRED
|
||||
- value: ''
|
||||
label: Auto-Entscheidungen
|
||||
processingPurpose: DATA_ANALYSIS
|
||||
employeeDataCategory: SENSITIVE
|
||||
type: CHECKBOX
|
||||
label: Nein
|
||||
processingPurpose: SYSTEM_OPERATION
|
||||
employeeDataCategory: NON_CRITICAL
|
||||
type: RADIOBUTTON
|
||||
visibilityConditions:
|
||||
operator: OR
|
||||
operator: AND
|
||||
conditions:
|
||||
- sourceFormElementReference: art_der_massnahme
|
||||
formElementExpectedValue: Einführung
|
||||
formElementOperator: EQUALS
|
||||
- sourceFormElementReference: art_der_massnahme
|
||||
formElementExpectedValue: Einführung mit einhergehender Ablösung
|
||||
formElementOperator: EQUALS
|
||||
- sourceFormElementReference: art_der_massnahme
|
||||
formElementExpectedValue: Änderung IT-System
|
||||
formElementOperator: EQUALS
|
||||
- nodeType: GROUP
|
||||
groupOperator: OR
|
||||
conditions:
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: art_der_massnahme
|
||||
formElementExpectedValue: Einführung
|
||||
formElementOperator: EQUALS
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: art_der_massnahme
|
||||
formElementExpectedValue: Einführung mit einhergehender Ablösung
|
||||
formElementOperator: EQUALS
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: art_der_massnahme
|
||||
formElementExpectedValue: Änderung IT-System
|
||||
formElementOperator: EQUALS
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_sichtbarkeit
|
||||
formElementExpectedValue: Für Administratoren
|
||||
formElementOperator: NOT_EQUALS
|
||||
- reference: sens_ki
|
||||
title: Kommt im System Künstliche Intelligenz zum Einsatz?
|
||||
description: ''
|
||||
|
||||
@@ -57,33 +57,48 @@ formElementSubSections:
|
||||
processingPurpose: SYSTEM_OPERATION
|
||||
employeeDataCategory: NON_CRITICAL
|
||||
- value: '[]'
|
||||
label: Systemfunktion/Verarbeitungsform
|
||||
label: Verarbeitungsform
|
||||
processingPurpose: SYSTEM_OPERATION
|
||||
employeeDataCategory: NON_CRITICAL
|
||||
- value: '[]'
|
||||
label: Kurzbeschreibung
|
||||
label: Verarbeitungszweck
|
||||
processingPurpose: SYSTEM_OPERATION
|
||||
employeeDataCategory: NON_CRITICAL
|
||||
- value: '[]'
|
||||
label: Datenkategorien
|
||||
processingPurpose: DATA_ANALYSIS
|
||||
employeeDataCategory: REVIEW_REQUIRED
|
||||
- value: '[]'
|
||||
label: Allgemeiner Zweck
|
||||
processingPurpose: DATA_ANALYSIS
|
||||
employeeDataCategory: REVIEW_REQUIRED
|
||||
- value: '[]'
|
||||
label: Betroffene Mitarbeiter
|
||||
processingPurpose: DATA_ANALYSIS
|
||||
employeeDataCategory: SENSITIVE
|
||||
- value: '[]'
|
||||
label: Häufigkeit/Anlass
|
||||
processingPurpose: SYSTEM_OPERATION
|
||||
employeeDataCategory: NON_CRITICAL
|
||||
- value: '[]'
|
||||
label: Leistungs-/Verhaltenskontrolle
|
||||
processingPurpose: DATA_ANALYSIS
|
||||
employeeDataCategory: SENSITIVE
|
||||
columnConfig:
|
||||
readOnlyDefaultValue: "Nein"
|
||||
readOnlyConditions:
|
||||
operator: OR
|
||||
conditions:
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_sichtbarkeit
|
||||
formElementExpectedValue: Für Administratoren
|
||||
formElementOperator: EQUALS
|
||||
- nodeType: GROUP
|
||||
groupOperator: AND
|
||||
conditions:
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_sichtbarkeit
|
||||
formElementExpectedValue: Für mehrere Rollen
|
||||
formElementOperator: EQUALS
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_auswertung
|
||||
formElementExpectedValue: Keine
|
||||
formElementOperator: EQUALS
|
||||
- title: Rollen-Sichtbarkeit (Einfache Darstellung)
|
||||
formElements:
|
||||
- reference: rollen_sichtbarkeit_einfach_tabelle
|
||||
@@ -128,7 +143,7 @@ formElementSubSections:
|
||||
formElementOperator: NOT_CONTAINS
|
||||
options:
|
||||
- value: '[]'
|
||||
label: Verarbeitungsvorgang-ID
|
||||
label: Verarbeitungs-ID
|
||||
processingPurpose: SYSTEM_OPERATION
|
||||
employeeDataCategory: NON_CRITICAL
|
||||
columnConfig:
|
||||
@@ -145,10 +160,26 @@ formElementSubSections:
|
||||
label: Export/Weitergabe
|
||||
processingPurpose: DATA_ANALYSIS
|
||||
employeeDataCategory: REVIEW_REQUIRED
|
||||
visibilityConditions:
|
||||
operator: AND
|
||||
conditions:
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_schnittstellen_export
|
||||
formElementExpectedValue: Exporte möglich
|
||||
formElementOperator: CONTAINS
|
||||
- value: '[]'
|
||||
label: Empfänger
|
||||
processingPurpose: DATA_ANALYSIS
|
||||
employeeDataCategory: REVIEW_REQUIRED
|
||||
visibilityConditions:
|
||||
operator: AND
|
||||
conditions:
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_schnittstellen_export
|
||||
formElementExpectedValue: Exporte möglich
|
||||
formElementOperator: CONTAINS
|
||||
- value: '[]'
|
||||
label: Personenbezug möglich
|
||||
processingPurpose: DATA_ANALYSIS
|
||||
@@ -164,39 +195,26 @@ formElementSubSections:
|
||||
description: ''
|
||||
type: TABLE
|
||||
visibilityConditions:
|
||||
operator: AND
|
||||
operator: OR
|
||||
conditions:
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_sichtbarkeit
|
||||
formElementExpectedValue: Für Administratoren
|
||||
formElementOperator: NOT_EQUALS
|
||||
sourceFormElementReference: sens_luv
|
||||
formElementExpectedValue: Aggregiert (Team)
|
||||
formElementOperator: CONTAINS
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_auswertung
|
||||
formElementExpectedValue: Funktionen vorhanden
|
||||
formElementOperator: EQUALS
|
||||
- nodeType: GROUP
|
||||
groupOperator: OR
|
||||
conditions:
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_luv
|
||||
formElementExpectedValue: Aggregiert (Team)
|
||||
formElementOperator: CONTAINS
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_luv
|
||||
formElementExpectedValue: Aggregiert (Abteilung)
|
||||
formElementOperator: CONTAINS
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_luv
|
||||
formElementExpectedValue: Individuell/vergleichend
|
||||
formElementOperator: CONTAINS
|
||||
sourceFormElementReference: sens_luv
|
||||
formElementExpectedValue: Aggregiert (Abteilung)
|
||||
formElementOperator: CONTAINS
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_luv
|
||||
formElementExpectedValue: Individuell/vergleichend
|
||||
formElementOperator: CONTAINS
|
||||
options:
|
||||
- value: '[]'
|
||||
label: Verarbeitungsvorgang-ID
|
||||
label: Verarbeitungs-ID
|
||||
processingPurpose: SYSTEM_OPERATION
|
||||
employeeDataCategory: NON_CRITICAL
|
||||
- value: '[]'
|
||||
@@ -204,13 +222,13 @@ formElementSubSections:
|
||||
processingPurpose: SYSTEM_OPERATION
|
||||
employeeDataCategory: NON_CRITICAL
|
||||
- value: '[]'
|
||||
label: Systemfunktion/Verarbeitungsform
|
||||
label: Verarbeitungsform
|
||||
processingPurpose: SYSTEM_OPERATION
|
||||
employeeDataCategory: NON_CRITICAL
|
||||
- value: '[]'
|
||||
label: Kurzbeschreibung
|
||||
processingPurpose: SYSTEM_OPERATION
|
||||
employeeDataCategory: NON_CRITICAL
|
||||
label: Verarbeitungszweck
|
||||
processingPurpose: DATA_ANALYSIS
|
||||
employeeDataCategory: REVIEW_REQUIRED
|
||||
- value: '[]'
|
||||
label: Datenkategorien
|
||||
processingPurpose: DATA_ANALYSIS
|
||||
@@ -223,14 +241,6 @@ formElementSubSections:
|
||||
label: Betroffene Mitarbeiter
|
||||
processingPurpose: DATA_ANALYSIS
|
||||
employeeDataCategory: SENSITIVE
|
||||
- value: '[]'
|
||||
label: Allgemeiner Zweck
|
||||
processingPurpose: DATA_ANALYSIS
|
||||
employeeDataCategory: REVIEW_REQUIRED
|
||||
- value: '[]'
|
||||
label: Häufigkeit/Anlass
|
||||
processingPurpose: SYSTEM_OPERATION
|
||||
employeeDataCategory: NON_CRITICAL
|
||||
- value: '[]'
|
||||
label: Rollen-Sichtbarkeit (grob)
|
||||
processingPurpose: DATA_ANALYSIS
|
||||
@@ -253,7 +263,7 @@ formElementSubSections:
|
||||
formElementExpectedValue: Für Administratoren
|
||||
formElementOperator: NOT_EQUALS
|
||||
- value: '[]'
|
||||
label: Export/Weitergabe (Ja/Nein + Ziel)
|
||||
label: Export/Weitergabe
|
||||
processingPurpose: DATA_ANALYSIS
|
||||
employeeDataCategory: REVIEW_REQUIRED
|
||||
visibilityConditions:
|
||||
@@ -262,14 +272,85 @@ formElementSubSections:
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_schnittstellen_export
|
||||
formElementExpectedValue: Nein
|
||||
formElementOperator: NOT_CONTAINS
|
||||
formElementExpectedValue: Exporte möglich
|
||||
formElementOperator: CONTAINS
|
||||
- value: '[]'
|
||||
label: Leistungs-/Verhaltenskontrolle beabsichtigt?
|
||||
processingPurpose: DATA_ANALYSIS
|
||||
employeeDataCategory: SENSITIVE
|
||||
columnConfig:
|
||||
isCheckbox: true
|
||||
- title: Rollen-Sichtbarkeit (Umfassende Darstellung)
|
||||
formElements:
|
||||
- reference: rollen_sichtbarkeit_umfassend_tabelle
|
||||
title: Welche Rollen können welche Verarbeitungsvorgänge sehen? (Umfassende Darstellung)
|
||||
description: ''
|
||||
type: TABLE
|
||||
visibilityConditions:
|
||||
operator: OR
|
||||
conditions:
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_luv
|
||||
formElementExpectedValue: Aggregiert (Team)
|
||||
formElementOperator: CONTAINS
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_luv
|
||||
formElementExpectedValue: Aggregiert (Abteilung)
|
||||
formElementOperator: CONTAINS
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_luv
|
||||
formElementExpectedValue: Individuell/vergleichend
|
||||
formElementOperator: CONTAINS
|
||||
options:
|
||||
- value: '[]'
|
||||
label: Verarbeitungs-ID
|
||||
processingPurpose: SYSTEM_OPERATION
|
||||
employeeDataCategory: NON_CRITICAL
|
||||
columnConfig:
|
||||
sourceTableReference: umfassende_datenverarbeitung_tabelle
|
||||
sourceColumnIndex: 0
|
||||
- value: '[]'
|
||||
label: Rollen-ID
|
||||
processingPurpose: SYSTEM_OPERATION
|
||||
employeeDataCategory: NON_CRITICAL
|
||||
columnConfig:
|
||||
sourceTableReference: rollenstamm_tabelle
|
||||
sourceColumnIndex: 0
|
||||
- value: '[]'
|
||||
label: Export/Weitergabe
|
||||
processingPurpose: DATA_ANALYSIS
|
||||
employeeDataCategory: REVIEW_REQUIRED
|
||||
visibilityConditions:
|
||||
operator: AND
|
||||
conditions:
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_schnittstellen_export
|
||||
formElementExpectedValue: Exporte möglich
|
||||
formElementOperator: CONTAINS
|
||||
- value: '[]'
|
||||
label: Empfänger
|
||||
processingPurpose: DATA_ANALYSIS
|
||||
employeeDataCategory: REVIEW_REQUIRED
|
||||
visibilityConditions:
|
||||
operator: AND
|
||||
conditions:
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_schnittstellen_export
|
||||
formElementExpectedValue: Exporte möglich
|
||||
formElementOperator: CONTAINS
|
||||
- value: '[]'
|
||||
label: Personenbezug möglich
|
||||
processingPurpose: DATA_ANALYSIS
|
||||
employeeDataCategory: SENSITIVE
|
||||
- value: '[]'
|
||||
label: Hinweise
|
||||
processingPurpose: SYSTEM_OPERATION
|
||||
employeeDataCategory: NON_CRITICAL
|
||||
- title: Angaben zur Leistungs-/Verhaltenskontrolle
|
||||
formElements:
|
||||
- reference: luv_details_tabelle
|
||||
@@ -279,7 +360,7 @@ formElementSubSections:
|
||||
tableRowPreset:
|
||||
sourceTableReference: umfassende_datenverarbeitung_tabelle
|
||||
filterCondition:
|
||||
sourceColumnIndex: 11
|
||||
sourceColumnIndex: 9
|
||||
expectedValue: 'true'
|
||||
operator: EQUALS
|
||||
columnMappings:
|
||||
@@ -287,39 +368,26 @@ formElementSubSections:
|
||||
targetColumnIndex: 0
|
||||
canAddRows: false
|
||||
visibilityConditions:
|
||||
operator: AND
|
||||
operator: OR
|
||||
conditions:
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_sichtbarkeit
|
||||
formElementExpectedValue: Für Administratoren
|
||||
formElementOperator: NOT_EQUALS
|
||||
sourceFormElementReference: sens_luv
|
||||
formElementExpectedValue: Aggregiert (Team)
|
||||
formElementOperator: CONTAINS
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_auswertung
|
||||
formElementExpectedValue: Funktionen vorhanden
|
||||
formElementOperator: EQUALS
|
||||
- nodeType: GROUP
|
||||
groupOperator: OR
|
||||
conditions:
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_luv
|
||||
formElementExpectedValue: Aggregiert (Team)
|
||||
formElementOperator: CONTAINS
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_luv
|
||||
formElementExpectedValue: Aggregiert (Abteilung)
|
||||
formElementOperator: CONTAINS
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_luv
|
||||
formElementExpectedValue: Individuell/vergleichend
|
||||
formElementOperator: CONTAINS
|
||||
sourceFormElementReference: sens_luv
|
||||
formElementExpectedValue: Aggregiert (Abteilung)
|
||||
formElementOperator: CONTAINS
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_luv
|
||||
formElementExpectedValue: Individuell/vergleichend
|
||||
formElementOperator: CONTAINS
|
||||
options:
|
||||
- value: '[]'
|
||||
label: Verarbeitungsvorgang-ID
|
||||
label: Verarbeitungs-ID
|
||||
processingPurpose: SYSTEM_OPERATION
|
||||
employeeDataCategory: NON_CRITICAL
|
||||
columnConfig:
|
||||
@@ -327,7 +395,11 @@ formElementSubSections:
|
||||
sourceColumnIndex: 0
|
||||
isReadOnly: true
|
||||
- value: '[]'
|
||||
label: Konkreter Kontrollzweck
|
||||
label: Kontrollzweck
|
||||
processingPurpose: DATA_ANALYSIS
|
||||
employeeDataCategory: SENSITIVE
|
||||
- value: '[]'
|
||||
label: Berechtigtes Kontrollinteresse
|
||||
processingPurpose: DATA_ANALYSIS
|
||||
employeeDataCategory: SENSITIVE
|
||||
- value: '[]'
|
||||
@@ -339,9 +411,9 @@ formElementSubSections:
|
||||
sourceColumnIndex: 0
|
||||
isMultipleAllowed: true
|
||||
- value: '[]'
|
||||
label: Entscheidungswirkung
|
||||
processingPurpose: DATA_ANALYSIS
|
||||
employeeDataCategory: SENSITIVE
|
||||
label: Häufigkeit/Anlass
|
||||
processingPurpose: SYSTEM_OPERATION
|
||||
employeeDataCategory: NON_CRITICAL
|
||||
- value: '[]'
|
||||
label: Granularität/Bezugsebene
|
||||
processingPurpose: DATA_ANALYSIS
|
||||
@@ -351,67 +423,23 @@ formElementSubSections:
|
||||
sourceColumnIndex: 0
|
||||
isMultipleAllowed: true
|
||||
- value: '[]'
|
||||
label: Drilldown bis Person möglich?
|
||||
label: Drilldown auf Individualebene
|
||||
processingPurpose: DATA_ANALYSIS
|
||||
employeeDataCategory: SENSITIVE
|
||||
- value: '[]'
|
||||
label: Ranking/Scoring
|
||||
processingPurpose: DATA_ANALYSIS
|
||||
employeeDataCategory: SENSITIVE
|
||||
visibilityConditions:
|
||||
operator: OR
|
||||
conditions:
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_art_analytische_funktionen
|
||||
formElementExpectedValue: Rankings
|
||||
formElementOperator: CONTAINS
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_art_analytische_funktionen
|
||||
formElementExpectedValue: Scores
|
||||
formElementOperator: CONTAINS
|
||||
columnConfig:
|
||||
isCheckbox: true
|
||||
rowVisibilityCondition:
|
||||
sourceColumnIndex: 5
|
||||
expectedValues:
|
||||
- "Aggregiert (Team)"
|
||||
- "Aggregiert (Abteilung)"
|
||||
operator: CONTAINS
|
||||
- value: '[]'
|
||||
label: Mindestgruppe/Schwelle
|
||||
processingPurpose: DATA_ANALYSIS
|
||||
employeeDataCategory: SENSITIVE
|
||||
- value: '[]'
|
||||
label: Automatisierte Alerts/Entscheidungen
|
||||
processingPurpose: DATA_ANALYSIS
|
||||
employeeDataCategory: SENSITIVE
|
||||
visibilityConditions:
|
||||
operator: AND
|
||||
conditions:
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_alarme
|
||||
formElementExpectedValue: Nein
|
||||
formElementOperator: NOT_CONTAINS
|
||||
- value: '[]'
|
||||
label: Schutzmaßnahmen/Governance
|
||||
processingPurpose: SYSTEM_OPERATION
|
||||
employeeDataCategory: REVIEW_REQUIRED
|
||||
- title: Zugriffsregeln hinsichtlich der Verarbeitungsvorgänge
|
||||
formElements:
|
||||
- reference: zugriffsregeln_tabelle
|
||||
title: Zugriffsregeln hinsichtlich der Verarbeitungsvorgänge
|
||||
description: ''
|
||||
type: TABLE
|
||||
visibilityConditions:
|
||||
operator: AND
|
||||
conditions:
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_sichtbarkeit
|
||||
formElementExpectedValue: Für Administratoren
|
||||
formElementOperator: NOT_EQUALS
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_auswertung
|
||||
formElementExpectedValue: Funktionen vorhanden
|
||||
formElementOperator: EQUALS
|
||||
- nodeType: GROUP
|
||||
groupOperator: OR
|
||||
operator: OR
|
||||
conditions:
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
@@ -423,14 +451,69 @@ formElementSubSections:
|
||||
sourceFormElementReference: sens_luv
|
||||
formElementExpectedValue: Aggregiert (Abteilung)
|
||||
formElementOperator: CONTAINS
|
||||
- value: '[]'
|
||||
label: Automatisierte Alerts
|
||||
processingPurpose: DATA_ANALYSIS
|
||||
employeeDataCategory: SENSITIVE
|
||||
visibilityConditions:
|
||||
operator: AND
|
||||
conditions:
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_luv
|
||||
formElementExpectedValue: Individuell/vergleichend
|
||||
formElementOperator: CONTAINS
|
||||
sourceFormElementReference: sens_alarme
|
||||
formElementExpectedValue: Nein
|
||||
formElementOperator: NOT_CONTAINS
|
||||
- value: '[]'
|
||||
label: Automatisierte Entscheidungen
|
||||
processingPurpose: DATA_ANALYSIS
|
||||
employeeDataCategory: SENSITIVE
|
||||
visibilityConditions:
|
||||
operator: AND
|
||||
conditions:
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_automatisierte_entscheidungen
|
||||
formElementExpectedValue: Ja
|
||||
formElementOperator: EQUALS
|
||||
- value: '[]'
|
||||
label: Art der Entscheidungsunterstützung
|
||||
processingPurpose: DATA_ANALYSIS
|
||||
employeeDataCategory: SENSITIVE
|
||||
visibilityConditions:
|
||||
operator: AND
|
||||
conditions:
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_automatisierte_entscheidungen
|
||||
formElementExpectedValue: Ja
|
||||
formElementOperator: EQUALS
|
||||
- title: Zugriffsregeln hinsichtlich der Verarbeitungsvorgänge
|
||||
formElements:
|
||||
- reference: zugriffsregeln_tabelle
|
||||
title: Zugriffsregeln hinsichtlich der Verarbeitungsvorgänge
|
||||
description: ''
|
||||
type: TABLE
|
||||
visibilityConditions:
|
||||
operator: OR
|
||||
conditions:
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_luv
|
||||
formElementExpectedValue: Aggregiert (Team)
|
||||
formElementOperator: CONTAINS
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_luv
|
||||
formElementExpectedValue: Aggregiert (Abteilung)
|
||||
formElementOperator: CONTAINS
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_luv
|
||||
formElementExpectedValue: Individuell/vergleichend
|
||||
formElementOperator: CONTAINS
|
||||
options:
|
||||
- value: '[]'
|
||||
label: Verarbeitungsvorgang-ID
|
||||
label: Verarbeitungs-ID
|
||||
processingPurpose: SYSTEM_OPERATION
|
||||
employeeDataCategory: NON_CRITICAL
|
||||
columnConfig:
|
||||
@@ -468,39 +551,26 @@ formElementSubSections:
|
||||
description: ''
|
||||
type: TABLE
|
||||
visibilityConditions:
|
||||
operator: AND
|
||||
operator: OR
|
||||
conditions:
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_sichtbarkeit
|
||||
formElementExpectedValue: Für Administratoren
|
||||
formElementOperator: NOT_EQUALS
|
||||
sourceFormElementReference: sens_luv
|
||||
formElementExpectedValue: Aggregiert (Team)
|
||||
formElementOperator: CONTAINS
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_auswertung
|
||||
formElementExpectedValue: Funktionen vorhanden
|
||||
formElementOperator: EQUALS
|
||||
- nodeType: GROUP
|
||||
groupOperator: OR
|
||||
conditions:
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_luv
|
||||
formElementExpectedValue: Aggregiert (Team)
|
||||
formElementOperator: CONTAINS
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_luv
|
||||
formElementExpectedValue: Aggregiert (Abteilung)
|
||||
formElementOperator: CONTAINS
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_luv
|
||||
formElementExpectedValue: Individuell/vergleichend
|
||||
formElementOperator: CONTAINS
|
||||
sourceFormElementReference: sens_luv
|
||||
formElementExpectedValue: Aggregiert (Abteilung)
|
||||
formElementOperator: CONTAINS
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_luv
|
||||
formElementExpectedValue: Individuell/vergleichend
|
||||
formElementOperator: CONTAINS
|
||||
options:
|
||||
- value: '[]'
|
||||
label: Verarbeitungsvorgang-ID
|
||||
label: Verarbeitungs-ID
|
||||
processingPurpose: SYSTEM_OPERATION
|
||||
employeeDataCategory: NON_CRITICAL
|
||||
columnConfig:
|
||||
@@ -532,4 +602,4 @@ formElementSubSections:
|
||||
- value: '[]'
|
||||
label: Bedingungen
|
||||
processingPurpose: SYSTEM_OPERATION
|
||||
employeeDataCategory: REVIEW_REQUIRED
|
||||
employeeDataCategory: REVIEW_REQUIRED
|
||||
|
||||
@@ -212,16 +212,6 @@ formElementSubSections:
|
||||
sourceFormElementReference: loeschkonzept_hinterlegen
|
||||
formElementExpectedValue: 'true'
|
||||
formElementOperator: EQUALS
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_sichtbarkeit
|
||||
formElementExpectedValue: Für Administratoren
|
||||
formElementOperator: NOT_EQUALS
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_auswertung
|
||||
formElementExpectedValue: Funktionen vorhanden
|
||||
formElementOperator: EQUALS
|
||||
- nodeType: GROUP
|
||||
groupOperator: OR
|
||||
conditions:
|
||||
|
||||
@@ -98,36 +98,23 @@ formElementSubSections:
|
||||
description: ''
|
||||
type: TABLE
|
||||
visibilityConditions:
|
||||
operator: AND
|
||||
operator: OR
|
||||
conditions:
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_sichtbarkeit
|
||||
formElementExpectedValue: Für Administratoren
|
||||
formElementOperator: NOT_EQUALS
|
||||
sourceFormElementReference: sens_luv
|
||||
formElementExpectedValue: Aggregiert (Team)
|
||||
formElementOperator: CONTAINS
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_auswertung
|
||||
formElementExpectedValue: Funktionen vorhanden
|
||||
formElementOperator: EQUALS
|
||||
- nodeType: GROUP
|
||||
groupOperator: OR
|
||||
conditions:
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_luv
|
||||
formElementExpectedValue: Aggregiert (Team)
|
||||
formElementOperator: CONTAINS
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_luv
|
||||
formElementExpectedValue: Aggregiert (Abteilung)
|
||||
formElementOperator: CONTAINS
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_luv
|
||||
formElementExpectedValue: Individuell/vergleichend
|
||||
formElementOperator: CONTAINS
|
||||
sourceFormElementReference: sens_luv
|
||||
formElementExpectedValue: Aggregiert (Abteilung)
|
||||
formElementOperator: CONTAINS
|
||||
- nodeType: LEAF
|
||||
formElementConditionType: SHOW
|
||||
sourceFormElementReference: sens_luv
|
||||
formElementExpectedValue: Individuell/vergleichend
|
||||
formElementOperator: CONTAINS
|
||||
options:
|
||||
- value: '[]'
|
||||
label: Schnittstellen-ID
|
||||
|
||||
@@ -198,7 +198,7 @@ const hasOverflow = ref(false)
|
||||
const { evaluateFormElementVisibility } = useFormElementVisibility()
|
||||
const { clearHiddenFormElementValues } = useFormElementValueClearing()
|
||||
const { processSpawnTriggers } = useSectionSpawning()
|
||||
const { cloneElement } = useClonableElements()
|
||||
const { cloneElement } = useFormElementDuplication()
|
||||
|
||||
const { isSwiping } = usePointerSwipe(stepperScrollEl, {
|
||||
threshold: 0,
|
||||
|
||||
@@ -183,8 +183,8 @@
|
||||
<script setup lang="ts">
|
||||
import type { AccordionItem } from '@nuxt/ui'
|
||||
import type { ApplicationFormDto, ApplicationFormVersionDto } from '~~/.api-client'
|
||||
import type { FormValueDiff, ValueChange, SectionChanges, TableRowDiff } from '~~/types/formDiff'
|
||||
import { compareApplicationFormValues, groupChangesBySection } from '~/utils/formDiff'
|
||||
import type { FormValueDiff, ValueChange, SectionChanges, TableRowDiff } from '~~/types/formSnapshotComparison'
|
||||
import { compareApplicationFormValues, groupChangesBySection } from '~/utils/formSnapshotComparison'
|
||||
|
||||
const props = defineProps<{
|
||||
open: boolean
|
||||
|
||||
@@ -3,20 +3,40 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { FormOptionDto } from '~~/.api-client'
|
||||
import type { FormElementDto, FormOptionDto } from '~~/.api-client'
|
||||
import { useFormElementVisibility } from '~/composables/useFormElementVisibility'
|
||||
|
||||
const props = defineProps<{
|
||||
formOptions: FormOptionDto[]
|
||||
allFormElements?: FormElementDto[]
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:formOptions', value: FormOptionDto[]): void
|
||||
}>()
|
||||
|
||||
// Map options to items format expected by UCheckboxGroup
|
||||
const items = computed(() => props.formOptions.map((option) => ({ label: option.label, value: option.label })))
|
||||
const { isFormOptionVisible } = useFormElementVisibility()
|
||||
|
||||
const visibleOptions = computed(() => {
|
||||
if (!props.allFormElements) return props.formOptions
|
||||
return props.formOptions.filter((opt) => isFormOptionVisible(opt.visibilityConditions, props.allFormElements!))
|
||||
})
|
||||
|
||||
// Auto-clear hidden options that are still selected
|
||||
watchEffect(() => {
|
||||
if (!props.allFormElements) return
|
||||
const hiddenSelected = props.formOptions.filter(
|
||||
(opt) => opt.value === 'true' && !isFormOptionVisible(opt.visibilityConditions, props.allFormElements!)
|
||||
)
|
||||
if (hiddenSelected.length === 0) return
|
||||
emit(
|
||||
'update:formOptions',
|
||||
props.formOptions.map((opt) => (hiddenSelected.includes(opt) ? { ...opt, value: 'false' } : opt))
|
||||
)
|
||||
})
|
||||
|
||||
const items = computed(() => visibleOptions.value.map((option) => ({ label: option.label, value: option.label })))
|
||||
|
||||
// Model value is an array of labels for checkboxes where value === 'true'
|
||||
const modelValue = computed({
|
||||
get: () => props.formOptions.filter((option) => option.value === 'true').map((option) => option.label),
|
||||
set: (selectedLabels: string[]) => {
|
||||
|
||||
@@ -3,19 +3,41 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { FormOptionDto } from '~~/.api-client'
|
||||
import type { FormElementDto, FormOptionDto } from '~~/.api-client'
|
||||
import { useFormElementVisibility } from '~/composables/useFormElementVisibility'
|
||||
|
||||
const props = defineProps<{
|
||||
label?: string
|
||||
formOptions: FormOptionDto[]
|
||||
allFormElements?: FormElementDto[]
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:formOptions', value: FormOptionDto[]): void
|
||||
}>()
|
||||
|
||||
const { isFormOptionVisible } = useFormElementVisibility()
|
||||
|
||||
const visibleOptions = computed(() => {
|
||||
if (!props.allFormElements) return props.formOptions
|
||||
return props.formOptions.filter((opt) => isFormOptionVisible(opt.visibilityConditions, props.allFormElements!))
|
||||
})
|
||||
|
||||
// Auto-clear selected option if it becomes hidden
|
||||
watchEffect(() => {
|
||||
if (!props.allFormElements) return
|
||||
const selectedOption = props.formOptions.find((opt) => opt.value === 'true')
|
||||
if (!selectedOption) return
|
||||
if (!isFormOptionVisible(selectedOption.visibilityConditions, props.allFormElements!)) {
|
||||
emit(
|
||||
'update:formOptions',
|
||||
props.formOptions.map((opt) => ({ ...opt, value: 'false' }))
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
// Our "label" is the "value" of the radio button
|
||||
const items = computed(() => props.formOptions.map((option) => ({ label: option.label, value: option.label })))
|
||||
const items = computed(() => visibleOptions.value.map((option) => ({ label: option.label, value: option.label })))
|
||||
|
||||
const modelValue = computed({
|
||||
get: () => props.formOptions.find((option) => option.value === 'true')?.label,
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
:disabled="disabled"
|
||||
:can-modify-rows="canModifyRows"
|
||||
:get-column-options="getColumnOptions"
|
||||
:read-only-column-indices="readOnlyColumnIndices"
|
||||
:is-cell-visible="isCellVisible"
|
||||
@update:cell="updateCell"
|
||||
@update:cell-value="updateCellValue"
|
||||
@update:checkbox-cell="updateCheckboxCell"
|
||||
@@ -60,6 +62,8 @@
|
||||
:disabled="disabled"
|
||||
:can-modify-rows="canModifyRows"
|
||||
:get-column-options="getColumnOptions"
|
||||
:read-only-column-indices="readOnlyColumnIndices"
|
||||
:is-cell-visible="isCellVisible"
|
||||
add-row-button-class="mt-4"
|
||||
@update:cell="updateCell"
|
||||
@update:cell-value="updateCellValue"
|
||||
@@ -151,6 +155,44 @@ const visibleColumns = computed<VisibleColumn[]>(() => {
|
||||
})
|
||||
})
|
||||
|
||||
const readOnlyColumnIndices = computed<Set<number>>(() => {
|
||||
if (!props.allFormElements) return new Set()
|
||||
|
||||
return new Set(
|
||||
props.formOptions
|
||||
.map((option, index) => ({ option, index }))
|
||||
.filter(({ option }) => {
|
||||
const conditions = option.columnConfig?.readOnlyConditions
|
||||
return conditions && isFormOptionVisible(conditions, props.allFormElements!)
|
||||
})
|
||||
.map(({ index }) => index)
|
||||
)
|
||||
})
|
||||
|
||||
// When columns become read-only, reset their values to the configured default
|
||||
watch(
|
||||
readOnlyColumnIndices,
|
||||
(currentSet, previousSet) => {
|
||||
const newlyReadOnlyIndices = [...currentSet].filter((i) => !previousSet?.has(i))
|
||||
if (newlyReadOnlyIndices.length === 0) return
|
||||
|
||||
const updatedOptions = props.formOptions.map((option, colIndex) => {
|
||||
if (!newlyReadOnlyIndices.includes(colIndex)) return option
|
||||
|
||||
const columnValues = parseColumnValues(option.value)
|
||||
const defaultValue = isColumnCheckbox(colIndex) ? false : (option.columnConfig?.readOnlyDefaultValue ?? '')
|
||||
const newValue = JSON.stringify(columnValues.map(() => defaultValue))
|
||||
|
||||
return newValue !== option.value ? { ...option, value: newValue } : option
|
||||
})
|
||||
|
||||
if (updatedOptions.some((opt, i) => opt !== props.formOptions[i])) {
|
||||
emit('update:formOptions', updatedOptions)
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
const dataColumns = computed<DataColumn[]>(() =>
|
||||
visibleColumns.value.map(({ originalIndex }) => ({
|
||||
key: `col_${originalIndex}`,
|
||||
@@ -179,23 +221,16 @@ const tableData = computed<TableRowData[]>(() => {
|
||||
if (props.formOptions.length === 0) return []
|
||||
|
||||
const columnData: CellValue[][] = props.formOptions.map((option, colIndex) => {
|
||||
try {
|
||||
const parsed = JSON.parse(option.value || '[]')
|
||||
if (!Array.isArray(parsed)) return []
|
||||
const parsed = parseColumnValues(option.value)
|
||||
|
||||
// For multi-select columns, each cell value is already an array
|
||||
// For checkbox columns, each cell value is a boolean
|
||||
// For single-select columns, each cell value is a string
|
||||
if (isColumnMultipleAllowed(colIndex)) {
|
||||
return parsed.map((val: CellValue) => (Array.isArray(val) ? val : []))
|
||||
}
|
||||
if (isColumnCheckbox(colIndex)) {
|
||||
return parsed.map((val: CellValue) => val === true)
|
||||
}
|
||||
return parsed
|
||||
} catch {
|
||||
return []
|
||||
// Normalize cell values based on column type
|
||||
if (isColumnMultipleAllowed(colIndex)) {
|
||||
return parsed.map((val) => (Array.isArray(val) ? val : []))
|
||||
}
|
||||
if (isColumnCheckbox(colIndex)) {
|
||||
return parsed.map((val) => val === true)
|
||||
}
|
||||
return parsed
|
||||
})
|
||||
|
||||
const rowCount = Math.max(...columnData.map((col) => col.length), 0)
|
||||
@@ -266,14 +301,7 @@ function updateCell(rowIndex: number, columnKey: string, value: string) {
|
||||
const updatedOptions = props.formOptions.map((option, index) => {
|
||||
if (index !== colIndex) return option
|
||||
|
||||
let columnValues: CellValue[]
|
||||
try {
|
||||
columnValues = JSON.parse(option.value || '[]')
|
||||
if (!Array.isArray(columnValues)) columnValues = []
|
||||
} catch {
|
||||
columnValues = []
|
||||
}
|
||||
|
||||
const columnValues = parseColumnValues(option.value)
|
||||
while (columnValues.length <= rowIndex) {
|
||||
columnValues.push('')
|
||||
}
|
||||
@@ -289,14 +317,7 @@ function updateCellValue(rowIndex: number, _columnKey: string, colIndex: number,
|
||||
const updatedOptions = props.formOptions.map((option, index) => {
|
||||
if (index !== colIndex) return option
|
||||
|
||||
let columnValues: CellValue[]
|
||||
try {
|
||||
columnValues = JSON.parse(option.value || '[]')
|
||||
if (!Array.isArray(columnValues)) columnValues = []
|
||||
} catch {
|
||||
columnValues = []
|
||||
}
|
||||
|
||||
const columnValues = parseColumnValues(option.value)
|
||||
const isMultiple = isColumnMultipleAllowed(colIndex)
|
||||
while (columnValues.length <= rowIndex) {
|
||||
columnValues.push(isMultiple ? [] : '')
|
||||
@@ -313,14 +334,7 @@ function updateCheckboxCell(rowIndex: number, colIndex: number, value: boolean)
|
||||
const updatedOptions = props.formOptions.map((option, index) => {
|
||||
if (index !== colIndex) return option
|
||||
|
||||
let columnValues: CellValue[]
|
||||
try {
|
||||
columnValues = JSON.parse(option.value || '[]')
|
||||
if (!Array.isArray(columnValues)) columnValues = []
|
||||
} catch {
|
||||
columnValues = []
|
||||
}
|
||||
|
||||
const columnValues = parseColumnValues(option.value)
|
||||
while (columnValues.length <= rowIndex) {
|
||||
columnValues.push(false)
|
||||
}
|
||||
@@ -334,24 +348,18 @@ function updateCheckboxCell(rowIndex: number, colIndex: number, value: boolean)
|
||||
|
||||
function addRow() {
|
||||
const updatedOptions = props.formOptions.map((option, colIndex) => {
|
||||
let columnValues: CellValue[]
|
||||
try {
|
||||
columnValues = JSON.parse(option.value || '[]')
|
||||
if (!Array.isArray(columnValues)) columnValues = []
|
||||
} catch {
|
||||
columnValues = []
|
||||
}
|
||||
const columnValues = parseColumnValues(option.value)
|
||||
|
||||
// For multi-select columns, initialize with empty array
|
||||
// For checkbox columns, initialize with false
|
||||
// Otherwise empty string
|
||||
let emptyValue: CellValue = ''
|
||||
if (isColumnMultipleAllowed(colIndex)) {
|
||||
emptyValue = []
|
||||
// Determine initial value based on column type
|
||||
let initialValue: CellValue = ''
|
||||
if (readOnlyColumnIndices.value.has(colIndex)) {
|
||||
initialValue = isColumnCheckbox(colIndex) ? false : (option.columnConfig?.readOnlyDefaultValue ?? '')
|
||||
} else if (isColumnMultipleAllowed(colIndex)) {
|
||||
initialValue = []
|
||||
} else if (isColumnCheckbox(colIndex)) {
|
||||
emptyValue = false
|
||||
initialValue = false
|
||||
}
|
||||
columnValues.push(emptyValue)
|
||||
columnValues.push(initialValue)
|
||||
|
||||
return { ...option, value: JSON.stringify(columnValues) }
|
||||
})
|
||||
@@ -361,19 +369,44 @@ function addRow() {
|
||||
|
||||
function removeRow(rowIndex: number) {
|
||||
const updatedOptions = props.formOptions.map((option) => {
|
||||
let columnValues: CellValue[]
|
||||
try {
|
||||
columnValues = JSON.parse(option.value || '[]')
|
||||
if (!Array.isArray(columnValues)) columnValues = []
|
||||
} catch {
|
||||
columnValues = []
|
||||
}
|
||||
|
||||
const columnValues = parseColumnValues(option.value)
|
||||
columnValues.splice(rowIndex, 1)
|
||||
|
||||
return { ...option, value: JSON.stringify(columnValues) }
|
||||
})
|
||||
|
||||
emit('update:formOptions', updatedOptions)
|
||||
}
|
||||
|
||||
function isCellVisible(colIndex: number, rowData: TableRowData): boolean {
|
||||
const option = props.formOptions[colIndex]
|
||||
const rowVisibility = option?.columnConfig?.rowVisibilityCondition
|
||||
if (!rowVisibility) return true
|
||||
|
||||
const { sourceColumnIndex, expectedValues, operator } = rowVisibility
|
||||
const sourceKey = `col_${sourceColumnIndex}`
|
||||
const cellValue = rowData[sourceKey]
|
||||
|
||||
let sourceValues: string[] = []
|
||||
if (Array.isArray(cellValue)) {
|
||||
sourceValues = cellValue
|
||||
} else if (typeof cellValue === 'string' && cellValue) {
|
||||
sourceValues = cellValue.split(',').map((v) => v.trim())
|
||||
}
|
||||
|
||||
if (operator === 'CONTAINS' || operator === 'EQUALS') {
|
||||
return (expectedValues ?? []).some((expected) =>
|
||||
sourceValues.some((v) => v.toLowerCase() === expected.toLowerCase())
|
||||
)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
function parseColumnValues(value: string | undefined): CellValue[] {
|
||||
try {
|
||||
const parsed = JSON.parse(value || '[]')
|
||||
return Array.isArray(parsed) ? parsed : []
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -2,54 +2,64 @@
|
||||
<div>
|
||||
<UTable :data="tableData" :columns="tableColumns" class="w-full" :ui="{ td: 'p-2' }">
|
||||
<template v-for="col in dataColumns" :key="col.key" #[`${col.key}-cell`]="slotProps">
|
||||
<!-- Column with cross-reference -->
|
||||
<USelectMenu
|
||||
v-if="hasColumnReference(col.colIndex)"
|
||||
:model-value="getCellValueForSelect(slotProps.row as TableRow<TableRowData>, col.key, col.colIndex)"
|
||||
:items="getColumnOptions(col.colIndex, (slotProps.row as TableRow<TableRowData>).original)"
|
||||
:disabled="disabled"
|
||||
:placeholder="$t('applicationForms.formElements.table.selectValue')"
|
||||
:multiple="isColumnMultipleAllowed(col.colIndex)"
|
||||
class="w-full min-w-32"
|
||||
@update:model-value="
|
||||
(val: string | string[]) =>
|
||||
$emit('update:cellValue', (slotProps.row as TableRow<TableRowData>).index, col.key, col.colIndex, val)
|
||||
<span
|
||||
v-if="
|
||||
props.isCellVisible &&
|
||||
!props.isCellVisible(col.colIndex, (slotProps.row as TableRow<TableRowData>).original)
|
||||
"
|
||||
/>
|
||||
<!-- Read-only column -->
|
||||
<span v-else-if="isColumnReadOnly(col.colIndex)" class="text-muted px-2 py-1">
|
||||
{{ formatCellDisplay(slotProps.row as any, col.key, col.colIndex) }}
|
||||
>
|
||||
-
|
||||
</span>
|
||||
<!-- Checkbox column -->
|
||||
<div v-else-if="isColumnCheckbox(col.colIndex)" class="flex justify-center">
|
||||
<UCheckbox
|
||||
:model-value="getCellValueForCheckbox(slotProps.row as TableRow<TableRowData>, col.key)"
|
||||
<template v-else>
|
||||
<!-- Column with cross-reference -->
|
||||
<USelectMenu
|
||||
v-if="hasColumnReference(col.colIndex)"
|
||||
:model-value="getCellValueForSelect(slotProps.row as TableRow<TableRowData>, col.key, col.colIndex)"
|
||||
:items="getColumnOptions(col.colIndex, (slotProps.row as TableRow<TableRowData>).original)"
|
||||
:disabled="disabled"
|
||||
:placeholder="$t('applicationForms.formElements.table.selectValue')"
|
||||
:multiple="isColumnMultipleAllowed(col.colIndex)"
|
||||
class="w-full min-w-32"
|
||||
@update:model-value="
|
||||
(val: boolean | 'indeterminate') =>
|
||||
$emit(
|
||||
'update:checkboxCell',
|
||||
(slotProps.row as TableRow<TableRowData>).index,
|
||||
col.colIndex,
|
||||
val === true
|
||||
)
|
||||
(val: string | string[]) =>
|
||||
$emit('update:cellValue', (slotProps.row as TableRow<TableRowData>).index, col.key, col.colIndex, val)
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<!-- Regular text input with auto-resizing textarea -->
|
||||
<UTextarea
|
||||
v-else
|
||||
:model-value="getCellValue(slotProps.row as TableRow<TableRowData>, col.key)"
|
||||
:disabled="disabled"
|
||||
:rows="1"
|
||||
autoresize
|
||||
:maxrows="0"
|
||||
class="w-full min-w-32"
|
||||
@update:model-value="
|
||||
(val: string | number) =>
|
||||
$emit('update:cell', (slotProps.row as TableRow<TableRowData>).index, col.key, String(val))
|
||||
"
|
||||
/>
|
||||
<!-- Read-only column -->
|
||||
<span v-else-if="isColumnReadOnly(col.colIndex)" class="text-muted px-2 py-1">
|
||||
{{ formatCellDisplay(slotProps.row as any, col.key, col.colIndex) }}
|
||||
</span>
|
||||
<!-- Checkbox column -->
|
||||
<div v-else-if="isColumnCheckbox(col.colIndex)" class="flex justify-center">
|
||||
<UCheckbox
|
||||
:model-value="getCellValueForCheckbox(slotProps.row as TableRow<TableRowData>, col.key)"
|
||||
:disabled="disabled"
|
||||
@update:model-value="
|
||||
(val: boolean | 'indeterminate') =>
|
||||
$emit(
|
||||
'update:checkboxCell',
|
||||
(slotProps.row as TableRow<TableRowData>).index,
|
||||
col.colIndex,
|
||||
val === true
|
||||
)
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<!-- Regular text input with auto-resizing textarea -->
|
||||
<UTextarea
|
||||
v-else
|
||||
:model-value="getCellValue(slotProps.row as TableRow<TableRowData>, col.key)"
|
||||
:disabled="disabled"
|
||||
:rows="1"
|
||||
autoresize
|
||||
:maxrows="0"
|
||||
class="w-full min-w-32"
|
||||
@update:model-value="
|
||||
(val: string | number) =>
|
||||
$emit('update:cell', (slotProps.row as TableRow<TableRowData>).index, col.key, String(val))
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
<template v-if="canModifyRows" #actions-cell="{ row }">
|
||||
<UButton
|
||||
@@ -102,6 +112,8 @@ const props = defineProps<{
|
||||
canModifyRows: boolean
|
||||
addRowButtonClass?: string
|
||||
getColumnOptions: (colIndex: number, currentRowData?: TableRowData) => string[]
|
||||
readOnlyColumnIndices?: Set<number>
|
||||
isCellVisible?: (colIndex: number, rowData: TableRowData) => boolean
|
||||
}>()
|
||||
|
||||
defineEmits<{
|
||||
@@ -119,7 +131,7 @@ function hasColumnReference(colIndex: number): boolean {
|
||||
|
||||
function isColumnReadOnly(colIndex: number): boolean {
|
||||
const option = props.formOptions[colIndex]
|
||||
return option?.columnConfig?.isReadOnly === true
|
||||
return option?.columnConfig?.isReadOnly === true || (props.readOnlyColumnIndices?.has(colIndex) ?? false)
|
||||
}
|
||||
|
||||
function isColumnMultipleAllowed(colIndex: number): boolean {
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import type { FormElementDto } from '~~/.api-client'
|
||||
|
||||
export function useClonableElements() {
|
||||
function cloneElement(element: FormElementDto, existingElements: FormElementDto[]): FormElementDto {
|
||||
const newReference = element.reference ? generateNextReference(existingElements, element.reference) : undefined
|
||||
const isTextField = element.type === 'TEXTAREA' || element.type === 'TEXTFIELD'
|
||||
export function useFormElementDuplication() {
|
||||
function cloneElement(elementToClone: FormElementDto, existingElements: FormElementDto[]): FormElementDto {
|
||||
const newReference = elementToClone.reference
|
||||
? generateNextReference(existingElements, elementToClone.reference)
|
||||
: undefined
|
||||
const isTextField = elementToClone.type === 'TEXTAREA' || elementToClone.type === 'TEXTFIELD'
|
||||
|
||||
const clonedElement = JSON.parse(JSON.stringify(element)) as FormElementDto
|
||||
const clonedElement = JSON.parse(JSON.stringify(elementToClone)) as FormElementDto
|
||||
const resetOptions = clonedElement.options.map((option) => ({
|
||||
...option,
|
||||
value: isTextField ? '' : option.value
|
||||
@@ -6,7 +6,7 @@ import type {
|
||||
FormOptionDto,
|
||||
FormElementType
|
||||
} from '~~/.api-client'
|
||||
import type { FormValueDiff, ValueChange, SectionChanges, TableDiff, TableRowDiff } from '~~/types/formDiff'
|
||||
import type { FormValueDiff, ValueChange, SectionChanges, TableDiff, TableRowDiff } from '~~/types/formSnapshotComparison'
|
||||
|
||||
// Element types that use true/false selection model
|
||||
const SELECTION_TYPES: FormElementType[] = ['SELECT', 'RADIOBUTTON', 'CHECKBOX', 'SWITCH']
|
||||
@@ -13,6 +13,8 @@
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "eslint . --fix",
|
||||
"test": "vitest run",
|
||||
"test:unit": "vitest run --project unit",
|
||||
"test:integration": "vitest run --project integration",
|
||||
"check": "pnpm run lint && pnpm run type-check && pnpm run format && pnpm run test",
|
||||
"api:generate": "openapi-generator-cli generate -i ../api/legalconsenthub.yml -g typescript-fetch -o .api-client"
|
||||
},
|
||||
@@ -36,6 +38,8 @@
|
||||
"@nuxt/eslint": "1.1.0",
|
||||
"@nuxt/test-utils": "^3.21.0",
|
||||
"@openapitools/openapi-generator-cli": "2.16.3",
|
||||
"@pinia/testing": "^0.1.7",
|
||||
"@vitest/coverage-v8": "4.0.16",
|
||||
"@vue/test-utils": "^2.4.6",
|
||||
"eslint": "9.20.1",
|
||||
"happy-dom": "^20.0.11",
|
||||
|
||||
115
legalconsenthub/pnpm-lock.yaml
generated
115
legalconsenthub/pnpm-lock.yaml
generated
@@ -60,6 +60,12 @@ importers:
|
||||
'@openapitools/openapi-generator-cli':
|
||||
specifier: 2.16.3
|
||||
version: 2.16.3
|
||||
'@pinia/testing':
|
||||
specifier: ^0.1.7
|
||||
version: 0.1.7(pinia@3.0.3(typescript@5.7.3)(vue@3.5.26(typescript@5.7.3)))(vue@3.5.26(typescript@5.7.3))
|
||||
'@vitest/coverage-v8':
|
||||
specifier: 4.0.16
|
||||
version: 4.0.16(vitest@4.0.16(@types/node@20.19.27)(happy-dom@20.0.11)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.2))
|
||||
'@vue/test-utils':
|
||||
specifier: ^2.4.6
|
||||
version: 2.4.6
|
||||
@@ -228,6 +234,10 @@ packages:
|
||||
resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@bcoe/v8-coverage@1.0.2':
|
||||
resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
'@bomb.sh/tab@0.0.10':
|
||||
resolution: {integrity: sha512-6ALS2rh/4LKn0Yxwm35V6LcgQuSiECHbqQo7+9g4rkgGyXZ0siOc8K+IuWIq/4u0Zkv2mevP9QSqgKhGIvLJMw==}
|
||||
hasBin: true
|
||||
@@ -1541,6 +1551,11 @@ packages:
|
||||
peerDependencies:
|
||||
pinia: ^3.0.3
|
||||
|
||||
'@pinia/testing@0.1.7':
|
||||
resolution: {integrity: sha512-xcDq6Ry/kNhZ5bsUMl7DeoFXwdume1NYzDggCiDUDKoPQ6Mo0eH9VU7bJvBtlurqe6byAntWoX5IhVFqWzRz/Q==}
|
||||
peerDependencies:
|
||||
pinia: '>=2.2.6'
|
||||
|
||||
'@pkgjs/parseargs@0.11.0':
|
||||
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
|
||||
engines: {node: '>=14'}
|
||||
@@ -2362,6 +2377,15 @@ packages:
|
||||
vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0
|
||||
vue: ^3.2.25
|
||||
|
||||
'@vitest/coverage-v8@4.0.16':
|
||||
resolution: {integrity: sha512-2rNdjEIsPRzsdu6/9Eq0AYAzYdpP6Bx9cje9tL3FE5XzXRQF1fNU9pe/1yE8fCrS0HD+fBtt6gLPh6LI57tX7A==}
|
||||
peerDependencies:
|
||||
'@vitest/browser': 4.0.16
|
||||
vitest: 4.0.16
|
||||
peerDependenciesMeta:
|
||||
'@vitest/browser':
|
||||
optional: true
|
||||
|
||||
'@vitest/expect@4.0.16':
|
||||
resolution: {integrity: sha512-eshqULT2It7McaJkQGLkPjPjNph+uevROGuIMJdG3V+0BSR2w9u6J9Lwu+E8cK5TETlfou8GRijhafIMhXsimA==}
|
||||
|
||||
@@ -2686,6 +2710,9 @@ packages:
|
||||
resolution: {integrity: sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
ast-v8-to-istanbul@0.3.12:
|
||||
resolution: {integrity: sha512-BRRC8VRZY2R4Z4lFIL35MwNXmwVqBityvOIwETtsCSwvjl0IdgFsy9NhdaA6j74nUdtJJlIypeRhpDam19Wq3g==}
|
||||
|
||||
ast-walker-scope@0.6.2:
|
||||
resolution: {integrity: sha512-1UWOyC50xI3QZkRuDj6PqDtpm1oHWtYs+NQGwqL/2R11eN3Q81PHAHPM0SWW3BNQm53UDwS//Jv8L4CCVLM1bQ==}
|
||||
engines: {node: '>=16.14.0'}
|
||||
@@ -3796,6 +3823,9 @@ packages:
|
||||
resolution: {integrity: sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==}
|
||||
engines: {node: ^16.14.0 || >=18.0.0}
|
||||
|
||||
html-escaper@2.0.2:
|
||||
resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==}
|
||||
|
||||
html-to-text@9.0.5:
|
||||
resolution: {integrity: sha512-qY60FjREgVZL03vJU6IfMV4GDjGBIoOyvuFdpBDIX9yTlDw0TjxVBQp+P8NvpdIXNJvfWBTNul7fsAQJq2FNpg==}
|
||||
engines: {node: '>=14'}
|
||||
@@ -3989,6 +4019,22 @@ packages:
|
||||
isomorphic.js@0.2.5:
|
||||
resolution: {integrity: sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==}
|
||||
|
||||
istanbul-lib-coverage@3.2.2:
|
||||
resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
istanbul-lib-report@3.0.1:
|
||||
resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
istanbul-lib-source-maps@5.0.6:
|
||||
resolution: {integrity: sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
istanbul-reports@3.2.0:
|
||||
resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
iterare@1.2.1:
|
||||
resolution: {integrity: sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -4012,6 +4058,9 @@ packages:
|
||||
resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
js-tokens@10.0.0:
|
||||
resolution: {integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==}
|
||||
|
||||
js-tokens@4.0.0:
|
||||
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
||||
|
||||
@@ -4257,6 +4306,10 @@ packages:
|
||||
magicast@0.5.1:
|
||||
resolution: {integrity: sha512-xrHS24IxaLrvuo613F719wvOIv9xPHFWQHuvGUBmPnCA/3MQxKI3b+r7n1jAoDHmsbC5bRhTZYR77invLAxVnw==}
|
||||
|
||||
make-dir@4.0.0:
|
||||
resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
markdown-it@14.1.0:
|
||||
resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==}
|
||||
hasBin: true
|
||||
@@ -6340,6 +6393,8 @@ snapshots:
|
||||
'@babel/helper-string-parser': 7.27.1
|
||||
'@babel/helper-validator-identifier': 7.28.5
|
||||
|
||||
'@bcoe/v8-coverage@1.0.2': {}
|
||||
|
||||
'@bomb.sh/tab@0.0.10(cac@6.7.14)(citty@0.1.6)':
|
||||
optionalDependencies:
|
||||
cac: 6.7.14
|
||||
@@ -7928,6 +7983,14 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- magicast
|
||||
|
||||
'@pinia/testing@0.1.7(pinia@3.0.3(typescript@5.7.3)(vue@3.5.26(typescript@5.7.3)))(vue@3.5.26(typescript@5.7.3))':
|
||||
dependencies:
|
||||
pinia: 3.0.3(typescript@5.7.3)(vue@3.5.26(typescript@5.7.3))
|
||||
vue-demi: 0.14.10(vue@3.5.26(typescript@5.7.3))
|
||||
transitivePeerDependencies:
|
||||
- '@vue/composition-api'
|
||||
- vue
|
||||
|
||||
'@pkgjs/parseargs@0.11.0':
|
||||
optional: true
|
||||
|
||||
@@ -8734,6 +8797,23 @@ snapshots:
|
||||
vite: 7.3.0(@types/node@20.19.27)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.2)
|
||||
vue: 3.5.26(typescript@5.7.3)
|
||||
|
||||
'@vitest/coverage-v8@4.0.16(vitest@4.0.16(@types/node@20.19.27)(happy-dom@20.0.11)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.2))':
|
||||
dependencies:
|
||||
'@bcoe/v8-coverage': 1.0.2
|
||||
'@vitest/utils': 4.0.16
|
||||
ast-v8-to-istanbul: 0.3.12
|
||||
istanbul-lib-coverage: 3.2.2
|
||||
istanbul-lib-report: 3.0.1
|
||||
istanbul-lib-source-maps: 5.0.6
|
||||
istanbul-reports: 3.2.0
|
||||
magicast: 0.5.1
|
||||
obug: 2.1.1
|
||||
std-env: 3.10.0
|
||||
tinyrainbow: 3.0.3
|
||||
vitest: 4.0.16(@types/node@20.19.27)(happy-dom@20.0.11)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.2)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@vitest/expect@4.0.16':
|
||||
dependencies:
|
||||
'@standard-schema/spec': 1.1.0
|
||||
@@ -9125,6 +9205,12 @@ snapshots:
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
|
||||
ast-v8-to-istanbul@0.3.12:
|
||||
dependencies:
|
||||
'@jridgewell/trace-mapping': 0.3.31
|
||||
estree-walker: 3.0.3
|
||||
js-tokens: 10.0.0
|
||||
|
||||
ast-walker-scope@0.6.2:
|
||||
dependencies:
|
||||
'@babel/parser': 7.28.5
|
||||
@@ -10347,6 +10433,8 @@ snapshots:
|
||||
dependencies:
|
||||
lru-cache: 10.4.3
|
||||
|
||||
html-escaper@2.0.2: {}
|
||||
|
||||
html-to-text@9.0.5:
|
||||
dependencies:
|
||||
'@selderee/plugin-htmlparser2': 0.11.0
|
||||
@@ -10536,6 +10624,27 @@ snapshots:
|
||||
|
||||
isomorphic.js@0.2.5: {}
|
||||
|
||||
istanbul-lib-coverage@3.2.2: {}
|
||||
|
||||
istanbul-lib-report@3.0.1:
|
||||
dependencies:
|
||||
istanbul-lib-coverage: 3.2.2
|
||||
make-dir: 4.0.0
|
||||
supports-color: 7.2.0
|
||||
|
||||
istanbul-lib-source-maps@5.0.6:
|
||||
dependencies:
|
||||
'@jridgewell/trace-mapping': 0.3.31
|
||||
debug: 4.4.3
|
||||
istanbul-lib-coverage: 3.2.2
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
istanbul-reports@3.2.0:
|
||||
dependencies:
|
||||
html-escaper: 2.0.2
|
||||
istanbul-lib-report: 3.0.1
|
||||
|
||||
iterare@1.2.1: {}
|
||||
|
||||
jackspeak@3.4.3:
|
||||
@@ -10558,6 +10667,8 @@ snapshots:
|
||||
|
||||
js-cookie@3.0.5: {}
|
||||
|
||||
js-tokens@10.0.0: {}
|
||||
|
||||
js-tokens@4.0.0: {}
|
||||
|
||||
js-tokens@9.0.1: {}
|
||||
@@ -10787,6 +10898,10 @@ snapshots:
|
||||
'@babel/types': 7.28.5
|
||||
source-map-js: 1.2.1
|
||||
|
||||
make-dir@4.0.0:
|
||||
dependencies:
|
||||
semver: 7.7.3
|
||||
|
||||
markdown-it@14.1.0:
|
||||
dependencies:
|
||||
argparse: 2.0.1
|
||||
|
||||
4
legalconsenthub/test/tsconfig.json
Normal file
4
legalconsenthub/test/tsconfig.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"extends": "../.nuxt/tsconfig.json",
|
||||
"include": ["**/*"]
|
||||
}
|
||||
@@ -0,0 +1,285 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { useFormElementDuplication } from '../../../app/composables/useFormElementDuplication'
|
||||
import type { FormElementDto, FormOptionDto, FormElementType } from '../../../.api-client'
|
||||
|
||||
// Helper to create a FormOptionDto
|
||||
function createOption(value: string, label: string): FormOptionDto {
|
||||
return {
|
||||
value,
|
||||
label,
|
||||
processingPurpose: 'NONE',
|
||||
employeeDataCategory: 'NONE'
|
||||
}
|
||||
}
|
||||
|
||||
// Helper to create a FormElementDto
|
||||
function createFormElement(
|
||||
reference: string,
|
||||
title: string,
|
||||
type: FormElementType,
|
||||
options: FormOptionDto[] = [],
|
||||
description?: string
|
||||
): FormElementDto {
|
||||
return {
|
||||
id: `id-${reference}`,
|
||||
reference,
|
||||
title,
|
||||
type,
|
||||
options,
|
||||
description
|
||||
}
|
||||
}
|
||||
|
||||
describe('useFormElementDuplication', () => {
|
||||
describe('cloneElement()', () => {
|
||||
it('should generate incremented reference from existing reference with suffix', () => {
|
||||
const { cloneElement } = useFormElementDuplication()
|
||||
|
||||
const elementToClone = createFormElement('modul_1', 'Module', 'TEXTFIELD', [createOption('value', '')])
|
||||
const existingElements: FormElementDto[] = [elementToClone]
|
||||
|
||||
const cloned = cloneElement(elementToClone, existingElements)
|
||||
|
||||
expect(cloned.reference).toBe('modul_2')
|
||||
})
|
||||
|
||||
it('should handle reference without numeric suffix (defaults to _2)', () => {
|
||||
const { cloneElement } = useFormElementDuplication()
|
||||
|
||||
const elementToClone = createFormElement('modul', 'Module', 'TEXTFIELD', [createOption('value', '')])
|
||||
const existingElements: FormElementDto[] = [elementToClone]
|
||||
|
||||
const cloned = cloneElement(elementToClone, existingElements)
|
||||
|
||||
expect(cloned.reference).toBe('modul_2')
|
||||
})
|
||||
|
||||
it('should clear id and formElementSubSectionId on cloned element', () => {
|
||||
const { cloneElement } = useFormElementDuplication()
|
||||
|
||||
const elementToClone = createFormElement('elem_1', 'Element', 'TEXTFIELD', [createOption('value', '')])
|
||||
elementToClone.id = 'some-uuid-id'
|
||||
elementToClone.formElementSubSectionId = 'subsection-uuid'
|
||||
const existingElements: FormElementDto[] = [elementToClone]
|
||||
|
||||
const cloned = cloneElement(elementToClone, existingElements)
|
||||
|
||||
expect(cloned.id).toBeUndefined()
|
||||
expect(cloned.formElementSubSectionId).toBeUndefined()
|
||||
})
|
||||
|
||||
it('should deep clone element (modifying clone does not affect original)', () => {
|
||||
const { cloneElement } = useFormElementDuplication()
|
||||
|
||||
const elementToClone = createFormElement('elem_1', 'Element', 'SELECT', [
|
||||
createOption('true', 'Yes'),
|
||||
createOption('false', 'No')
|
||||
])
|
||||
const existingElements: FormElementDto[] = [elementToClone]
|
||||
|
||||
const cloned = cloneElement(elementToClone, existingElements)
|
||||
|
||||
cloned.options[0]!.value = 'modified'
|
||||
cloned.title = 'Modified Title'
|
||||
|
||||
expect(elementToClone.options[0]!.value).toBe('true')
|
||||
expect(elementToClone.title).toBe('Element')
|
||||
})
|
||||
|
||||
it('should handle multiple existing clones and generate correct next reference', () => {
|
||||
const { cloneElement } = useFormElementDuplication()
|
||||
|
||||
const element1 = createFormElement('modul_1', 'Module', 'TEXTFIELD', [createOption('value1', '')])
|
||||
const element2 = createFormElement('modul_2', 'Module', 'TEXTFIELD', [createOption('value2', '')])
|
||||
const element3 = createFormElement('modul_3', 'Module', 'TEXTFIELD', [createOption('value3', '')])
|
||||
const existingElements: FormElementDto[] = [element1, element2, element3]
|
||||
|
||||
const cloned = cloneElement(element1, existingElements)
|
||||
|
||||
expect(cloned.reference).toBe('modul_4')
|
||||
})
|
||||
|
||||
it('should handle elements with no options', () => {
|
||||
const { cloneElement } = useFormElementDuplication()
|
||||
|
||||
const elementToClone = createFormElement('elem_1', 'Element', 'TEXTFIELD', [])
|
||||
const existingElements: FormElementDto[] = [elementToClone]
|
||||
|
||||
const cloned = cloneElement(elementToClone, existingElements)
|
||||
|
||||
expect(cloned.options).toEqual([])
|
||||
expect(cloned.reference).toBe('elem_2')
|
||||
})
|
||||
|
||||
it('should handle sparse numeric suffix correctly', () => {
|
||||
const { cloneElement } = useFormElementDuplication()
|
||||
|
||||
const element1 = createFormElement('item_1', 'Item', 'TEXTFIELD', [createOption('v1', '')])
|
||||
const element3 = createFormElement('item_3', 'Item', 'TEXTFIELD', [createOption('v3', '')])
|
||||
const existingElements: FormElementDto[] = [element1, element3]
|
||||
|
||||
const cloned = cloneElement(element1, existingElements)
|
||||
|
||||
expect(cloned.reference).toBe('item_4')
|
||||
})
|
||||
|
||||
it('should handle element with formElementSubSectionId undefined already', () => {
|
||||
const { cloneElement } = useFormElementDuplication()
|
||||
|
||||
const elementToClone = createFormElement('elem_1', 'Element', 'TEXTFIELD', [createOption('value', '')])
|
||||
const existingElements: FormElementDto[] = [elementToClone]
|
||||
|
||||
const cloned = cloneElement(elementToClone, existingElements)
|
||||
|
||||
expect(cloned.formElementSubSectionId).toBeUndefined()
|
||||
})
|
||||
|
||||
describe('option value handling by form element type', () => {
|
||||
it('should reset TEXTAREA option values to empty string', () => {
|
||||
const { cloneElement } = useFormElementDuplication()
|
||||
|
||||
const elementToClone = createFormElement('textarea_1', 'My Title', 'TEXTAREA', [
|
||||
createOption('Original text content', '')
|
||||
])
|
||||
const existingElements: FormElementDto[] = [elementToClone]
|
||||
|
||||
const cloned = cloneElement(elementToClone, existingElements)
|
||||
|
||||
expect(cloned.options[0]!.value).toBe('')
|
||||
expect(cloned.reference).toBe('textarea_2')
|
||||
})
|
||||
|
||||
it('should reset TEXTFIELD option values to empty string', () => {
|
||||
const { cloneElement } = useFormElementDuplication()
|
||||
|
||||
const elementToClone = createFormElement('textfield_1', 'My Title', 'TEXTFIELD', [
|
||||
createOption('Original text content', '')
|
||||
])
|
||||
const existingElements: FormElementDto[] = [elementToClone]
|
||||
|
||||
const cloned = cloneElement(elementToClone, existingElements)
|
||||
|
||||
expect(cloned.options[0]!.value).toBe('')
|
||||
expect(cloned.reference).toBe('textfield_2')
|
||||
})
|
||||
|
||||
it('should preserve SELECT option values', () => {
|
||||
const { cloneElement } = useFormElementDuplication()
|
||||
|
||||
const elementToClone = createFormElement('select_1', 'Choice', 'SELECT', [
|
||||
createOption('false', 'Option A'),
|
||||
createOption('true', 'Option B')
|
||||
])
|
||||
const existingElements: FormElementDto[] = [elementToClone]
|
||||
|
||||
const cloned = cloneElement(elementToClone, existingElements)
|
||||
|
||||
expect(cloned.options[0]!.value).toBe('false')
|
||||
expect(cloned.options[1]!.value).toBe('true')
|
||||
expect(cloned.reference).toBe('select_2')
|
||||
})
|
||||
|
||||
it('should preserve CHECKBOX option values', () => {
|
||||
const { cloneElement } = useFormElementDuplication()
|
||||
|
||||
const elementToClone = createFormElement('checkbox_1', 'Features', 'CHECKBOX', [
|
||||
createOption('true', 'Feature A'),
|
||||
createOption('false', 'Feature B')
|
||||
])
|
||||
const existingElements: FormElementDto[] = [elementToClone]
|
||||
|
||||
const cloned = cloneElement(elementToClone, existingElements)
|
||||
|
||||
expect(cloned.options[0]!.value).toBe('true')
|
||||
expect(cloned.options[1]!.value).toBe('false')
|
||||
expect(cloned.reference).toBe('checkbox_2')
|
||||
})
|
||||
|
||||
it('should preserve RADIOBUTTON option values', () => {
|
||||
const { cloneElement } = useFormElementDuplication()
|
||||
|
||||
const elementToClone = createFormElement('radio_1', 'Gender', 'RADIOBUTTON', [
|
||||
createOption('true', 'Male'),
|
||||
createOption('false', 'Female')
|
||||
])
|
||||
const existingElements: FormElementDto[] = [elementToClone]
|
||||
|
||||
const cloned = cloneElement(elementToClone, existingElements)
|
||||
|
||||
expect(cloned.options[0]!.value).toBe('true')
|
||||
expect(cloned.options[1]!.value).toBe('false')
|
||||
expect(cloned.reference).toBe('radio_2')
|
||||
})
|
||||
|
||||
it('should handle SWITCH element option values', () => {
|
||||
const { cloneElement } = useFormElementDuplication()
|
||||
|
||||
const elementToClone = createFormElement('switch_1', 'Enable', 'SWITCH', [createOption('true', 'Enabled')])
|
||||
const existingElements: FormElementDto[] = [elementToClone]
|
||||
|
||||
const cloned = cloneElement(elementToClone, existingElements)
|
||||
|
||||
expect(cloned.options[0]!.value).toBe('true')
|
||||
expect(cloned.reference).toBe('switch_2')
|
||||
})
|
||||
|
||||
it('should handle DATE element option values', () => {
|
||||
const { cloneElement } = useFormElementDuplication()
|
||||
|
||||
const elementToClone = createFormElement('date_1', 'Start Date', 'DATE', [createOption('2024-01-15', 'label')])
|
||||
const existingElements: FormElementDto[] = [elementToClone]
|
||||
|
||||
const cloned = cloneElement(elementToClone, existingElements)
|
||||
|
||||
expect(cloned.options[0]!.value).toBe('2024-01-15')
|
||||
expect(cloned.reference).toBe('date_2')
|
||||
})
|
||||
|
||||
it('should handle RICH_TEXT element option values', () => {
|
||||
const { cloneElement } = useFormElementDuplication()
|
||||
|
||||
const elementToClone = createFormElement('richtext_1', 'Notes', 'RICH_TEXT', [
|
||||
createOption('<p>Content</p>', '')
|
||||
])
|
||||
const existingElements: FormElementDto[] = [elementToClone]
|
||||
|
||||
const cloned = cloneElement(elementToClone, existingElements)
|
||||
|
||||
expect(cloned.options[0]!.value).toBe('<p>Content</p>')
|
||||
expect(cloned.reference).toBe('richtext_2')
|
||||
})
|
||||
|
||||
it('should handle TABLE element option values', () => {
|
||||
const { cloneElement } = useFormElementDuplication()
|
||||
|
||||
const elementToClone = createFormElement('table_1', 'Employees', 'TABLE', [
|
||||
createOption('["row1", "row2"]', 'Name'),
|
||||
createOption('["dev", "designer"]', 'Role')
|
||||
])
|
||||
const existingElements: FormElementDto[] = [elementToClone]
|
||||
|
||||
const cloned = cloneElement(elementToClone, existingElements)
|
||||
|
||||
expect(cloned.options[0]!.value).toBe('["row1", "row2"]')
|
||||
expect(cloned.options[1]!.value).toBe('["dev", "designer"]')
|
||||
expect(cloned.reference).toBe('table_2')
|
||||
})
|
||||
|
||||
it('should preserve all FormOptionDto properties when cloning', () => {
|
||||
const { cloneElement } = useFormElementDuplication()
|
||||
|
||||
const option = createOption('true', 'Yes')
|
||||
option.processingPurpose = 'BUSINESS_PROCESS'
|
||||
option.employeeDataCategory = 'SENSITIVE'
|
||||
|
||||
const elementToClone = createFormElement('elem_1', 'Element', 'SELECT', [option])
|
||||
const existingElements: FormElementDto[] = [elementToClone]
|
||||
|
||||
const cloned = cloneElement(elementToClone, existingElements)
|
||||
|
||||
expect(cloned.options[0]!.processingPurpose).toBe('BUSINESS_PROCESS')
|
||||
expect(cloned.options[0]!.employeeDataCategory).toBe('SENSITIVE')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,268 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import type {
|
||||
FormElementDto,
|
||||
FormElementSectionDto,
|
||||
FormElementSubSectionDto,
|
||||
FormOptionDto
|
||||
} from '../../../.api-client'
|
||||
import { useFormElementValueClearing } from '../../../app/composables/useFormElementValueClearing'
|
||||
|
||||
describe('useFormElementValueClearing', () => {
|
||||
const { clearHiddenFormElementValues } = useFormElementValueClearing()
|
||||
|
||||
// Visibility state constants
|
||||
const VISIBLE = true
|
||||
const HIDDEN = false
|
||||
|
||||
// Helper functions for creating test data
|
||||
function createFormOption(overrides: Partial<FormOptionDto> = {}): FormOptionDto {
|
||||
return {
|
||||
value: '',
|
||||
label: 'Option',
|
||||
...overrides
|
||||
} as FormOptionDto
|
||||
}
|
||||
|
||||
function createFormElement(type: string, overrides: Partial<FormElementDto> = {}): FormElementDto {
|
||||
return {
|
||||
id: 'element1',
|
||||
reference: 'ref_element1',
|
||||
title: 'Element',
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
type: type as any,
|
||||
options: [createFormOption()],
|
||||
...overrides
|
||||
}
|
||||
}
|
||||
|
||||
function createSubSection(formElements: FormElementDto[] = []): FormElementSubSectionDto {
|
||||
return {
|
||||
id: 'subsection1',
|
||||
title: 'SubSection',
|
||||
formElements: formElements.length > 0 ? formElements : [createFormElement('TEXTFIELD')]
|
||||
}
|
||||
}
|
||||
|
||||
function createSection(subsections: FormElementSubSectionDto[] = []): FormElementSectionDto {
|
||||
return {
|
||||
id: 'section1',
|
||||
title: 'Section',
|
||||
formElementSubSections: subsections.length > 0 ? subsections : [createSubSection()]
|
||||
}
|
||||
}
|
||||
|
||||
describe('clearHiddenFormElementValues', () => {
|
||||
it('should return sections unchanged when no elements become newly hidden', () => {
|
||||
const sections = [createSection()]
|
||||
const previousMap = new Map([['element1', VISIBLE]])
|
||||
const currentMap = new Map([['element1', VISIBLE]])
|
||||
|
||||
const result = clearHiddenFormElementValues(sections, previousMap, currentMap)
|
||||
|
||||
expect(result).toEqual(sections)
|
||||
})
|
||||
|
||||
it('should clear value of newly hidden TEXTFIELD element', () => {
|
||||
const element = createFormElement('TEXTFIELD', {
|
||||
id: 'element1',
|
||||
options: [createFormOption({ value: 'some text' })]
|
||||
})
|
||||
const subsection = createSubSection([element])
|
||||
const section = createSection([subsection])
|
||||
|
||||
const previousMap = new Map([['element1', VISIBLE]])
|
||||
const currentMap = new Map([['element1', HIDDEN]])
|
||||
|
||||
const result = clearHiddenFormElementValues([section], previousMap, currentMap)
|
||||
|
||||
expect(result[0]!.formElementSubSections[0]!.formElements[0]!.options[0]!.value).toBe('')
|
||||
})
|
||||
|
||||
it('should clear value of newly hidden SELECT element', () => {
|
||||
const element = createFormElement('SELECT', {
|
||||
id: 'element1',
|
||||
options: [createFormOption({ value: 'option1' }), createFormOption({ value: 'option2' })]
|
||||
})
|
||||
const subsection = createSubSection([element])
|
||||
const section = createSection([subsection])
|
||||
|
||||
const previousMap = new Map([['element1', VISIBLE]])
|
||||
const currentMap = new Map([['element1', HIDDEN]])
|
||||
|
||||
const result = clearHiddenFormElementValues([section], previousMap, currentMap)
|
||||
|
||||
expect(result[0]!.formElementSubSections[0]!.formElements[0]!.options[0]!.value).toBe('false')
|
||||
expect(result[0]!.formElementSubSections[0]!.formElements[0]!.options[1]!.value).toBe('false')
|
||||
})
|
||||
|
||||
it('should clear value of newly hidden CHECKBOX element', () => {
|
||||
const element = createFormElement('CHECKBOX', {
|
||||
id: 'element1',
|
||||
options: [createFormOption({ value: 'true' }), createFormOption({ value: 'true' })]
|
||||
})
|
||||
const subsection = createSubSection([element])
|
||||
const section = createSection([subsection])
|
||||
|
||||
const previousMap = new Map([['element1', VISIBLE]])
|
||||
const currentMap = new Map([['element1', HIDDEN]])
|
||||
|
||||
const result = clearHiddenFormElementValues([section], previousMap, currentMap)
|
||||
|
||||
expect(result[0]!.formElementSubSections[0]!.formElements[0]!.options[0]!.value).toBe('false')
|
||||
expect(result[0]!.formElementSubSections[0]!.formElements[0]!.options[1]!.value).toBe('false')
|
||||
})
|
||||
|
||||
it('should clear value of newly hidden RADIOBUTTON element', () => {
|
||||
const element = createFormElement('RADIOBUTTON', {
|
||||
id: 'element1',
|
||||
options: [createFormOption({ value: 'option1' }), createFormOption({ value: 'option2' })]
|
||||
})
|
||||
const subsection = createSubSection([element])
|
||||
const section = createSection([subsection])
|
||||
|
||||
const previousMap = new Map([['element1', VISIBLE]])
|
||||
const currentMap = new Map([['element1', HIDDEN]])
|
||||
|
||||
const result = clearHiddenFormElementValues([section], previousMap, currentMap)
|
||||
|
||||
expect(result[0]!.formElementSubSections[0]!.formElements[0]!.options[0]!.value).toBe('false')
|
||||
expect(result[0]!.formElementSubSections[0]!.formElements[0]!.options[1]!.value).toBe('false')
|
||||
})
|
||||
|
||||
it('should not clear visible elements', () => {
|
||||
const originalValue = 'original value'
|
||||
const element = createFormElement('TEXTFIELD', {
|
||||
id: 'element1',
|
||||
options: [createFormOption({ value: originalValue })]
|
||||
})
|
||||
const subsection = createSubSection([element])
|
||||
const section = createSection([subsection])
|
||||
|
||||
const previousMap = new Map([['element1', VISIBLE]])
|
||||
const currentMap = new Map([['element1', VISIBLE]])
|
||||
|
||||
const result = clearHiddenFormElementValues([section], previousMap, currentMap)
|
||||
|
||||
expect(result[0]!.formElementSubSections[0]!.formElements[0]!.options[0]!.value).toBe(originalValue)
|
||||
})
|
||||
|
||||
it('should not clear elements that were already hidden (previousMap false, currentMap false)', () => {
|
||||
const originalValue = 'original value'
|
||||
const element = createFormElement('TEXTFIELD', {
|
||||
id: 'element1',
|
||||
options: [createFormOption({ value: originalValue })]
|
||||
})
|
||||
const subsection = createSubSection([element])
|
||||
const section = createSection([subsection])
|
||||
|
||||
const previousMap = new Map([['element1', HIDDEN]])
|
||||
const currentMap = new Map([['element1', HIDDEN]])
|
||||
|
||||
const result = clearHiddenFormElementValues([section], previousMap, currentMap)
|
||||
|
||||
expect(result[0]!.formElementSubSections[0]!.formElements[0]!.options[0]!.value).toBe(originalValue)
|
||||
})
|
||||
|
||||
it('should handle elements identified by id', () => {
|
||||
const element = createFormElement('TEXTFIELD', {
|
||||
id: 'element1',
|
||||
reference: undefined,
|
||||
options: [createFormOption({ value: 'text' })]
|
||||
})
|
||||
const subsection = createSubSection([element])
|
||||
const section = createSection([subsection])
|
||||
|
||||
const previousMap = new Map([['element1', VISIBLE]])
|
||||
const currentMap = new Map([['element1', HIDDEN]])
|
||||
|
||||
const result = clearHiddenFormElementValues([section], previousMap, currentMap)
|
||||
|
||||
expect(result[0]!.formElementSubSections[0]!.formElements[0]!.options[0]!.value).toBe('')
|
||||
})
|
||||
|
||||
it('should handle elements identified by reference (when id is undefined)', () => {
|
||||
const element = createFormElement('TEXTFIELD', {
|
||||
id: undefined,
|
||||
reference: 'ref_element1',
|
||||
options: [createFormOption({ value: 'text' })]
|
||||
})
|
||||
const subsection = createSubSection([element])
|
||||
const section = createSection([subsection])
|
||||
|
||||
const previousMap = new Map([['ref_element1', VISIBLE]])
|
||||
const currentMap = new Map([['ref_element1', HIDDEN]])
|
||||
|
||||
const result = clearHiddenFormElementValues([section], previousMap, currentMap)
|
||||
|
||||
expect(result[0]!.formElementSubSections[0]!.formElements[0]!.options[0]!.value).toBe('')
|
||||
})
|
||||
|
||||
it('should handle multiple sections with multiple subsections', () => {
|
||||
const element1 = createFormElement('TEXTFIELD', {
|
||||
id: 'element1',
|
||||
options: [createFormOption({ value: 'text1' })]
|
||||
})
|
||||
const element2 = createFormElement('SELECT', {
|
||||
id: 'element2',
|
||||
options: [createFormOption({ value: 'option' })]
|
||||
})
|
||||
|
||||
const subsection1 = createSubSection([element1])
|
||||
const subsection2 = createSubSection([element2])
|
||||
const section1 = createSection([subsection1, subsection2])
|
||||
|
||||
const element3 = createFormElement('CHECKBOX', {
|
||||
id: 'element3',
|
||||
options: [createFormOption({ value: 'true' })]
|
||||
})
|
||||
const subsection3 = createSubSection([element3])
|
||||
const section2 = createSection([subsection3])
|
||||
|
||||
const previousMap = new Map([
|
||||
['element1', VISIBLE],
|
||||
['element2', VISIBLE],
|
||||
['element3', VISIBLE]
|
||||
])
|
||||
const currentMap = new Map([
|
||||
['element1', HIDDEN],
|
||||
['element2', VISIBLE],
|
||||
['element3', HIDDEN]
|
||||
])
|
||||
|
||||
const result = clearHiddenFormElementValues([section1, section2], previousMap, currentMap)
|
||||
|
||||
expect(result[0]!.formElementSubSections[0]!.formElements[0]!.options[0]!.value).toBe('')
|
||||
expect(result[0]!.formElementSubSections[1]!.formElements[0]!.options[0]!.value).toBe('option')
|
||||
expect(result[1]!.formElementSubSections[0]!.formElements[0]!.options[0]!.value).toBe('false')
|
||||
})
|
||||
|
||||
it('should handle mixed TEXTFIELD and SELECT in same section', () => {
|
||||
const textElement = createFormElement('TEXTFIELD', {
|
||||
id: 'text1',
|
||||
options: [createFormOption({ value: 'text content' })]
|
||||
})
|
||||
const selectElement = createFormElement('SELECT', {
|
||||
id: 'select1',
|
||||
options: [createFormOption({ value: 'val1' }), createFormOption({ value: 'val2' })]
|
||||
})
|
||||
|
||||
const subsection = createSubSection([textElement, selectElement])
|
||||
const section = createSection([subsection])
|
||||
|
||||
const previousMap = new Map([
|
||||
['text1', VISIBLE],
|
||||
['select1', VISIBLE]
|
||||
])
|
||||
const currentMap = new Map([
|
||||
['text1', HIDDEN],
|
||||
['select1', HIDDEN]
|
||||
])
|
||||
|
||||
const result = clearHiddenFormElementValues([section], previousMap, currentMap)
|
||||
|
||||
expect(result[0]!.formElementSubSections[0]!.formElements[0]!.options[0]!.value).toBe('')
|
||||
expect(result[0]!.formElementSubSections[0]!.formElements[1]!.options[0]!.value).toBe('false')
|
||||
expect(result[0]!.formElementSubSections[0]!.formElements[1]!.options[1]!.value).toBe('false')
|
||||
})
|
||||
})
|
||||
})
|
||||
237
legalconsenthub/test/unit/composables/useFormStepper.spec.ts
Normal file
237
legalconsenthub/test/unit/composables/useFormStepper.spec.ts
Normal file
@@ -0,0 +1,237 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
||||
import type { FormElementSectionDto } from '../../../.api-client'
|
||||
import { useFormStepper } from '../../../app/composables/useFormStepper'
|
||||
import { ref } from 'vue'
|
||||
|
||||
describe('useFormStepper', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
describe('visibleSections', () => {
|
||||
it('should skip only template sections and keep non-template sections', () => {
|
||||
const sections: FormElementSectionDto[] = [
|
||||
{
|
||||
id: '1',
|
||||
title: 'Normal Section',
|
||||
shortTitle: 'Normal',
|
||||
description: 'A normal section',
|
||||
isTemplate: false,
|
||||
formElementSubSections: []
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
title: 'Template Section',
|
||||
shortTitle: 'Tmpl',
|
||||
description: 'A template section',
|
||||
isTemplate: true,
|
||||
formElementSubSections: []
|
||||
}
|
||||
]
|
||||
|
||||
const composable = useFormStepper(sections)
|
||||
const stepperItems = composable.stepperItems.value
|
||||
|
||||
expect(stepperItems).toHaveLength(1)
|
||||
expect(stepperItems[0]?.title).toBe('Normal')
|
||||
})
|
||||
})
|
||||
|
||||
describe('stepperItems', () => {
|
||||
it('should be empty when no sections provided', () => {
|
||||
const composable = useFormStepper([])
|
||||
const stepperItems = composable.stepperItems.value
|
||||
|
||||
expect(stepperItems).toEqual([])
|
||||
expect(stepperItems).toHaveLength(0)
|
||||
})
|
||||
|
||||
it('should return empty array when all sections are templates', () => {
|
||||
const sections: FormElementSectionDto[] = [
|
||||
{
|
||||
id: '1',
|
||||
title: 'Template 1',
|
||||
shortTitle: 'T1',
|
||||
description: 'Template description',
|
||||
isTemplate: true,
|
||||
formElementSubSections: []
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
title: 'Template 2',
|
||||
shortTitle: 'T2',
|
||||
description: 'Another template',
|
||||
isTemplate: true,
|
||||
formElementSubSections: []
|
||||
}
|
||||
]
|
||||
|
||||
const composable = useFormStepper(sections)
|
||||
const stepperItems = composable.stepperItems.value
|
||||
|
||||
expect(stepperItems).toHaveLength(0)
|
||||
})
|
||||
|
||||
it('should update stepperItems when formElementSections changes', async () => {
|
||||
const sections = ref<FormElementSectionDto[]>([
|
||||
{
|
||||
id: '1',
|
||||
title: 'Section 1',
|
||||
shortTitle: 'S1',
|
||||
description: 'First',
|
||||
isTemplate: false,
|
||||
formElementSubSections: []
|
||||
}
|
||||
])
|
||||
|
||||
const composable = useFormStepper(sections)
|
||||
expect(composable.stepperItems.value).toHaveLength(1)
|
||||
|
||||
// Update sections by mutating the ref
|
||||
sections.value = [
|
||||
...sections.value,
|
||||
{
|
||||
id: '2',
|
||||
title: 'Section 2',
|
||||
shortTitle: 'S2',
|
||||
description: 'Second',
|
||||
isTemplate: false,
|
||||
formElementSubSections: []
|
||||
}
|
||||
]
|
||||
|
||||
const stepperItems = composable.stepperItems.value
|
||||
expect(stepperItems).toHaveLength(2)
|
||||
})
|
||||
})
|
||||
|
||||
describe('currentFormElementSection', () => {
|
||||
it('should return the section at activeStepperItemIndex', () => {
|
||||
const sections: FormElementSectionDto[] = [
|
||||
{
|
||||
id: '1',
|
||||
title: 'Section 1',
|
||||
shortTitle: 'S1',
|
||||
description: 'First',
|
||||
isTemplate: false,
|
||||
formElementSubSections: []
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
title: 'Section 2',
|
||||
shortTitle: 'S2',
|
||||
description: 'Second',
|
||||
isTemplate: false,
|
||||
formElementSubSections: []
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
title: 'Section 3',
|
||||
shortTitle: 'S3',
|
||||
description: 'Third',
|
||||
isTemplate: false,
|
||||
formElementSubSections: []
|
||||
}
|
||||
]
|
||||
|
||||
const composable = useFormStepper(sections)
|
||||
|
||||
// Initially at index 0
|
||||
let current = composable.currentFormElementSection.value
|
||||
expect(current?.id).toBe('1')
|
||||
|
||||
// Change index to 1
|
||||
composable.activeStepperItemIndex.value = 1
|
||||
current = composable.currentFormElementSection.value
|
||||
expect(current?.id).toBe('2')
|
||||
|
||||
// Change index to 2
|
||||
composable.activeStepperItemIndex.value = 2
|
||||
current = composable.currentFormElementSection.value
|
||||
expect(current?.id).toBe('3')
|
||||
})
|
||||
|
||||
it('should filter templates when determining current section', () => {
|
||||
const sections: FormElementSectionDto[] = [
|
||||
{
|
||||
id: '1',
|
||||
title: 'Section 1',
|
||||
shortTitle: 'S1',
|
||||
description: 'First',
|
||||
isTemplate: false,
|
||||
formElementSubSections: []
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
title: 'Template Section',
|
||||
shortTitle: 'T1',
|
||||
description: 'Template',
|
||||
isTemplate: true,
|
||||
formElementSubSections: []
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
title: 'Section 3',
|
||||
shortTitle: 'S3',
|
||||
description: 'Third',
|
||||
isTemplate: false,
|
||||
formElementSubSections: []
|
||||
}
|
||||
]
|
||||
|
||||
const composable = useFormStepper(sections)
|
||||
|
||||
// activeStepperItemIndex refers to visible sections only
|
||||
composable.activeStepperItemIndex.value = 1
|
||||
const current = composable.currentFormElementSection.value
|
||||
// Should get the second visible section (id 3), skipping template
|
||||
expect(current?.id).toBe('3')
|
||||
})
|
||||
})
|
||||
|
||||
describe('undefined formElementSections', () => {
|
||||
it('should handle undefined formElementSections', () => {
|
||||
const composable = useFormStepper(undefined)
|
||||
const stepperItems = composable.stepperItems.value
|
||||
|
||||
expect(stepperItems).toEqual([])
|
||||
expect(stepperItems).toHaveLength(0)
|
||||
})
|
||||
|
||||
it('should default to empty array and return undefined for currentSection', () => {
|
||||
const composable = useFormStepper(undefined)
|
||||
|
||||
expect(composable.stepperItems.value).toHaveLength(0)
|
||||
expect(composable.currentFormElementSection.value).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe('stepper and navigateStepper', () => {
|
||||
it.each`
|
||||
direction
|
||||
${'forward'}
|
||||
${'backward'}
|
||||
`(
|
||||
'should call onNavigate callback if provided during navigation ($direction)',
|
||||
async ({ direction }: { direction: 'forward' | 'backward' }) => {
|
||||
const sections: FormElementSectionDto[] = [
|
||||
{
|
||||
id: '1',
|
||||
title: 'Section 1',
|
||||
shortTitle: 'S1',
|
||||
description: 'First',
|
||||
isTemplate: false,
|
||||
formElementSubSections: []
|
||||
}
|
||||
]
|
||||
|
||||
const onNavigate = vi.fn()
|
||||
const composable = useFormStepper(sections, { onNavigate })
|
||||
|
||||
await composable.navigateStepper(direction)
|
||||
|
||||
expect(onNavigate).toHaveBeenCalled()
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
464
legalconsenthub/test/unit/composables/useSectionSpawning.spec.ts
Normal file
464
legalconsenthub/test/unit/composables/useSectionSpawning.spec.ts
Normal file
@@ -0,0 +1,464 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { useSectionSpawning } from '../../../app/composables/useSectionSpawning'
|
||||
import type {
|
||||
FormElementSectionDto,
|
||||
FormElementSubSectionDto,
|
||||
FormElementDto,
|
||||
FormOptionDto,
|
||||
SectionSpawnTriggerDto,
|
||||
FormElementType
|
||||
} from '../../../.api-client'
|
||||
|
||||
// Helper to create a FormOptionDto
|
||||
function createOption(value: string, label: string): FormOptionDto {
|
||||
return {
|
||||
value,
|
||||
label,
|
||||
processingPurpose: 'NONE',
|
||||
employeeDataCategory: 'NONE'
|
||||
}
|
||||
}
|
||||
|
||||
// Helper to create a FormElementDto
|
||||
function createFormElement(
|
||||
reference: string,
|
||||
title: string,
|
||||
type: FormElementType,
|
||||
options: FormOptionDto[] = [],
|
||||
sectionSpawnTriggers: SectionSpawnTriggerDto[] = []
|
||||
): FormElementDto {
|
||||
return {
|
||||
id: `id-${reference}`,
|
||||
reference,
|
||||
title,
|
||||
type,
|
||||
options,
|
||||
sectionSpawnTriggers
|
||||
}
|
||||
}
|
||||
|
||||
// Helper to create a SectionSpawnTriggerDto
|
||||
function createSpawnTrigger(
|
||||
templateReference: string,
|
||||
conditionType: 'SHOW' | 'HIDE' = 'SHOW',
|
||||
expectedValue: string = 'true',
|
||||
operator: 'EQUALS' | 'NOT_EQUALS' | 'IS_EMPTY' | 'IS_NOT_EMPTY' | 'CONTAINS' | 'NOT_CONTAINS' = 'EQUALS'
|
||||
): SectionSpawnTriggerDto {
|
||||
return {
|
||||
templateReference,
|
||||
sectionSpawnConditionType: conditionType,
|
||||
sectionSpawnExpectedValue: expectedValue,
|
||||
sectionSpawnOperator: operator
|
||||
}
|
||||
}
|
||||
|
||||
// Helper to create a template section with title interpolation
|
||||
function createTemplateSection(
|
||||
templateReference: string,
|
||||
title: string,
|
||||
titleTemplate?: string,
|
||||
shortTitle?: string,
|
||||
description?: string,
|
||||
formElementSubSections: FormElementSubSectionDto[] = []
|
||||
): FormElementSectionDto {
|
||||
return {
|
||||
id: `template-${templateReference}`,
|
||||
title,
|
||||
shortTitle,
|
||||
description,
|
||||
titleTemplate,
|
||||
isTemplate: true,
|
||||
templateReference,
|
||||
formElementSubSections
|
||||
}
|
||||
}
|
||||
|
||||
describe('useSectionSpawning', () => {
|
||||
it('should remove spawned section when condition no longer met', () => {
|
||||
const { processSpawnTriggers } = useSectionSpawning()
|
||||
|
||||
const templateSection = createTemplateSection('template-1', 'Template Section')
|
||||
const spawnedSection: FormElementSectionDto = {
|
||||
id: 'spawned-1',
|
||||
title: 'Spawned Section',
|
||||
isTemplate: false,
|
||||
spawnedFromElementReference: 'trigger_1',
|
||||
templateReference: 'template-1',
|
||||
formElementSubSections: []
|
||||
}
|
||||
|
||||
const trigger = createSpawnTrigger('template-1', 'SHOW', 'true', 'EQUALS')
|
||||
const triggerElement = createFormElement(
|
||||
'trigger_1',
|
||||
'Trigger',
|
||||
'SELECT',
|
||||
[createOption('true', 'No'), createOption('false', 'Yes')],
|
||||
[trigger]
|
||||
)
|
||||
|
||||
const sections = [templateSection, spawnedSection]
|
||||
const result = processSpawnTriggers(sections, [triggerElement])
|
||||
|
||||
expect(result).toHaveLength(1)
|
||||
expect(result[0]!.isTemplate).toBe(true)
|
||||
})
|
||||
|
||||
it('should NOT remove spawned section if another trigger for same template still satisfies condition', () => {
|
||||
const { processSpawnTriggers } = useSectionSpawning()
|
||||
|
||||
const templateSection = createTemplateSection('template-1', 'Template Section')
|
||||
const spawnedSection: FormElementSectionDto = {
|
||||
id: 'spawned-1',
|
||||
title: 'Spawned Section',
|
||||
isTemplate: false,
|
||||
spawnedFromElementReference: 'trigger_1',
|
||||
templateReference: 'template-1',
|
||||
formElementSubSections: []
|
||||
}
|
||||
|
||||
const trigger1 = createSpawnTrigger('template-1', 'SHOW', 'true', 'EQUALS')
|
||||
const trigger2 = createSpawnTrigger('template-1', 'SHOW', 'maybe', 'EQUALS')
|
||||
const triggerElement = createFormElement(
|
||||
'trigger_1',
|
||||
'Trigger',
|
||||
'SELECT',
|
||||
[createOption('false', 'No'), createOption('false', 'Yes'), createOption('true', 'Maybe')],
|
||||
[trigger1, trigger2]
|
||||
)
|
||||
|
||||
const sections = [templateSection, spawnedSection]
|
||||
const result = processSpawnTriggers(sections, [triggerElement])
|
||||
|
||||
expect(result).toHaveLength(2)
|
||||
expect(result[1]!.spawnedFromElementReference).toBe('trigger_1')
|
||||
})
|
||||
|
||||
it('should update titles with {{triggerValue}} interpolation when value changes', () => {
|
||||
const { processSpawnTriggers } = useSectionSpawning()
|
||||
|
||||
const templateSection = createTemplateSection(
|
||||
'template-1',
|
||||
'Section title',
|
||||
'Section template title {{triggerValue}}',
|
||||
'Short title {{triggerValue}}',
|
||||
'Description {{triggerValue}}'
|
||||
)
|
||||
const spawnedSection: FormElementSectionDto = {
|
||||
id: 'spawned-1',
|
||||
title: 'Section OldValue',
|
||||
shortTitle: 'Short OldValue',
|
||||
description: 'Description OldValue',
|
||||
isTemplate: false,
|
||||
spawnedFromElementReference: 'trigger_1',
|
||||
templateReference: 'template-1',
|
||||
formElementSubSections: []
|
||||
}
|
||||
|
||||
const trigger = createSpawnTrigger('template-1', 'SHOW', 'yes', 'EQUALS')
|
||||
const triggerElement = createFormElement(
|
||||
'trigger_1',
|
||||
'Trigger',
|
||||
'SELECT',
|
||||
[createOption('false', 'No'), createOption('true', 'Yes')],
|
||||
[trigger]
|
||||
)
|
||||
|
||||
const sections = [templateSection, spawnedSection]
|
||||
const result = processSpawnTriggers(sections, [triggerElement])
|
||||
|
||||
expect(result[1]!.title).toBe('Section template title Yes')
|
||||
expect(result[1]!.shortTitle).toBe('Short title Yes')
|
||||
expect(result[1]!.description).toBe('Description Yes')
|
||||
})
|
||||
|
||||
it('should insert spawned section after template section', () => {
|
||||
const { processSpawnTriggers } = useSectionSpawning()
|
||||
|
||||
const templateSection = createTemplateSection('template-1', 'Template Section')
|
||||
const otherSection: FormElementSectionDto = {
|
||||
id: 'other-1',
|
||||
title: 'Other Section',
|
||||
isTemplate: false,
|
||||
formElementSubSections: []
|
||||
}
|
||||
|
||||
const trigger = createSpawnTrigger('template-1', 'SHOW', 'Yes', 'EQUALS')
|
||||
const triggerElement = createFormElement(
|
||||
'trigger_1',
|
||||
'Trigger',
|
||||
'SELECT',
|
||||
[createOption('false', 'No'), createOption('true', 'Yes')],
|
||||
[trigger]
|
||||
)
|
||||
|
||||
const sections = [templateSection, otherSection]
|
||||
const result = processSpawnTriggers(sections, [triggerElement])
|
||||
|
||||
expect(result).toHaveLength(3)
|
||||
expect(result[0]!.isTemplate).toBe(true)
|
||||
expect(result[1]!.spawnedFromElementReference).toBe('trigger_1')
|
||||
expect(result[2]!.id).toBe('other-1')
|
||||
})
|
||||
|
||||
it('should handle TEXTFIELD element value extraction', () => {
|
||||
const { processSpawnTriggers } = useSectionSpawning()
|
||||
|
||||
const templateSection = createTemplateSection('template-1', 'Section {{triggerValue}}', 'Section {{triggerValue}}')
|
||||
const trigger = createSpawnTrigger('template-1', 'SHOW', 'john', 'EQUALS')
|
||||
const triggerElement = createFormElement('trigger_1', 'Name', 'TEXTFIELD', [createOption('john', '')], [trigger])
|
||||
|
||||
const sections = [templateSection]
|
||||
const result = processSpawnTriggers(sections, [triggerElement])
|
||||
|
||||
expect(result).toHaveLength(2)
|
||||
expect(result[1]!.title).toBe('Section john')
|
||||
})
|
||||
|
||||
it('should interpolate shortTitle with {{triggerValue}}', () => {
|
||||
const { processSpawnTriggers } = useSectionSpawning()
|
||||
|
||||
const templateSection = createTemplateSection('template-1', 'Template', undefined, 'Short {{triggerValue}}')
|
||||
const spawnedSection: FormElementSectionDto = {
|
||||
id: 'spawned-1',
|
||||
title: 'Spawned',
|
||||
shortTitle: 'Short OldValue',
|
||||
isTemplate: false,
|
||||
spawnedFromElementReference: 'trigger_1',
|
||||
templateReference: 'template-1',
|
||||
formElementSubSections: []
|
||||
}
|
||||
|
||||
const trigger = createSpawnTrigger('template-1', 'SHOW', 'yes', 'EQUALS')
|
||||
const triggerElement = createFormElement(
|
||||
'trigger_1',
|
||||
'Trigger',
|
||||
'SELECT',
|
||||
[createOption('false', 'No'), createOption('true', 'Yes')],
|
||||
[trigger]
|
||||
)
|
||||
|
||||
const sections = [templateSection, spawnedSection]
|
||||
const result = processSpawnTriggers(sections, [triggerElement])
|
||||
|
||||
expect(result[1]!.shortTitle).toBe('Short Yes')
|
||||
})
|
||||
|
||||
it('should interpolate description with {{triggerValue}}', () => {
|
||||
const { processSpawnTriggers } = useSectionSpawning()
|
||||
|
||||
const templateSection = createTemplateSection(
|
||||
'template-1',
|
||||
'Template',
|
||||
undefined,
|
||||
undefined,
|
||||
'Description {{triggerValue}}'
|
||||
)
|
||||
const spawnedSection: FormElementSectionDto = {
|
||||
id: 'spawned-1',
|
||||
title: 'Spawned',
|
||||
description: 'Description OldValue',
|
||||
isTemplate: false,
|
||||
spawnedFromElementReference: 'trigger_1',
|
||||
templateReference: 'template-1',
|
||||
formElementSubSections: []
|
||||
}
|
||||
|
||||
const trigger = createSpawnTrigger('template-1', 'SHOW', 'yes', 'EQUALS')
|
||||
const triggerElement = createFormElement(
|
||||
'trigger_1',
|
||||
'Trigger',
|
||||
'SELECT',
|
||||
[createOption('false', 'No'), createOption('true', 'Yes')],
|
||||
[trigger]
|
||||
)
|
||||
|
||||
const sections = [templateSection, spawnedSection]
|
||||
const result = processSpawnTriggers(sections, [triggerElement])
|
||||
|
||||
expect(result[1]!.description).toBe('Description Yes')
|
||||
})
|
||||
|
||||
it('should preserve non-triggering elements during processing', () => {
|
||||
const { processSpawnTriggers } = useSectionSpawning()
|
||||
|
||||
const templateSection = createTemplateSection('template-1', 'Template')
|
||||
const nonTriggerElement = createFormElement('other_1', 'Other', 'TEXTFIELD', [createOption('value', '')])
|
||||
|
||||
const trigger = createSpawnTrigger('template-1', 'SHOW', 'Yes', 'EQUALS')
|
||||
const triggerElement = createFormElement(
|
||||
'trigger_1',
|
||||
'Trigger',
|
||||
'SELECT',
|
||||
[createOption('false', 'No'), createOption('true', 'Yes')],
|
||||
[trigger]
|
||||
)
|
||||
|
||||
const sections = [templateSection]
|
||||
const result = processSpawnTriggers(sections, [triggerElement, nonTriggerElement])
|
||||
|
||||
expect(result).toHaveLength(2)
|
||||
expect(result[1]!.spawnedFromElementReference).toBe('trigger_1')
|
||||
})
|
||||
|
||||
it('should handle empty trigger list on element', () => {
|
||||
const { processSpawnTriggers } = useSectionSpawning()
|
||||
|
||||
const templateSection = createTemplateSection('template-1', 'Template')
|
||||
const triggerElement = createFormElement(
|
||||
'trigger_1',
|
||||
'Trigger',
|
||||
'SELECT',
|
||||
[createOption('false', 'No'), createOption('true', 'Yes')],
|
||||
[]
|
||||
)
|
||||
|
||||
const sections = [templateSection]
|
||||
const result = processSpawnTriggers(sections, [triggerElement])
|
||||
|
||||
expect(result).toHaveLength(1)
|
||||
})
|
||||
|
||||
describe('operators', () => {
|
||||
describe('EQUALS', () => {
|
||||
it('should spawn section when SHOW/EQUALS condition is met with SELECT element', () => {
|
||||
const { processSpawnTriggers } = useSectionSpawning()
|
||||
|
||||
const templateSection = createTemplateSection('template-1', 'Template Section', 'Section Title Template')
|
||||
const trigger = createSpawnTrigger('template-1', 'SHOW', 'Yes', 'EQUALS')
|
||||
const triggerElement = createFormElement(
|
||||
'trigger_1',
|
||||
'Trigger',
|
||||
'SELECT',
|
||||
[createOption('false', 'No'), createOption('true', 'Yes')],
|
||||
[trigger]
|
||||
)
|
||||
|
||||
const result = processSpawnTriggers([templateSection], [triggerElement])
|
||||
|
||||
expect(result).toHaveLength(2)
|
||||
expect(result[1]!.spawnedFromElementReference).toBe('trigger_1')
|
||||
expect(result[1]!.templateReference).toBe('template-1')
|
||||
expect(result[1]!.title).toBe('Section Title Template')
|
||||
})
|
||||
|
||||
it('should NOT spawn section when SHOW/EQUALS condition is NOT met', () => {
|
||||
const { processSpawnTriggers } = useSectionSpawning()
|
||||
|
||||
const templateSection = createTemplateSection('template-1', 'Template Section')
|
||||
const trigger = createSpawnTrigger('template-1', 'SHOW', 'Yes', 'EQUALS')
|
||||
const triggerElement = createFormElement(
|
||||
'trigger_1',
|
||||
'Trigger',
|
||||
'SELECT',
|
||||
[createOption('true', 'No'), createOption('false', 'Yes')],
|
||||
[trigger]
|
||||
)
|
||||
|
||||
const sections = [templateSection]
|
||||
const result = processSpawnTriggers(sections, [triggerElement])
|
||||
|
||||
expect(result).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('should handle case-insensitive comparison for EQUALS operator', () => {
|
||||
const { processSpawnTriggers } = useSectionSpawning()
|
||||
|
||||
const templateSection = createTemplateSection('template-1', 'Template')
|
||||
const trigger = createSpawnTrigger('template-1', 'SHOW', 'YES', 'EQUALS')
|
||||
const triggerElement = createFormElement(
|
||||
'trigger_1',
|
||||
'Trigger',
|
||||
'SELECT',
|
||||
[createOption('false', 'No'), createOption('true', 'yes')],
|
||||
[trigger]
|
||||
)
|
||||
|
||||
const sections = [templateSection]
|
||||
const result = processSpawnTriggers(sections, [triggerElement])
|
||||
|
||||
expect(result).toHaveLength(2)
|
||||
})
|
||||
})
|
||||
|
||||
describe('NOT_EQUALS', () => {
|
||||
it('should handle NOT_EQUALS operator', () => {
|
||||
const { processSpawnTriggers } = useSectionSpawning()
|
||||
|
||||
const templateSection = createTemplateSection('template-1', 'Template')
|
||||
const trigger = createSpawnTrigger('template-1', 'SHOW', 'Yes', 'NOT_EQUALS')
|
||||
const triggerElement = createFormElement(
|
||||
'trigger_1',
|
||||
'Trigger',
|
||||
'SELECT',
|
||||
[createOption('true', 'No'), createOption('false', 'Yes')],
|
||||
[trigger]
|
||||
)
|
||||
|
||||
const sections = [templateSection]
|
||||
const result = processSpawnTriggers(sections, [triggerElement])
|
||||
|
||||
expect(result).toHaveLength(2)
|
||||
expect(result[1]!.spawnedFromElementReference).toBe('trigger_1')
|
||||
})
|
||||
})
|
||||
|
||||
describe('IS_EMPTY', () => {
|
||||
it('should handle IS_EMPTY operator', () => {
|
||||
const { processSpawnTriggers } = useSectionSpawning()
|
||||
|
||||
const templateSection = createTemplateSection('template-1', 'Template Section')
|
||||
const trigger = createSpawnTrigger('template-1', 'SHOW', 'PLACEHOLDER', 'IS_EMPTY')
|
||||
const triggerElement = createFormElement('trigger_1', 'Trigger', 'TEXTFIELD', [createOption('', '')], [trigger])
|
||||
|
||||
const sections = [templateSection]
|
||||
const result = processSpawnTriggers(sections, [triggerElement])
|
||||
|
||||
expect(result).toHaveLength(2)
|
||||
expect(result[1]!.spawnedFromElementReference).toBe('trigger_1')
|
||||
})
|
||||
})
|
||||
|
||||
describe('IS_NOT_EMPTY', () => {
|
||||
it('should handle IS_NOT_EMPTY operator', () => {
|
||||
const { processSpawnTriggers } = useSectionSpawning()
|
||||
|
||||
const templateSection = createTemplateSection('template-1', 'Template Section')
|
||||
const trigger = createSpawnTrigger('template-1', 'SHOW', 'PLACEHOLDER', 'IS_NOT_EMPTY')
|
||||
const triggerElement = createFormElement(
|
||||
'trigger_1',
|
||||
'Trigger',
|
||||
'TEXTFIELD',
|
||||
[createOption('some value', '')],
|
||||
[trigger]
|
||||
)
|
||||
|
||||
const sections = [templateSection]
|
||||
const result = processSpawnTriggers(sections, [triggerElement])
|
||||
|
||||
expect(result).toHaveLength(2)
|
||||
expect(result[1]!.spawnedFromElementReference).toBe('trigger_1')
|
||||
})
|
||||
})
|
||||
|
||||
describe('HIDE', () => {
|
||||
it('should handle HIDE condition type', () => {
|
||||
const { processSpawnTriggers } = useSectionSpawning()
|
||||
|
||||
const templateSection = createTemplateSection('template-1', 'Template Section')
|
||||
const trigger = createSpawnTrigger('template-1', 'HIDE', 'No', 'EQUALS')
|
||||
const triggerElement = createFormElement(
|
||||
'trigger_1',
|
||||
'Trigger',
|
||||
'SELECT',
|
||||
[createOption('true', 'No'), createOption('false', 'Yes')],
|
||||
[trigger]
|
||||
)
|
||||
|
||||
const sections = [templateSection]
|
||||
const result = processSpawnTriggers(sections, [triggerElement])
|
||||
|
||||
expect(result).toHaveLength(1)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { compareApplicationFormValues, groupChangesBySection } from '../../app/utils/formDiff'
|
||||
import { compareApplicationFormValues, groupChangesBySection } from '../../../app/utils/formSnapshotComparison'
|
||||
import type {
|
||||
ApplicationFormDto,
|
||||
ApplicationFormSnapshotDto,
|
||||
@@ -11,7 +11,7 @@ import type {
|
||||
FormElementSectionSnapshotDto,
|
||||
FormElementSubSectionSnapshotDto,
|
||||
FormElementType
|
||||
} from '../../.api-client'
|
||||
} from '../../../.api-client'
|
||||
|
||||
// Helper to create a minimal FormOptionDto
|
||||
function createOption(value: string, label: string): FormOptionDto {
|
||||
@@ -96,7 +96,7 @@ function createSnapshot(elements: FormElementSnapshotDto[], sectionTitle = 'Test
|
||||
}
|
||||
}
|
||||
|
||||
describe('formDiff', () => {
|
||||
describe('formSnapshotComparison', () => {
|
||||
describe('compareApplicationFormValues', () => {
|
||||
describe('TEXTFIELD element', () => {
|
||||
it('should detect new answer (empty → filled)', () => {
|
||||
@@ -110,9 +110,9 @@ describe('formDiff', () => {
|
||||
const diff = compareApplicationFormValues(current, version)
|
||||
|
||||
expect(diff.newAnswers).toHaveLength(1)
|
||||
expect(diff.newAnswers[0].elementTitle).toBe('Name')
|
||||
expect(diff.newAnswers[0].currentLabel).toBe('John Doe')
|
||||
expect(diff.newAnswers[0].previousLabel).toBeNull()
|
||||
expect(diff.newAnswers[0]?.elementTitle).toBe('Name')
|
||||
expect(diff.newAnswers[0]?.currentLabel).toBe('John Doe')
|
||||
expect(diff.newAnswers[0]?.previousLabel).toBeNull()
|
||||
expect(diff.changedAnswers).toHaveLength(0)
|
||||
expect(diff.clearedAnswers).toHaveLength(0)
|
||||
})
|
||||
@@ -128,8 +128,8 @@ describe('formDiff', () => {
|
||||
const diff = compareApplicationFormValues(current, version)
|
||||
|
||||
expect(diff.changedAnswers).toHaveLength(1)
|
||||
expect(diff.changedAnswers[0].previousLabel).toBe('John Doe')
|
||||
expect(diff.changedAnswers[0].currentLabel).toBe('Jane Doe')
|
||||
expect(diff.changedAnswers[0]?.previousLabel).toBe('John Doe')
|
||||
expect(diff.changedAnswers[0]?.currentLabel).toBe('Jane Doe')
|
||||
expect(diff.newAnswers).toHaveLength(0)
|
||||
expect(diff.clearedAnswers).toHaveLength(0)
|
||||
})
|
||||
@@ -143,8 +143,8 @@ describe('formDiff', () => {
|
||||
const diff = compareApplicationFormValues(current, version)
|
||||
|
||||
expect(diff.clearedAnswers).toHaveLength(1)
|
||||
expect(diff.clearedAnswers[0].previousLabel).toBe('John Doe')
|
||||
expect(diff.clearedAnswers[0].currentLabel).toBeNull()
|
||||
expect(diff.clearedAnswers[0]?.previousLabel).toBe('John Doe')
|
||||
expect(diff.clearedAnswers[0]?.currentLabel).toBeNull()
|
||||
expect(diff.newAnswers).toHaveLength(0)
|
||||
expect(diff.changedAnswers).toHaveLength(0)
|
||||
})
|
||||
@@ -179,7 +179,7 @@ describe('formDiff', () => {
|
||||
const diff = compareApplicationFormValues(current, version)
|
||||
|
||||
expect(diff.newAnswers).toHaveLength(1)
|
||||
expect(diff.newAnswers[0].currentLabel).toBe('This is a long description text.')
|
||||
expect(diff.newAnswers[0]?.currentLabel).toBe('This is a long description text.')
|
||||
})
|
||||
|
||||
it('should detect changed answer', () => {
|
||||
@@ -193,8 +193,8 @@ describe('formDiff', () => {
|
||||
const diff = compareApplicationFormValues(current, version)
|
||||
|
||||
expect(diff.changedAnswers).toHaveLength(1)
|
||||
expect(diff.changedAnswers[0].previousLabel).toBe('Original text')
|
||||
expect(diff.changedAnswers[0].currentLabel).toBe('Updated text')
|
||||
expect(diff.changedAnswers[0]?.previousLabel).toBe('Original text')
|
||||
expect(diff.changedAnswers[0]?.currentLabel).toBe('Updated text')
|
||||
})
|
||||
|
||||
it('should detect cleared answer', () => {
|
||||
@@ -206,7 +206,7 @@ describe('formDiff', () => {
|
||||
const diff = compareApplicationFormValues(current, version)
|
||||
|
||||
expect(diff.clearedAnswers).toHaveLength(1)
|
||||
expect(diff.clearedAnswers[0].previousLabel).toBe('Some text')
|
||||
expect(diff.clearedAnswers[0]?.previousLabel).toBe('Some text')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -224,7 +224,7 @@ describe('formDiff', () => {
|
||||
const diff = compareApplicationFormValues(current, version)
|
||||
|
||||
expect(diff.newAnswers).toHaveLength(1)
|
||||
expect(diff.newAnswers[0].currentLabel).toBe('<p>Rich <strong>text</strong> content</p>')
|
||||
expect(diff.newAnswers[0]?.currentLabel).toBe('<p>Rich <strong>text</strong> content</p>')
|
||||
})
|
||||
|
||||
it('should detect changed answer', () => {
|
||||
@@ -238,8 +238,8 @@ describe('formDiff', () => {
|
||||
const diff = compareApplicationFormValues(current, version)
|
||||
|
||||
expect(diff.changedAnswers).toHaveLength(1)
|
||||
expect(diff.changedAnswers[0].previousLabel).toBe('<p>Original</p>')
|
||||
expect(diff.changedAnswers[0].currentLabel).toBe('<p>Updated</p>')
|
||||
expect(diff.changedAnswers[0]?.previousLabel).toBe('<p>Original</p>')
|
||||
expect(diff.changedAnswers[0]?.currentLabel).toBe('<p>Updated</p>')
|
||||
})
|
||||
|
||||
it('should detect cleared answer', () => {
|
||||
@@ -264,7 +264,7 @@ describe('formDiff', () => {
|
||||
const diff = compareApplicationFormValues(current, version)
|
||||
|
||||
expect(diff.newAnswers).toHaveLength(1)
|
||||
expect(diff.newAnswers[0].currentLabel).toBe('2024-01-15')
|
||||
expect(diff.newAnswers[0]?.currentLabel).toBe('2024-01-15')
|
||||
})
|
||||
|
||||
it('should detect changed answer', () => {
|
||||
@@ -278,8 +278,8 @@ describe('formDiff', () => {
|
||||
const diff = compareApplicationFormValues(current, version)
|
||||
|
||||
expect(diff.changedAnswers).toHaveLength(1)
|
||||
expect(diff.changedAnswers[0].previousLabel).toBe('2024-01-15')
|
||||
expect(diff.changedAnswers[0].currentLabel).toBe('2024-02-20')
|
||||
expect(diff.changedAnswers[0]?.previousLabel).toBe('2024-01-15')
|
||||
expect(diff.changedAnswers[0]?.currentLabel).toBe('2024-02-20')
|
||||
})
|
||||
|
||||
it('should detect cleared answer', () => {
|
||||
@@ -314,8 +314,8 @@ describe('formDiff', () => {
|
||||
const diff = compareApplicationFormValues(current, version)
|
||||
|
||||
expect(diff.newAnswers).toHaveLength(1)
|
||||
expect(diff.newAnswers[0].currentLabel).toBe('Medium')
|
||||
expect(diff.newAnswers[0].previousLabel).toBeNull()
|
||||
expect(diff.newAnswers[0]?.currentLabel).toBe('Medium')
|
||||
expect(diff.newAnswers[0]?.previousLabel).toBeNull()
|
||||
})
|
||||
|
||||
it('should detect changed answer (different option selected)', () => {
|
||||
@@ -337,8 +337,8 @@ describe('formDiff', () => {
|
||||
const diff = compareApplicationFormValues(current, version)
|
||||
|
||||
expect(diff.changedAnswers).toHaveLength(1)
|
||||
expect(diff.changedAnswers[0].previousLabel).toBe('Low')
|
||||
expect(diff.changedAnswers[0].currentLabel).toBe('High')
|
||||
expect(diff.changedAnswers[0]?.previousLabel).toBe('Low')
|
||||
expect(diff.changedAnswers[0]?.currentLabel).toBe('High')
|
||||
})
|
||||
|
||||
it('should detect cleared answer (option selected → nothing selected)', () => {
|
||||
@@ -360,8 +360,8 @@ describe('formDiff', () => {
|
||||
const diff = compareApplicationFormValues(current, version)
|
||||
|
||||
expect(diff.clearedAnswers).toHaveLength(1)
|
||||
expect(diff.clearedAnswers[0].previousLabel).toBe('Medium')
|
||||
expect(diff.clearedAnswers[0].currentLabel).toBeNull()
|
||||
expect(diff.clearedAnswers[0]?.previousLabel).toBe('Medium')
|
||||
expect(diff.clearedAnswers[0]?.currentLabel).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -385,7 +385,7 @@ describe('formDiff', () => {
|
||||
const diff = compareApplicationFormValues(current, version)
|
||||
|
||||
expect(diff.newAnswers).toHaveLength(1)
|
||||
expect(diff.newAnswers[0].currentLabel).toBe('Male')
|
||||
expect(diff.newAnswers[0]?.currentLabel).toBe('Male')
|
||||
})
|
||||
|
||||
it('should detect changed answer', () => {
|
||||
@@ -407,8 +407,8 @@ describe('formDiff', () => {
|
||||
const diff = compareApplicationFormValues(current, version)
|
||||
|
||||
expect(diff.changedAnswers).toHaveLength(1)
|
||||
expect(diff.changedAnswers[0].previousLabel).toBe('Male')
|
||||
expect(diff.changedAnswers[0].currentLabel).toBe('Female')
|
||||
expect(diff.changedAnswers[0]?.previousLabel).toBe('Male')
|
||||
expect(diff.changedAnswers[0]?.currentLabel).toBe('Female')
|
||||
})
|
||||
|
||||
it('should detect cleared answer', () => {
|
||||
@@ -430,7 +430,7 @@ describe('formDiff', () => {
|
||||
const diff = compareApplicationFormValues(current, version)
|
||||
|
||||
expect(diff.clearedAnswers).toHaveLength(1)
|
||||
expect(diff.clearedAnswers[0].previousLabel).toBe('Other')
|
||||
expect(diff.clearedAnswers[0]?.previousLabel).toBe('Other')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -454,7 +454,7 @@ describe('formDiff', () => {
|
||||
const diff = compareApplicationFormValues(current, version)
|
||||
|
||||
expect(diff.newAnswers).toHaveLength(1)
|
||||
expect(diff.newAnswers[0].currentLabel).toBe('Feature A')
|
||||
expect(diff.newAnswers[0]?.currentLabel).toBe('Feature A')
|
||||
})
|
||||
|
||||
it('should detect new answer (multiple checkboxes selected)', () => {
|
||||
@@ -476,7 +476,7 @@ describe('formDiff', () => {
|
||||
const diff = compareApplicationFormValues(current, version)
|
||||
|
||||
expect(diff.newAnswers).toHaveLength(1)
|
||||
expect(diff.newAnswers[0].currentLabel).toBe('Feature A, Feature B')
|
||||
expect(diff.newAnswers[0]?.currentLabel).toBe('Feature A, Feature B')
|
||||
})
|
||||
|
||||
it('should detect changed answer (different checkboxes selected)', () => {
|
||||
@@ -498,8 +498,8 @@ describe('formDiff', () => {
|
||||
const diff = compareApplicationFormValues(current, version)
|
||||
|
||||
expect(diff.changedAnswers).toHaveLength(1)
|
||||
expect(diff.changedAnswers[0].previousLabel).toBe('Feature A')
|
||||
expect(diff.changedAnswers[0].currentLabel).toBe('Feature B, Feature C')
|
||||
expect(diff.changedAnswers[0]?.previousLabel).toBe('Feature A')
|
||||
expect(diff.changedAnswers[0]?.currentLabel).toBe('Feature B, Feature C')
|
||||
})
|
||||
|
||||
it('should detect cleared answer (all checkboxes deselected)', () => {
|
||||
@@ -521,7 +521,7 @@ describe('formDiff', () => {
|
||||
const diff = compareApplicationFormValues(current, version)
|
||||
|
||||
expect(diff.clearedAnswers).toHaveLength(1)
|
||||
expect(diff.clearedAnswers[0].previousLabel).toBe('Feature A, Feature B')
|
||||
expect(diff.clearedAnswers[0]?.previousLabel).toBe('Feature A, Feature B')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -537,7 +537,7 @@ describe('formDiff', () => {
|
||||
const diff = compareApplicationFormValues(current, version)
|
||||
|
||||
expect(diff.newAnswers).toHaveLength(1)
|
||||
expect(diff.newAnswers[0].currentLabel).toBe('Enabled')
|
||||
expect(diff.newAnswers[0]?.currentLabel).toBe('Enabled')
|
||||
})
|
||||
|
||||
it('should detect cleared answer (switch turned off)', () => {
|
||||
@@ -551,7 +551,7 @@ describe('formDiff', () => {
|
||||
const diff = compareApplicationFormValues(current, version)
|
||||
|
||||
expect(diff.clearedAnswers).toHaveLength(1)
|
||||
expect(diff.clearedAnswers[0].previousLabel).toBe('Enabled')
|
||||
expect(diff.clearedAnswers[0]?.previousLabel).toBe('Enabled')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -573,11 +573,11 @@ describe('formDiff', () => {
|
||||
const diff = compareApplicationFormValues(current, version)
|
||||
|
||||
expect(diff.newAnswers).toHaveLength(1)
|
||||
expect(diff.newAnswers[0].currentLabel).toBe('2 Zeilen')
|
||||
expect(diff.newAnswers[0].tableDiff).toBeDefined()
|
||||
expect(diff.newAnswers[0].tableDiff!.addedCount).toBe(2)
|
||||
expect(diff.newAnswers[0].tableDiff!.removedCount).toBe(0)
|
||||
expect(diff.newAnswers[0].tableDiff!.modifiedCount).toBe(0)
|
||||
expect(diff.newAnswers[0]?.currentLabel).toBe('2 Zeilen')
|
||||
expect(diff.newAnswers[0]?.tableDiff).toBeDefined()
|
||||
expect(diff.newAnswers[0]?.tableDiff!.addedCount).toBe(2)
|
||||
expect(diff.newAnswers[0]?.tableDiff!.removedCount).toBe(0)
|
||||
expect(diff.newAnswers[0]?.tableDiff!.modifiedCount).toBe(0)
|
||||
})
|
||||
|
||||
it('should detect rows removed', () => {
|
||||
@@ -594,10 +594,10 @@ describe('formDiff', () => {
|
||||
const diff = compareApplicationFormValues(current, version)
|
||||
|
||||
expect(diff.clearedAnswers).toHaveLength(1)
|
||||
expect(diff.clearedAnswers[0].previousLabel).toBe('2 Zeilen')
|
||||
expect(diff.clearedAnswers[0].tableDiff).toBeDefined()
|
||||
expect(diff.clearedAnswers[0].tableDiff!.removedCount).toBe(2)
|
||||
expect(diff.clearedAnswers[0].tableDiff!.addedCount).toBe(0)
|
||||
expect(diff.clearedAnswers[0]?.previousLabel).toBe('2 Zeilen')
|
||||
expect(diff.clearedAnswers[0]?.tableDiff).toBeDefined()
|
||||
expect(diff.clearedAnswers[0]?.tableDiff!.removedCount).toBe(2)
|
||||
expect(diff.clearedAnswers[0]?.tableDiff!.addedCount).toBe(0)
|
||||
})
|
||||
|
||||
it('should detect modified rows when row count changes', () => {
|
||||
@@ -619,12 +619,12 @@ describe('formDiff', () => {
|
||||
const diff = compareApplicationFormValues(current, version)
|
||||
|
||||
expect(diff.changedAnswers).toHaveLength(1)
|
||||
expect(diff.changedAnswers[0].tableDiff).toBeDefined()
|
||||
expect(diff.changedAnswers[0].tableDiff!.addedCount).toBe(1)
|
||||
expect(diff.changedAnswers[0].tableDiff!.modifiedCount).toBe(1)
|
||||
expect(diff.changedAnswers[0].tableDiff!.rows[1].changeType).toBe('modified')
|
||||
expect(diff.changedAnswers[0].tableDiff!.rows[1].previousValues['Name']).toBe('Jane')
|
||||
expect(diff.changedAnswers[0].tableDiff!.rows[1].currentValues['Name']).toBe('Janet')
|
||||
expect(diff.changedAnswers[0]?.tableDiff).toBeDefined()
|
||||
expect(diff.changedAnswers[0]?.tableDiff!.addedCount).toBe(1)
|
||||
expect(diff.changedAnswers[0]?.tableDiff!.modifiedCount).toBe(1)
|
||||
expect(diff.changedAnswers[0]?.tableDiff!.rows[1]?.changeType).toBe('modified')
|
||||
expect(diff.changedAnswers[0]?.tableDiff!.rows[1]?.previousValues['Name']).toBe('Jane')
|
||||
expect(diff.changedAnswers[0]?.tableDiff!.rows[1]?.currentValues['Name']).toBe('Janet')
|
||||
})
|
||||
|
||||
it('should detect modified rows when row count is the same', () => {
|
||||
@@ -644,12 +644,12 @@ describe('formDiff', () => {
|
||||
const diff = compareApplicationFormValues(current, version)
|
||||
|
||||
expect(diff.changedAnswers).toHaveLength(1)
|
||||
expect(diff.changedAnswers[0].tableDiff).toBeDefined()
|
||||
expect(diff.changedAnswers[0].tableDiff!.addedCount).toBe(0)
|
||||
expect(diff.changedAnswers[0].tableDiff!.modifiedCount).toBe(1)
|
||||
expect(diff.changedAnswers[0].tableDiff!.rows[1].changeType).toBe('modified')
|
||||
expect(diff.changedAnswers[0].tableDiff!.rows[1].previousValues['Name']).toBe('Jane')
|
||||
expect(diff.changedAnswers[0].tableDiff!.rows[1].currentValues['Name']).toBe('Janet')
|
||||
expect(diff.changedAnswers[0]?.tableDiff).toBeDefined()
|
||||
expect(diff.changedAnswers[0]?.tableDiff!.addedCount).toBe(0)
|
||||
expect(diff.changedAnswers[0]?.tableDiff!.modifiedCount).toBe(1)
|
||||
expect(diff.changedAnswers[0]?.tableDiff!.rows[1]?.changeType).toBe('modified')
|
||||
expect(diff.changedAnswers[0]?.tableDiff!.rows[1]?.previousValues['Name']).toBe('Jane')
|
||||
expect(diff.changedAnswers[0]?.tableDiff!.rows[1]?.currentValues['Name']).toBe('Janet')
|
||||
})
|
||||
|
||||
it('should detect mixed changes (added, removed, modified)', () => {
|
||||
@@ -669,12 +669,12 @@ describe('formDiff', () => {
|
||||
const diff = compareApplicationFormValues(current, version)
|
||||
|
||||
expect(diff.changedAnswers).toHaveLength(1)
|
||||
expect(diff.changedAnswers[0].tableDiff).toBeDefined()
|
||||
expect(diff.changedAnswers[0]?.tableDiff).toBeDefined()
|
||||
// Row 0: modified (John → John Updated, Developer → Senior Dev)
|
||||
// Row 1: modified (Jane → New Person, Designer → Manager)
|
||||
// Row 2: removed (Bob, Tester)
|
||||
expect(diff.changedAnswers[0].tableDiff!.modifiedCount).toBe(2)
|
||||
expect(diff.changedAnswers[0].tableDiff!.removedCount).toBe(1)
|
||||
expect(diff.changedAnswers[0]?.tableDiff!.modifiedCount).toBe(2)
|
||||
expect(diff.changedAnswers[0]?.tableDiff!.removedCount).toBe(1)
|
||||
})
|
||||
|
||||
it('should handle single row correctly', () => {
|
||||
@@ -694,7 +694,7 @@ describe('formDiff', () => {
|
||||
const diff = compareApplicationFormValues(current, version)
|
||||
|
||||
expect(diff.newAnswers).toHaveLength(1)
|
||||
expect(diff.newAnswers[0].currentLabel).toBe('1 Zeile')
|
||||
expect(diff.newAnswers[0]?.currentLabel).toBe('1 Zeile')
|
||||
})
|
||||
|
||||
it('should handle boolean values in table cells', () => {
|
||||
@@ -715,8 +715,8 @@ describe('formDiff', () => {
|
||||
const diff = compareApplicationFormValues(current, version)
|
||||
|
||||
expect(diff.changedAnswers).toHaveLength(1)
|
||||
expect(diff.changedAnswers[0].tableDiff!.rows[0].previousValues['Enabled']).toBe('Nein')
|
||||
expect(diff.changedAnswers[0].tableDiff!.rows[0].currentValues['Enabled']).toBe('Ja')
|
||||
expect(diff.changedAnswers[0]?.tableDiff!.rows[0]?.previousValues['Enabled']).toBe('Nein')
|
||||
expect(diff.changedAnswers[0]?.tableDiff!.rows[0]?.currentValues['Enabled']).toBe('Ja')
|
||||
})
|
||||
|
||||
it('should handle array values in table cells', () => {
|
||||
@@ -736,8 +736,8 @@ describe('formDiff', () => {
|
||||
const diff = compareApplicationFormValues(current, version)
|
||||
|
||||
expect(diff.changedAnswers).toHaveLength(1)
|
||||
expect(diff.changedAnswers[0].tableDiff!.rows[0].previousValues['Tags']).toBe('Tag1')
|
||||
expect(diff.changedAnswers[0].tableDiff!.rows[0].currentValues['Tags']).toBe('Tag1, Tag2')
|
||||
expect(diff.changedAnswers[0]?.tableDiff!.rows[0]?.previousValues['Tags']).toBe('Tag1')
|
||||
expect(diff.changedAnswers[0]?.tableDiff!.rows[0]?.currentValues['Tags']).toBe('Tag1, Tag2')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -751,8 +751,8 @@ describe('formDiff', () => {
|
||||
const diff = compareApplicationFormValues(current, version)
|
||||
|
||||
expect(diff.clearedAnswers).toHaveLength(1)
|
||||
expect(diff.clearedAnswers[0].elementTitle).toBe('Name')
|
||||
expect(diff.clearedAnswers[0].previousLabel).toBe('John Doe')
|
||||
expect(diff.clearedAnswers[0]?.elementTitle).toBe('Name')
|
||||
expect(diff.clearedAnswers[0]?.previousLabel).toBe('John Doe')
|
||||
})
|
||||
|
||||
it('should not report removed element if it had no value', () => {
|
||||
@@ -777,8 +777,8 @@ describe('formDiff', () => {
|
||||
const diff = compareApplicationFormValues(current, version)
|
||||
|
||||
expect(diff.newAnswers).toHaveLength(1)
|
||||
expect(diff.newAnswers[0].elementTitle).toBe('Name')
|
||||
expect(diff.newAnswers[0].currentLabel).toBe('John Doe')
|
||||
expect(diff.newAnswers[0]?.elementTitle).toBe('Name')
|
||||
expect(diff.newAnswers[0]?.currentLabel).toBe('John Doe')
|
||||
})
|
||||
|
||||
it('should not report new element if it has no value', () => {
|
||||
@@ -813,13 +813,13 @@ describe('formDiff', () => {
|
||||
const diff = compareApplicationFormValues(current, version)
|
||||
|
||||
expect(diff.newAnswers).toHaveLength(1)
|
||||
expect(diff.newAnswers[0].elementTitle).toBe('Name')
|
||||
expect(diff.newAnswers[0]?.elementTitle).toBe('Name')
|
||||
|
||||
expect(diff.changedAnswers).toHaveLength(1)
|
||||
expect(diff.changedAnswers[0].elementTitle).toBe('Email')
|
||||
expect(diff.changedAnswers[0]?.elementTitle).toBe('Email')
|
||||
|
||||
expect(diff.clearedAnswers).toHaveLength(1)
|
||||
expect(diff.clearedAnswers[0].elementTitle).toBe('Status')
|
||||
expect(diff.clearedAnswers[0]?.elementTitle).toBe('Status')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1088,8 +1088,8 @@ describe('formDiff', () => {
|
||||
const grouped = groupChangesBySection(diff)
|
||||
|
||||
expect(grouped).toHaveLength(1)
|
||||
expect(grouped[0].sectionTitle).toBe('Section A')
|
||||
expect(grouped[0].changes).toHaveLength(3)
|
||||
expect(grouped[0]?.sectionTitle).toBe('Section A')
|
||||
expect(grouped[0]?.changes).toHaveLength(3)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -4,18 +4,32 @@ import { defineVitestProject } from '@nuxt/test-utils/config'
|
||||
export default defineConfig({
|
||||
test: {
|
||||
projects: [
|
||||
{
|
||||
test: {
|
||||
name: 'unit',
|
||||
include: ['test/{e2e,unit}/*.{test,spec}.ts'],
|
||||
environment: 'node'
|
||||
}
|
||||
},
|
||||
await defineVitestProject({
|
||||
test: {
|
||||
name: 'nuxt',
|
||||
include: ['test/nuxt/*.{test,spec}.ts'],
|
||||
environment: 'nuxt'
|
||||
name: 'unit',
|
||||
include: ['test/unit/**/*.{test,spec}.ts'],
|
||||
environment: 'nuxt',
|
||||
environmentOptions: {
|
||||
nuxt: {
|
||||
domEnvironment: 'happy-dom'
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
await defineVitestProject({
|
||||
test: {
|
||||
name: 'integration',
|
||||
include: ['test/integration/**/*.{test,spec}.ts'],
|
||||
environment: 'nuxt',
|
||||
environmentOptions: {
|
||||
nuxt: {
|
||||
domEnvironment: 'happy-dom',
|
||||
mock: {
|
||||
intersectionObserver: true,
|
||||
indexedDb: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user