Kräv kundens besked även i klienten innan avslut

Grinden på servern har alltid krävt kundbeskedet när arbete faktiskt
utförts. Klientens kanAvslutas gjorde inte det, och glappet syntes först
när tio ärenden av olika karaktär kördes hela vägen genom gränssnittet:
samtliga tio gick att stänga på skärmen med utfört arbete och utan att
kunden hade sagt ja.

Det är samma sorts fel som slutsatsen hade, på ett annat villkor — och
det värre av de två, eftersom ett utfört arbete utan registrerat besked
är precis vad en tvist handlar om.

Hindertexten namnger nu kundbeskedet, och ett test speglar klientens
avslutsvillkor mot grindens hela åtgärdskedja så att nästa villkor som
läggs till i grinden inte kan glömmas i klienten.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012EQg3rJsrQ1ZNTvkzmQAtt
This commit is contained in:
Claude
2026-08-05 17:41:45 +00:00
parent 6fa7953012
commit 8daeb41ba1
2 changed files with 59 additions and 2 deletions
@@ -9,6 +9,7 @@
// inte blivit slutsatsen måste bemötas. Utan den är en felsökning en // inte blivit slutsatsen måste bemötas. Utan den är en felsökning en
// gissning som råkade stämma — och det är precis den skillnaden en // gissning som råkade stämma — och det är precis den skillnaden en
// försäkringsbedömare försöker avgöra. // försäkringsbedömare försöker avgöra.
import { readFileSync } from "node:fs";
import { describe, expect, it } from "vitest"; import { describe, expect, it } from "vitest";
import { import {
MINSTA_MOTIVERING, MINSTA_MOTIVERING,
@@ -225,3 +226,52 @@ describe("avslutsvillkoret är detsamma i klienten och i grinden", () => {
expect(await slutsatsHinder(logg)).not.toHaveLength(0); expect(await slutsatsHinder(logg)).not.toHaveLength(0);
}); });
}); });
// ---- Hela avslutskedjan, inte bara slutsatsen ---------------------------
//
// Hittat genom att köra tio ärenden av olika karaktär hela vägen genom
// gränssnittet: samtliga gick att stänga på skärmen med utfört arbete och
// utan kundbesked — men grinden på servern kräver beskedet. Samma glapp
// som slutsatsen hade, på ett annat villkor.
//
// Testet speglar klientens kanAvslutas mot grinden för hela kedjan, så
// att nästa villkor som läggs till i grinden inte kan glömmas i klienten.
describe("klientens avslutsvillkor täcker hela grindens åtgärdskedja", () => {
const KEDJA = [
{ typ: "reproducering", status: "ja", beskrivning: "Reproducerad vid provkörning i 90 km/h på plan väg." },
{
typ: "felorsak",
avvikelse: "Höger framhjul har 38 g obalans mot gränsen 10 g.",
orsaker: ["Normalt slitage"],
underlag: ["Mätresultat"],
sakerhet: "hog",
atgard: "Balansera om höger framhjul.",
},
{ typ: "atgard_utford", beskrivning: "Balanserade om höger framhjul.", utford: true },
{ typ: "kvalitetskontroll", resultat: "symptomet_borta", beskrivning: "Ny provkörning — ingen vibration." },
];
const gateHinder = async (handelser: unknown[]) => {
const { grinda } = await import("../../../../services/gemensam/grind.mjs");
const { ALLA_METODIKER } = await import("../../../../services/gemensam/metodiker.mjs");
return grinda(handelser, ALLA_METODIKER.at(-1)).map((h) => h.id);
};
it("utfört arbete utan kundbesked spärras av grinden", async () => {
expect(await gateHinder(KEDJA)).toContain("kundbeslut");
});
it("med kundbesked försvinner det hindret", async () => {
const med = [...KEDJA, { typ: "kundbeslut", beslut: "godkant", kanal: "telefon" }];
expect(await gateHinder(med)).not.toContain("kundbeslut");
});
it("klienten kräver samma sak — kundbeslut ingår i underlagKlart", () => {
const kod = readFileSync("src/pages/felsokning/ArendeSida.tsx", "utf8");
const rad = kod.match(/const underlagKlart =[\s\S]*?;/)?.[0] ?? "";
expect(rad).toContain("reproducering(arende)");
expect(rad).toContain("felorsaker(arende)");
expect(rad).toContain("kvalitetskontroll(arende)");
expect(rad).toContain("kundbeslut(arende)");
});
});
@@ -1257,11 +1257,17 @@ function GuideFlik({
// Underlaget: det som gör att en slutsats över huvud taget går att // Underlaget: det som gör att en slutsats över huvud taget går att
// motivera. Innan detta är på plats vore varför-frågan en begäran om // motivera. Innan detta är på plats vore varför-frågan en begäran om
// en gissning. // en gissning.
//
// Kundbeskedet hör till kedjan när arbete faktiskt utförts. Grinden på
// servern har alltid krävt det; klienten gjorde det inte, med följden
// att ett ärende gick att stänga på skärmen och nekas först vid synk.
// Samma sorts glapp som slutsatsen hade — hittat genom att köra tio
// ärenden hela vägen och jämföra mot grinden.
const underlagKlart = const underlagKlart =
!!reproducering(arende) && !!reproducering(arende) &&
felorsaker(arende).length > 0 && felorsaker(arende).length > 0 &&
atgardsposter.length > 0 && atgardsposter.length > 0 &&
(!utfordAtgard || !!kvalitetskontroll(arende)); (!utfordAtgard || (!!kvalitetskontroll(arende) && !!kundbeslut(arende)));
// ALVA-RULE-200. Samma granskning som kvalitetsgrinden på servern kör // ALVA-RULE-200. Samma granskning som kvalitetsgrinden på servern kör
// — avsiktligt samma funktion, inte en klientkopia av regeln. En // — avsiktligt samma funktion, inte en klientkopia av regeln. En
@@ -1443,7 +1449,8 @@ function Avslutshinder({ underlagKlart, brister }: { underlagKlart: boolean; bri
{!underlagKlart && ( {!underlagKlart && (
<p className="mb-2 text-[13px] leading-[20px] text-[#333333]"> <p className="mb-2 text-[13px] leading-[20px] text-[#333333]">
Underlaget är ofullständigt: avslut kräver symptomverifiering, felorsaksanalys, dokumenterad åtgärd och Underlaget är ofullständigt: avslut kräver symptomverifiering, felorsaksanalys, dokumenterad åtgärd och
vid utförd åtgärd kvalitetskontroll av att symptomet är borta. Se panelerna nedan. vid utförd åtgärd kvalitetskontroll av att symptomet är borta samt kundens besked åtgärdsförslaget.
Se panelerna nedan.
</p> </p>
)} )}
{brister.length > 0 && ( {brister.length > 0 && (