CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (pull_request) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Canceled after 0s
CI / Web (dms-browser) (pull_request) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (push) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Canceled after 0s
CI / Web (dms-browser) (push) Canceled after 0s
CI / Web (pdf-viewer) (push) Canceled after 0s
CI / Web (shl) (push) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (push) Canceled after 0s
Supply-Chain-Scan (npm) / npm-audit (push) Canceled after 0s
CI / Web (pdf-viewer) (pull_request) Canceled after 0s
CI / Web (shl) (pull_request) Canceled after 0s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Canceled after 0s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Canceled after 0s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Canceled after 0s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
Neue additive status-Spalte (open/in_progress/done) auf tasks,
status.go (SetStatus/ByStatus/GetStatus/ListAll) haelt completed_at
synchron fuer TSK-03s escalation.Store. Neuer HTTP-Layer taskapi
(GET /tasks, PATCH /tasks/{id}/status), Tenant-Kontext-Durchsetzung
wie backchannelapi (VOR-06). Neue Next.js-Oberflaeche
web/workflow-tasks (Liste + Kanban, Drag&Drop + Tastaturbedienung).
tasks-a11y-fixture fuer den echten Browser-Test, kein Produktions-
binary vorhanden das geaendert werden musste.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
125 lines
3.7 KiB
Go
125 lines
3.7 KiB
Go
// TSK-04: Kanban-Status je Aufgabe, additiv zu task.go (TSK-01). completed_at
|
|
// (escalation.go, TSK-03) bleibt die fuer Eskalation massgebliche Spalte --
|
|
// SetStatus haelt sie synchron, damit escalation.Store weiterhin unveraendert
|
|
// funktioniert.
|
|
package task
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
)
|
|
|
|
type Status string
|
|
|
|
const (
|
|
StatusOpen Status = "open"
|
|
StatusInProgress Status = "in_progress"
|
|
StatusDone Status = "done"
|
|
)
|
|
|
|
var ErrInvalidStatus = errors.New("task: unzulaessiger status")
|
|
|
|
func validStatus(s Status) bool {
|
|
return s == StatusOpen || s == StatusInProgress || s == StatusDone
|
|
}
|
|
|
|
// SetStatus setzt den Kanban-Status einer Aufgabe (Akzeptanzkriterium 4).
|
|
// Beim Wechsel nach "done" wird completed_at gesetzt, beim Wechsel weg davon
|
|
// wieder auf NULL zurueckgesetzt, damit escalation.Store (TSK-03) konsistent
|
|
// bleibt.
|
|
func (s *Store) SetStatus(ctx context.Context, id string, status Status) error {
|
|
if !validStatus(status) {
|
|
return ErrInvalidStatus
|
|
}
|
|
var tag interface{ RowsAffected() int64 }
|
|
var err error
|
|
if status == StatusDone {
|
|
tag, err = s.pool.Exec(ctx, `UPDATE tasks SET status = $2, completed_at = now() WHERE id = $1`, id, string(status))
|
|
} else {
|
|
tag, err = s.pool.Exec(ctx, `UPDATE tasks SET status = $2, completed_at = NULL WHERE id = $1`, id, string(status))
|
|
}
|
|
if err != nil {
|
|
return fmt.Errorf("task: status setzen: %w", err)
|
|
}
|
|
if tag.RowsAffected() == 0 {
|
|
return ErrNotFound
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ByStatus liefert alle Aufgaben mit dem angegebenen Status, sortiert nach
|
|
// Erstellung (fuer die Kanban-Spalten, Akzeptanzkriterium 4).
|
|
func (s *Store) ByStatus(ctx context.Context, status Status) ([]Task, error) {
|
|
rows, err := s.pool.Query(ctx, `SELECT id FROM tasks WHERE status = $1 ORDER BY created_at`, string(status))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("task: nach status lesen: %w", err)
|
|
}
|
|
var ids []string
|
|
for rows.Next() {
|
|
var id string
|
|
if err := rows.Scan(&id); err != nil {
|
|
rows.Close()
|
|
return nil, fmt.Errorf("task: id scannen: %w", err)
|
|
}
|
|
ids = append(ids, id)
|
|
}
|
|
rows.Close()
|
|
if err := rows.Err(); err != nil {
|
|
return nil, fmt.Errorf("task: iterieren: %w", err)
|
|
}
|
|
out := make([]Task, 0, len(ids))
|
|
for _, id := range ids {
|
|
t, err := s.Get(ctx, id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
out = append(out, t)
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// GetStatus liest nur den Status einer Aufgabe (fuer Praeflug-Pruefungen in
|
|
// der API-Schicht, ohne die komplette Aufgabe inkl. Referenzen zu laden).
|
|
func (s *Store) GetStatus(ctx context.Context, id string) (Status, error) {
|
|
var status string
|
|
if err := s.pool.QueryRow(ctx, `SELECT status FROM tasks WHERE id = $1`, id).Scan(&status); err != nil {
|
|
return "", ErrNotFound
|
|
}
|
|
return Status(status), nil
|
|
}
|
|
|
|
// ListAll liefert alle Aufgaben mit Status und Faelligkeit, fuer Liste und
|
|
// Kanban-Ansicht zusammen (Akzeptanzkriterien 1-3). limit/offset ist bewusst
|
|
// nicht Teil dieser Signatur -- AC3 verlangt Nutzbarkeit bei 200 Aufgaben,
|
|
// nicht Paginierung; das Frontend filtert/sortiert clientseitig ueber diese
|
|
// vollstaendige Liste.
|
|
func (s *Store) ListAll(ctx context.Context) ([]Task, error) {
|
|
rows, err := s.pool.Query(ctx, `SELECT id FROM tasks ORDER BY created_at`)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("task: alle lesen: %w", err)
|
|
}
|
|
var ids []string
|
|
for rows.Next() {
|
|
var id string
|
|
if err := rows.Scan(&id); err != nil {
|
|
rows.Close()
|
|
return nil, fmt.Errorf("task: id scannen: %w", err)
|
|
}
|
|
ids = append(ids, id)
|
|
}
|
|
rows.Close()
|
|
if err := rows.Err(); err != nil {
|
|
return nil, fmt.Errorf("task: iterieren: %w", err)
|
|
}
|
|
out := make([]Task, 0, len(ids))
|
|
for _, id := range ids {
|
|
t, err := s.Get(ctx, id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
out = append(out, t)
|
|
}
|
|
return out, nil
|
|
}
|