diff --git a/docs/MVP.md b/docs/MVP.md index 740856f..219acd7 100644 --- a/docs/MVP.md +++ b/docs/MVP.md @@ -38,6 +38,7 @@ Demomanus för visning: [DEMO.md](DEMO.md). Knappen **Skapa demoärende** på st | Pre-diagnostik | ✅ Ingen felsökning förrän grundkontrollerna är gjorda eller motiverade: fordonshistorik — tidigare ärenden på samma fordon hämtas automatiskt med sina felorsaker (server i inloggat läge, lokala storen annars) och orsakskedjan kopplas med ett tryck; Ja/Nej med obligatorisk orsak → kvalitetsvarning, **ingående mätarställning** (foto av instrumentpanelen, bildtolkningen föreslår värdet), kundens felbeskrivning verifierad och tidiga observationer hanterade. Metodiken låses upp först därefter. **Utgående mätarställning** fotograferas inför avslut och blir obligatorisk i grinden när ärendet stängs. | | Symptomverifiering (SVP) | ✅ Kundens beskrivning ≠ konstaterat fel: beskrivningen dokumenteras ordagrant, förtydligas via metodikens symptomfrågor (generiska metodiken har SVP-setet när/var/hur) och reproduceras — Ja (hur/förhållanden), Delvis (vad kunde/kunde inte) eller Nej (obligatorisk motivering). Rapportens beviskedja skiljer kundens beskrivning, verifierad observation, felorsaksanalys och rekommenderad åtgärd; formuleringen "kunde inte reproduceras under de förhållanden som rådde" används i stället för "felet konstaterat" (kodat även i orkesterns grundprompt). | | Felorsaksanalys | ✅ Obligatorisk före avslut: konstaterad avvikelse (kvalitetsregeln avvisar "trasig/defekt/sliten" utan förklaring), orsakskategorier (inkl. Okänd orsak med krav på motivering), minst en evidenskälla som valideras mot loggen, säkerhetsnivå (medel/låg kräver stärkande kontroller) och rekommenderad åtgärd. Avslutsknappen spärrad tills SVP + felorsak finns; kvalitetsgrinden gör båda obligatoriska vid stängning. Eget avsnitt i slutrapporten. | +| Kundgodkännande | ✅ Åtgärdsförslag lämnas till kund innan arbetet påbörjas (förifyllt ur felorsaksanalysen, med uppskattad kostnad) och **visas i Live Share** — kunden ser vad som föreslås. Kundens besked registreras med utfall, kanal (telefon/på plats/e-post/SMS/delningslänk) och motivering vid avböjt. Åtgärdsknappen är låst tills beskedet finns och förblir låst vid avböjt; kvalitetsgrinden flaggar hårt om arbete utförts trots avböjt förslag. Publikt godkännande direkt i delningslänken är en medveten avgränsning. | | Åtgärd och kvalitetskontroll | ✅ Arbetsflödets sista led: åtgärden dokumenteras (vad som gjordes + delar) eller motiveras varför den uteblev (kunden avböjde, väntar på reservdel …). Har en åtgärd utförts krävs **kvalitetskontroll** — symptomet borta / kvarstår / delvis / kunde inte verifieras, med beskrivning av hur det verifierades under samma förhållanden som symptomet reproducerades. Kvarstående symptom flaggas i grinden i stället för att döljas. Avslutsknappen är spärrad tills hela kedjan symptomverifiering → felorsak → åtgärd → kvalitetskontroll är komplett; rapporten har ett eget avsnitt ”Utförd åtgärd och verifiering”. | | Ärendeidentitet | ✅ Fordonsobjektet som röd tråd: identiteten (AO-nummer, claim-/garantinummer, skadenummer, regnr, VIN, miltal, kund) registreras en gång — normalt via arbetsorderskanningen — och återanvänds i identitetsraden i arbetsytan (med ärendetypsval), låst panel överst i Live Share, slutrapportens första sida (Ärendeinformation + Fordonsinformation) och exporten. | | Instrumentavläsning (visual-first) | ✅ Kameran som universellt gränssnitt: `📷 Instrument` i Dokumentera-panelen fotograferar multimetrar, diagnosskärmar, batteritestare m.m. — bildtolkningen identifierar instrumenttyp och extraherar värden/enheter/felkoder med konfidens per värde; teknikern bekräftar innan något loggas. Originalbilden loggas alltid tillsammans med de strukturerade mätvärdena — strukturerad data ersätter aldrig originalevidensen. Ingen integration mot diagnossystem krävs. | diff --git a/docs/moduler/evidensmotor.md b/docs/moduler/evidensmotor.md index 7c5e466..a4f4a3a 100644 --- a/docs/moduler/evidensmotor.md +++ b/docs/moduler/evidensmotor.md @@ -76,6 +76,7 @@ alla obligatoriska rader är gröna: | Fordonshistorik kontrollerad eller motiverad | Obligatorisk | | Ingående mätarställning dokumenterad | Obligatorisk | | Kundens felbeskrivning verifierad | Rekommenderas | +| Kundens besked på åtgärdsförslaget | Obligatoriskt när arbete utförts | | Åtgärd dokumenterad eller motiverad | Obligatorisk vid avslut | | Kvalitetskontroll genomförd | Obligatorisk vid avslut efter utförd åtgärd | | Utgående mätarställning | Obligatorisk vid avslut | @@ -161,6 +162,29 @@ Flottdatan är redan igång: **felorsaksstatistiken** i arbetsledarvyn organisationen — vilka komponenter fallerar av slitage, vilka efter tidigare reparationer, vilka tyder på konstruktionsproblem. +## Kundgodkännande före arbete + +Verkstaden får aldrig utföra föreslaget arbete utan att kundens besked är +registrerat och spårbart: + +- **Åtgärdsförslaget** skrivs i guiden (förifyllt ur felorsaksanalysens + rekommenderade åtgärd) med eventuell uppskattad kostnad, och **visas + för kunden i Live Share** — det är kunddelbart material. +- **Kundens besked** registreras med utfall (godkänt/avböjt/delvis), + **kanal** (telefon, på plats, e-post, SMS, delningslänk) och + motivering vid avböjt/delvis. Loggposten bär vem i verkstaden som tog + emot beskedet och när. +- **Knappen "Dokumentera utförd åtgärd" är låst** så länge ett förslag + saknar besked — och förblir låst vid avböjt besked. Vägen "Ingen + åtgärd utförd" är öppen och hänvisar till det registrerade beskedet. +- Kvalitetsgrinden kräver registrerat besked när arbete utförts, och + flaggar konflikten *"Utfört arbete trots avböjt åtgärdsförslag"* som + ett hårt fel. + +Avgränsning: kunden godkänner i dagsläget genom kontakt med verkstaden — +plattformen registrerar beskedet. Publikt godkännande direkt i +delningslänken kräver en skrivande publik endpoint och hanteras separat. + ## Åtgärdsfasen (Repair & Verification) Loopen som symptomverifieringen öppnade sluts här — ett ärende kan inte diff --git a/services/plattform/openapi.yaml b/services/plattform/openapi.yaml index dec0d5b..75b3fa2 100644 --- a/services/plattform/openapi.yaml +++ b/services/plattform/openapi.yaml @@ -654,6 +654,8 @@ components: - historik_kontrollerad - matarstallning - video + - atgardsforslag + - kundbeslut - atgard_utford - kvalitetskontroll - reproducering diff --git a/src/felsokning/DelatArendeVy.tsx b/src/felsokning/DelatArendeVy.tsx index 9dc5242..4bcbf47 100644 --- a/src/felsokning/DelatArendeVy.tsx +++ b/src/felsokning/DelatArendeVy.tsx @@ -5,7 +5,7 @@ // bort även lokalt. import type { Arende } from "./domain"; -import { handelseRubrik } from "./domain"; +import { KUNDBESLUT_LABEL, handelseRubrik } from "./domain"; import { arAvslutat, arendeidentitet, brief, foton, tidsfordelningsRader, videor } from "./projektioner"; import { metodikForArende } from "./store"; import { tidDatum, tidKlockslag } from "./format"; @@ -59,6 +59,10 @@ export function DelatArendeVy({ const avslutat = arAvslutat(arende); const bilder = foton(arende); const klipp = videor(arende); + // Åtgärdsförslaget är avsett för kunden — det visas alltid överst i + // delningsvyn, tillsammans med det besked som registrerats. + const forslag = arende.handelser.filter((p) => p.handelse.typ === "atgardsforslag"); + const beslut = [...arende.handelser].reverse().find((p) => p.handelse.typ === "kundbeslut"); const matvarden = arende.handelser.filter((p) => p.handelse.typ === "matvarde"); const kundposter = redanFiltrerad ? arende.handelser @@ -93,6 +97,33 @@ export function DelatArendeVy({ )} + {forslag.length > 0 && ( + + {forslag.map((p) => { + const h = p.handelse; + if (h.typ !== "atgardsforslag") return null; + return ( +
+

