Fordonshistorik med orsakskedja, felorsaksstatistik och egen ikongrafik
Fordonshistoriken är nu verklig i stället för självrapporterad:
- GET /api/fordon/{identifierare}/historik: organisationens tidigare
ärenden på samma objekt (regnr/VIN, case-okänsligt) med dokumenterade
felorsaker — organisationsgränsen gäller alltid
- Pre-diagnostikens historiksteg visar tidigare ärenden automatiskt
(servern i inloggat läge, lokala storen offline) och orsakskedjan
kopplas med ett tryck: "kopplat till tidigare ärende #N — …"
- GET /api/statistik/felorsaker (arbetsledare/admin): flottdata —
orsakskategorierna ur alla felorsaksanalyser aggregerade per
organisation, visade som stapelöversikt i arbetsledarvyn
Egen ikongrafik i stället för emojis (src/felsokning/ikoner.tsx):
enkla industriella linjeikoner i SVG (kamera, förstoringsglas, länk,
diagram, kugghjul, bock, kryss, varning, mikrofon, uppdatera, klocka)
och färgpunkter för tillförlitlighets- och statusnivåer. Etiketterna
är ren text ("Hög/Medel/Låg", "Hypotes (ej verifierad)") — maskinellt
verifierat emojifritt i klicktestet.
58 vitest-tester, integrationstest (historik, isolering, statistikens
behörighet) och OpenAPI-validering gröna.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012EQg3rJsrQ1ZNTvkzmQAtt
This commit is contained in:
@@ -166,6 +166,29 @@ curl -s -X POST "$BAS/api/delningar/$PARTNERKOD/aterkalla" -H "Authorization: Be
|
||||
KOD=$(curl -s -o /dev/null -w "%{http_code}" "$BAS/api/delad/$PARTNERKOD")
|
||||
kontroll "återkallad delning ger 404" "$KOD" "404"
|
||||
|
||||
# 10a. Fordonshistorik och felorsaksstatistik
|
||||
curl -s -X POST "$BAS/api/arenden" -H "Authorization: Bearer $TOKEN_A" -H 'Content-Type: application/json' \
|
||||
-d '{"id":"arende-test2","nummer":2,"skapad":"2026-08-03T09:00:00Z"}' >/dev/null
|
||||
curl -s -X POST "$BAS/api/arenden" -H "Authorization: Bearer $TOKEN_A" -H 'Content-Type: application/json' \
|
||||
-d '{"id":"arende-test3","nummer":3,"skapad":"2026-08-03T10:00:00Z"}' >/dev/null
|
||||
curl -s -X POST "$BAS/api/arenden/arende-test2/handelser" -H "Authorization: Bearer $TOKEN_A" -H 'Content-Type: application/json' \
|
||||
-d '{"handelser":[{"id":"f1","tidpunkt":"2026-08-03T09:01:00Z","anvandare":"Anna","handelse":{"typ":"objekt_identifierat","objekt":{"typ":"Personbil","identifierare":"XYZ999","identifieringsmetod":"Regnr","beskrivning":"VW Golf 2023"}}}]}' >/dev/null
|
||||
curl -s -X POST "$BAS/api/arenden/arende-test3/handelser" -H "Authorization: Bearer $TOKEN_A" -H 'Content-Type: application/json' \
|
||||
-d '{"handelser":[
|
||||
{"id":"f2","tidpunkt":"2026-08-03T10:01:00Z","anvandare":"Johan","handelse":{"typ":"objekt_identifierat","objekt":{"typ":"Personbil","identifierare":"xyz999","identifieringsmetod":"Regnr","beskrivning":"VW Golf 2023"}}},
|
||||
{"id":"f3","tidpunkt":"2026-08-03T10:02:00Z","anvandare":"Johan","handelse":{"typ":"felorsak","avvikelse":"Vattenpumpen läcker vid axeltätningen.","orsaker":["Normalt slitage","Ålder"],"underlag":["Foto"],"sakerhet":"hog","atgard":"Byt vattenpump."}}
|
||||
]}' >/dev/null
|
||||
HIST=$(curl -s "$BAS/api/fordon/XYZ999/historik" -H "Authorization: Bearer $TOKEN_J")
|
||||
kontroll "fordonshistoriken hittar båda ärendena (case-okänsligt)" "$(echo "$HIST" | falt .arenden.length)" "2"
|
||||
kontroll "historiken bär felorsakerna" "$(echo "$HIST" | falt '.arenden.flatMap(a=>a.felorsaker).length')" "1"
|
||||
KOD=$(curl -s -o /dev/null -w "%{http_code}" "$BAS/api/fordon/XYZ999/historik" -H "Authorization: Bearer $TOKEN_B")
|
||||
kontroll "org B ser inte org A:s fordonshistorik" "$(curl -s "$BAS/api/fordon/XYZ999/historik" -H "Authorization: Bearer $TOKEN_B" | falt .arenden.length)" "0"
|
||||
KOD=$(curl -s -o /dev/null -w "%{http_code}" "$BAS/api/statistik/felorsaker" -H "Authorization: Bearer $TOKEN_J")
|
||||
kontroll "tekniker nekas felorsaksstatistiken" "$KOD" "403"
|
||||
STAT=$(curl -s "$BAS/api/statistik/felorsaker" -H "Authorization: Bearer $TOKEN_L")
|
||||
kontroll "statistiken räknar orsakskategorier" "$(echo "$STAT" | falt .orsaker.length)" "2"
|
||||
kontroll "statistiken är organisationsknuten data" "$(echo "$STAT" | falt '.orsaker[0].antal')" "1"
|
||||
|
||||
# 10b. ECM Knowledge Library: regelpaketet serveras till inloggade klienter
|
||||
REGLER=$(curl -s "$BAS/api/ecm/regler" -H "Authorization: Bearer $TOKEN_J")
|
||||
kontroll "regelpaketet serveras" "$(echo "$REGLER" | falt .version)" "2.0"
|
||||
|
||||
@@ -170,6 +170,65 @@ paths:
|
||||
orsakskategorier: { type: array, items: { type: string } }
|
||||
underlagskallor: { type: array, items: { type: string } }
|
||||
|
||||
/api/fordon/{identifierare}/historik:
|
||||
get:
|
||||
tags: [Ärenden]
|
||||
summary: Fordonshistorik — tidigare ärenden på samma objekt
|
||||
description: >
|
||||
Organisationens tidigare ärenden där objektet (regnr/VIN) matchar,
|
||||
med dokumenterade felorsaker. Underlag för pre-diagnostikens
|
||||
historiksteg och orsakskedjan.
|
||||
parameters:
|
||||
- name: identifierare
|
||||
in: path
|
||||
required: true
|
||||
schema: { type: string }
|
||||
responses:
|
||||
"200":
|
||||
description: Tidigare ärenden (senaste 20).
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
arenden:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
id: { type: string }
|
||||
nummer: { type: integer }
|
||||
skapad: { type: string, format: date-time }
|
||||
avslutat: { type: boolean }
|
||||
felbeskrivning: { type: string, nullable: true }
|
||||
felorsaker: { type: array, items: { type: object, additionalProperties: true } }
|
||||
"401": { $ref: "#/components/responses/Fel" }
|
||||
|
||||
/api/statistik/felorsaker:
|
||||
get:
|
||||
tags: [Översikt]
|
||||
summary: Felorsaksstatistik per orsakskategori
|
||||
description: >
|
||||
Flottdata ur dokumenterade felorsaksanalyser i organisationen.
|
||||
Kräver rollen `arbetsledare` eller `admin`.
|
||||
responses:
|
||||
"200":
|
||||
description: Antal per orsakskategori, fallande.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
orsaker:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
orsak: { type: string }
|
||||
antal: { type: integer }
|
||||
"401": { $ref: "#/components/responses/Fel" }
|
||||
"403": { $ref: "#/components/responses/Fel" }
|
||||
|
||||
/api/organisation:
|
||||
get:
|
||||
tags: [Organisation]
|
||||
|
||||
@@ -312,6 +312,59 @@ export function skapaServer() {
|
||||
return svara(res, 200, { ok: true });
|
||||
}
|
||||
|
||||
// Fordonshistorik: organisationens tidigare ärenden på samma objekt
|
||||
// (regnr/VIN), med dokumenterade felorsaker — pre-diagnostikens
|
||||
// historiksteg och orsakskedjan. Organisationsgränsen gäller alltid.
|
||||
const fordonVag = vag.match(/^\/api\/fordon\/([^/]+)\/historik$/);
|
||||
if (req.method === "GET" && fordonVag) {
|
||||
const ident = decodeURIComponent(fordonVag[1]).trim().toUpperCase();
|
||||
if (!ident) return svara(res, 400, { error: "Identifierare saknas." });
|
||||
const rader = await pool.query(
|
||||
`select a.id, a.nummer, a.skapad,
|
||||
coalesce(bool_or(h.handelse->>'typ' = 'arende_avslutat'), false) as avslutat,
|
||||
(array_agg(h.handelse->>'text')
|
||||
filter (where h.handelse->>'typ' = 'felbeskrivning'))[1] as felbeskrivning,
|
||||
coalesce(json_agg(json_build_object(
|
||||
'avvikelse', h.handelse->>'avvikelse',
|
||||
'orsaker', h.handelse->'orsaker',
|
||||
'atgard', h.handelse->>'atgard'))
|
||||
filter (where h.handelse->>'typ' = 'felorsak'), '[]') as felorsaker
|
||||
from felsokning_arenden a
|
||||
join felsokning_handelser h on h.arende_id = a.id
|
||||
where a.organisation_id = $1
|
||||
and a.id in (
|
||||
select arende_id from felsokning_handelser
|
||||
where handelse->>'typ' = 'objekt_identifierat'
|
||||
and upper(handelse->'objekt'->>'identifierare') = $2
|
||||
)
|
||||
group by a.id
|
||||
order by a.skapad desc
|
||||
limit 20`,
|
||||
[anspr.org, ident],
|
||||
);
|
||||
return svara(res, 200, { arenden: rader.rows });
|
||||
}
|
||||
|
||||
// Flottdata: felorsaksstatistik per orsakskategori över hela
|
||||
// organisationen (arbetsledare/admin) — återkommande fel blir
|
||||
// synliga när orsakerna är strukturerad data.
|
||||
if (req.method === "GET" && vag === "/api/statistik/felorsaker") {
|
||||
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 orsak, count(*)::int as antal
|
||||
from felsokning_arenden a
|
||||
join felsokning_handelser h on h.arende_id = a.id,
|
||||
jsonb_array_elements_text(h.handelse->'orsaker') as orsak
|
||||
where a.organisation_id = $1 and h.handelse->>'typ' = 'felorsak'
|
||||
group by orsak
|
||||
order by antal desc`,
|
||||
[anspr.org],
|
||||
);
|
||||
return svara(res, 200, { orsaker: rader.rows });
|
||||
}
|
||||
|
||||
// Organisationsöversikt för arbetsledare/admin: alla ärenden med
|
||||
// status, deltagande tekniker och sammanfattning — härlett ur
|
||||
// händelseloggen, aldrig lagrat separat.
|
||||
|
||||
Reference in New Issue
Block a user