Arbetsledarvyn: organisationsöversikt med status och statistik

Rollen arbetsledare får sin vy enligt Master Prompt ('se alla ärenden,
följa status, skapa statistik'):

- Ny behörighetsstyrd endpoint GET /api/oversikt (arbetsledare/admin):
  organisationens alla ärenden med status, antal händelser, deltagande
  tekniker, objekt och felbeskrivning — allt härlett ur händelseloggen
  i en SQL-fråga, aldrig lagrat separat. Tekniker nekas med 403.
- Ny sida /felsokning/oversikt: statistikpanel (totalt/pågående/
  avslutade + genomsnittlig ledtid), ärendelista med status och
  'Öppna ärendet' som hämtar händelserna till enheten — konfliktfri
  flätning om en lokal kopia redan finns. Länkas från kontopanelen
  för arbetsledare/admin.
- Integrationstestet utökat: tekniker nekas översikten, arbetsledaren
  ser organisationens ärenden och härledningarna (felbeskrivning,
  status, händelseantal) stämmer.

Ej implementerat (medvetet): omfördelning av ärenden — överlämnings-
händelsen täcker handover tills en ansvarig-modell införs.

Verifierat: integrationstestets 17 kontroller gröna mot Postgres 16,
28 vitest-tester gröna, produktionsbygge ok, Playwright-röktest grönt.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012EQg3rJsrQ1ZNTvkzmQAtt
This commit is contained in:
Claude
2026-08-03 07:07:25 +00:00
parent b069c9a12f
commit 7b7cc855c7
7 changed files with 226 additions and 1 deletions
+29
View File
@@ -220,6 +220,35 @@ export function skapaServer() {
}
}
// Organisationsöversikt för arbetsledare/admin: alla ärenden med
// status, deltagande tekniker och sammanfattning — härlett ur
// händelseloggen, aldrig lagrat separat.
if (req.method === "GET" && vag === "/api/oversikt") {
if (anspr.roll !== "arbetsledare" && anspr.roll !== "admin") {
return svara(res, 403, { error: "Kräver arbetsledar- eller administratörsbehörighet." });
}
const rader = await pool.query(
`select a.id, a.nummer, a.skapad, a.delningskod, a.metodik_id,
count(h.id)::int as antal_handelser,
min(h.tidpunkt) as forsta,
max(h.tidpunkt) as senaste,
coalesce(bool_or(h.handelse->>'typ' = 'arende_avslutat'), false) as avslutat,
(array_agg(h.handelse->'objekt'->>'beskrivning')
filter (where h.handelse->>'typ' = 'objekt_identifierat'))[1] as objekt,
(array_agg(h.handelse->>'text')
filter (where h.handelse->>'typ' = 'felbeskrivning'))[1] as felbeskrivning,
array_agg(distinct h.anvandare) filter (where h.anvandare is not null) as tekniker
from felsokning_arenden a
left join felsokning_handelser h on h.arende_id = a.id
where a.organisation_id = $1
group by a.id
order by max(h.tidpunkt) desc nulls last
limit 200`,
[anspr.org],
);
return svara(res, 200, { arenden: rader.rows });
}
if (req.method === "GET" && vag === "/api/arenden") {
const rader = await pool.query(
`select id, nummer, skapad, delningskod, metodik_id from felsokning_arenden