Katalogtabellen zeigten bisher nur "nicht aktuell geprüft" als Binärflag; jetzt eine dreistufige Ampel (aktuell/bald fällig/ abgelaufen) mit denselben Farbtoken wie die KI-VO-Risikoampel. Zusätzlich: .table-scroll bekommt einen rechten Fade-Rand, damit eine am Fensterrand abgeschnittene breite Tabelle als "hier geht's mit Scrollen weiter" erkennbar ist statt wie ein Rendering-Fehler auszusehen.
225 lines
9.2 KiB
Go
225 lines
9.2 KiB
Go
// Ebene 4 — "eigene Werkzeug-Freigaben/-Sperrungen" (siehe CLAUDE.md,
|
|
// Fünf Ebenen): ein Mandant kann einen zentralen Katalogeintrag für
|
|
// sich sperren, ohne den zentralen Katalog selbst zu verändern (das
|
|
// bleibt Sache der Plattform, siehe betreiber_werkzeug_handlers.go),
|
|
// UND eigene, mandantenspezifische Katalogeinträge pflegen
|
|
// (`werkzeug.account_id` = der eigene Account) — dieselbe
|
|
// Formular-Vorlage wie der Betreiber nutzt (werkzeugFormData,
|
|
// betreiber-werkzeug-form.html, ActionBase unterscheidet die Ziel-URL).
|
|
package web
|
|
|
|
import (
|
|
"net/http"
|
|
"strings"
|
|
)
|
|
|
|
type mandantWerkzeugItem struct {
|
|
ID string
|
|
Name string
|
|
Anbieter string
|
|
Verarbeitungsort string
|
|
AVVVerfuegbar bool
|
|
TrainingStandard bool
|
|
Aufbewahrung string
|
|
GeeigneteZwecke string
|
|
LetztePruefung string
|
|
PruefStatusLabel string
|
|
PruefStatusCSS string
|
|
Gesperrt bool
|
|
}
|
|
|
|
type eigenesWerkzeugItem struct {
|
|
ID string
|
|
Name string
|
|
Anbieter string
|
|
Verarbeitungsort string
|
|
LetztePruefung string
|
|
}
|
|
|
|
type mandantWerkzeugListeData struct {
|
|
Title string
|
|
Nav navData
|
|
Werkzeuge []mandantWerkzeugItem
|
|
EigeneWerkzeuge []eigenesWerkzeugItem
|
|
}
|
|
|
|
// handleMandantWerkzeugListe zeigt den zentralen Katalog aus Sicht des
|
|
// eigenen Mandanten mit Sperren/Entsperren-Aktion je Eintrag, sowie
|
|
// die eigenen (mandantenspezifischen) Katalogeinträge mit Bearbeiten/
|
|
// Löschen.
|
|
func (s *Server) handleMandantWerkzeugListe(w http.ResponseWriter, r *http.Request) {
|
|
accountID := currentUser(r).AccountID
|
|
zentral, err := s.store.ListZentraleWerkzeuge(r.Context())
|
|
if err != nil {
|
|
http.Error(w, "Werkzeugkatalog konnte nicht geladen werden: "+err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
sperren, err := s.store.ListWerkzeugSperrenForAccount(r.Context(), accountID)
|
|
if err != nil {
|
|
http.Error(w, "Sperrungen konnten nicht geladen werden: "+err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
eigene, err := s.store.ListWerkzeugeForAccount(r.Context(), accountID)
|
|
if err != nil {
|
|
http.Error(w, "Werkzeuge konnten nicht geladen werden: "+err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
gesperrt := make(map[string]bool, len(sperren))
|
|
for _, sp := range sperren {
|
|
gesperrt[sp.WerkzeugID] = true
|
|
}
|
|
|
|
data := mandantWerkzeugListeData{Title: "Werkzeugkatalog", Nav: navFor(r)}
|
|
for _, wz := range zentral {
|
|
label, css := pruefStatus(wz.LetztePruefung)
|
|
data.Werkzeuge = append(data.Werkzeuge, mandantWerkzeugItem{
|
|
ID: wz.ID, Name: wz.Name, Anbieter: wz.Anbieter, Verarbeitungsort: wz.Verarbeitungsort,
|
|
AVVVerfuegbar: wz.AVVVerfuegbar, TrainingStandard: wz.TrainingStandard,
|
|
Aufbewahrung: formatAufbewahrung(wz.AufbewahrungTage),
|
|
GeeigneteZwecke: strings.Join(wz.GeeigneteZwecke, ", "),
|
|
LetztePruefung: wz.LetztePruefung.Format("02.01.2006"),
|
|
PruefStatusLabel: label, PruefStatusCSS: css,
|
|
Gesperrt: gesperrt[wz.ID],
|
|
})
|
|
}
|
|
for _, wz := range eigene {
|
|
if wz.AccountID == nil {
|
|
continue // zentral, oben schon gelistet
|
|
}
|
|
data.EigeneWerkzeuge = append(data.EigeneWerkzeuge, eigenesWerkzeugItem{
|
|
ID: wz.ID, Name: wz.Name, Anbieter: wz.Anbieter, Verarbeitungsort: wz.Verarbeitungsort,
|
|
LetztePruefung: wz.LetztePruefung.Format("02.01.2006"),
|
|
})
|
|
}
|
|
if err := s.templates.ExecuteTemplate(w, "mandant-werkzeuge", data); err != nil {
|
|
http.Error(w, "Seite konnte nicht gerendert werden", http.StatusInternalServerError)
|
|
}
|
|
}
|
|
|
|
func (s *Server) handleMandantWerkzeugNeuForm(w http.ResponseWriter, r *http.Request) {
|
|
data := werkzeugFormData{
|
|
Title: "Eigenes Werkzeug anlegen", Nav: navFor(r), ActionBase: "/verwaltung/werkzeuge",
|
|
Verarbeitungsorte: gueltigeVerarbeitungsorte, ZweckeOptionen: gueltigeZwecke, ZweckeAusgewaehlt: map[string]bool{},
|
|
}
|
|
if err := s.templates.ExecuteTemplate(w, "betreiber-werkzeug-form", data); err != nil {
|
|
http.Error(w, "Seite konnte nicht gerendert werden", http.StatusInternalServerError)
|
|
}
|
|
}
|
|
|
|
// handleMandantWerkzeugCreate legt einen mandantenspezifischen
|
|
// Katalogeintrag an (`werkzeug.account_id` = eigener Account) — sichtbar
|
|
// nur für diesen Mandanten, verändert den zentralen Katalog nicht.
|
|
func (s *Server) handleMandantWerkzeugCreate(w http.ResponseWriter, r *http.Request) {
|
|
if err := r.ParseForm(); err != nil {
|
|
http.Error(w, "ungültiges Formular", http.StatusBadRequest)
|
|
return
|
|
}
|
|
accountID := currentUser(r).AccountID
|
|
in, errMsg := werkzeugInputFromForm(r, &accountID)
|
|
if errMsg != "" {
|
|
data := werkzeugFormDataFromRequest(r, "Eigenes Werkzeug anlegen", "/verwaltung/werkzeuge", "", errMsg)
|
|
if err := s.templates.ExecuteTemplate(w, "betreiber-werkzeug-form", data); err != nil {
|
|
http.Error(w, "Seite konnte nicht gerendert werden", http.StatusInternalServerError)
|
|
}
|
|
return
|
|
}
|
|
if _, err := s.store.CreateWerkzeug(r.Context(), in); err != nil {
|
|
data := werkzeugFormDataFromRequest(r, "Eigenes Werkzeug anlegen", "/verwaltung/werkzeuge", "", "Werkzeug konnte nicht angelegt werden: "+err.Error())
|
|
if err := s.templates.ExecuteTemplate(w, "betreiber-werkzeug-form", data); err != nil {
|
|
http.Error(w, "Seite konnte nicht gerendert werden", http.StatusInternalServerError)
|
|
}
|
|
return
|
|
}
|
|
http.Redirect(w, r, "/verwaltung/werkzeuge", http.StatusSeeOther)
|
|
}
|
|
|
|
// handleMandantWerkzeugEditForm zeigt einen eigenen Katalogeintrag zum
|
|
// Bearbeiten. Ein zentraler oder fremder Eintrag ist hier nicht
|
|
// erreichbar (404) — das bleibt Sache der Plattform bzw. des jeweils
|
|
// anderen Mandanten.
|
|
func (s *Server) handleMandantWerkzeugEditForm(w http.ResponseWriter, r *http.Request) {
|
|
wz, err := s.store.GetWerkzeug(r.Context(), r.PathValue("id"))
|
|
accountID := currentUser(r).AccountID
|
|
if err != nil || wz.AccountID == nil || *wz.AccountID != accountID {
|
|
http.Error(w, "Werkzeug nicht gefunden", http.StatusNotFound)
|
|
return
|
|
}
|
|
data := werkzeugFormDataFromWerkzeug(r, wz, "Eigenes Werkzeug bearbeiten", "/verwaltung/werkzeuge")
|
|
if err := s.templates.ExecuteTemplate(w, "betreiber-werkzeug-form", data); err != nil {
|
|
http.Error(w, "Seite konnte nicht gerendert werden", http.StatusInternalServerError)
|
|
}
|
|
}
|
|
|
|
// handleMandantWerkzeugUpdate speichert Änderungen an einem eigenen
|
|
// Katalogeintrag.
|
|
func (s *Server) handleMandantWerkzeugUpdate(w http.ResponseWriter, r *http.Request) {
|
|
id := r.PathValue("id")
|
|
accountID := currentUser(r).AccountID
|
|
wz, err := s.store.GetWerkzeug(r.Context(), id)
|
|
if err != nil || wz.AccountID == nil || *wz.AccountID != accountID {
|
|
http.Error(w, "Werkzeug nicht gefunden", http.StatusNotFound)
|
|
return
|
|
}
|
|
if err := r.ParseForm(); err != nil {
|
|
http.Error(w, "ungültiges Formular", http.StatusBadRequest)
|
|
return
|
|
}
|
|
in, errMsg := werkzeugInputFromForm(r, &accountID)
|
|
if errMsg != "" {
|
|
data := werkzeugFormDataFromRequest(r, "Eigenes Werkzeug bearbeiten", "/verwaltung/werkzeuge", id, errMsg)
|
|
if err := s.templates.ExecuteTemplate(w, "betreiber-werkzeug-form", data); err != nil {
|
|
http.Error(w, "Seite konnte nicht gerendert werden", http.StatusInternalServerError)
|
|
}
|
|
return
|
|
}
|
|
if _, err := s.store.UpdateWerkzeug(r.Context(), id, in); err != nil {
|
|
data := werkzeugFormDataFromRequest(r, "Eigenes Werkzeug bearbeiten", "/verwaltung/werkzeuge", id, "Werkzeug konnte nicht gespeichert werden: "+err.Error())
|
|
if err := s.templates.ExecuteTemplate(w, "betreiber-werkzeug-form", data); err != nil {
|
|
http.Error(w, "Seite konnte nicht gerendert werden", http.StatusInternalServerError)
|
|
}
|
|
return
|
|
}
|
|
http.Redirect(w, r, "/verwaltung/werkzeuge", http.StatusSeeOther)
|
|
}
|
|
|
|
// handleMandantWerkzeugDelete entfernt einen eigenen Katalogeintrag.
|
|
func (s *Server) handleMandantWerkzeugDelete(w http.ResponseWriter, r *http.Request) {
|
|
id := r.PathValue("id")
|
|
accountID := currentUser(r).AccountID
|
|
wz, err := s.store.GetWerkzeug(r.Context(), id)
|
|
if err != nil || wz.AccountID == nil || *wz.AccountID != accountID {
|
|
http.Error(w, "Werkzeug nicht gefunden", http.StatusNotFound)
|
|
return
|
|
}
|
|
if err := s.store.DeleteWerkzeug(r.Context(), id); err != nil {
|
|
http.Error(w, "Werkzeug konnte nicht gelöscht werden: "+err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
http.Redirect(w, r, "/verwaltung/werkzeuge", http.StatusSeeOther)
|
|
}
|
|
|
|
// handleMandantWerkzeugSperren sperrt einen zentralen Katalogeintrag
|
|
// für den eigenen Mandanten — der zentrale Eintrag selbst bleibt
|
|
// unverändert, er verschwindet nur aus ListWerkzeugeForAccount für
|
|
// diesen einen Mandanten.
|
|
func (s *Server) handleMandantWerkzeugSperren(w http.ResponseWriter, r *http.Request) {
|
|
if err := r.ParseForm(); err != nil {
|
|
http.Error(w, "ungültiges Formular", http.StatusBadRequest)
|
|
return
|
|
}
|
|
grund := r.FormValue("grund")
|
|
if _, err := s.store.CreateWerkzeugSperre(r.Context(), currentUser(r).AccountID, r.PathValue("id"), grund); err != nil {
|
|
http.Error(w, "Werkzeug konnte nicht gesperrt werden: "+err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
http.Redirect(w, r, "/verwaltung/werkzeuge", http.StatusSeeOther)
|
|
}
|
|
|
|
func (s *Server) handleMandantWerkzeugEntsperren(w http.ResponseWriter, r *http.Request) {
|
|
if err := s.store.DeleteWerkzeugSperre(r.Context(), currentUser(r).AccountID, r.PathValue("id")); err != nil {
|
|
http.Error(w, "Sperrung konnte nicht aufgehoben werden: "+err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
http.Redirect(w, r, "/verwaltung/werkzeuge", http.StatusSeeOther)
|
|
}
|