// Löschfristen je Datenklasse (Migration 0020) — schließt rules/OPEN.md, // Punkt 4. Die Tageswerte sind KEINE gesetzliche Vorgabe (die DSGVO // nennt keine festen Fristen, nur den Grundsatz "so lange wie für den // Zweck nötig", Art. 5 Abs. 1 lit. e) — deshalb pro Mandant einstellbar, // vom Datenschutzbeauftragten hinterlegt, mit risikogestaffelten // Vorschlagswerten vorbelegt. Wie alle anderen /verwaltung/*-Seiten // bewusst admin-only (konsistent mit Nutzer-/Abteilungs-/Genehmiger- // Rollen-Verwaltung) statt an den Namen einer Genehmiger-Rolle // gekoppelt — Genehmiger-Rollen sind laut Architektur explizit ohne // eigenes Zugriffsrecht (siehe CLAUDE.md, Freigabe-Workflow). package web import ( "context" "net/http" "strconv" ) // standardLoeschfristen sind risikogestaffelte Vorschlagswerte, mit // denen jede neue Firma vorbelegt wird — je sensibler die Datenklasse, // desto kürzer die vorgeschlagene Frist. Frei editierbar unter // /verwaltung/loeschfristen, keine Rechtsvorgabe. var standardLoeschfristen = []struct { DatenklasseID string MaxTage int }{ {"oeffentlich", 365}, {"intern", 180}, {"auftragsdaten", 90}, {"personenbezogen", 90}, {"berufsgeheimnis", 30}, {"besondere_kategorie", 30}, } // seedStandardLoeschfristen wird direkt nach dem Anlegen einer neuen // Firma aufgerufen (öffentliche Registrierung und Betreiber-Firmenanlage), // analog zu seedStandardGenehmigerRollen. func (s *Server) seedStandardLoeschfristen(ctx context.Context, accountID string) error { for _, l := range standardLoeschfristen { if _, err := s.store.UpsertLoeschfristEinstellung(ctx, accountID, l.DatenklasseID, l.MaxTage); err != nil { return err } } return nil } type loeschfristZeile struct { DatenklasseID string MaxTage int } type loeschfristenListeData struct { Title string Nav navData Zeilen []loeschfristZeile Error string Gespeichert bool } func (s *Server) handleLoeschfristenListe(w http.ResponseWriter, r *http.Request) { accountID := currentUser(r).AccountID einstellungen, err := s.store.ListLoeschfristEinstellungenForAccount(r.Context(), accountID) if err != nil { http.Error(w, "Löschfristen konnten nicht geladen werden: "+err.Error(), http.StatusInternalServerError) return } data := loeschfristenListeData{Title: "Löschfristen", Nav: navFor(r), Gespeichert: r.URL.Query().Get("gespeichert") == "1"} for _, e := range einstellungen { data.Zeilen = append(data.Zeilen, loeschfristZeile{DatenklasseID: e.DatenklasseID, MaxTage: e.MaxTage}) } if err := s.templates.ExecuteTemplate(w, "loeschfristen-liste", data); err != nil { http.Error(w, "Seite konnte nicht gerendert werden", http.StatusInternalServerError) } } func (s *Server) handleLoeschfristenSpeichern(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 for _, l := range standardLoeschfristen { raw := r.FormValue("max_tage_" + l.DatenklasseID) if raw == "" { continue } maxTage, err := strconv.Atoi(raw) if err != nil || maxTage < 0 { http.Error(w, "ungültiger Wert für "+l.DatenklasseID, http.StatusBadRequest) return } if _, err := s.store.UpsertLoeschfristEinstellung(r.Context(), accountID, l.DatenklasseID, maxTage); err != nil { http.Error(w, "Speichern fehlgeschlagen: "+err.Error(), http.StatusInternalServerError) return } } http.Redirect(w, r, "/verwaltung/loeschfristen?gespeichert=1", http.StatusSeeOther) }