package store import ( "context" "encoding/json" "errors" "fmt" "time" "github.com/jackc/pgx/v5" ) // Entscheidung ist die Entscheidung eines/einer Verantwortlichen über // einen Antrag, auf Basis einer Bewertung (Vorschlag). Append-only — // siehe Migration. WerkzeugSnapshot ist der eingefrorene, vollständige // Werkzeug-Datensatz zum Entscheidungszeitpunkt (nil, wenn kein // Werkzeug gewählt wurde, z. B. bei "abgelehnt") — ein späterer // Katalog-Wandel darf nicht rückwirkend verändern, worauf die // Entscheidung beruhte. type Entscheidung struct { ID string AntragID string BewertungID string EntscheiderUserID string Entscheidung string WerkzeugID *string WerkzeugSnapshot *Werkzeug Begruendung string GueltigBis *time.Time CreatedAt time.Time } const entscheidungColumns = `id, antrag_id, bewertung_id, entscheider_user_id, entscheidung, werkzeug_id, werkzeug_snapshot, begruendung, gueltig_bis, created_at` func scanEntscheidung(row interface { Scan(dest ...any) error }) (Entscheidung, error) { var e Entscheidung var snapshotRaw []byte err := row.Scan( &e.ID, &e.AntragID, &e.BewertungID, &e.EntscheiderUserID, &e.Entscheidung, &e.WerkzeugID, &snapshotRaw, &e.Begruendung, &e.GueltigBis, &e.CreatedAt, ) if err != nil { return Entscheidung{}, err } if snapshotRaw != nil { var w Werkzeug if err := json.Unmarshal(snapshotRaw, &w); err != nil { return Entscheidung{}, fmt.Errorf("store: werkzeug_snapshot unmarshal: %w", err) } e.WerkzeugSnapshot = &w } return e, nil } // EntscheidungInput bündelt die Felder einer neuen Entscheidung. type EntscheidungInput struct { AntragID string BewertungID string EntscheiderUserID string Entscheidung string WerkzeugID *string WerkzeugSnapshot *Werkzeug Begruendung string GueltigBis *time.Time } // CreateEntscheidung speichert eine Entscheidung über einen Antrag. func (s *Store) CreateEntscheidung(ctx context.Context, in EntscheidungInput) (Entscheidung, error) { var snapshotJSON []byte if in.WerkzeugSnapshot != nil { var err error snapshotJSON, err = json.Marshal(in.WerkzeugSnapshot) if err != nil { return Entscheidung{}, fmt.Errorf("store: werkzeug_snapshot marshal: %w", err) } } row := s.Pool.QueryRow(ctx, ` INSERT INTO entscheidung ( antrag_id, bewertung_id, entscheider_user_id, entscheidung, werkzeug_id, werkzeug_snapshot, begruendung, gueltig_bis ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING `+entscheidungColumns, in.AntragID, in.BewertungID, in.EntscheiderUserID, in.Entscheidung, in.WerkzeugID, snapshotJSON, in.Begruendung, in.GueltigBis, ) e, err := scanEntscheidung(row) if err != nil { return Entscheidung{}, fmt.Errorf("store: create entscheidung: %w", err) } return e, nil } // GetLatestEntscheidungForAntrag liefert die zuletzt getroffene // Entscheidung eines Antrags. Liefert ErrNotFound, wenn noch keine // Entscheidung existiert. func (s *Store) GetLatestEntscheidungForAntrag(ctx context.Context, antragID string) (Entscheidung, error) { row := s.Pool.QueryRow(ctx, ` SELECT `+entscheidungColumns+` FROM entscheidung WHERE antrag_id = $1 ORDER BY created_at DESC LIMIT 1 `, antragID) e, err := scanEntscheidung(row) if errors.Is(err, pgx.ErrNoRows) { return Entscheidung{}, ErrNotFound } if err != nil { return Entscheidung{}, fmt.Errorf("store: get latest entscheidung: %w", err) } return e, nil }