diff --git a/legalconsenthub-backend/api/legalconsenthub.yml b/legalconsenthub-backend/api/legalconsenthub.yml index 43a4b0f..05be21b 100644 --- a/legalconsenthub-backend/api/legalconsenthub.yml +++ b/legalconsenthub-backend/api/legalconsenthub.yml @@ -275,6 +275,111 @@ paths: "503": $ref: "https://api.swaggerhub.com/domains/smartbear-public/ProblemDetails/1.0.0#/components/responses/ServiceUnavailable" + /application-forms/{id}/versions: + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + get: + summary: Get all versions of an application form + operationId: getApplicationFormVersions + tags: + - application-form-version + responses: + "200": + description: List of versions + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/ApplicationFormVersionListItemDto" + "400": + $ref: "https://api.swaggerhub.com/domains/smartbear-public/ProblemDetails/1.0.0#/components/responses/BadRequest" + "401": + $ref: "https://api.swaggerhub.com/domains/smartbear-public/ProblemDetails/1.0.0#/components/responses/Unauthorized" + "404": + $ref: "https://api.swaggerhub.com/domains/smartbear-public/ProblemDetails/1.0.0#/components/responses/NotFound" + "500": + $ref: "https://api.swaggerhub.com/domains/smartbear-public/ProblemDetails/1.0.0#/components/responses/ServerError" + "503": + $ref: "https://api.swaggerhub.com/domains/smartbear-public/ProblemDetails/1.0.0#/components/responses/ServiceUnavailable" + + /application-forms/{id}/versions/{versionNumber}: + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - name: versionNumber + in: path + required: true + schema: + type: integer + get: + summary: Get specific version with full snapshot + operationId: getApplicationFormVersion + tags: + - application-form-version + responses: + "200": + description: Version details with snapshot + content: + application/json: + schema: + $ref: "#/components/schemas/ApplicationFormVersionDto" + "400": + $ref: "https://api.swaggerhub.com/domains/smartbear-public/ProblemDetails/1.0.0#/components/responses/BadRequest" + "401": + $ref: "https://api.swaggerhub.com/domains/smartbear-public/ProblemDetails/1.0.0#/components/responses/Unauthorized" + "404": + $ref: "https://api.swaggerhub.com/domains/smartbear-public/ProblemDetails/1.0.0#/components/responses/NotFound" + "500": + $ref: "https://api.swaggerhub.com/domains/smartbear-public/ProblemDetails/1.0.0#/components/responses/ServerError" + "503": + $ref: "https://api.swaggerhub.com/domains/smartbear-public/ProblemDetails/1.0.0#/components/responses/ServiceUnavailable" + + /application-forms/{id}/versions/{versionNumber}/restore: + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - name: versionNumber + in: path + required: true + schema: + type: integer + post: + summary: Restore a specific version + operationId: restoreApplicationFormVersion + tags: + - application-form-version + responses: + "200": + description: Restored application form + content: + application/json: + schema: + $ref: "#/components/schemas/ApplicationFormDto" + "400": + $ref: "https://api.swaggerhub.com/domains/smartbear-public/ProblemDetails/1.0.0#/components/responses/BadRequest" + "401": + $ref: "https://api.swaggerhub.com/domains/smartbear-public/ProblemDetails/1.0.0#/components/responses/Unauthorized" + "404": + $ref: "https://api.swaggerhub.com/domains/smartbear-public/ProblemDetails/1.0.0#/components/responses/NotFound" + "500": + $ref: "https://api.swaggerhub.com/domains/smartbear-public/ProblemDetails/1.0.0#/components/responses/ServerError" + "503": + $ref: "https://api.swaggerhub.com/domains/smartbear-public/ProblemDetails/1.0.0#/components/responses/ServiceUnavailable" + ####### Application Form Templates ####### /application-form-templates: get: @@ -1017,6 +1122,92 @@ components: items: $ref: "#/components/schemas/ApplicationFormDto" + ApplicationFormSnapshotDto: + type: object + required: + - name + - status + - organizationId + - sections + properties: + name: + type: string + status: + $ref: "#/components/schemas/ApplicationFormStatus" + organizationId: + type: string + sections: + type: array + items: + $ref: "#/components/schemas/FormElementSectionSnapshotDto" + + ####### ApplicationFormVersion ####### + ApplicationFormVersionDto: + type: object + required: + - id + - applicationFormId + - versionNumber + - name + - status + - organizationId + - snapshot + - createdBy + - createdAt + properties: + id: + type: string + format: uuid + applicationFormId: + type: string + format: uuid + versionNumber: + type: integer + name: + type: string + status: + $ref: "#/components/schemas/ApplicationFormStatus" + organizationId: + type: string + snapshot: + $ref: "#/components/schemas/ApplicationFormSnapshotDto" + createdBy: + $ref: "#/components/schemas/UserDto" + createdAt: + type: string + format: date-time + + ApplicationFormVersionListItemDto: + type: object + required: + - id + - applicationFormId + - versionNumber + - name + - status + - organizationId + - createdBy + - createdAt + properties: + id: + type: string + format: uuid + applicationFormId: + type: string + format: uuid + versionNumber: + type: integer + name: + type: string + status: + $ref: "#/components/schemas/ApplicationFormStatus" + organizationId: + type: string + createdBy: + $ref: "#/components/schemas/UserDto" + createdAt: + type: string + format: date-time ####### Form ####### FormElementSectionDto: @@ -1044,6 +1235,23 @@ components: type: string format: uuid + FormElementSectionSnapshotDto: + type: object + required: + - title + - elements + properties: + title: + type: string + shortTitle: + type: string + description: + type: string + elements: + type: array + items: + $ref: "#/components/schemas/FormElementSnapshotDto" + CreateFormElementSectionDto: type: object required: @@ -1089,6 +1297,23 @@ components: type: string format: uuid + FormElementSnapshotDto: + type: object + required: + - type + - options + properties: + title: + type: string + description: + type: string + type: + $ref: "#/components/schemas/FormElementType" + options: + type: array + items: + $ref: "#/components/schemas/FormOptionDto" + CreateFormElementDto: type: object required: diff --git a/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/application_form/ApplicationFormService.kt b/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/application_form/ApplicationFormService.kt index c4ddf1e..50662c6 100644 --- a/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/application_form/ApplicationFormService.kt +++ b/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/application_form/ApplicationFormService.kt @@ -1,5 +1,6 @@ package com.betriebsratkanzlei.legalconsenthub.application_form +import com.betriebsratkanzlei.legalconsenthub.application_form_version.ApplicationFormVersionService import com.betriebsratkanzlei.legalconsenthub.error.ApplicationFormInvalidStateException import com.betriebsratkanzlei.legalconsenthub.error.ApplicationFormNotCreatedException import com.betriebsratkanzlei.legalconsenthub.error.ApplicationFormNotDeletedException @@ -8,6 +9,7 @@ import com.betriebsratkanzlei.legalconsenthub.error.ApplicationFormNotUpdatedExc import com.betriebsratkanzlei.legalconsenthub.error.FormElementSectionNotFoundException import com.betriebsratkanzlei.legalconsenthub.form_element.FormElementMapper import com.betriebsratkanzlei.legalconsenthub.notification.NotificationService +import com.betriebsratkanzlei.legalconsenthub.user.UserService import com.betriebsratkanzlei.legalconsenthub_api.model.ApplicationFormDto import com.betriebsratkanzlei.legalconsenthub_api.model.ApplicationFormStatus import com.betriebsratkanzlei.legalconsenthub_api.model.CreateApplicationFormDto @@ -25,6 +27,8 @@ class ApplicationFormService( private val applicationFormMapper: ApplicationFormMapper, private val formElementMapper: FormElementMapper, private val notificationService: NotificationService, + private val versionService: ApplicationFormVersionService, + private val userService: UserService, ) { fun createApplicationForm(createApplicationFormDto: CreateApplicationFormDto): ApplicationForm { val applicationForm = applicationFormMapper.toApplicationForm(createApplicationFormDto) @@ -35,6 +39,9 @@ class ApplicationFormService( throw ApplicationFormNotCreatedException(e) } + val currentUser = userService.getCurrentUser() + versionService.createVersion(savedApplicationForm, currentUser) + return savedApplicationForm } @@ -58,6 +65,9 @@ class ApplicationFormService( throw ApplicationFormNotUpdatedException(e, applicationFormDto.id) } + val currentUser = userService.getCurrentUser() + versionService.createVersion(updatedApplicationForm, currentUser) + return updatedApplicationForm } @@ -90,6 +100,9 @@ class ApplicationFormService( throw ApplicationFormNotUpdatedException(e, id) } + val currentUser = userService.getCurrentUser() + versionService.createVersion(savedApplicationForm, currentUser) + createNotificationForOrganization(savedApplicationForm) return savedApplicationForm diff --git a/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/application_form_version/ApplicationFormVersion.kt b/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/application_form_version/ApplicationFormVersion.kt new file mode 100644 index 0000000..bd00f67 --- /dev/null +++ b/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/application_form_version/ApplicationFormVersion.kt @@ -0,0 +1,47 @@ +package com.betriebsratkanzlei.legalconsenthub.application_form_version + +import com.betriebsratkanzlei.legalconsenthub.application_form.ApplicationForm +import com.betriebsratkanzlei.legalconsenthub.user.User +import com.betriebsratkanzlei.legalconsenthub_api.model.ApplicationFormStatus +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.EntityListeners +import jakarta.persistence.EnumType +import jakarta.persistence.Enumerated +import jakarta.persistence.GeneratedValue +import jakarta.persistence.Id +import jakarta.persistence.JoinColumn +import jakarta.persistence.ManyToOne +import org.springframework.data.annotation.CreatedDate +import org.springframework.data.jpa.domain.support.AuditingEntityListener +import java.time.LocalDateTime +import java.util.UUID + +@Entity +@EntityListeners(AuditingEntityListener::class) +class ApplicationFormVersion( + @Id + @GeneratedValue + var id: UUID? = null, + @ManyToOne + @JoinColumn(name = "application_form_id", nullable = false) + var applicationForm: ApplicationForm, + @Column(nullable = false) + var versionNumber: Int, + @Column(nullable = false) + var name: String, + @Enumerated(EnumType.STRING) + @Column(nullable = false) + var status: ApplicationFormStatus, + @Column(nullable = false) + var organizationId: String, + @Column(nullable = false, columnDefinition = "TEXT") + var snapshotData: String, + @ManyToOne + @JoinColumn(name = "created_by_id", nullable = false) + var createdBy: User, + @CreatedDate + @Column(nullable = false) + var createdAt: LocalDateTime? = null, +) + diff --git a/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/application_form_version/ApplicationFormVersionController.kt b/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/application_form_version/ApplicationFormVersionController.kt new file mode 100644 index 0000000..e683ee6 --- /dev/null +++ b/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/application_form_version/ApplicationFormVersionController.kt @@ -0,0 +1,52 @@ +package com.betriebsratkanzlei.legalconsenthub.application_form_version + +import com.betriebsratkanzlei.legalconsenthub.application_form.ApplicationFormMapper +import com.betriebsratkanzlei.legalconsenthub.user.UserService +import com.betriebsratkanzlei.legalconsenthub_api.api.ApplicationFormVersionApi +import com.betriebsratkanzlei.legalconsenthub_api.model.ApplicationFormDto +import com.betriebsratkanzlei.legalconsenthub_api.model.ApplicationFormVersionDto +import com.betriebsratkanzlei.legalconsenthub_api.model.ApplicationFormVersionListItemDto +import org.springframework.http.ResponseEntity +import org.springframework.security.access.prepost.PreAuthorize +import org.springframework.web.bind.annotation.RestController +import java.util.UUID + +@RestController +class ApplicationFormVersionController( + private val versionService: ApplicationFormVersionService, + private val versionMapper: ApplicationFormVersionMapper, + private val applicationFormMapper: ApplicationFormMapper, + private val userService: UserService, +) : ApplicationFormVersionApi { + @PreAuthorize( + "hasAnyRole('CHIEF_EXECUTIVE_OFFICER', 'BUSINESS_DEPARTMENT', 'IT_DEPARTMENT', 'HUMAN_RESOURCES', 'HEAD_OF_WORKS_COUNCIL', 'WORKS_COUNCIL', 'EMPLOYEE')", + ) + override fun getApplicationFormVersions(id: UUID): ResponseEntity> { + val versions = versionService.getVersionsByApplicationFormId(id) + return ResponseEntity.ok(versions.map { versionMapper.toApplicationFormVersionListItemDto(it) }) + } + + @PreAuthorize( + "hasAnyRole('CHIEF_EXECUTIVE_OFFICER', 'BUSINESS_DEPARTMENT', 'IT_DEPARTMENT', 'HUMAN_RESOURCES', 'HEAD_OF_WORKS_COUNCIL', 'WORKS_COUNCIL', 'EMPLOYEE')", + ) + override fun getApplicationFormVersion( + id: UUID, + versionNumber: Int, + ): ResponseEntity { + val version = versionService.getVersion(id, versionNumber) + return ResponseEntity.ok(versionMapper.toApplicationFormVersionDto(version)) + } + + @PreAuthorize( + "hasAnyRole('CHIEF_EXECUTIVE_OFFICER', 'BUSINESS_DEPARTMENT', 'IT_DEPARTMENT', 'HUMAN_RESOURCES', 'HEAD_OF_WORKS_COUNCIL', 'WORKS_COUNCIL')", + ) + override fun restoreApplicationFormVersion( + id: UUID, + versionNumber: Int, + ): ResponseEntity { + val currentUser = userService.getCurrentUser() + val restoredForm = versionService.restoreVersion(id, versionNumber, currentUser) + return ResponseEntity.ok(applicationFormMapper.toApplicationFormDto(restoredForm)) + } +} + diff --git a/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/application_form_version/ApplicationFormVersionMapper.kt b/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/application_form_version/ApplicationFormVersionMapper.kt new file mode 100644 index 0000000..81d129a --- /dev/null +++ b/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/application_form_version/ApplicationFormVersionMapper.kt @@ -0,0 +1,51 @@ +package com.betriebsratkanzlei.legalconsenthub.application_form_version + +import com.betriebsratkanzlei.legalconsenthub.user.UserMapper +import com.betriebsratkanzlei.legalconsenthub_api.model.ApplicationFormSnapshotDto +import com.betriebsratkanzlei.legalconsenthub_api.model.ApplicationFormVersionDto +import com.betriebsratkanzlei.legalconsenthub_api.model.ApplicationFormVersionListItemDto +import com.fasterxml.jackson.databind.ObjectMapper +import org.springframework.stereotype.Component + +@Component +class ApplicationFormVersionMapper( + private val userMapper: UserMapper, + private val objectMapper: ObjectMapper, +) { + fun toApplicationFormVersionDto(version: ApplicationFormVersion): ApplicationFormVersionDto { + val snapshot = objectMapper.readValue(version.snapshotData, ApplicationFormSnapshotDto::class.java) + + return ApplicationFormVersionDto( + id = version.id ?: throw IllegalStateException("ApplicationFormVersion ID must not be null!"), + applicationFormId = + version.applicationForm.id + ?: throw IllegalStateException("ApplicationForm ID must not be null!"), + versionNumber = version.versionNumber, + name = version.name, + status = version.status, + organizationId = version.organizationId, + snapshot = snapshot, + createdBy = userMapper.toUserDto(version.createdBy), + createdAt = + version.createdAt + ?: throw IllegalStateException("ApplicationFormVersion createdAt must not be null!"), + ) + } + + fun toApplicationFormVersionListItemDto(version: ApplicationFormVersion): ApplicationFormVersionListItemDto = + ApplicationFormVersionListItemDto( + id = version.id ?: throw IllegalStateException("ApplicationFormVersion ID must not be null!"), + applicationFormId = + version.applicationForm.id + ?: throw IllegalStateException("ApplicationForm ID must not be null!"), + versionNumber = version.versionNumber, + name = version.name, + status = version.status, + organizationId = version.organizationId, + createdBy = userMapper.toUserDto(version.createdBy), + createdAt = + version.createdAt + ?: throw IllegalStateException("ApplicationFormVersion createdAt must not be null!"), + ) +} + diff --git a/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/application_form_version/ApplicationFormVersionRepository.kt b/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/application_form_version/ApplicationFormVersionRepository.kt new file mode 100644 index 0000000..651f9c4 --- /dev/null +++ b/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/application_form_version/ApplicationFormVersionRepository.kt @@ -0,0 +1,19 @@ +package com.betriebsratkanzlei.legalconsenthub.application_form_version + +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.stereotype.Repository +import java.util.Optional +import java.util.UUID + +@Repository +interface ApplicationFormVersionRepository : JpaRepository { + fun findByApplicationFormIdOrderByVersionNumberDesc(applicationFormId: UUID): List + + fun findByApplicationFormIdAndVersionNumber( + applicationFormId: UUID, + versionNumber: Int, + ): Optional + + fun countByApplicationFormId(applicationFormId: UUID): Int +} + diff --git a/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/application_form_version/ApplicationFormVersionService.kt b/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/application_form_version/ApplicationFormVersionService.kt new file mode 100644 index 0000000..b959a1d --- /dev/null +++ b/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/application_form_version/ApplicationFormVersionService.kt @@ -0,0 +1,160 @@ +package com.betriebsratkanzlei.legalconsenthub.application_form_version + +import com.betriebsratkanzlei.legalconsenthub.application_form.ApplicationForm +import com.betriebsratkanzlei.legalconsenthub.application_form.ApplicationFormRepository +import com.betriebsratkanzlei.legalconsenthub.error.ApplicationFormNotFoundException +import com.betriebsratkanzlei.legalconsenthub.error.ApplicationFormVersionNotFoundException +import com.betriebsratkanzlei.legalconsenthub.form_element.FormElement +import com.betriebsratkanzlei.legalconsenthub.form_element.FormElementSection +import com.betriebsratkanzlei.legalconsenthub.form_element.FormOption +import com.betriebsratkanzlei.legalconsenthub.user.User +import com.betriebsratkanzlei.legalconsenthub_api.model.ApplicationFormSnapshotDto +import com.betriebsratkanzlei.legalconsenthub_api.model.FormElementSnapshotDto +import com.betriebsratkanzlei.legalconsenthub_api.model.FormElementSectionSnapshotDto +import com.betriebsratkanzlei.legalconsenthub_api.model.FormOptionDto +import com.fasterxml.jackson.databind.ObjectMapper +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional +import java.util.UUID + +@Service +class ApplicationFormVersionService( + private val versionRepository: ApplicationFormVersionRepository, + private val applicationFormRepository: ApplicationFormRepository, + private val objectMapper: ObjectMapper, +) { + @Transactional + fun createVersion( + applicationForm: ApplicationForm, + user: User, + ): ApplicationFormVersion { + val nextVersionNumber = versionRepository.countByApplicationFormId(applicationForm.id!!) + 1 + + val snapshot = createSnapshot(applicationForm) + val snapshotJson = objectMapper.writeValueAsString(snapshot) + + val version = + ApplicationFormVersion( + applicationForm = applicationForm, + versionNumber = nextVersionNumber, + name = applicationForm.name, + status = applicationForm.status, + organizationId = applicationForm.organizationId, + snapshotData = snapshotJson, + createdBy = user, + ) + + return versionRepository.save(version) + } + + fun getVersionsByApplicationFormId(applicationFormId: UUID): List = + versionRepository.findByApplicationFormIdOrderByVersionNumberDesc(applicationFormId) + + fun getVersion( + applicationFormId: UUID, + versionNumber: Int, + ): ApplicationFormVersion = + versionRepository + .findByApplicationFormIdAndVersionNumber(applicationFormId, versionNumber) + .orElseThrow { ApplicationFormVersionNotFoundException(applicationFormId, versionNumber) } + + @Transactional + fun restoreVersion( + applicationFormId: UUID, + versionNumber: Int, + user: User, + ): ApplicationForm { + val version = getVersion(applicationFormId, versionNumber) + val applicationForm = + applicationFormRepository + .findById(applicationFormId) + .orElseThrow { ApplicationFormNotFoundException(applicationFormId) } + + val snapshot = objectMapper.readValue(version.snapshotData, ApplicationFormSnapshotDto::class.java) + + applicationForm.name = snapshot.name + applicationForm.status = snapshot.status + applicationForm.organizationId = snapshot.organizationId + applicationForm.lastModifiedBy = user + + applicationForm.formElementSections.clear() + snapshot.sections.forEach { sectionSnapshot -> + val section = createSectionFromSnapshot(sectionSnapshot, applicationForm) + applicationForm.formElementSections.add(section) + } + + val restoredForm = applicationFormRepository.save(applicationForm) + + createVersion(restoredForm, user) + + return restoredForm + } + + private fun createSnapshot(applicationForm: ApplicationForm): ApplicationFormSnapshotDto = + ApplicationFormSnapshotDto( + name = applicationForm.name, + status = applicationForm.status, + organizationId = applicationForm.organizationId, + sections = + applicationForm.formElementSections.map { section -> + FormElementSectionSnapshotDto( + title = section.title, + shortTitle = section.shortTitle, + description = section.description, + elements = + section.formElements.map { element -> + FormElementSnapshotDto( + title = element.title, + description = element.description, + type = element.type, + options = + element.options.map { option -> + FormOptionDto( + value = option.value, + label = option.label, + processingPurpose = option.processingPurpose, + employeeDataCategory = option.employeeDataCategory, + ) + }, + ) + }, + ) + }, + ) + + private fun createSectionFromSnapshot( + sectionSnapshot: FormElementSectionSnapshotDto, + applicationForm: ApplicationForm, + ): FormElementSection { + val section = + FormElementSection( + title = sectionSnapshot.title, + shortTitle = sectionSnapshot.shortTitle, + description = sectionSnapshot.description, + applicationForm = applicationForm, + ) + + sectionSnapshot.elements.forEach { elementSnapshot -> + val element = + FormElement( + title = elementSnapshot.title, + description = elementSnapshot.description, + type = elementSnapshot.type, + formElementSection = section, + options = + elementSnapshot.options.map { optionDto -> + FormOption( + value = optionDto.value, + label = optionDto.label, + processingPurpose = optionDto.processingPurpose, + employeeDataCategory = optionDto.employeeDataCategory, + ) + }.toMutableList(), + ) + section.formElements.add(element) + } + + return section + } +} + diff --git a/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/error/ApplicationFormVersionNotFoundException.kt b/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/error/ApplicationFormVersionNotFoundException.kt new file mode 100644 index 0000000..520828d --- /dev/null +++ b/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/error/ApplicationFormVersionNotFoundException.kt @@ -0,0 +1,9 @@ +package com.betriebsratkanzlei.legalconsenthub.error + +import java.util.UUID + +class ApplicationFormVersionNotFoundException( + applicationFormId: UUID, + versionNumber: Int, +) : RuntimeException("Application form version $versionNumber for application form $applicationFormId not found") + diff --git a/legalconsenthub/app/components/TheForm.vue b/legalconsenthub/app/components/TheForm.vue new file mode 100644 index 0000000..bdc18ab --- /dev/null +++ b/legalconsenthub/app/components/TheForm.vue @@ -0,0 +1,103 @@ + + + diff --git a/legalconsenthub/app/components/VersionHistory.vue b/legalconsenthub/app/components/VersionHistory.vue new file mode 100644 index 0000000..00e5b26 --- /dev/null +++ b/legalconsenthub/app/components/VersionHistory.vue @@ -0,0 +1,183 @@ + + + + diff --git a/legalconsenthub/app/composables/applicationFormVersion/useApplicationFormVersion.ts b/legalconsenthub/app/composables/applicationFormVersion/useApplicationFormVersion.ts new file mode 100644 index 0000000..b996003 --- /dev/null +++ b/legalconsenthub/app/composables/applicationFormVersion/useApplicationFormVersion.ts @@ -0,0 +1,43 @@ +import type { ApplicationFormVersionDto, ApplicationFormVersionListItemDto, ApplicationFormDto } from '~~/.api-client' +import { useApplicationFormVersionApi } from '~/composables' + +export function useApplicationFormVersion() { + const versionApi = useApplicationFormVersionApi() + + async function getVersions(applicationFormId: string): Promise { + try { + return await versionApi.getVersions(applicationFormId) + } catch (e: unknown) { + console.error(`Failed retrieving versions for application form ${applicationFormId}:`, e) + return Promise.reject(e) + } + } + + async function getVersion(applicationFormId: string, versionNumber: number): Promise { + try { + return await versionApi.getVersion(applicationFormId, versionNumber) + } catch (e: unknown) { + console.error(`Failed retrieving version ${versionNumber} for application form ${applicationFormId}:`, e) + return Promise.reject(e) + } + } + + async function restoreVersion(applicationFormId: string, versionNumber: number): Promise { + if (!applicationFormId) { + return Promise.reject(new Error('Application form ID missing')) + } + + try { + return await versionApi.restoreVersion(applicationFormId, versionNumber) + } catch (e: unknown) { + console.error(`Failed restoring version ${versionNumber} for application form ${applicationFormId}:`, e) + return Promise.reject(e) + } + } + + return { + getVersions, + getVersion, + restoreVersion + } +} diff --git a/legalconsenthub/app/composables/applicationFormVersion/useApplicationFormVersionApi.ts b/legalconsenthub/app/composables/applicationFormVersion/useApplicationFormVersionApi.ts new file mode 100644 index 0000000..884b55b --- /dev/null +++ b/legalconsenthub/app/composables/applicationFormVersion/useApplicationFormVersionApi.ts @@ -0,0 +1,50 @@ +import { + ApplicationFormVersionApi, + Configuration, + type ApplicationFormVersionDto, + type ApplicationFormVersionListItemDto, + type ApplicationFormDto +} from '~~/.api-client' +import { cleanDoubleSlashes, withoutTrailingSlash } from 'ufo' +import { wrappedFetchWrap } from '~/utils/wrappedFetch' + +export function useApplicationFormVersionApi() { + const appBaseUrl = useRuntimeConfig().app.baseURL + const { serverApiBasePath, clientProxyBasePath } = useRuntimeConfig().public + + const basePath = withoutTrailingSlash( + cleanDoubleSlashes( + import.meta.client + ? appBaseUrl + clientProxyBasePath + : useRequestURL().origin + clientProxyBasePath + serverApiBasePath + ) + ) + + const versionApiClient = new ApplicationFormVersionApi( + new Configuration({ basePath, fetchApi: wrappedFetchWrap(useRequestFetch()) }) + ) + + async function getVersions(applicationFormId: string): Promise { + return versionApiClient.getApplicationFormVersions({ id: applicationFormId }) + } + + async function getVersion(applicationFormId: string, versionNumber: number): Promise { + return versionApiClient.getApplicationFormVersion({ + id: applicationFormId, + versionNumber + }) + } + + async function restoreVersion(applicationFormId: string, versionNumber: number): Promise { + return versionApiClient.restoreApplicationFormVersion({ + id: applicationFormId, + versionNumber + }) + } + + return { + getVersions, + getVersion, + restoreVersion + } +} diff --git a/legalconsenthub/app/composables/index.ts b/legalconsenthub/app/composables/index.ts index 6991f5a..f063728 100644 --- a/legalconsenthub/app/composables/index.ts +++ b/legalconsenthub/app/composables/index.ts @@ -1,4 +1,7 @@ export { useApplicationFormTemplate } from './applicationFormTemplate/useApplicationFormTemplate' export { useApplicationForm } from './applicationForm/useApplicationForm' +export { useApplicationFormVersion } from './applicationFormVersion/useApplicationFormVersion' +export { useApplicationFormVersionApi } from './applicationFormVersion/useApplicationFormVersionApi' +export { useApplicationFormNavigation } from './useApplicationFormNavigation' export { useNotification } from './notification/useNotification' export { useNotificationApi } from './notification/useNotificationApi' diff --git a/legalconsenthub/app/composables/useApplicationFormNavigation.ts b/legalconsenthub/app/composables/useApplicationFormNavigation.ts new file mode 100644 index 0000000..66f7cbe --- /dev/null +++ b/legalconsenthub/app/composables/useApplicationFormNavigation.ts @@ -0,0 +1,65 @@ +import type { ApplicationFormDto } from '~~/.api-client' + +export async function useApplicationFormNavigation(applicationFormId: string) { + const { getApplicationFormById } = useApplicationForm() + + const { data, error, refresh } = await useAsyncData( + `application-form-${applicationFormId}`, + async () => await getApplicationFormById(applicationFormId) + ) + + if (error.value) { + throw createError({ statusText: error.value.message }) + } + + const applicationForm = computed(() => data?.value as ApplicationFormDto) + + const navigationLinks = computed(() => [ + [ + { + label: 'Formular', + icon: 'i-lucide-file', + to: `/application-forms/${applicationForm.value.id}/0`, + exact: true + }, + { + label: 'Versionen', + icon: 'i-lucide-file-clock', + to: `/application-forms/${applicationForm.value.id}/versions`, + exact: true + } + ], + [ + { + label: 'PDF-Vorschau', + icon: 'i-lucide-file-text', + to: `/api/application-forms/${applicationForm.value.id}/pdf`, + target: '_blank' + } + ] + ]) + + const dropdownItems = [ + [ + { + label: 'Neuer Mitbestimmungsantrag', + icon: 'i-lucide-send', + to: '/create' + } + ] + ] + + function updateApplicationForm(updatedForm: ApplicationFormDto) { + data.value = updatedForm + } + + return { + applicationForm, + navigationLinks, + dropdownItems, + refresh, + updateApplicationForm, + error + } +} + diff --git a/legalconsenthub/app/layouts/default.vue b/legalconsenthub/app/layouts/default.vue index 7103d24..3f2b3f6 100644 --- a/legalconsenthub/app/layouts/default.vue +++ b/legalconsenthub/app/layouts/default.vue @@ -36,7 +36,24 @@ diff --git a/legalconsenthub/app/pages/application-forms/[id]/versions.vue b/legalconsenthub/app/pages/application-forms/[id]/versions.vue new file mode 100644 index 0000000..9237eb1 --- /dev/null +++ b/legalconsenthub/app/pages/application-forms/[id]/versions.vue @@ -0,0 +1,45 @@ + + + +