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:
@@ -24,31 +24,33 @@ const testSessionCookie = "deklarix_session"
|
||||
// fakeStore ist eine In-Memory-Implementierung von web.Store, damit die
|
||||
// Handler-Tests keine echte Postgres-Instanz brauchen.
|
||||
type fakeStore struct {
|
||||
mu sync.Mutex
|
||||
nextID int
|
||||
accounts map[string]store.Account
|
||||
users map[string]store.User
|
||||
usersByEmail map[string]string // email -> user id
|
||||
sessions map[string]store.Session
|
||||
auditLog []store.AuditEntry
|
||||
abteilungen map[string][]store.Abteilung // accountID -> Abteilungen
|
||||
antraege map[string]store.Antrag
|
||||
werkzeuge map[string]store.Werkzeug
|
||||
bewertungen map[string][]store.Bewertung // antragID -> Bewertungen, älteste zuerst
|
||||
entscheidungen map[string][]store.Entscheidung // antragID -> Entscheidungen, älteste zuerst
|
||||
mu sync.Mutex
|
||||
nextID int
|
||||
accounts map[string]store.Account
|
||||
users map[string]store.User
|
||||
usersByEmail map[string]string // email -> user id
|
||||
sessions map[string]store.Session
|
||||
auditLog []store.AuditEntry
|
||||
abteilungen map[string][]store.Abteilung // accountID -> Abteilungen
|
||||
antraege map[string]store.Antrag
|
||||
werkzeuge map[string]store.Werkzeug
|
||||
bewertungen map[string][]store.Bewertung // antragID -> Bewertungen, älteste zuerst
|
||||
entscheidungen map[string][]store.Entscheidung // antragID -> Entscheidungen, älteste zuerst
|
||||
registereintraege map[string][]store.Registereintrag // accountID -> Registereintraege
|
||||
}
|
||||
|
||||
func newFakeStore() *fakeStore {
|
||||
return &fakeStore{
|
||||
accounts: map[string]store.Account{},
|
||||
users: map[string]store.User{},
|
||||
usersByEmail: map[string]string{},
|
||||
sessions: map[string]store.Session{},
|
||||
abteilungen: map[string][]store.Abteilung{},
|
||||
antraege: map[string]store.Antrag{},
|
||||
werkzeuge: map[string]store.Werkzeug{},
|
||||
bewertungen: map[string][]store.Bewertung{},
|
||||
entscheidungen: map[string][]store.Entscheidung{},
|
||||
accounts: map[string]store.Account{},
|
||||
users: map[string]store.User{},
|
||||
usersByEmail: map[string]string{},
|
||||
sessions: map[string]store.Session{},
|
||||
abteilungen: map[string][]store.Abteilung{},
|
||||
antraege: map[string]store.Antrag{},
|
||||
werkzeuge: map[string]store.Werkzeug{},
|
||||
bewertungen: map[string][]store.Bewertung{},
|
||||
entscheidungen: map[string][]store.Entscheidung{},
|
||||
registereintraege: map[string][]store.Registereintrag{},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,6 +189,19 @@ func (f *fakeStore) ListAbteilungenForAccount(ctx context.Context, accountID str
|
||||
return f.abteilungen[accountID], nil
|
||||
}
|
||||
|
||||
func (f *fakeStore) GetAbteilung(ctx context.Context, id string) (store.Abteilung, error) {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
for _, list := range f.abteilungen {
|
||||
for _, a := range list {
|
||||
if a.ID == id {
|
||||
return a, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return store.Abteilung{}, store.ErrNotFound
|
||||
}
|
||||
|
||||
func (f *fakeStore) CreateAntrag(ctx context.Context, accountID, erstellerUserID string, abteilungID *string, titel string) (store.Antrag, error) {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
@@ -385,6 +400,26 @@ func (f *fakeStore) GetLatestEntscheidungForAntrag(ctx context.Context, antragID
|
||||
return es[len(es)-1], nil
|
||||
}
|
||||
|
||||
func (f *fakeStore) CreateRegistereintrag(ctx context.Context, in store.RegistereintragInput) (store.Registereintrag, error) {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
e := store.Registereintrag{
|
||||
ID: f.newID(), AccountID: in.AccountID, AntragID: in.AntragID, EntscheidungID: in.EntscheidungID,
|
||||
Zweck: in.Zweck, Abteilung: in.Abteilung, Werkzeug: in.Werkzeug,
|
||||
Datenklasse: in.Datenklasse, Einstufung: in.Einstufung, Auflagen: in.Auflagen,
|
||||
Verantwortlicher: in.Verantwortlicher, EntschiedenAm: in.EntschiedenAm, GueltigBis: in.GueltigBis,
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
f.registereintraege[in.AccountID] = append(f.registereintraege[in.AccountID], e)
|
||||
return e, nil
|
||||
}
|
||||
|
||||
func (f *fakeStore) ListRegistereintraegeForAccount(ctx context.Context, accountID string) ([]store.Registereintrag, error) {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
return f.registereintraege[accountID], nil
|
||||
}
|
||||
|
||||
// ─── Test-Setup ───────────────────────────────────────────────────────
|
||||
|
||||
// loadTestRegelwerk lädt die echten rules/*.yaml-Dateien — dieselben,
|
||||
|
||||
Reference in New Issue
Block a user