{h.beskrivning}

+ {h.uppskattadKostnad && ( +

Uppskattad kostnad: {h.uppskattadKostnad}

+ )} +
+ ); + })} + {beslut?.handelse.typ === "kundbeslut" ? ( +

+ Ditt besked: {KUNDBESLUT_LABEL[beslut.handelse.beslut]}{" "} + (registrerat via {beslut.handelse.kanal}) +

+ ) : ( +

+ Inväntar ditt besked — kontakta verkstaden för att godkänna eller avböja. +

+ )} +
+ )} + {b.utfordaKontroller.map((k, i) => (

{k.text}

diff --git a/src/felsokning/__tests__/ecm.test.ts b/src/felsokning/__tests__/ecm.test.ts index 4d1d770..066e6eb 100644 --- a/src/felsokning/__tests__/ecm.test.ts +++ b/src/felsokning/__tests__/ecm.test.ts @@ -445,3 +445,59 @@ describe("Åtgärdsfasen och kvalitetskontroll", () => { } }); }); + +describe("Kundgodkännande före arbete", () => { + const FORSLAG: Handelse = { + typ: "atgardsforslag", + beskrivning: "Balansera om höger framhjul och genomför ny provkörning.", + uppskattadKostnad: "890 kr", + }; + const ATGARD: Handelse = { typ: "atgard_utford", beskrivning: "Balanserade om höger framhjul.", utford: true }; + + it("lämnat förslag kräver registrerat besked innan arbetet utförs", async () => { + const { atgardsforslag, kundbeslut } = await import("../ecm"); + const utanBesked = byggArende([OBJEKT, ...PREDIAG, FORSLAG, ATGARD]); + expect(atgardsforslag(utanBesked)).toHaveLength(1); + expect(kundbeslut(utanBesked)).toBeUndefined(); + const rad = kvalitetsgrind(utanBesked, VIBRATION_METODIK).find((r) => r.id === "kundgodkannande"); + expect(rad?.kravs).toBe(true); + expect(rad?.ok).toBe(false); + expect(rad?.detalj).toContain("registrera kundens besked"); + + const medBesked = byggArende([ + OBJEKT, + ...PREDIAG, + FORSLAG, + { typ: "kundbeslut", beslut: "godkant", kanal: "Telefon" }, + ATGARD, + ]); + const rad2 = kvalitetsgrind(medBesked, VIBRATION_METODIK).find((r) => r.id === "kundgodkannande"); + expect(rad2?.ok).toBe(true); + expect(rad2?.detalj).toContain("Telefon"); + }); + + it("utfört arbete trots avböjt förslag flaggas som konflikt", () => { + const konflikt = byggArende([ + OBJEKT, + ...PREDIAG, + FORSLAG, + { typ: "kundbeslut", beslut: "avbojt", kanal: "Telefon", kommentar: "Kunden vill vänta." }, + ATGARD, + ]); + const rad = kvalitetsgrind(konflikt, VIBRATION_METODIK).find((r) => r.id === "godkannande_konflikt"); + expect(rad?.ok).toBe(false); + expect(rad?.kravs).toBe(true); + }); + + it("utan lämnat förslag ställs inget godkännandekrav", () => { + const utanForslag = byggArende([OBJEKT, ...PREDIAG, ATGARD]); + expect(kvalitetsgrind(utanForslag, VIBRATION_METODIK).some((r) => r.id === "kundgodkannande")).toBe(false); + }); + + it("demoärendet visar hela kundkedjan: förslag → godkännande → åtgärd", () => { + const demo = byggDemoArende(1); + const typer = demo.handelser.map((p) => p.handelse.typ); + expect(typer.indexOf("atgardsforslag")).toBeLessThan(typer.indexOf("kundbeslut")); + expect(typer.indexOf("kundbeslut")).toBeLessThan(typer.indexOf("atgard_utford")); + }); +}); diff --git a/src/felsokning/demo.ts b/src/felsokning/demo.ts index 77aeda9..88fb58f 100644 --- a/src/felsokning/demo.ts +++ b/src/felsokning/demo.ts @@ -130,6 +130,19 @@ export function byggDemoArende(nummer: number, anvandare = "Anna"): Arende { nastaSteg: "Balansera om höger framhjul och genomför ny provkörning i 80–100 km/h.", modell: "demo", }), + post(86, { + typ: "atgardsforslag", + beskrivning: "Balansera om höger framhjul och genomför ny provkörning i 80–100 km/h.", + uppskattadKostnad: "890 kr inkl. moms", + }), + post(87, { typ: "kategori_byte", kategori: "kundkontakt" }), + post(87, { + typ: "kundbeslut", + beslut: "godkant", + kanal: "Telefon", + kommentar: "Kunden godkände ombalansering.", + }), + post(87, { typ: "kategori_byte", kategori: "aktiv_felsokning" }), post(87, { typ: "atgard_utford", beskrivning: "Balanserade om höger framhjul och monterade ny balansvikt på fälgens insida.", diff --git a/src/felsokning/domain.ts b/src/felsokning/domain.ts index d0e0bb0..3ff9e0c 100644 --- a/src/felsokning/domain.ts +++ b/src/felsokning/domain.ts @@ -29,6 +29,13 @@ export const TIDKATEGORI_LABEL: Record = { paus: "Paus", }; +// Kundens besked på ett lämnat åtgärdsförslag. +export const KUNDBESLUT_LABEL: Record<"godkant" | "avbojt" | "delvis", string> = { + godkant: "Godkänt", + avbojt: "Avböjt", + delvis: "Delvis godkänt", +}; + // Kvalitetskontrollens utfall efter utförd åtgärd. export const KVALITETSKONTROLL_LABEL: Record< "symptomet_borta" | "kvarstar" | "delvis" | "ej_verifierbar", @@ -112,6 +119,22 @@ export type Handelse = motivering?: string; ytterligareKontroller?: string; } + // Kundkommunikation: åtgärdsförslag lämnat till kund för godkännande + // innan arbetet påbörjas. Kunddelbart — visas i Live Share. + | { + typ: "atgardsforslag"; + beskrivning: string; + uppskattadKostnad?: string; + uppskattadTid?: string; + } + // Kundens besked på förslaget, med kanal och tidpunkt (loggposten bär + // vem i verkstaden som tog emot beskedet). + | { + typ: "kundbeslut"; + beslut: "godkant" | "avbojt" | "delvis"; + kanal: string; + kommentar?: string; + } // Åtgärdsfasen: vad som faktiskt gjordes — eller varför ingen åtgärd // utfördes (kunden avböjde, väntar på delar …). Kopplas till den // felorsak åtgärden svarar mot. @@ -222,6 +245,10 @@ export function handelseRubrik(post: LoggPost): string { : `Symptomet kunde inte reproduceras — ${h.beskrivning}`; case "felorsak": return `Felorsak (${TILLFORLITLIGHET_LABEL[h.sakerhet]}): ${h.avvikelse} — ${h.orsaker.join(", ")}`; + case "atgardsforslag": + return `Åtgärdsförslag till kund: ${h.beskrivning}${h.uppskattadKostnad ? ` — uppskattad kostnad ${h.uppskattadKostnad}` : ""}`; + case "kundbeslut": + return `Kundens besked (${h.kanal}): ${KUNDBESLUT_LABEL[h.beslut]}${h.kommentar ? ` — ${h.kommentar}` : ""}`; case "atgard_utford": return h.utford ? `Åtgärd utförd: ${h.beskrivning}${h.delar ? ` (delar: ${h.delar})` : ""}` diff --git a/src/felsokning/ecm.ts b/src/felsokning/ecm.ts index 339da39..2729648 100644 --- a/src/felsokning/ecm.ts +++ b/src/felsokning/ecm.ts @@ -209,6 +209,22 @@ export const INGEN_ATGARD_ORSAKER = [ "Kostnadsförslag lämnat, inväntar besked", ]; +// Kanaler för kundens besked — beskedet ska alltid gå att härleda till +// ett faktiskt samtal, besök eller meddelande. +export const KUNDKANALER = ["Telefon", "På plats", "E-post", "SMS", "Delningslänk"]; + +export function atgardsforslag(arende: Arende) { + return arende.handelser.filter((p) => p.handelse.typ === "atgardsforslag"); +} + +export function kundbeslut(arende: Arende) { + let senaste: { beslut: "godkant" | "avbojt" | "delvis"; kanal: string; kommentar?: string } | undefined; + for (const post of arende.handelser) { + if (post.handelse.typ === "kundbeslut") senaste = post.handelse; + } + return senaste; +} + export function atgarder(arende: Arende) { return arende.handelser.filter((p) => p.handelse.typ === "atgard_utford"); } @@ -568,6 +584,31 @@ export function kvalitetsgrind(arende: Arende, metodik: Metodik): GrindRad[] { : "Ingen åtgärd utförd; orsaken är dokumenterad.", }); + // Kundgodkännande: har ett åtgärdsförslag lämnats ska kundens besked + // vara registrerat innan arbetet utförs — arbete utan besked flaggas. + const forslag = atgardsforslag(arende); + const beslut = kundbeslut(arende); + if (forslag.length > 0 || beslut) { + rader.push({ + id: "kundgodkannande", + rubrik: "Kundens besked på åtgärdsförslaget registrerat", + ok: !!beslut, + kravs: utfordAtgard, + detalj: beslut + ? `${beslut.beslut === "godkant" ? "Godkänt" : beslut.beslut === "avbojt" ? "Avböjt" : "Delvis godkänt"} via ${beslut.kanal}.` + : "Åtgärdsförslag lämnat — registrera kundens besked innan arbetet utförs.", + }); + if (beslut?.beslut === "avbojt" && utfordAtgard) { + rader.push({ + id: "godkannande_konflikt", + rubrik: "Utfört arbete trots avböjt åtgärdsförslag", + ok: false, + kravs: true, + detalj: "Kunden avböjde åtgärden men arbete är dokumenterat som utfört — kontrollera underlaget.", + }); + } + } + // Kvalitetskontroll: är symptomet borta? Krävs när en åtgärd faktiskt // utförts — annars finns inget att verifiera. const kk = kvalitetskontroll(arende); diff --git a/src/pages/felsokning/ArendeSida.tsx b/src/pages/felsokning/ArendeSida.tsx index ffde872..88d610e 100644 --- a/src/pages/felsokning/ArendeSida.tsx +++ b/src/pages/felsokning/ArendeSida.tsx @@ -1,7 +1,7 @@ import { useEffect, useMemo, useRef, useState } from "react"; import { Link, useParams } from "react-router-dom"; import type { Arende, Handelse, TidKategori, Tillforlitlighet } from "@/felsokning/domain"; -import { KVALITETSKONTROLL_LABEL, TIDKATEGORI_LABEL, TILLFORLITLIGHET_LABEL, handelseRubrik } from "@/felsokning/domain"; +import { KUNDBESLUT_LABEL, KVALITETSKONTROLL_LABEL, TIDKATEGORI_LABEL, TILLFORLITLIGHET_LABEL, handelseRubrik } from "@/felsokning/domain"; import type { Metodik, NastaSteg } from "@/felsokning/metodik"; import { nastaSteg } from "@/felsokning/metodik"; import { @@ -45,15 +45,18 @@ import { MARKOR_FELBESKRIVNING_VERIFIERAD, MARKOR_INGA_TIDIGA_OBSERVATIONER, INGEN_ATGARD_ORSAKER, + KUNDKANALER, MARKOR_TIDIGA_OBSERVATIONER_KLARA, aktivtRegelpaket, atgarder, + atgardsforslag, arendetyp, felorsaker, granskaAvvikelse, grindGodkand, kvalitetsgrind, hamtaRegelpaket, + kundbeslut, kvalitetskontroll, preDiagnostik, reproducering, @@ -715,6 +718,19 @@ function AtgardsPanel({ arende, skicka }: { arende: Arende; skicka: (h: Handelse const kk = kvalitetskontroll(arende); const repro = reproducering(arende); + const forslag = atgardsforslag(arende); + const beslut = kundbeslut(arende); + const senasteFelorsak = felorsaker(arende).at(-1); + const foreslagenAtgard = + senasteFelorsak?.handelse.typ === "felorsak" ? senasteFelorsak.handelse.atgard : ""; + + const [forslagsLage, setForslagsLage] = useState(false); + const [forslagText, setForslagText] = useState(""); + const [kostnad, setKostnad] = useState(""); + const [beslutsVal, setBeslutsVal] = useState<"" | "godkant" | "avbojt" | "delvis">(""); + const [kanal, setKanal] = useState(""); + const [beslutsKommentar, setBeslutsKommentar] = useState(""); + const [lage, setLage] = useState<"" | "utford" | "ingen">(""); const [beskrivning, setBeskrivning] = useState(""); const [delar, setDelar] = useState(""); @@ -746,9 +762,140 @@ function AtgardsPanel({ arende, skicka }: { arende: Arende; skicka: (h: Handelse

)} + {/* Kundkommunikation: förslaget lämnas för godkännande innan + arbetet påbörjas, och beskedet registreras med kanal. */} + {forslag.map((p) => { + const h = p.handelse; + if (h.typ !== "atgardsforslag") return null; + return ( +

+ Åtgärdsförslag till kund: {h.beskrivning} + {h.uppskattadKostnad && · Uppskattad kostnad: {h.uppskattadKostnad}} +

+ ); + })} + {beslut && ( +

+ Kundens besked ({beslut.kanal}): {KUNDBESLUT_LABEL[beslut.beslut]} + {beslut.kommentar && — {beslut.kommentar}} +

+ )} + + {forslag.length === 0 && !forslagsLage && poster.length === 0 && ( + { + setForslagsLage(true); + setForslagText(foreslagenAtgard); + }} + > + Lämna åtgärdsförslag till kund + + )} + + {forslagsLage && ( +
+ + +
+ setForslagsLage(false)}> + Avbryt + + { + skicka({ + typ: "atgardsforslag", + beskrivning: forslagText.trim(), + uppskattadKostnad: kostnad.trim() || undefined, + }); + setForslagsLage(false); + setForslagText(""); + setKostnad(""); + }} + > + Lämna förslag + +
+
+ )} + + {forslag.length > 0 && !beslut && ( +
+

+ Registrera kundens besked innan arbetet påbörjas +

+
+ {(["godkant", "avbojt", "delvis"] as const).map((val) => ( + + ))} +
+ {beslutsVal && ( + <> +

Hur lämnades beskedet?

+
+ {KUNDKANALER.map((k) => ( + + ))} +
+ + { + skicka({ + typ: "kundbeslut", + beslut: beslutsVal, + kanal, + kommentar: beslutsKommentar.trim() || undefined, + }); + setBeslutsVal(""); + setKanal(""); + setBeslutsKommentar(""); + }} + > + Registrera besked + + + )} +
+ )} + {poster.length === 0 && !lage && (
- setLage("utford")}> + 0 && (!beslut || beslut.beslut === "avbojt")} + onClick={() => setLage("utford")} + > Dokumentera utförd åtgärd setLage("ingen")}> @@ -806,6 +953,11 @@ function AtgardsPanel({ arende, skicka }: { arende: Arende; skicka: (h: Handelse ))}
+ {beslut?.beslut === "avbojt" && ( +

+ Kunden har avböjt åtgärden via {beslut.kanal} — beskedet finns registrerat i loggen. +

+ )}
setLage("")}> Avbryt @@ -2005,6 +2157,8 @@ function RapportFlik({ const orsakerLista = felorsaker(arende); const atgardsRader = atgarder(arende); const kkRad = kvalitetskontroll(arende); + const forslagRader = atgardsforslag(arende); + const beslutRad = kundbeslut(arende); let matIn: string | undefined; let matUt: string | undefined; for (const p of arende.handelser) { @@ -2161,6 +2315,29 @@ function RapportFlik({ ); })} + {(forslagRader.length > 0 || beslutRad) && ( + + {forslagRader.map((p) => { + const h = p.handelse; + if (h.typ !== "atgardsforslag") return null; + return ( +
+ {identitetsRader([ + ["Föreslagen åtgärd", h.beskrivning], + ["Uppskattad kostnad", h.uppskattadKostnad], + ["Uppskattad tid", h.uppskattadTid], + ])} +
+ ); + })} + {beslutRad && + identitetsRader([ + ["Kundens besked", KUNDBESLUT_LABEL[beslutRad.beslut]], + ["Lämnat via", beslutRad.kanal], + ["Kommentar", beslutRad.kommentar], + ])} +
+ )} {atgardsRader.length === 0 &&

Ingen åtgärd dokumenterad ännu.

} {atgardsRader.map((p) => {