feat: Registereintrag und Export (Schritt 6 der Baureihenfolge)
Jede Genehmigung (genehmigt/genehmigt_mit_auflagen) erzeugt jetzt automatisch einen append-only registereintrag mit Zweck, Abteilung, Werkzeug, Datenklasse, Einstufung, Auflagen, Verantwortliche/r und Gültigkeit. Ebene 3 (verantwortlicher/pruefer) kann das Register unter GET /registereintraege einsehen und als CSV oder PDF exportieren. Der PDF-Export nutzt das bereits vorhandene, bisher ungenutzte go-pdf/fpdf (keine neue Abhängigkeit) mit UnicodeTranslatorFromDescriptor für deutsche Umlaute. Abgelehnte Entscheidungen und Rückfragen erzeugen bewusst keinen Registereintrag. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
133
internal/web/register_handlers.go
Normal file
133
internal/web/register_handlers.go
Normal file
@@ -0,0 +1,133 @@
|
||||
// Ebene 3 — Register und Export (Schritt 6 der Baureihenfolge, siehe
|
||||
// CLAUDE.md). Jede Genehmigung erzeugt automatisch einen Registereintrag
|
||||
// (siehe handleFallEntscheiden); diese Seiten zeigen und exportieren
|
||||
// sie. Reuse des bereits vorhandenen (bisher ungenutzten) go-pdf/fpdf-
|
||||
// Dependency statt einer neuen Bibliothek für den PDF-Export.
|
||||
package web
|
||||
|
||||
import (
|
||||
"encoding/csv"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/go-pdf/fpdf"
|
||||
)
|
||||
|
||||
type registerEintragView struct {
|
||||
Zweck string
|
||||
Abteilung string
|
||||
Werkzeug string
|
||||
Datenklasse string
|
||||
Einstufung string
|
||||
Auflagen string
|
||||
Verantwortlicher string
|
||||
EntschiedenAm string
|
||||
GueltigBis string
|
||||
}
|
||||
|
||||
type registerListeData struct {
|
||||
Title string
|
||||
Nav navData
|
||||
Eintraege []registerEintragView
|
||||
}
|
||||
|
||||
func (s *Server) registerEintraegeFuerAccount(r *http.Request) ([]registerEintragView, error) {
|
||||
eintraege, err := s.store.ListRegistereintraegeForAccount(r.Context(), currentUser(r).AccountID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
views := make([]registerEintragView, len(eintraege))
|
||||
for i, e := range eintraege {
|
||||
gueltigBis := ""
|
||||
if e.GueltigBis != nil {
|
||||
gueltigBis = e.GueltigBis.Format("02.01.2006")
|
||||
}
|
||||
views[i] = registerEintragView{
|
||||
Zweck: e.Zweck, Abteilung: e.Abteilung, Werkzeug: e.Werkzeug,
|
||||
Datenklasse: e.Datenklasse, Einstufung: e.Einstufung, Auflagen: strings.Join(e.Auflagen, "; "),
|
||||
Verantwortlicher: e.Verantwortlicher, EntschiedenAm: e.EntschiedenAm.Format("02.01.2006 15:04"), GueltigBis: gueltigBis,
|
||||
}
|
||||
}
|
||||
return views, nil
|
||||
}
|
||||
|
||||
// handleRegisterListe zeigt das Register (alle Genehmigungen des
|
||||
// Mandanten) — Ebene 3, für verantwortlicher und pruefer gleichermaßen
|
||||
// lesbar.
|
||||
func (s *Server) handleRegisterListe(w http.ResponseWriter, r *http.Request) {
|
||||
views, err := s.registerEintraegeFuerAccount(r)
|
||||
if err != nil {
|
||||
http.Error(w, "Register konnte nicht geladen werden: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
data := registerListeData{Title: "Register", Nav: navFor(r), Eintraege: views}
|
||||
if err := s.templates.ExecuteTemplate(w, "register-liste", data); err != nil {
|
||||
http.Error(w, "Seite konnte nicht gerendert werden", http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
var registerCSVHeader = []string{"Zweck", "Abteilung", "Werkzeug", "Datenklasse", "Einstufung", "Auflagen", "Verantwortliche/r", "Entschieden am", "Gültig bis"}
|
||||
|
||||
func registerCSVRow(e registerEintragView) []string {
|
||||
return []string{e.Zweck, e.Abteilung, e.Werkzeug, e.Datenklasse, e.Einstufung, e.Auflagen, e.Verantwortlicher, e.EntschiedenAm, e.GueltigBis}
|
||||
}
|
||||
|
||||
// handleRegisterExportCSV exportiert das Register als CSV.
|
||||
func (s *Server) handleRegisterExportCSV(w http.ResponseWriter, r *http.Request) {
|
||||
views, err := s.registerEintraegeFuerAccount(r)
|
||||
if err != nil {
|
||||
http.Error(w, "Register konnte nicht geladen werden: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "text/csv; charset=utf-8")
|
||||
w.Header().Set("Content-Disposition", `attachment; filename="register.csv"`)
|
||||
cw := csv.NewWriter(w)
|
||||
_ = cw.Write(registerCSVHeader)
|
||||
for _, e := range views {
|
||||
_ = cw.Write(registerCSVRow(e))
|
||||
}
|
||||
cw.Flush()
|
||||
}
|
||||
|
||||
// handleRegisterExportPDF exportiert das Register als PDF — eine
|
||||
// einfache Tabelle, keine Gestaltung über den Design-System-Anspruch
|
||||
// des restlichen Produkts hinaus.
|
||||
func (s *Server) handleRegisterExportPDF(w http.ResponseWriter, r *http.Request) {
|
||||
views, err := s.registerEintraegeFuerAccount(r)
|
||||
if err != nil {
|
||||
http.Error(w, "Register konnte nicht geladen werden: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
pdf := fpdf.New("L", "mm", "A4", "")
|
||||
// cp1252 deckt deutsche Umlaute/ß ab — die eingebauten Helvetica-
|
||||
// Fonts von fpdf können sonst kein UTF-8 darstellen.
|
||||
tr := pdf.UnicodeTranslatorFromDescriptor("")
|
||||
pdf.AddPage()
|
||||
pdf.SetFont("Helvetica", "B", 14)
|
||||
pdf.CellFormat(0, 10, tr("Register"), "", 1, "L", false, 0, "")
|
||||
pdf.Ln(2)
|
||||
|
||||
colWidths := []float64{45, 25, 35, 30, 30, 55, 40, 30, 25}
|
||||
pdf.SetFont("Helvetica", "B", 8)
|
||||
for i, h := range registerCSVHeader {
|
||||
pdf.CellFormat(colWidths[i], 8, tr(h), "1", 0, "L", false, 0, "")
|
||||
}
|
||||
pdf.Ln(-1)
|
||||
|
||||
pdf.SetFont("Helvetica", "", 8)
|
||||
for _, e := range views {
|
||||
row := registerCSVRow(e)
|
||||
for i, cell := range row {
|
||||
pdf.CellFormat(colWidths[i], 8, tr(cell), "1", 0, "L", false, 0, "")
|
||||
}
|
||||
pdf.Ln(-1)
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/pdf")
|
||||
w.Header().Set("Content-Disposition", `attachment; filename="register.pdf"`)
|
||||
if err := pdf.Output(w); err != nil {
|
||||
http.Error(w, fmt.Sprintf("PDF konnte nicht erzeugt werden: %v", err), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user