Å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. |
|
| 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). |
|
| 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. |
|
| 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. |
|
| Ä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. |
|
| 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. |
|
| 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 |
|
| Fordonshistorik kontrollerad eller motiverad | Obligatorisk |
|
||||||
| Ingående mätarställning dokumenterad | Obligatorisk |
|
| Ingående mätarställning dokumenterad | Obligatorisk |
|
||||||
| Kundens felbeskrivning verifierad | Rekommenderas |
|
| 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 |
|
| Utgående mätarställning | Obligatorisk vid avslut |
|
||||||
| Metodikens kontroller: evidens eller dokumenterat undantag | Obligatorisk |
|
| Metodikens kontroller: evidens eller dokumenterat undantag | Obligatorisk |
|
||||||
| Foton för fotokrävande kontroller | 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
|
organisationen — vilka komponenter fallerar av slitage, vilka efter
|
||||||
tidigare reparationer, vilka tyder på konstruktionsproblem.
|
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)
|
## Ärendeidentitet (Case Identity & Vehicle Context)
|
||||||
|
|
||||||
Fordonsobjektet är den röda tråden: identiteten registreras **en gång**
|
Fordonsobjektet är den röda tråden: identiteten registreras **en gång**
|
||||||
|
|||||||
@@ -654,6 +654,8 @@ components:
|
|||||||
- historik_kontrollerad
|
- historik_kontrollerad
|
||||||
- matarstallning
|
- matarstallning
|
||||||
- video
|
- video
|
||||||
|
- atgard_utford
|
||||||
|
- kvalitetskontroll
|
||||||
- reproducering
|
- reproducering
|
||||||
- felorsak
|
- felorsak
|
||||||
- export_skapad
|
- export_skapad
|
||||||
|
|||||||
@@ -363,3 +363,85 @@ describe("Videoevidens (E3) och signerat avslut", () => {
|
|||||||
expect(rad?.detalj).toContain("Erik");
|
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.",
|
nastaSteg: "Balansera om höger framhjul och genomför ny provkörning i 80–100 km/h.",
|
||||||
modell: "demo",
|
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(90, { typ: "overlamning", fran: anvandare, till: "Johan" }),
|
||||||
post(92, { typ: "kategori_byte", kategori: "administration" }, "Johan"),
|
post(92, { typ: "kategori_byte", kategori: "administration" }, "Johan"),
|
||||||
post(95, { typ: "kommentar", text: "Balanserat om höger framhjul, ny provkörning planerad." }, "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",
|
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
|
// Fordonsobjektet är den röda tråden genom hela ärendet: identiteten
|
||||||
// registreras en gång och återanvänds i felsökning, Live Share, rapport
|
// registreras en gång och återanvänds i felsökning, Live Share, rapport
|
||||||
// och export. Ärendereferenserna (AO/claim/skadenummer) hör till objektet
|
// och export. Ärendereferenserna (AO/claim/skadenummer) hör till objektet
|
||||||
@@ -101,6 +112,23 @@ export type Handelse =
|
|||||||
motivering?: string;
|
motivering?: string;
|
||||||
ytterligareKontroller?: 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: "export_skapad"; format: string; version: number }
|
||||||
| {
|
| {
|
||||||
typ: "ai_svar";
|
typ: "ai_svar";
|
||||||
@@ -194,6 +222,12 @@ export function handelseRubrik(post: LoggPost): string {
|
|||||||
: `Symptomet kunde inte reproduceras — ${h.beskrivning}`;
|
: `Symptomet kunde inte reproduceras — ${h.beskrivning}`;
|
||||||
case "felorsak":
|
case "felorsak":
|
||||||
return `Felorsak (${TILLFORLITLIGHET_LABEL[h.sakerhet]}): ${h.avvikelse} — ${h.orsaker.join(", ")}`;
|
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":
|
case "export_skapad":
|
||||||
return `Export skapad: ${h.format}, version ${h.version}`;
|
return `Export skapad: ${h.format}, version ${h.version}`;
|
||||||
case "ai_svar": {
|
case "ai_svar": {
|
||||||
|
|||||||
@@ -196,6 +196,31 @@ export function felorsaker(arende: Arende) {
|
|||||||
return arende.handelser.filter((p) => p.handelse.typ === "felorsak");
|
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) --
|
// -- Symptom Verification Protocol (SVP) --
|
||||||
// Kundens beskrivning → förtydligande (metodikens symptomfrågor) →
|
// Kundens beskrivning → förtydligande (metodikens symptomfrågor) →
|
||||||
// reproducering (eller dokumenterat ej reproducerbar). Rapporten skiljer
|
// 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.",
|
: "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.
|
// Utgående mätarställning: obligatorisk först när ärendet avslutas.
|
||||||
const matUt = handelser.some((h) => h.typ === "matarstallning" && h.lage === "utgaende");
|
const matUt = handelser.some((h) => h.typ === "matarstallning" && h.lage === "utgaende");
|
||||||
rader.push({
|
rader.push({
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useEffect, useMemo, useRef, useState } from "react";
|
import { useEffect, useMemo, useRef, useState } from "react";
|
||||||
import { Link, useParams } from "react-router-dom";
|
import { Link, useParams } from "react-router-dom";
|
||||||
import type { Arende, Handelse, TidKategori, Tillforlitlighet } from "@/felsokning/domain";
|
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 type { Metodik, NastaSteg } from "@/felsokning/metodik";
|
||||||
import { nastaSteg } from "@/felsokning/metodik";
|
import { nastaSteg } from "@/felsokning/metodik";
|
||||||
import {
|
import {
|
||||||
@@ -44,14 +44,17 @@ import {
|
|||||||
ECM_VERSION,
|
ECM_VERSION,
|
||||||
MARKOR_FELBESKRIVNING_VERIFIERAD,
|
MARKOR_FELBESKRIVNING_VERIFIERAD,
|
||||||
MARKOR_INGA_TIDIGA_OBSERVATIONER,
|
MARKOR_INGA_TIDIGA_OBSERVATIONER,
|
||||||
|
INGEN_ATGARD_ORSAKER,
|
||||||
MARKOR_TIDIGA_OBSERVATIONER_KLARA,
|
MARKOR_TIDIGA_OBSERVATIONER_KLARA,
|
||||||
aktivtRegelpaket,
|
aktivtRegelpaket,
|
||||||
|
atgarder,
|
||||||
arendetyp,
|
arendetyp,
|
||||||
felorsaker,
|
felorsaker,
|
||||||
granskaAvvikelse,
|
granskaAvvikelse,
|
||||||
grindGodkand,
|
grindGodkand,
|
||||||
kvalitetsgrind,
|
kvalitetsgrind,
|
||||||
hamtaRegelpaket,
|
hamtaRegelpaket,
|
||||||
|
kvalitetskontroll,
|
||||||
preDiagnostik,
|
preDiagnostik,
|
||||||
reproducering,
|
reproducering,
|
||||||
reproduceringsText,
|
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
|
// Navigationsträd (vänsterkolumnen): ärendets vyer plus metodikens steg
|
||||||
// med status — som en mappstruktur i ett klassiskt verkstadssystem.
|
// med status — som en mappstruktur i ett klassiskt verkstadssystem.
|
||||||
function VyTrad({
|
function VyTrad({
|
||||||
@@ -895,8 +1074,16 @@ function GuideFlik({
|
|||||||
|
|
||||||
const senasteAiSvar = [...arende.handelser].reverse().find((p) => p.handelse.typ === "ai_svar");
|
const senasteAiSvar = [...arende.handelser].reverse().find((p) => p.handelse.typ === "ai_svar");
|
||||||
|
|
||||||
// Avslut kräver SVP (reproducering dokumenterad) + felorsaksanalys.
|
// Avslut kräver hela kedjan: symptomverifiering, felorsaksanalys,
|
||||||
const kanAvslutas = !!reproducering(arende) && felorsaker(arende).length > 0;
|
// 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) {
|
if (avslutat) {
|
||||||
return (
|
return (
|
||||||
@@ -936,7 +1123,7 @@ function GuideFlik({
|
|||||||
</StorKnapp>
|
</StorKnapp>
|
||||||
{!kanAvslutas && (
|
{!kanAvslutas && (
|
||||||
<p className="mt-2 text-[12px] font-semibold text-[#9A6700]">
|
<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>
|
</p>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
@@ -986,6 +1173,10 @@ function GuideFlik({
|
|||||||
säkerhetsnivå. Obligatorisk för avslut. */}
|
säkerhetsnivå. Obligatorisk för avslut. */}
|
||||||
<FelorsaksPanel arende={arende} skicka={skicka} />
|
<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
|
{/* Utgående mätarställning: obligatorisk för kvalitetsgrinden när
|
||||||
ärendet avslutas — erbjuds så fort metodiken är genomarbetad. */}
|
ärendet avslutas — erbjuds så fort metodiken är genomarbetad. */}
|
||||||
{!arende.handelser.some((p) => p.handelse.typ === "matarstallning" && p.handelse.lage === "utgaende") && (
|
{!arende.handelser.some((p) => p.handelse.typ === "matarstallning" && p.handelse.lage === "utgaende") && (
|
||||||
@@ -996,7 +1187,8 @@ function GuideFlik({
|
|||||||
|
|
||||||
{!kanAvslutas && (
|
{!kanAvslutas && (
|
||||||
<p className="mb-2 text-[12px] font-semibold text-[#9A6700]">
|
<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>
|
</p>
|
||||||
)}
|
)}
|
||||||
<div className="grid grid-cols-2 gap-2">
|
<div className="grid grid-cols-2 gap-2">
|
||||||
@@ -1811,6 +2003,8 @@ function RapportFlik({
|
|||||||
const typ = arendetyp(arende);
|
const typ = arendetyp(arende);
|
||||||
const repro = reproducering(arende);
|
const repro = reproducering(arende);
|
||||||
const orsakerLista = felorsaker(arende);
|
const orsakerLista = felorsaker(arende);
|
||||||
|
const atgardsRader = atgarder(arende);
|
||||||
|
const kkRad = kvalitetskontroll(arende);
|
||||||
let matIn: string | undefined;
|
let matIn: string | undefined;
|
||||||
let matUt: string | undefined;
|
let matUt: string | undefined;
|
||||||
for (const p of arende.handelser) {
|
for (const p of arende.handelser) {
|
||||||
@@ -1967,6 +2161,30 @@ function RapportFlik({
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</Panel>
|
</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">
|
<Panel rubrik="Sammanfattning">
|
||||||
<p className="text-[14px]">Total arbetstid: <span className="font-semibold">{b.totalArbetstid}</span></p>
|
<p className="text-[14px]">Total arbetstid: <span className="font-semibold">{b.totalArbetstid}</span></p>
|
||||||
{fordelning.length > 0 && (
|
{fordelning.length > 0 && (
|
||||||
|
|||||||
Reference in New Issue
Block a user