feat(fullstack): Add title and description to form element, add HTML and PDF endpoints for application form

This commit is contained in:
2025-05-25 10:35:18 +02:00
parent 502d4a7297
commit d553668893
9 changed files with 272 additions and 1 deletions

View File

@@ -4,6 +4,10 @@ import com.betriebsratkanzlei.legalconsenthub_api.api.ApplicationFormApi
import com.betriebsratkanzlei.legalconsenthub_api.model.ApplicationFormDto
import com.betriebsratkanzlei.legalconsenthub_api.model.CreateApplicationFormDto
import com.betriebsratkanzlei.legalconsenthub_api.model.PagedApplicationFormDto
import org.springframework.core.io.ByteArrayResource
import org.springframework.core.io.Resource
import org.springframework.http.HttpHeaders
import org.springframework.http.MediaType
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.RestController
import java.util.UUID
@@ -13,6 +17,7 @@ class ApplicationFormController(
val applicationFormService: ApplicationFormService,
val pagedApplicationFormMapper: PagedApplicationFormMapper,
val applicationFormMapper: ApplicationFormMapper,
val applicationFormFormatService: ApplicationFormFormatService
) : ApplicationFormApi {
override fun createApplicationForm(createApplicationFormDto: CreateApplicationFormDto): ResponseEntity<ApplicationFormDto> {
@@ -40,6 +45,23 @@ class ApplicationFormController(
)
}
override fun getApplicationFormHtml(id: UUID): ResponseEntity<String> {
val applicationForm = applicationFormService.getApplicationFormById(id)
return ResponseEntity.ok(
applicationFormFormatService.generateHtml(applicationForm)
)
}
override fun getApplicationFormPdf(id: UUID): ResponseEntity<Resource> {
val applicationForm = applicationFormService.getApplicationFormById(id)
val pdfBytes = applicationFormFormatService.generatePdf(applicationForm)
val resource = ByteArrayResource(pdfBytes)
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=\"form-$id.pdf\"")
.contentType(MediaType.APPLICATION_PDF)
.body(resource)
}
override fun updateApplicationForm(
id: UUID,
applicationFormDto: ApplicationFormDto

View File

@@ -0,0 +1,31 @@
package com.betriebsratkanzlei.legalconsenthub.application_form
import org.springframework.stereotype.Service
import org.thymeleaf.TemplateEngine
import org.thymeleaf.context.Context
import java.io.ByteArrayOutputStream
import com.openhtmltopdf.pdfboxout.PdfRendererBuilder
@Service
class ApplicationFormFormatService(
private val templateEngine: TemplateEngine
) {
fun generatePdf(applicationForm: ApplicationForm): ByteArray {
val htmlContent = generateHtml(applicationForm)
val outputStream = ByteArrayOutputStream()
PdfRendererBuilder().useFastMode()
.withHtmlContent(htmlContent, null)
.toStream(outputStream)
.run()
return outputStream.toByteArray()
}
fun generateHtml(applicationForm: ApplicationForm): String {
val context = Context().apply {
setVariable("applicationForm", applicationForm)
}
return templateEngine.process("application_form_template", context)
}
}

View File

@@ -17,7 +17,7 @@ import jakarta.persistence.OneToMany
import java.util.UUID;
@Entity
class FormElement (
class FormElement(
@Id
@GeneratedValue
var id: UUID? = null,
@@ -26,6 +26,10 @@ class FormElement (
@JoinColumn(name = "application_form_id", nullable = false)
var applicationForm: ApplicationForm? = null,
var title: String? = null,
var description: String? = null,
@ElementCollection
@CollectionTable(name = "form_element_options", joinColumns = [JoinColumn(name = "form_element_id")])
var options: MutableList<FormOption> = mutableListOf(),

View File

@@ -15,6 +15,8 @@ class FormElementMapper(
fun toFormElementDto(formElement: FormElement): FormElementDto {
return FormElementDto(
id = formElement.id ?: throw IllegalStateException("ApplicationForm ID must not be null!"),
title = formElement.title,
description = formElement.description,
options = formElement.options.map { formOptionMapper.toFormOptionDto(it) },
type = formElement.type,
applicationFormId = formElement.applicationForm?.id
@@ -28,6 +30,8 @@ class FormElementMapper(
return FormElement(
id = formElement.id,
title = formElement.title,
description = formElement.description,
options = formElement.options.map { formOptionMapper.toFormOption(it) }.toMutableList(),
type = formElement.type,
applicationForm = applicationForm
@@ -37,6 +41,8 @@ class FormElementMapper(
fun toFormElement(formElement: CreateFormElementDto, applicationForm: ApplicationForm): FormElement {
return FormElement(
id = null,
title = formElement.title,
description = formElement.description,
options = formElement.options.map { formOptionMapper.toFormOption(it) }.toMutableList(),
type = formElement.type,
applicationForm = applicationForm

View File

@@ -0,0 +1,119 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
<title th:text="${applicationForm.name}"></title>
<style>
body {
font-family: 'Times New Roman', Times, serif;
line-height: 1.4;
margin: 40px;
color: #333;
}
header {
text-align: center;
margin-bottom: 40px;
}
header img.logo {
max-height: 80px;
margin-bottom: 10px;
}
header h1 {
font-size: 1.8em;
letter-spacing: 1px;
margin: 0;
}
.meta {
margin-bottom: 30px;
}
.meta .field {
margin-bottom: 5px;
}
.meta label {
font-weight: bold;
display: inline-block;
width: 150px;
}
.contract-section {
margin-bottom: 25px;
}
.contract-section h2 {
font-size: 1.3em;
border-bottom: 1px solid #aaa;
padding-bottom: 5px;
margin-bottom: 15px;
}
.form-element {
margin-bottom: 20px;
}
.form-element h3 {
font-size: 1.1em;
margin-bottom: 5px;
}
.form-element p.description {
font-style: italic;
color: #666;
margin: 5px 0 10px;
}
footer {
text-align: center;
font-size: 0.9em;
color: #666;
margin-top: 50px;
}
</style>
</head>
<body>
<header>
<h1 th:text="${applicationForm.name}"></h1>
</header>
<div class="meta">
<div class="field">
<label>ID:</label>
<span th:text="${applicationForm.id}"></span>
</div>
<div class="field">
<label>Erstellt von:</label>
<span th:text="${applicationForm.createdBy.name}"></span>
</div>
<div class="field">
<label>Erstellt am:</label>
<span th:text="${#temporals.format(applicationForm.createdAt, 'dd.MM.yyyy')}"></span>
</div>
<div class="field">
<label>Organisation:</label>
<span th:text="${applicationForm.organizationId}"></span>
</div>
</div>
<div class="contract-section">
<h2>Formularelemente</h2>
<div th:each="elem : ${applicationForm.formElements}" class="form-element">
<h3 th:text="${elem.title}"></h3>
<p class="description" th:if="${elem.description}" th:text="${elem.description}"></p>
<ul>
<li th:each="option : ${elem.options}" th:if="${option.value == 'true'}" th:text="${option.label}"></li>
</ul>
<p th:if="${elem.options.?[value == 'true'].isEmpty()}">Keine Auswahl getroffen</p>
</div>
</div>
<footer>
<p>Dieses Dokument wurde automatisch erzeugt und ist ohne Unterschrift gültig.</p>
</footer>
</body>
</html>