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>
134 lines
4.6 KiB
Go
134 lines
4.6 KiB
Go
// 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)
|
|
}
|
|
}
|