Checklisten move from one-per-slug localStorage state to a template/instance
model. A user creates multiple named instances of each template (UPC SoC,
EPA Einspruch, …), each with its own checkbox state in paliad.checklist_instances
and an optional akte_id for office-wide visibility.
- Migration 014: paliad.checklist_instances + RLS mirroring the Termine
pattern (akte_id nullable → creator-only; akte_id set → can_see_akte gate).
- Static template data moves out of internal/handlers into internal/checklisten
so both handlers and the new ChecklistInstanceService can reference it
without an import cycle.
- ChecklistInstanceService: CRUD + state merge via `state || $n::jsonb`
so concurrent checkbox toggles don't clobber each other. Reset clears
state to {}. Akte-linked mutations append akten_events audit rows.
- Handlers: GET/POST /api/checklisten/{slug}/instances, GET/PATCH/DELETE
/api/checklisten/instances/{id}, POST .../reset, GET /api/akten/{id}/checklisten.
- /checklisten/{slug} redesigned to show template metadata + instance
table + "Neue Instanz" modal (with optional Akte dropdown). The
interactive checkboxes move to /checklisten/instances/{id} where the
state is DB-backed and Reset posts to the server. Fixes the original
Reset button regression — it now operates on real server state rather
than silently failing client-side.
- Akten detail grows a Checklisten tab listing linked instances with
progress bars; only loads on tab activation.
- localStorage-based progress removed from the overview grid (state no
longer lives there).
- DE + EN i18n keys added.
Verified: bun run build clean; go build ./...; go vet ./...; go test ./...
all green.
67 lines
2.9 KiB
SQL
67 lines
2.9 KiB
SQL
-- Phase K: paliad.checklist_instances — per-user (and optionally per-Akte)
|
|
-- instantiations of the static Checklisten templates defined in Go.
|
|
--
|
|
-- Templates stay in handlers.checklists (static Go data). A row here is one
|
|
-- user's personal checklist for one situation: a name, a link to an Akte
|
|
-- (optional), and the current checkbox state as a jsonb map
|
|
-- ({item_key: true}). State lives in the DB (not localStorage) so checkboxes
|
|
-- survive browser changes, and office-mates can share them via an Akte link.
|
|
--
|
|
-- Visibility mirrors paliad.termine (akte_id nullable):
|
|
-- - akte_id NULL → creator-only (personal instance)
|
|
-- - akte_id NOT NULL → parent Akte's office-scoped gate
|
|
--
|
|
-- template_slug is a FK concept only — the slug's validity is enforced in
|
|
-- the service layer against the static Go list.
|
|
|
|
CREATE TABLE paliad.checklist_instances (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
template_slug text NOT NULL,
|
|
name text NOT NULL,
|
|
akte_id uuid REFERENCES paliad.akten(id) ON DELETE SET NULL,
|
|
state jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
created_by uuid NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
updated_at timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
|
|
CREATE INDEX checklist_instances_template_idx ON paliad.checklist_instances (template_slug);
|
|
CREATE INDEX checklist_instances_akte_idx ON paliad.checklist_instances (akte_id) WHERE akte_id IS NOT NULL;
|
|
CREATE INDEX checklist_instances_created_by_idx ON paliad.checklist_instances (created_by);
|
|
|
|
ALTER TABLE paliad.checklist_instances ENABLE ROW LEVEL SECURITY;
|
|
|
|
-- RLS — mirrors the termine policy so personal instances are creator-only
|
|
-- while Akte-linked instances follow the office-scoped can_see_akte gate.
|
|
CREATE POLICY checklist_instances_select ON paliad.checklist_instances
|
|
FOR SELECT TO authenticated
|
|
USING (
|
|
(akte_id IS NULL AND created_by = auth.uid())
|
|
OR (akte_id IS NOT NULL AND paliad.can_see_akte(akte_id))
|
|
);
|
|
CREATE POLICY checklist_instances_insert ON paliad.checklist_instances
|
|
FOR INSERT TO authenticated
|
|
WITH CHECK (
|
|
created_by = auth.uid()
|
|
AND (
|
|
akte_id IS NULL
|
|
OR paliad.can_see_akte(akte_id)
|
|
)
|
|
);
|
|
CREATE POLICY checklist_instances_update ON paliad.checklist_instances
|
|
FOR UPDATE TO authenticated
|
|
USING (
|
|
(akte_id IS NULL AND created_by = auth.uid())
|
|
OR (akte_id IS NOT NULL AND paliad.can_see_akte(akte_id))
|
|
)
|
|
WITH CHECK (
|
|
(akte_id IS NULL AND created_by = auth.uid())
|
|
OR (akte_id IS NOT NULL AND paliad.can_see_akte(akte_id))
|
|
);
|
|
CREATE POLICY checklist_instances_delete ON paliad.checklist_instances
|
|
FOR DELETE TO authenticated
|
|
USING (
|
|
(akte_id IS NULL AND created_by = auth.uid())
|
|
OR (akte_id IS NOT NULL AND paliad.can_see_akte(akte_id))
|
|
);
|