// 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" "time" ) type mandantWerkzeugItem struct { ID string Name string Anbieter string Verarbeitungsort string AVVVerfuegbar bool TrainingStandard bool Aufbewahrung string GeeigneteZwecke string LetztePruefung string NichtAktuell bool 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 { 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"), NichtAktuell: time.Since(wz.LetztePruefung) > werkzeugPruefungVeraltetNach, 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) }