Spärra avslut utan slutsats även i klienten (ALVA-RULE-200)
Kvalitetsgrinden på servern krävde en slutsats, men klientens avslutsknapp gjorde inte det. Ett ärende gick därför att stänga på skärmen utan ett varför, och teknikern fick beskedet först vid synk — den sämsta tänkbara tidpunkten, eftersom bilen då har lämnat verkstaden. Klienten anropar nu granskaSlutsats() — samma funktion som grinden, inte en klientkopia av regeln — och avslutsknappen är spärrad tills den är ren. Slutsatspanelen visas så fort underlaget är helt, inte bara när metodikens sista steg är avbockat, så att frågan kommer när den går att besvara och inte som ett hinder efteråt. Hindret säger vad som fattas i stället för att bara konstatera att kraven inte är uppfyllda: först underlaget, sedan de enskilda bristerna i slutsatsen. Fyra tester jämför verdikt mellan klient och grind för samma logg, så att de två ändarna inte kan glida isär igen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012EQg3rJsrQ1ZNTvkzmQAtt
This commit is contained in:
@@ -8,6 +8,7 @@ import { fasFor, klaraFaser } from "../../../../services/gemensam/faser.mjs";
|
||||
import { Fasrad } from "@/alva/komponenter";
|
||||
import { Slutsatspanel } from "@/felsokning/Slutsats";
|
||||
import { sammanfatta } from "../../../../services/gemensam/sammanfattning.mjs";
|
||||
import { granskaSlutsats } from "../../../../services/gemensam/motivering.mjs";
|
||||
import { arendebeteckning, fasDefinition } from "@/alva/system";
|
||||
import {
|
||||
arAvslutat,
|
||||
@@ -1252,12 +1253,26 @@ function GuideFlik({
|
||||
// å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 =
|
||||
|
||||
// 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
|
||||
// en gissning.
|
||||
const underlagKlart =
|
||||
!!reproducering(arende) &&
|
||||
felorsaker(arende).length > 0 &&
|
||||
atgardsposter.length > 0 &&
|
||||
(!utfordAtgard || !!kvalitetskontroll(arende));
|
||||
|
||||
// ALVA-RULE-200. Samma granskning som kvalitetsgrinden på servern kör
|
||||
// — avsiktligt samma funktion, inte en klientkopia av regeln. En
|
||||
// tekniker ska aldrig få veta vid synk att ärendet inte gick att
|
||||
// stänga; hindret ska synas på skärmen där arbetet utförs.
|
||||
const slutsatsbrister = granskaSlutsats(
|
||||
[...arende.handelser].reverse().find((p) => p.handelse.typ === "slutsats")?.handelse,
|
||||
arende.handelser.map((p) => p.handelse),
|
||||
);
|
||||
const kanAvslutas = underlagKlart && slutsatsbrister.length === 0;
|
||||
|
||||
if (avslutat) {
|
||||
return (
|
||||
<Panel rubrik="Felsökningen är avslutad">
|
||||
@@ -1304,10 +1319,12 @@ function GuideFlik({
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* ALVA-RULE-200. Panelen visas när metodiken är genomförd — det
|
||||
är då varför-frågan har ett underlag att vila på. Att fråga
|
||||
tidigare vore att be om en gissning. */}
|
||||
{steg.klart && <Slutsatspanel arende={arende} skicka={skicka} />}
|
||||
{/* ALVA-RULE-200. Panelen visas när det finns ett underlag att
|
||||
vila på — antingen för att metodiken är genomförd eller för att
|
||||
kedjan reproducering → felorsak → åtgärd → kvalitetskontroll är
|
||||
hel. Att fråga tidigare vore att be om en gissning; att fråga
|
||||
först vid avslutsknappen vore att neka någon som redan är klar. */}
|
||||
{(steg.klart || underlagKlart) && <Slutsatspanel arende={arende} skicka={skicka} />}
|
||||
|
||||
<Panel rubrik={`Metodik: ${metodik.namn} · Steg: ${steg.steg.rubrik}`}>
|
||||
{steg.klart ? (
|
||||
@@ -1320,11 +1337,7 @@ function GuideFlik({
|
||||
<StorKnapp variant="fara" disabled={!kanAvslutas} onClick={() => skicka({ typ: "arende_avslutat", signatur: anvandare })}>
|
||||
Avsluta felsökning
|
||||
</StorKnapp>
|
||||
{!kanAvslutas && (
|
||||
<p className="mt-2 text-[12px] font-semibold text-[#9A6700]">
|
||||
Avslut kräver symptomverifiering, felorsaksanalys, åtgärd och kvalitetskontroll — se panelerna nedan.
|
||||
</p>
|
||||
)}
|
||||
{!kanAvslutas && <Avslutshinder underlagKlart={underlagKlart} brister={slutsatsbrister} />}
|
||||
</>
|
||||
) : steg.sparr ? (
|
||||
// Säkerhetsspärr. Metodiken går inte vidare — svaret är ett
|
||||
@@ -1397,12 +1410,7 @@ function GuideFlik({
|
||||
</Panel>
|
||||
)}
|
||||
|
||||
{!kanAvslutas && (
|
||||
<p className="mb-2 text-[12px] font-semibold text-[#9A6700]">
|
||||
Avslut kräver hela kedjan: symptomverifiering, felorsaksanalys, dokumenterad åtgärd och — vid utförd åtgärd
|
||||
— kvalitetskontroll av att symptomet är borta.
|
||||
</p>
|
||||
)}
|
||||
{!kanAvslutas && <Avslutshinder underlagKlart={underlagKlart} brister={slutsatsbrister} />}
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<StorKnapp variant="sekundar" onClick={() => setVisaOverlamning(true)}>
|
||||
Lämna över arbete
|
||||
@@ -1419,6 +1427,36 @@ function GuideFlik({
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Varför avslut är spärrat — i klartext, på skärmen, medan arbetet pågår.
|
||||
*
|
||||
* Ett hinder som bara säger "kraven är inte uppfyllda" tvingar teknikern
|
||||
* att leta. Här står vad som fattas, i den ordning det behöver åtgärdas:
|
||||
* först underlaget, sedan varför-frågan.
|
||||
*/
|
||||
function Avslutshinder({ underlagKlart, brister }: { underlagKlart: boolean; brister: { falt: string; text: string }[] }) {
|
||||
return (
|
||||
<div className="mb-2 border-l-2 border-[#9A6700] bg-[#FFFBF0] px-4 py-4">
|
||||
<p className="mb-2 text-[12px] font-semibold uppercase tracking-[0.08em] text-[#9A6700]">
|
||||
Ärendet kan inte avslutas ännu
|
||||
</p>
|
||||
{!underlagKlart && (
|
||||
<p className="mb-2 text-[13px] leading-[20px] text-[#333333]">
|
||||
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.
|
||||
</p>
|
||||
)}
|
||||
{brister.length > 0 && (
|
||||
<ul className="ml-4 list-disc text-[13px] leading-[20px] text-[#333333]">
|
||||
{brister.map((b) => (
|
||||
<li key={b.falt}>{b.text}</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function KategoriRad({ arende, skicka }: { arende: Arende; skicka: (h: Handelse) => void }) {
|
||||
let aktiv: TidKategori = "aktiv_felsokning";
|
||||
for (const post of arende.handelser) {
|
||||
|
||||
Reference in New Issue
Block a user