Åtgärdsfas med kvalitetskontroll — loopen sluts
Ärendet slutade tidigare vid rekommenderad åtgärd. Nu dokumenteras hela kedjan: symptomverifiering → felorsak → åtgärd → kvalitetskontroll. - Ny händelse atgard_utford: vad som gjordes (med delar) — eller varför ingen åtgärd utfördes, med orsak ur regelpaketet (kunden avböjde, väntar på reservdel, endast utredning beställd …) - Ny händelse kvalitetskontroll: symptomet borta / kvarstår / delvis / kunde inte verifieras, med beskrivning av hur verifieringen gjordes — under samma förhållanden som symptomet en gång reproducerades - Kvalitetsgrinden: "Åtgärd dokumenterad eller motiverad" obligatorisk vid avslut, "Kvalitetskontroll genomförd" obligatorisk när en åtgärd faktiskt utförts. Kvarstående symptom flaggas — ärendet bör inte avslutas som åtgärdat - Avslutsknappen spärrad tills kedjan är komplett; rapporten har eget avsnitt "Utförd åtgärd och verifiering" i beviskedjan - Demoärendet bär hela kedjan (ombalansering → verifierad provkörning) 65 vitest-tester, integrationstest och OpenAPI-validering gröna; klicktestet bekräftar att avslut öppnas först när kedjan är hel. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012EQg3rJsrQ1ZNTvkzmQAtt
This commit is contained in:
@@ -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. |
|
||||
| Å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. |
|
||||
| Utskrift | ✅ Kundrapport och Live Share-vy skrivs ut svart på vitt; interaktiva element döljs automatiskt. Utskriften går genom ECM-kvalitetsgrinden. |
|
||||
|
||||
@@ -76,6 +76,8 @@ alla obligatoriska rader är gröna:
|
||||
| Fordonshistorik kontrollerad eller motiverad | Obligatorisk |
|
||||
| Ingående mätarställning dokumenterad | Obligatorisk |
|
||||
| Kundens felbeskrivning verifierad | Rekommenderas |
|
||||
| Å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 |
|
||||
| Metodikens kontroller: evidens eller dokumenterat undantag | Obligatorisk |
|
||||
| Foton för fotokrävande kontroller | Obligatorisk |
|
||||
@@ -159,6 +161,25 @@ Flottdatan är redan igång: **felorsaksstatistiken** i arbetsledarvyn
|
||||
organisationen — vilka komponenter fallerar av slitage, vilka efter
|
||||
tidigare reparationer, vilka tyder på konstruktionsproblem.
|
||||
|
||||
## Åtgärdsfasen (Repair & Verification)
|
||||
|
||||
Loopen som symptomverifieringen öppnade sluts här — ett ärende kan inte
|
||||
avslutas utan att det framgår vad som gjordes och om det hjälpte:
|
||||
|
||||
1. **Åtgärd dokumenterad eller motiverad** — antingen vad som faktiskt
|
||||
utfördes (med eventuella delar), eller varför ingen åtgärd gjordes
|
||||
(kunden avböjde, väntar på reservdel, endast utredning beställd,
|
||||
kostnadsförslag lämnat, åtgärd hos annan verkstad).
|
||||
2. **Kvalitetskontroll** — obligatorisk när en åtgärd faktiskt utförts:
|
||||
är symptomet borta, kvarstår det helt eller delvis, eller kunde det
|
||||
inte verifieras? Utfallet dokumenteras med hur verifieringen gick
|
||||
till (samma förhållanden som symptomet reproducerades under).
|
||||
|
||||
Kvarstående symptom döljs aldrig: grinden skriver ut att ärendet inte
|
||||
bör avslutas som åtgärdat. Avslutsknappen är spärrad tills kedjan
|
||||
**symptomverifiering → felorsaksanalys → åtgärd → kvalitetskontroll** är
|
||||
komplett, och rapporten redovisar den i egna avsnitt.
|
||||
|
||||
## Ärendeidentitet (Case Identity & Vehicle Context)
|
||||
|
||||
Fordonsobjektet är den röda tråden: identiteten registreras **en gång**
|
||||
|
||||
@@ -654,6 +654,8 @@ components:
|
||||
- historik_kontrollerad
|
||||
- matarstallning
|
||||
- video
|
||||
- atgard_utford
|
||||
- kvalitetskontroll
|
||||
- reproducering
|
||||
- felorsak
|
||||
- export_skapad
|
||||
|
||||
@@ -363,3 +363,85 @@ describe("Videoevidens (E3) och signerat avslut", () => {
|
||||
expect(rad?.detalj).toContain("Erik");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Åtgärdsfasen och kvalitetskontroll", () => {
|
||||
it("avslut kräver dokumenterad åtgärd — och kvalitetskontroll när åtgärd utförts", async () => {
|
||||
const { atgarder, kvalitetskontroll } = await import("../ecm");
|
||||
const bas: Handelse[] = [
|
||||
OBJEKT,
|
||||
...PREDIAG,
|
||||
{ typ: "reproducering", status: "ja", beskrivning: "Reproducerad vid provkörning." },
|
||||
{
|
||||
typ: "felorsak",
|
||||
avvikelse: "Framhjulen uppvisar obalans om 38 g på höger sida.",
|
||||
orsaker: ["Yttre påverkan"],
|
||||
underlag: ["Direkt observation"],
|
||||
sakerhet: "hog",
|
||||
atgard: "Balansera om hjulet.",
|
||||
},
|
||||
{ typ: "observation", text: "Obalans uppmätt" },
|
||||
];
|
||||
|
||||
// Utan åtgärd: grinden kräver den vid avslut.
|
||||
const utanAtgard = byggArende([...bas, { typ: "arende_avslutat", signatur: "Erik" }]);
|
||||
const rad = kvalitetsgrind(utanAtgard, VIBRATION_METODIK).find((r) => r.id === "atgard");
|
||||
expect(rad?.kravs).toBe(true);
|
||||
expect(rad?.ok).toBe(false);
|
||||
|
||||
// Utförd åtgärd utan kvalitetskontroll: kontrollen blir obligatorisk.
|
||||
const utanKk = byggArende([
|
||||
...bas,
|
||||
{ typ: "atgard_utford", beskrivning: "Balanserade om höger framhjul.", utford: true },
|
||||
{ typ: "arende_avslutat", signatur: "Erik" },
|
||||
]);
|
||||
expect(atgarder(utanKk)).toHaveLength(1);
|
||||
const kkRad = kvalitetsgrind(utanKk, VIBRATION_METODIK).find((r) => r.id === "kvalitetskontroll");
|
||||
expect(kkRad?.kravs).toBe(true);
|
||||
expect(kkRad?.ok).toBe(false);
|
||||
|
||||
// Komplett kedja: åtgärd + verifierat borta.
|
||||
const komplett = byggArende([
|
||||
...bas,
|
||||
{ typ: "atgard_utford", beskrivning: "Balanserade om höger framhjul.", utford: true },
|
||||
{ typ: "kvalitetskontroll", resultat: "symptomet_borta", beskrivning: "Ny provkörning utan vibration." },
|
||||
{ typ: "arende_avslutat", signatur: "Erik" },
|
||||
]);
|
||||
expect(kvalitetskontroll(komplett)?.resultat).toBe("symptomet_borta");
|
||||
const rader = kvalitetsgrind(komplett, VIBRATION_METODIK);
|
||||
expect(rader.find((r) => r.id === "atgard")?.ok).toBe(true);
|
||||
expect(rader.find((r) => r.id === "kvalitetskontroll")?.detalj).toContain("verifierat borta");
|
||||
});
|
||||
|
||||
it("motiverat uteblivande godtas utan kvalitetskontroll", () => {
|
||||
const ingenAtgard = byggArende([
|
||||
OBJEKT,
|
||||
...PREDIAG,
|
||||
{ typ: "atgard_utford", beskrivning: "Ingen åtgärd utförd", utford: false, motivering: "Kunden avböjde åtgärd" },
|
||||
{ typ: "arende_avslutat", signatur: "Erik" },
|
||||
]);
|
||||
const rader = kvalitetsgrind(ingenAtgard, VIBRATION_METODIK);
|
||||
expect(rader.find((r) => r.id === "atgard")?.ok).toBe(true);
|
||||
expect(rader.find((r) => r.id === "atgard")?.detalj).toContain("orsaken är dokumenterad");
|
||||
// Inget att verifiera → kvalitetskontrollen är inte obligatorisk.
|
||||
expect(rader.find((r) => r.id === "kvalitetskontroll")?.kravs).toBe(false);
|
||||
});
|
||||
|
||||
it("kvarstående symptom flaggas i grinden i stället för att döljas", () => {
|
||||
const kvarstar = byggArende([
|
||||
OBJEKT,
|
||||
...PREDIAG,
|
||||
{ typ: "atgard_utford", beskrivning: "Bytte hjullager höger fram.", utford: true },
|
||||
{ typ: "kvalitetskontroll", resultat: "kvarstar", beskrivning: "Missljudet finns kvar vid 60 km/h." },
|
||||
]);
|
||||
const rad = kvalitetsgrind(kvarstar, VIBRATION_METODIK).find((r) => r.id === "kvalitetskontroll");
|
||||
expect(rad?.detalj).toContain("bör inte avslutas som åtgärdat");
|
||||
});
|
||||
|
||||
it("demoärendet bär hela kedjan symptom → orsak → åtgärd → verifiering", () => {
|
||||
const demo = byggDemoArende(1);
|
||||
const typer = demo.handelser.map((p) => p.handelse.typ);
|
||||
for (const typ of ["reproducering", "felorsak", "atgard_utford", "kvalitetskontroll"]) {
|
||||
expect(typer, typ).toContain(typ);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -130,6 +130,17 @@ 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(87, {
|
||||
typ: "atgard_utford",
|
||||
beskrivning: "Balanserade om höger framhjul och monterade ny balansvikt på fälgens insida.",
|
||||
delar: "Balansvikter 40 g",
|
||||
utford: true,
|
||||
}),
|
||||
post(88, {
|
||||
typ: "kvalitetskontroll",
|
||||
resultat: "symptomet_borta",
|
||||
beskrivning: "Ny provkörning 80–110 km/h på samma vägsträcka — ingen vibration i ratten.",
|
||||
}),
|
||||
post(90, { typ: "overlamning", fran: anvandare, till: "Johan" }),
|
||||
post(92, { typ: "kategori_byte", kategori: "administration" }, "Johan"),
|
||||
post(95, { typ: "kommentar", text: "Balanserat om höger framhjul, ny provkörning planerad." }, "Johan"),
|
||||
|
||||
@@ -29,6 +29,17 @@ export const TIDKATEGORI_LABEL: Record<TidKategori, string> = {
|
||||
paus: "Paus",
|
||||
};
|
||||
|
||||
// Kvalitetskontrollens utfall efter utförd åtgärd.
|
||||
export const KVALITETSKONTROLL_LABEL: Record<
|
||||
"symptomet_borta" | "kvarstar" | "delvis" | "ej_verifierbar",
|
||||
string
|
||||
> = {
|
||||
symptomet_borta: "Symptomet är borta",
|
||||
kvarstar: "Symptomet kvarstår",
|
||||
delvis: "Symptomet delvis kvar",
|
||||
ej_verifierbar: "Kunde inte verifieras",
|
||||
};
|
||||
|
||||
// Fordonsobjektet är den röda tråden genom hela ärendet: identiteten
|
||||
// registreras en gång och återanvänds i felsökning, Live Share, rapport
|
||||
// och export. Ärendereferenserna (AO/claim/skadenummer) hör till objektet
|
||||
@@ -101,6 +112,23 @@ export type Handelse =
|
||||
motivering?: string;
|
||||
ytterligareKontroller?: 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.
|
||||
| {
|
||||
typ: "atgard_utford";
|
||||
beskrivning: string;
|
||||
delar?: string;
|
||||
utford: boolean;
|
||||
motivering?: string;
|
||||
}
|
||||
// Kvalitetskontroll efter åtgärd: är symptomet borta? Slutar loopen
|
||||
// som symptomverifieringen (SVP) öppnade.
|
||||
| {
|
||||
typ: "kvalitetskontroll";
|
||||
resultat: "symptomet_borta" | "kvarstar" | "delvis" | "ej_verifierbar";
|
||||
beskrivning: string;
|
||||
}
|
||||
| { typ: "export_skapad"; format: string; version: number }
|
||||
| {
|
||||
typ: "ai_svar";
|
||||
@@ -194,6 +222,12 @@ 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 "atgard_utford":
|
||||
return h.utford
|
||||
? `Åtgärd utförd: ${h.beskrivning}${h.delar ? ` (delar: ${h.delar})` : ""}`
|
||||
: `Ingen åtgärd utförd — ${h.motivering ?? "orsak saknas"}`;
|
||||
case "kvalitetskontroll":
|
||||
return `Kvalitetskontroll: ${KVALITETSKONTROLL_LABEL[h.resultat]} — ${h.beskrivning}`;
|
||||
case "export_skapad":
|
||||
return `Export skapad: ${h.format}, version ${h.version}`;
|
||||
case "ai_svar": {
|
||||
|
||||
@@ -196,6 +196,31 @@ export function felorsaker(arende: Arende) {
|
||||
return arende.handelser.filter((p) => p.handelse.typ === "felorsak");
|
||||
}
|
||||
|
||||
// -- Åtgärdsfasen (Repair & Verification) --
|
||||
// Loopen som symptomverifieringen öppnade sluts här: åtgärden
|
||||
// dokumenteras (eller motiveras varför den uteblev) och kvalitets-
|
||||
// kontrollen visar om symptomet faktiskt är borta.
|
||||
|
||||
export const INGEN_ATGARD_ORSAKER = [
|
||||
"Kunden avböjde åtgärd",
|
||||
"Väntar på reservdel",
|
||||
"Åtgärd utförs av annan verkstad",
|
||||
"Endast utredning beställd",
|
||||
"Kostnadsförslag lämnat, inväntar besked",
|
||||
];
|
||||
|
||||
export function atgarder(arende: Arende) {
|
||||
return arende.handelser.filter((p) => p.handelse.typ === "atgard_utford");
|
||||
}
|
||||
|
||||
export function kvalitetskontroll(arende: Arende) {
|
||||
let senaste: { resultat: "symptomet_borta" | "kvarstar" | "delvis" | "ej_verifierbar"; beskrivning: string } | undefined;
|
||||
for (const post of arende.handelser) {
|
||||
if (post.handelse.typ === "kvalitetskontroll") senaste = post.handelse;
|
||||
}
|
||||
return senaste;
|
||||
}
|
||||
|
||||
// -- Symptom Verification Protocol (SVP) --
|
||||
// Kundens beskrivning → förtydligande (metodikens symptomfrågor) →
|
||||
// reproducering (eller dokumenterat ej reproducerbar). Rapporten skiljer
|
||||
@@ -526,6 +551,42 @@ export function kvalitetsgrind(arende: Arende, metodik: Metodik): GrindRad[] {
|
||||
: "Beskriv varför felet uppstått — eller ange varför orsaken inte kunnat fastställas.",
|
||||
});
|
||||
|
||||
// Åtgärdsfasen: reparationen dokumenterad — eller motiverat varför
|
||||
// ingen åtgärd utfördes. Obligatoriskt innan ärendet avslutas.
|
||||
const atgardsposter = atgarder(arende);
|
||||
const utfordAtgard = atgardsposter.some((p) => p.handelse.typ === "atgard_utford" && p.handelse.utford);
|
||||
rader.push({
|
||||
id: "atgard",
|
||||
rubrik: "Åtgärd dokumenterad eller motiverad",
|
||||
ok: atgardsposter.length > 0,
|
||||
kravs: avslutat,
|
||||
detalj:
|
||||
atgardsposter.length === 0
|
||||
? "Dokumentera vad som gjordes — eller varför ingen åtgärd utfördes."
|
||||
: utfordAtgard
|
||||
? undefined
|
||||
: "Ingen åtgärd utförd; orsaken är dokumenterad.",
|
||||
});
|
||||
|
||||
// Kvalitetskontroll: är symptomet borta? Krävs när en åtgärd faktiskt
|
||||
// utförts — annars finns inget att verifiera.
|
||||
const kk = kvalitetskontroll(arende);
|
||||
rader.push({
|
||||
id: "kvalitetskontroll",
|
||||
rubrik: "Kvalitetskontroll genomförd — symptomet verifierat",
|
||||
ok: !!kk,
|
||||
kravs: avslutat && utfordAtgard,
|
||||
detalj: kk
|
||||
? kk.resultat === "symptomet_borta"
|
||||
? "Symptomet är verifierat borta efter åtgärd."
|
||||
: kk.resultat === "ej_verifierbar"
|
||||
? `Kunde inte verifieras: ${kk.beskrivning}`
|
||||
: `Symptomet kvarstår helt eller delvis — ärendet bör inte avslutas som åtgärdat.`
|
||||
: utfordAtgard
|
||||
? "Verifiera att symptomet är borta efter åtgärden (provkörning/återtest)."
|
||||
: "Ingen åtgärd utförd — inget att verifiera.",
|
||||
});
|
||||
|
||||
// Utgående mätarställning: obligatorisk först när ärendet avslutas.
|
||||
const matUt = handelser.some((h) => h.typ === "matarstallning" && h.lage === "utgaende");
|
||||
rader.push({
|
||||
|
||||
@@ -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 { TIDKATEGORI_LABEL, TILLFORLITLIGHET_LABEL, handelseRubrik } from "@/felsokning/domain";
|
||||
import { KVALITETSKONTROLL_LABEL, TIDKATEGORI_LABEL, TILLFORLITLIGHET_LABEL, handelseRubrik } from "@/felsokning/domain";
|
||||
import type { Metodik, NastaSteg } from "@/felsokning/metodik";
|
||||
import { nastaSteg } from "@/felsokning/metodik";
|
||||
import {
|
||||
@@ -44,14 +44,17 @@ import {
|
||||
ECM_VERSION,
|
||||
MARKOR_FELBESKRIVNING_VERIFIERAD,
|
||||
MARKOR_INGA_TIDIGA_OBSERVATIONER,
|
||||
INGEN_ATGARD_ORSAKER,
|
||||
MARKOR_TIDIGA_OBSERVATIONER_KLARA,
|
||||
aktivtRegelpaket,
|
||||
atgarder,
|
||||
arendetyp,
|
||||
felorsaker,
|
||||
granskaAvvikelse,
|
||||
grindGodkand,
|
||||
kvalitetsgrind,
|
||||
hamtaRegelpaket,
|
||||
kvalitetskontroll,
|
||||
preDiagnostik,
|
||||
reproducering,
|
||||
reproduceringsText,
|
||||
@@ -702,6 +705,182 @@ function FelorsaksPanel({ arende, skicka }: { arende: Arende; skicka: (h: Handel
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// Åtgärdsfasen: reparationen dokumenteras (eller motiveras varför den
|
||||
// uteblev) och kvalitetskontrollen sluter loopen som symptom-
|
||||
// verifieringen öppnade — är symptomet faktiskt borta?
|
||||
function AtgardsPanel({ arende, skicka }: { arende: Arende; skicka: (h: Handelse) => void }) {
|
||||
const poster = atgarder(arende);
|
||||
const utford = poster.some((p) => p.handelse.typ === "atgard_utford" && p.handelse.utford);
|
||||
const kk = kvalitetskontroll(arende);
|
||||
const repro = reproducering(arende);
|
||||
|
||||
const [lage, setLage] = useState<"" | "utford" | "ingen">("");
|
||||
const [beskrivning, setBeskrivning] = useState("");
|
||||
const [delar, setDelar] = useState("");
|
||||
const [orsak, setOrsak] = useState("");
|
||||
const [kkResultat, setKkResultat] = useState<"" | "symptomet_borta" | "kvarstar" | "delvis" | "ej_verifierbar">("");
|
||||
const [kkText, setKkText] = useState("");
|
||||
|
||||
return (
|
||||
<Panel rubrik="Åtgärd och kvalitetskontroll">
|
||||
{poster.map((p) => {
|
||||
const h = p.handelse;
|
||||
if (h.typ !== "atgard_utford") return null;
|
||||
return (
|
||||
<p key={p.id} className="border-b border-[#EBEBEB] py-1 text-[13px] last:border-0">
|
||||
{h.utford ? (
|
||||
<>
|
||||
<span className="font-semibold">Utförd åtgärd:</span> {h.beskrivning}
|
||||
{h.delar && <span className="text-[#4A5560]"> · Delar: {h.delar}</span>}
|
||||
</>
|
||||
) : (
|
||||
<span className="text-[#9A6700]">Ingen åtgärd utförd — {h.motivering}</span>
|
||||
)}
|
||||
</p>
|
||||
);
|
||||
})}
|
||||
{kk && (
|
||||
<p className={`py-1 text-[13px] font-semibold ${kk.resultat === "symptomet_borta" ? "text-[#1E6B34]" : "text-[#9A6700]"}`}>
|
||||
Kvalitetskontroll: {KVALITETSKONTROLL_LABEL[kk.resultat]} — <span className="font-normal">{kk.beskrivning}</span>
|
||||
</p>
|
||||
)}
|
||||
|
||||
{poster.length === 0 && !lage && (
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<StorKnapp variant="sekundar" onClick={() => setLage("utford")}>
|
||||
Dokumentera utförd åtgärd
|
||||
</StorKnapp>
|
||||
<StorKnapp variant="sekundar" onClick={() => setLage("ingen")}>
|
||||
Ingen åtgärd utförd
|
||||
</StorKnapp>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{lage === "utford" && (
|
||||
<div className="mt-2">
|
||||
<TextFalt
|
||||
label="Vad gjordes? (kopplat till den dokumenterade felorsaken)"
|
||||
varde={beskrivning}
|
||||
satt={setBeskrivning}
|
||||
flerRad
|
||||
rost
|
||||
platshallare="T.ex. Balanserade om höger framhjul, ny balansvikt 40 g på insidan."
|
||||
/>
|
||||
<TextFalt label="Delar (valfritt)" varde={delar} satt={setDelar} platshallare="T.ex. Balansvikter, artikelnr 30-1234" rost />
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<StorKnapp variant="sekundar" onClick={() => setLage("")}>
|
||||
Avbryt
|
||||
</StorKnapp>
|
||||
<StorKnapp
|
||||
disabled={beskrivning.trim().length < 10}
|
||||
onClick={() => {
|
||||
skicka({ typ: "atgard_utford", beskrivning: beskrivning.trim(), delar: delar.trim() || undefined, utford: true });
|
||||
setLage("");
|
||||
setBeskrivning("");
|
||||
setDelar("");
|
||||
}}
|
||||
>
|
||||
Spara åtgärd
|
||||
</StorKnapp>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{lage === "ingen" && (
|
||||
<div className="mt-2">
|
||||
<p className="mb-2 text-[12px] font-semibold uppercase tracking-wide text-[#4A5560]">
|
||||
Orsak till att ingen åtgärd utfördes (obligatorisk)
|
||||
</p>
|
||||
<div className="mb-2 grid gap-1">
|
||||
{INGEN_ATGARD_ORSAKER.map((val) => (
|
||||
<button
|
||||
key={val}
|
||||
onClick={() => setOrsak(val)}
|
||||
className={`min-h-8 rounded border px-2 text-left text-[12px] font-medium ${
|
||||
orsak === val ? "border-[#00437A] bg-[#D6E4F2]" : "border-[#C6C6C6] bg-white"
|
||||
}`}
|
||||
>
|
||||
{val}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<TextFalt label="Eller egen orsak" varde={orsak && !INGEN_ATGARD_ORSAKER.includes(orsak) ? orsak : ""} satt={setOrsak} rost />
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<StorKnapp variant="sekundar" onClick={() => setLage("")}>
|
||||
Avbryt
|
||||
</StorKnapp>
|
||||
<StorKnapp
|
||||
disabled={!orsak.trim()}
|
||||
onClick={() => {
|
||||
skicka({ typ: "atgard_utford", beskrivning: "Ingen åtgärd utförd", utford: false, motivering: orsak.trim() });
|
||||
setLage("");
|
||||
setOrsak("");
|
||||
}}
|
||||
>
|
||||
Dokumentera
|
||||
</StorKnapp>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{utford && !kk && (
|
||||
<div className="mt-3 border-t border-[#DDDDDD] pt-2">
|
||||
<p className="mb-2 text-[12px] font-semibold uppercase tracking-wide text-[#4A5560]">
|
||||
Kvalitetskontroll — är symptomet borta efter åtgärden?
|
||||
</p>
|
||||
{repro?.status === "ja" && (
|
||||
<p className="mb-2 text-[12px] text-[#707070]">
|
||||
Symptomet reproducerades vid undersökningen — återskapa samma förhållanden för att verifiera.
|
||||
</p>
|
||||
)}
|
||||
<div className="mb-2 grid grid-cols-2 gap-2">
|
||||
{(["symptomet_borta", "kvarstar", "delvis", "ej_verifierbar"] as const).map((val) => (
|
||||
<button
|
||||
key={val}
|
||||
onClick={() => setKkResultat(val)}
|
||||
className={`min-h-9 rounded border px-2 text-[12px] font-semibold ${
|
||||
kkResultat === val ? "border-[#00437A] bg-[#00437A] text-white" : "border-[#ADADAD] bg-white text-[#333333]"
|
||||
}`}
|
||||
>
|
||||
{KVALITETSKONTROLL_LABEL[val]}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
{kkResultat && (
|
||||
<>
|
||||
<TextFalt
|
||||
label={
|
||||
kkResultat === "symptomet_borta"
|
||||
? "Hur verifierades det? (förhållanden, hastighet, provkörning …)"
|
||||
: kkResultat === "ej_verifierbar"
|
||||
? "Varför kunde det inte verifieras? (obligatorisk)"
|
||||
: "Vad kvarstår? (obligatorisk)"
|
||||
}
|
||||
varde={kkText}
|
||||
satt={setKkText}
|
||||
flerRad
|
||||
rost
|
||||
platshallare="T.ex. Ny provkörning 80–110 km/h på samma vägsträcka — ingen vibration."
|
||||
/>
|
||||
<StorKnapp
|
||||
disabled={!kkText.trim()}
|
||||
onClick={() => {
|
||||
skicka({ typ: "kvalitetskontroll", resultat: kkResultat, beskrivning: kkText.trim() });
|
||||
setKkResultat("");
|
||||
setKkText("");
|
||||
}}
|
||||
>
|
||||
Spara kvalitetskontroll
|
||||
</StorKnapp>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
|
||||
// Navigationsträd (vänsterkolumnen): ärendets vyer plus metodikens steg
|
||||
// med status — som en mappstruktur i ett klassiskt verkstadssystem.
|
||||
function VyTrad({
|
||||
@@ -895,8 +1074,16 @@ function GuideFlik({
|
||||
|
||||
const senasteAiSvar = [...arende.handelser].reverse().find((p) => p.handelse.typ === "ai_svar");
|
||||
|
||||
// Avslut kräver SVP (reproducering dokumenterad) + felorsaksanalys.
|
||||
const kanAvslutas = !!reproducering(arende) && felorsaker(arende).length > 0;
|
||||
// Avslut kräver hela kedjan: symptomverifiering, felorsaksanalys,
|
||||
// dokumenterad åtgärd (eller motiverat uteblivande) och — när en
|
||||
// åtgärd faktiskt utförts — kvalitetskontroll av att symptomet är borta.
|
||||
const atgardsposter = atgarder(arende);
|
||||
const utfordAtgard = atgardsposter.some((p) => p.handelse.typ === "atgard_utford" && p.handelse.utford);
|
||||
const kanAvslutas =
|
||||
!!reproducering(arende) &&
|
||||
felorsaker(arende).length > 0 &&
|
||||
atgardsposter.length > 0 &&
|
||||
(!utfordAtgard || !!kvalitetskontroll(arende));
|
||||
|
||||
if (avslutat) {
|
||||
return (
|
||||
@@ -936,7 +1123,7 @@ function GuideFlik({
|
||||
</StorKnapp>
|
||||
{!kanAvslutas && (
|
||||
<p className="mt-2 text-[12px] font-semibold text-[#9A6700]">
|
||||
Avslut kräver dokumenterad symptomverifiering och felorsaksanalys — se panelerna nedan.
|
||||
Avslut kräver symptomverifiering, felorsaksanalys, åtgärd och kvalitetskontroll — se panelerna nedan.
|
||||
</p>
|
||||
)}
|
||||
</>
|
||||
@@ -986,6 +1173,10 @@ function GuideFlik({
|
||||
säkerhetsnivå. Obligatorisk för avslut. */}
|
||||
<FelorsaksPanel arende={arende} skicka={skicka} />
|
||||
|
||||
{/* Åtgärdsfasen: vad som gjordes — och kvalitetskontrollen som
|
||||
verifierar att symptomet faktiskt är borta. */}
|
||||
{felorsaker(arende).length > 0 && <AtgardsPanel arende={arende} skicka={skicka} />}
|
||||
|
||||
{/* Utgående mätarställning: obligatorisk för kvalitetsgrinden när
|
||||
ärendet avslutas — erbjuds så fort metodiken är genomarbetad. */}
|
||||
{!arende.handelser.some((p) => p.handelse.typ === "matarstallning" && p.handelse.lage === "utgaende") && (
|
||||
@@ -996,7 +1187,8 @@ function GuideFlik({
|
||||
|
||||
{!kanAvslutas && (
|
||||
<p className="mb-2 text-[12px] font-semibold text-[#9A6700]">
|
||||
Avslut kräver dokumenterad symptomverifiering och felorsaksanalys — annars är slutsatsen inte spårbar.
|
||||
Avslut kräver hela kedjan: symptomverifiering, felorsaksanalys, dokumenterad åtgärd och — vid utförd åtgärd
|
||||
— kvalitetskontroll av att symptomet är borta.
|
||||
</p>
|
||||
)}
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
@@ -1811,6 +2003,8 @@ function RapportFlik({
|
||||
const typ = arendetyp(arende);
|
||||
const repro = reproducering(arende);
|
||||
const orsakerLista = felorsaker(arende);
|
||||
const atgardsRader = atgarder(arende);
|
||||
const kkRad = kvalitetskontroll(arende);
|
||||
let matIn: string | undefined;
|
||||
let matUt: string | undefined;
|
||||
for (const p of arende.handelser) {
|
||||
@@ -1967,6 +2161,30 @@ function RapportFlik({
|
||||
);
|
||||
})}
|
||||
</Panel>
|
||||
<Panel rubrik="Utförd åtgärd och verifiering">
|
||||
{atgardsRader.length === 0 && <p className="text-[14px] text-[#4A5560]">Ingen åtgärd dokumenterad ännu.</p>}
|
||||
{atgardsRader.map((p) => {
|
||||
const h = p.handelse;
|
||||
if (h.typ !== "atgard_utford") return null;
|
||||
return (
|
||||
<div key={p.id} className="mb-2">
|
||||
{h.utford ? (
|
||||
identitetsRader([
|
||||
["Utförd åtgärd", h.beskrivning],
|
||||
["Delar", h.delar],
|
||||
])
|
||||
) : (
|
||||
<p className="text-[14px] text-[#9A6700]">Ingen åtgärd utförd — {h.motivering}</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{kkRad &&
|
||||
identitetsRader([
|
||||
["Kvalitetskontroll", KVALITETSKONTROLL_LABEL[kkRad.resultat]],
|
||||
["Verifiering", kkRad.beskrivning],
|
||||
])}
|
||||
</Panel>
|
||||
<Panel rubrik="Sammanfattning">
|
||||
<p className="text-[14px]">Total arbetstid: <span className="font-semibold">{b.totalArbetstid}</span></p>
|
||||
{fordelning.length > 0 && (
|
||||
|
||||
Reference in New Issue
Block a user