// VOR-02: Freitext-Vermerke an einer Akte. Nach dem Speichern // unveraenderlich (Datenbank-Trigger in 0006_case_notes.up.sql, nicht nur // Anwendungslogik -- gleiches Muster wie workflow_instance_history aus // ENG-04), damit die Akte als Nachweis taugt. package caseobj import ( "context" "errors" "fmt" "time" ) // Note ist ein einzelner, unveraenderlicher Aktenvermerk. type Note struct { ID string CaseID string Author string Body string CreatedAt time.Time } // AddNote fuegt der Akte einen neuen Vermerk hinzu (Akzeptanzkriterium 1: // nur Ergaenzen, es gibt bewusst keine Update-Methode). Autor und Text // sind Pflichtfelder (Akzeptanzkriterium 2, Pruefung 2). func (s *Service) AddNote(ctx context.Context, caseID, author, body string) (Note, error) { if author == "" { return Note{}, fmt.Errorf("caseobj: autor ist pflicht") } if body == "" { return Note{}, fmt.Errorf("caseobj: vermerktext ist pflicht") } var n Note err := s.pool.QueryRow(ctx, ` INSERT INTO case_notes (case_id, author, body) VALUES ($1, $2, $3) RETURNING id, case_id, author, body, created_at `, caseID, author, body).Scan(&n.ID, &n.CaseID, &n.Author, &n.Body, &n.CreatedAt) if err != nil { if isForeignKeyViolation(err) { return Note{}, ErrNotFound } return Note{}, fmt.Errorf("caseobj: vermerk anlegen: %w", err) } return n, nil } // Notes liefert die Vermerke einer Akte in chronologischer Reihenfolge // (Akzeptanzkriterium 3, Pruefung 3). func (s *Service) Notes(ctx context.Context, caseID string) ([]Note, error) { rows, err := s.pool.Query(ctx, ` SELECT id, case_id, author, body, created_at FROM case_notes WHERE case_id = $1 ORDER BY created_at ASC, id ASC `, caseID) if err != nil { return nil, fmt.Errorf("caseobj: vermerke lesen: %w", err) } defer rows.Close() var out []Note for rows.Next() { var n Note if err := rows.Scan(&n.ID, &n.CaseID, &n.Author, &n.Body, &n.CreatedAt); err != nil { return nil, fmt.Errorf("caseobj: vermerk lesen: %w", err) } out = append(out, n) } return out, rows.Err() } func isForeignKeyViolation(err error) bool { var pgErr interface{ SQLState() string } if errors.As(err, &pgErr) { return pgErr.SQLState() == "23503" } return false }