From 28bfc29a5c509f735db367c5be65e0fd344c0c2c Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 7 Aug 2026 08:55:02 +0000 Subject: [PATCH] =?UTF-8?q?Betalarsp=C3=A5ret:=20beviskraven=20f=C3=B6ljer?= =?UTF-8?q?=20den=20som=20betalar=20=E2=80=94=20ALVA=203.6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Garantistandarden FGS-1.0 (fabriksgaranti, vagnskadegaranti, försäkring, extern garantigivare, leasing/fleet, goodwill) inarbetad i regelmotorn och bokförd i docs/GARANTISTANDARD-FGS.md (ALVA-DOC-0011): vad som redan var uppfyllt, vad som införs nu, och den ärliga gränsen — ODIS/DISS/SAGA2 är tillverkarsystem; ALVA bär bevispaketet och eskaleringsspåret, den ersätter dem inte. Tre nya händelsetyper i det stängda schemat: betalare (spår, namn, claim-/skadereferens, godkännande), eskalering (öppnad/besvarad, kanal, referens) och reservdel (artikelnummer, serienummer, batch, sparad del). Grinden kräver fastställd betalare för de ärendetyper där någon annan än kunden betalar, godkännande för goodwill och externa garantigivare, och spärrar avslut när en eskalering är öppnad utan dokumenterat svar — matchad per referens. Claim- och skadenummer kan komma ur betalaren, inte bara ur skannad arbetsorder. Tre nya ärendetyper med regelpaketskrav: Body damage warranty, Extended warranty, Leasing or fleet. Betalare, eskalering och reservdel är interna i delningsfiltret — kunden ser åtgärden, inte förhandlingen. Fyndet på vägen, åtgärdat med regressionstest: serverns ärendetypskrav var en tyst no-op. Servern matade den distribuerade paketformen (arendetypRegler) in i en grindfunktion som bara läste den slimmade (arendetyper[typ].krav) — garanti- och försäkringskraven upprätthölls bara som råd i klienten. C-2-mönstret, återuppstånden genom ett filformat. Grinden läser nu båda formerna, och regressionstestet låser att den distribuerade formen spärrar på servern. UI: betalar- och eskaleringspaneler i ärendevyn (panelen visas när ärendetypen kräver den), grindnyckeln grind.eskalering på alla tio språken, regelpaket 2.1 speglat klient/server med likhetstestet intakt. 781 enhetstester, genomgång 4/4 inom budget, portalspärr, integrationstest mot Postgres, typkontroll, lint på baslinjen, artefaktmätning och språkrunda gröna. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_012EQg3rJsrQ1ZNTvkzmQAtt --- .../app/src/felsokning/__tests__/ecm.test.ts | 2 +- .../src/felsokning/__tests__/grind.test.ts | 71 +++++++++ felsokning/app/src/felsokning/domain.ts | 42 ++++++ felsokning/app/src/felsokning/ecm.ts | 41 +++++- .../app/src/pages/felsokning/ArendeSida.tsx | 136 +++++++++++++++++- felsokning/docs/GARANTISTANDARD-FGS.md | 75 ++++++++++ felsokning/services/gemensam/grind.mjs | 43 +++++- felsokning/services/gemensam/handelser.mjs | 11 ++ felsokning/services/gemensam/sprak/de.mjs | 2 + felsokning/services/gemensam/sprak/en.mjs | 2 + felsokning/services/gemensam/sprak/es.mjs | 2 + felsokning/services/gemensam/sprak/fr.mjs | 2 + felsokning/services/gemensam/sprak/it.mjs | 2 + felsokning/services/gemensam/sprak/nl.mjs | 2 + felsokning/services/gemensam/sprak/pl.mjs | 2 + felsokning/services/gemensam/sprak/pt.mjs | 2 + felsokning/services/gemensam/sprak/ro.mjs | 2 + felsokning/services/gemensam/sprak/sv.mjs | 2 + felsokning/services/gemensam/version.mjs | 14 ++ felsokning/services/plattform/ecm-regler.json | 82 ++++++++++- .../services/plattform/integrationstest.sh | 4 +- felsokning/services/plattform/server.mjs | 5 +- 22 files changed, 530 insertions(+), 16 deletions(-) create mode 100644 felsokning/docs/GARANTISTANDARD-FGS.md diff --git a/felsokning/app/src/felsokning/__tests__/ecm.test.ts b/felsokning/app/src/felsokning/__tests__/ecm.test.ts index 6513d39..fb34027 100644 --- a/felsokning/app/src/felsokning/__tests__/ecm.test.ts +++ b/felsokning/app/src/felsokning/__tests__/ecm.test.ts @@ -181,7 +181,7 @@ describe("Traceability Engine + ärendeidentitet", () => { it("spårbarhetspaketet innehåller version, grindstatus och evidensposter", () => { const arende = byggArende([OBJEKT, { typ: "foto", beskrivning: "x", dataUrl: "data:" }]); const paket = sparbarhetspaket(arende, VIBRATION_METODIK); - expect(paket.ecmVersion).toBe("2.0"); + expect(paket.ecmVersion).toBe("2.1"); expect(paket.arendetyp).toBe("Private customer"); expect(paket.evidensposter).toHaveLength(1); expect(paket.kvalitetsgrind.some((r) => r.id === "objekt" && r.ok)).toBe(true); diff --git a/felsokning/app/src/felsokning/__tests__/grind.test.ts b/felsokning/app/src/felsokning/__tests__/grind.test.ts index 131d54c..11e76b8 100644 --- a/felsokning/app/src/felsokning/__tests__/grind.test.ts +++ b/felsokning/app/src/felsokning/__tests__/grind.test.ts @@ -572,3 +572,74 @@ describe("mätarställningen ska vara fotograferad, inte bara inskriven", () => expect(grinda(tomt, GENERISK).map((h) => h.id)).toContain("matarstallning_ingaende"); }); }); + +// ---- FGS-1.0: betalarspåret och eskaleringen ---------------------------- +// +// Betalaren avgör beviskraven, och en öppnad teknisk eskalering utan +// dokumenterat svar spärrar avslutet — bulletinernas "invänta svar innan +// ytterligare åtgärder" som grindregel, inte som råd. +describe("FGS · betalare och eskalering", () => { + it("en öppnad eskalering spärrar avslutet, ett dokumenterat svar släpper", () => { + const oppen = [...komplettLogg(), { typ: "eskalering", status: "oppnad", beskrivning: "DISS query", referens: "D-1" }]; + expect(grinda(oppen, GENERISK).map((h) => h.id)).toContain("eskalering"); + const besvarad = [...oppen, { typ: "eskalering", status: "besvarad", beskrivning: "Factory answer", referens: "D-1" }]; + expect(grinda(besvarad, GENERISK)).toEqual([]); + }); + + it("svaret matchas per referens — ett svar på fel förfrågan släpper inte", () => { + const logg = [ + ...komplettLogg(), + { typ: "eskalering", status: "oppnad", beskrivning: "Battery query", referens: "D-1" }, + { typ: "eskalering", status: "oppnad", beskrivning: "HV query", referens: "D-2" }, + { typ: "eskalering", status: "besvarad", beskrivning: "Answer", referens: "D-1" }, + ]; + const hinder = grinda(logg, GENERISK).filter((h) => h.id === "eskalering"); + expect(hinder).toHaveLength(1); + expect(hinder[0].detalj).toContain("D-2"); + }); + + it("betalarkravet uppfylls av en registrerad betalare med spår och namn", () => { + const paket = { arendetypRegler: { Warranty: [{ id: "w_betalare", rubrik: "Payer established", krav: "betalare", detaljVidBrist: "Record the payer." }] } }; + const utan = [{ typ: "arendetyp_satt", arendetyp: "Warranty" }]; + expect(grindaArendetyp(utan, paket).map((h) => h.id)).toEqual(["arendetyp_betalare"]); + const med = [...utan, { typ: "betalare", spar: "fabriksgaranti", namn: "Volkswagen AG" }]; + expect(grindaArendetyp(med, paket)).toEqual([]); + }); + + it("claim-referensen kan komma ur betalaren, inte bara ur skannad arbetsorder", () => { + const paket = { arendetyper: { Warranty: { krav: ["claim"] } } }; + const utan = [{ typ: "arendetyp_satt", arendetyp: "Warranty" }]; + expect(grindaArendetyp(utan, paket).map((h) => h.id)).toEqual(["arendetyp_claim"]); + const med = [...utan, { typ: "betalare", spar: "fabriksgaranti", namn: "VW", referens: "CL-4711" }]; + expect(grindaArendetyp(med, paket)).toEqual([]); + }); + + it("goodwill kräver godkännande — en betalare utan klartecken räcker inte", () => { + const paket = { arendetypRegler: { Goodwill: [{ id: "g", rubrik: "Approval", krav: "godkannande", detaljVidBrist: "Approval required." }] } }; + const utanKlartecken = [ + { typ: "arendetyp_satt", arendetyp: "Goodwill" }, + { typ: "betalare", spar: "goodwill", namn: "Importören" }, + ]; + expect(grindaArendetyp(utanKlartecken, paket).map((h) => h.id)).toEqual(["arendetyp_godkannande"]); + const med = [...utanKlartecken, { typ: "betalare", spar: "goodwill", namn: "Importören", godkannande: "GW-9" }]; + expect(grindaArendetyp(med, paket)).toEqual([]); + }); + + it("den distribuerade paketformen (arendetypRegler) utvärderas på servern", () => { + // Regression: servern matade in filens rika form i en funktion som + // bara läste den slimmade — varje ärendetypskrav blev en tyst no-op + // och spärren fanns bara som råd i klienten. C-2, återuppstånden + // genom ett format. + const rikt = { + arendetypRegler: { + Warranty: [{ id: "garanti_miltal", rubrik: "Mileage documented", krav: "miltal", detaljVidBrist: "Odometer reading required." }], + }, + }; + const utan = [{ typ: "arendetyp_satt", arendetyp: "Warranty" }]; + const hinder = grindaArendetyp(utan, rikt); + expect(hinder.map((h) => h.id)).toEqual(["arendetyp_miltal"]); + expect(hinder[0].rubrik).toBe("Mileage documented"); + expect(hinder[0].detalj).toBe("Odometer reading required."); + expect(grindaArendetyp([...utan, { typ: "matarstallning", lage: "ingaende", varde: "12000" }], rikt)).toEqual([]); + }); +}); diff --git a/felsokning/app/src/felsokning/domain.ts b/felsokning/app/src/felsokning/domain.ts index 890a792..27f1292 100644 --- a/felsokning/app/src/felsokning/domain.ts +++ b/felsokning/app/src/felsokning/domain.ts @@ -87,6 +87,27 @@ export interface Bilaga { dataUrl?: string; } +// FGS-1.0 §3: betalarspåren. Datavärdena är invarianta (svenska tokens i +// loggen, precis som "godkant"/"ingaende"); etiketterna är gränssnitt. +export type Betalarspar = + | "fabriksgaranti" + | "vagnskadegaranti" + | "forsakring" + | "extern_garanti" + | "leasing" + | "goodwill" + | "kund"; + +export const BETALARSPAR_LABEL: Record = { + fabriksgaranti: "Factory warranty", + vagnskadegaranti: "Body damage warranty", + forsakring: "Insurance", + extern_garanti: "External warranty", + leasing: "Leasing or fleet", + goodwill: "Goodwill", + kund: "Customer pays", +}; + export type Handelse = | { typ: "objekt_identifierat"; objekt: Objekt } | ({ typ: "arbetsorder_skannad"; falt: ArbetsorderFalt[] } & Bilaga) @@ -137,6 +158,19 @@ export type Handelse = // Ärendetypen styr vilka dokumentationskrav ECM ställer (garanti, // försäkring, reklamation …). | { typ: "arendetyp_satt"; arendetyp: string } + // FGS-1.0: betalarspåret. Vem som ersätter arbetet avgör beviskraven — + // fabrik, vagnskadegaranti, försäkringsbolag, extern garantigivare, + // leasing/fleet, goodwill eller kunden själv. Referensen är claim-, + // skade- eller policynummer; godkännandet är betalarens klartecken. + | { typ: "betalare"; spar: Betalarspar; namn: string; referens?: string; godkannande?: string } + // Teknisk eskalering (DISS-mönstret): öppnad förfrågan till tillverkare + // eller garantigivare. En öppnad eskalering utan besvarad spärrar + // avslutet — svaret ska inväntas och dokumenteras. + | { typ: "eskalering"; status: "oppnad" | "besvarad"; beskrivning: string; referens?: string; kanal?: string } + // Reservdelsdokumentation: artikelnummer och spårbarhet för utbytt del. + // `sparad` betyder att den demonterade delen behålls i väntan på + // garantibeslut eller revision. + | { typ: "reservdel"; artikelnummer: string; beskrivning: string; serienummer?: string; batch?: string; sparad?: boolean } // Pre-diagnostik: fordonshistoriken kontrollerad — eller motiverat // varför inte (kvalitetsvarning). | { typ: "historik_kontrollerad"; kontrollerad: boolean; kommentar?: string } @@ -312,6 +346,14 @@ export function handelseRubrik(post: LoggPost): string { return `Ansvarig tekniker: ${h.ansvarig}`; case "arendetyp_satt": return `Case type: ${h.arendetyp}`; + case "betalare": + return `Payer: ${BETALARSPAR_LABEL[h.spar]} — ${h.namn}${h.referens ? ` (ref ${h.referens})` : ""}${h.godkannande ? ` — approval ${h.godkannande}` : ""}`; + case "eskalering": + return h.status === "oppnad" + ? `Escalation opened${h.kanal ? ` via ${h.kanal}` : ""}${h.referens ? ` (${h.referens})` : ""}: ${h.beskrivning}` + : `Escalation answered${h.referens ? ` (${h.referens})` : ""}: ${h.beskrivning}`; + case "reservdel": + return `Part documented: ${h.artikelnummer} — ${h.beskrivning}${h.serienummer ? `, s/n ${h.serienummer}` : ""}${h.batch ? `, batch ${h.batch}` : ""}${h.sparad ? " — old part retained" : ""}`; case "historik_kontrollerad": return h.kontrollerad ? `Fordonshistorik kontrollerad${h.kommentar ? `: ${h.kommentar}` : ""}` diff --git a/felsokning/app/src/felsokning/ecm.ts b/felsokning/app/src/felsokning/ecm.ts index ce95f17..3df4730 100644 --- a/felsokning/app/src/felsokning/ecm.ts +++ b/felsokning/app/src/felsokning/ecm.ts @@ -22,7 +22,7 @@ import type { Arende, LoggPost } from "./domain"; import type { Metodik } from "./metodik"; import { arendetypNu } from "../../../services/gemensam/sprak/index.mjs"; -export const ECM_VERSION = "2.0"; +export const ECM_VERSION = "2.1"; // ---- 1. Evidence Engine ----------------------------------------------- @@ -345,6 +345,9 @@ export const ARENDETYPER = [ "Insurance", "Complaint", "Used-vehicle warranty", + "Body damage warranty", + "Extended warranty", + "Leasing or fleet", "Internal quality check", "Technical investigation", ] as const; @@ -372,7 +375,7 @@ export function arendetyp(arende: Arende): Arendetyp { // Klienten cachar senast hämtade paket och faller tillbaka till det // inbyggda standardpaketet offline/i lokalt läge. -export type ComplianceKravTyp = "miltal" | "historik" | "claim" | "skadenummer" | "foto"; +export type ComplianceKravTyp = "miltal" | "historik" | "claim" | "skadenummer" | "foto" | "betalare" | "godkannande"; export interface ComplianceRegel { id: string; @@ -403,11 +406,21 @@ const KRAV_KONTROLLER: Record boolean> = a.handelser.some((p) => p.handelse.typ === "matarstallning" && !p.handelse.undantag) || !!objektFalt(a, "miltal"), historik: (a) => a.handelser.some((p) => p.handelse.typ === "historik_kontrollerad" && p.handelse.kontrollerad), - claim: (a) => !!objektFalt(a, "claim"), - skadenummer: (a) => !!objektFalt(a, "skadenummer"), + // Referensen kan komma ur den skannade arbetsordern ELLER ur en + // registrerad betalare — alla verkstäder skannar inte (FGS-1.0 §4). + claim: (a) => !!objektFalt(a, "claim") || betalarReferens(a), + skadenummer: (a) => !!objektFalt(a, "skadenummer") || betalarReferens(a), foto: (a) => a.handelser.some((p) => p.handelse.typ === "foto"), + betalare: (a) => + a.handelser.some((p) => p.handelse.typ === "betalare" && p.handelse.namn.trim() !== "" && p.handelse.spar.trim() !== ""), + godkannande: (a) => + a.handelser.some((p) => p.handelse.typ === "betalare" && (p.handelse.godkannande ?? "").trim() !== ""), }; +function betalarReferens(a: Arende): boolean { + return a.handelser.some((p) => p.handelse.typ === "betalare" && (p.handelse.referens ?? "").trim() !== ""); +} + // Inbyggt standardpaket = fallback när servern inte nåtts ännu. export const STANDARD_REGELPAKET: RegelPaket = { version: ECM_VERSION, @@ -415,15 +428,18 @@ export const STANDARD_REGELPAKET: RegelPaket = { "Warranty": [ { id: "garanti_miltal", rubrik: "Mileage documented", krav: "miltal", detaljVidBrist: "Warranty cases require a documented odometer reading." }, { id: "garanti_historik", rubrik: "Service history checked", krav: "historik", detaljVidBrist: "Warranty cases require a checked service history." }, - { id: "garanti_claim", rubrik: "Claim or warranty number recorded", krav: "claim", detaljVidBrist: "State the claim or warranty number (read from the work order)." }, + { id: "garanti_claim", rubrik: "Claim or warranty number recorded", krav: "claim", detaljVidBrist: "State the claim or warranty number — from the scanned work order or the recorded payer." }, + { id: "garanti_betalare", rubrik: "Payer established", krav: "betalare", detaljVidBrist: "Record the payer — track, name and reference — before closing." }, ], Goodwill: [ { id: "goodwill_miltal", rubrik: "Mileage documented", krav: "miltal", detaljVidBrist: "Goodwill cases require a documented odometer reading." }, { id: "goodwill_historik", rubrik: "Service history checked", krav: "historik", detaljVidBrist: "Goodwill cases require a checked service history." }, + { id: "goodwill_godkannande", rubrik: "Grantor's approval recorded", krav: "godkannande", detaljVidBrist: "Goodwill requires the grantor's documented approval before closing." }, ], "Insurance": [ { id: "forsakring_skadenummer", rubrik: "Claim number recorded", krav: "skadenummer", detaljVidBrist: "Insurance cases require a claim number (read from the work order)." }, { id: "forsakring_bildbevis", rubrik: "Photographic evidence present", krav: "foto", detaljVidBrist: "Insurance cases require photographic documentation." }, + { id: "forsakring_betalare", rubrik: "Payer established", krav: "betalare", detaljVidBrist: "Record the insurer — name and claim reference — before closing." }, ], "Complaint": [ { id: "reklamation_historik", rubrik: "History and previous attempts checked", krav: "historik", detaljVidBrist: "Complaint cases require a checked history (previous repairs and attempts)." }, @@ -431,6 +447,21 @@ export const STANDARD_REGELPAKET: RegelPaket = { "Used-vehicle warranty": [ { id: "begagnat_miltal", rubrik: "Mileage documented", krav: "miltal", detaljVidBrist: "Used-vehicle warranty cases require a documented odometer reading." }, ], + "Body damage warranty": [ + { id: "vagnskada_betalare", rubrik: "Payer established", krav: "betalare", detaljVidBrist: "Record the warranty administrator — name and reference — before closing." }, + { id: "vagnskada_miltal", rubrik: "Mileage documented", krav: "miltal", detaljVidBrist: "Body damage warranty cases require a documented odometer reading." }, + { id: "vagnskada_bildbevis", rubrik: "Photographic evidence present", krav: "foto", detaljVidBrist: "Body damage warranty cases require photographs of the damage." }, + ], + "Extended warranty": [ + { id: "forlangd_betalare", rubrik: "Payer established", krav: "betalare", detaljVidBrist: "Record the warranty provider — name and reference — before closing." }, + { id: "forlangd_claim", rubrik: "Claim authorization recorded", krav: "claim", detaljVidBrist: "External warranties require a claim or authorization number before repair is reimbursed." }, + { id: "forlangd_miltal", rubrik: "Mileage documented", krav: "miltal", detaljVidBrist: "Extended warranty cases require a documented odometer reading." }, + { id: "forlangd_historik", rubrik: "Service history checked", krav: "historik", detaljVidBrist: "Extended warranty terms often condition cover on service history — check and document it." }, + ], + "Leasing or fleet": [ + { id: "leasing_betalare", rubrik: "Payer established", krav: "betalare", detaljVidBrist: "Record the leasing or fleet company — name and reference — before closing." }, + { id: "leasing_miltal", rubrik: "Mileage documented", krav: "miltal", detaljVidBrist: "Leasing and fleet cases require a documented odometer reading." }, + ], }, undantagsorsaker: UNDANTAGSORSAKER, orsakskategorier: ORSAKSKATEGORIER, diff --git a/felsokning/app/src/pages/felsokning/ArendeSida.tsx b/felsokning/app/src/pages/felsokning/ArendeSida.tsx index 5010a67..4c1dea9 100644 --- a/felsokning/app/src/pages/felsokning/ArendeSida.tsx +++ b/felsokning/app/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 { KUNDBESLUT_LABEL, KVALITETSKONTROLL_LABEL, TIDKATEGORI_LABEL, TILLFORLITLIGHET_LABEL, handelseRubrik } from "@/felsokning/domain"; +import type { Arende, Betalarspar, Handelse, TidKategori, Tillforlitlighet } from "@/felsokning/domain"; +import { BETALARSPAR_LABEL, 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 { fasFor, klaraFaser } from "../../../../services/gemensam/faser.mjs"; @@ -1444,6 +1444,19 @@ function GuideFlik({ )} + {/* FGS-1.0: betalaren avgör beviskraven — grinden kräver spår, namn + och referens för de ärendetyper där någon annan än kunden + betalar. Panelen visas när ärendetypen kräver den eller när en + betalare redan registrerats. */} + {(BETALARTYPER.includes(arendetyp(arende)) || + arende.handelser.some((p) => p.handelse.typ === "betalare")) && ( + + )} + + {/* Teknisk eskalering (DISS-mönstret): en öppnad förfrågan utan + dokumenterat svar spärrar avslutet. */} + + {!kanAvslutas && }
setVisaOverlamning(true)}> @@ -1461,6 +1474,125 @@ function GuideFlik({ ); } +// Ärendetyper där någon annan än kunden ersätter arbetet — betalaren ska +// vara fastställd innan grinden släpper avslutet (FGS-1.0 §1/§18). +const BETALARTYPER: readonly string[] = [ + "Warranty", + "Goodwill", + "Insurance", + "Used-vehicle warranty", + "Body damage warranty", + "Extended warranty", + "Leasing or fleet", +]; + +function BetalarePanel({ arende, skicka }: { arende: Arende; skicka: (h: Handelse) => void }) { + const [spar, setSpar] = useState("fabriksgaranti"); + const [namn, setNamn] = useState(""); + const [referens, setReferens] = useState(""); + const [godkannande, setGodkannande] = useState(""); + const senaste = [...arende.handelser].reverse().find((p) => p.handelse.typ === "betalare")?.handelse; + return ( + + {senaste?.typ === "betalare" && ( +

+ Recorded: {BETALARSPAR_LABEL[senaste.spar]} — {senaste.namn} + {senaste.referens ? ` (ref ${senaste.referens})` : ""} + {senaste.godkannande ? ` — approval ${senaste.godkannande}` : ""} +

+ )} +
+ + + + +
+ { + skicka({ + typ: "betalare", + spar, + namn: namn.trim(), + ...(referens.trim() ? { referens: referens.trim() } : {}), + ...(godkannande.trim() ? { godkannande: godkannande.trim() } : {}), + }); + setNamn(""); + setReferens(""); + setGodkannande(""); + }} + > + Record payer + +
+ ); +} + +function EskaleringPanel({ arende, skicka }: { arende: Arende; skicka: (h: Handelse) => void }) { + const [text, setText] = useState(""); + const [kanal, setKanal] = useState(""); + const [referens, setReferens] = useState(""); + // Samma matchning som grinden: öppnade per referens, besvarade tar bort. + const oppna = new Map(); + for (const post of arende.handelser) { + const h = post.handelse; + if (h.typ !== "eskalering") continue; + const nyckel = (h.referens ?? "").trim(); + if (h.status === "oppnad") oppna.set(nyckel, h.beskrivning); + else if (oppna.has(nyckel)) oppna.delete(nyckel); + else if (nyckel === "" && oppna.size === 1) oppna.clear(); + } + const skickaMed = (status: "oppnad" | "besvarad") => { + skicka({ + typ: "eskalering", + status, + beskrivning: text.trim(), + ...(referens.trim() ? { referens: referens.trim() } : {}), + ...(kanal.trim() ? { kanal: kanal.trim() } : {}), + }); + setText(""); + setKanal(""); + setReferens(""); + }; + return ( + + {oppna.size > 0 && ( +

+ Open, awaiting answer:{" "} + {[...oppna.entries()].map(([r, b]) => (r ? `${r} — ${b}` : b)).join(" · ")} +

+ )} +
+ + + +
+
+ skickaMed("oppnad")}> + Open escalation + + skickaMed("besvarad")}> + Document the answer + +
+
+ ); +} + /** * Varför avslut är spärrat — i klartext, på skärmen, medan arbetet pågår. * diff --git a/felsokning/docs/GARANTISTANDARD-FGS.md b/felsokning/docs/GARANTISTANDARD-FGS.md new file mode 100644 index 0000000..44d98c1 --- /dev/null +++ b/felsokning/docs/GARANTISTANDARD-FGS.md @@ -0,0 +1,75 @@ +# Garantistandarden FGS-1.0 kartlagd mot plattformen + +**ALVA 3.6 · 2026-08-06 · ALVA-DOC-0011** + +Underlaget är den sammanställda Felsöknings- och Garantistandarden +FGS-1.0: ett gemensamt flöde för fyra betalande parter — fabriksgaranti, +vagnskade-/bilskadegaranti, försäkringsbolag och externa garantigivare — +byggt på offentligt verifierbara källor (EU:s konsument- och +konkurrensregler, Volkswagen Groups offentliga garanti- och +revisionsunderlag kring ODIS/GFF/DISS/SAGA2, svenska försäkringsbolags +och garantiadministratörers publicerade flöden). + +Detta dokument bokför hur standarden förhåller sig till plattformen: +vad som redan var uppfyllt, vad som infördes med ALVA 3.6, och var +gränsen mot tillverkarnas egna system går. Grundhållningen är densamma +som FGS §18: ett ärende som inte kan granskas i efterhand utan +kompletteringar är inte klart. + +--- + +## 1. Uppfyllt före denna utgåva + +| FGS | Plattformens motsvarighet | +| --- | --- | +| §4 Mottagning (VIN, regnr, miltal, datum, servicehistorik) | Objektidentifiering med registerslagning, mätarställning in/ut med fotokrav, historikkontroll med motiveringstvång vid nej — allt grindat | +| §5 Kundens beskrivning ordagrant | Felbeskrivningen är en egen händelse; symptomsteget frågar vad/när/var och skriver aldrig om kundens ord | +| §6 Reproducering före diagnos | Symptomverifiering med ja/delvis/nej och motivering — grindkrav sedan ALVA 1.x | +| §7 Diagnos, §8 teknisk verifiering | Metodikbiblioteket (16 metodiker, 176 kontroller efter fackgranskningen i ALVA-DOC-0009), evidenskrav på varje kontroll | +| §10 Complaint–Cause–Correction | Felbeskrivning → felorsak (avvikelse, orsaker, underlag, konfidens) → åtgärd + kvalitetskontroll "symptomet verifierat avhjälpt" — och slutsatsen (ALVA-RULE-200) tvingar fram *varför* | +| §12/15 Provkörning, slutkontroll | Provkörningssteg med dokumentationskrav; kvalitetskontrollen är grindad | +| §17 Revisionssäkring | Append-only-logg, hashkedja, förseglat avslut, spårbarhetspaket — varje fråga i FGS §17 besvaras ur loggen | +| Miltal/historik/claim/skadenummer/foto per ärendetyp | Datadrivet regelpaket (ECM Knowledge Library), signerat vid extern distribution | + +## 2. Infört med ALVA 3.6 + +| FGS | Åtgärd | +| --- | --- | +| §1/§3 Ärendeklassificering per betalare | Händelsen `betalare`: spår (fabriksgaranti, vagnskadegaranti, försäkring, extern garanti, leasing/fleet, goodwill, kund), namn, referens, godkännande. Grindkravet `betalare` för alla spår där någon annan än kunden betalar | +| §3 Typ B/C/D saknade | Tre nya ärendetyper med egna regelpaketskrav: Body damage warranty, Extended warranty, Leasing or fleet | +| §8 DISS-mönstret | Händelsen `eskalering` (öppnad/besvarad, kanal, referens). En öppnad eskalering utan dokumenterat svar spärrar avslutet, matchad per referens — "invänta svar" är en grindregel, inte ett råd | +| §11/§12 Claim-/godkännandenummer | Claim- och skadenummer kan komma ur den registrerade betalaren, inte bara ur skannad arbetsorder. Goodwill och externa garantigivare kräver dokumenterat godkännande (`godkannande`-kravet) före avslut — FGS §12: "förhandsbesked innan reparation fortsätter" | +| §13 Reservdelar | Händelsen `reservdel`: artikelnummer, serienummer, batch, beskrivning och markering att den demonterade delen sparas i väntan på garantibeslut | +| Delningsminimering | Betalare, eskalering och reservdel är interna i delningsfiltret: kunden ser åtgärden, inte förhandlingen med betalaren | + +**Fynd under arbetet, åtgärdat och regressionstestat:** serverns +ärendetypskrav var en tyst no-op. Servern matade den distribuerade +paketformen (`arendetypRegler`) in i en grindfunktion som bara läste +den slimmade formen (`arendetyper[typ].krav`) — garanti- och +försäkringskraven upprätthölls därmed bara som råd i klienten. Det är +C-2-mönstret från revision 1, återuppstånden genom ett filformat. +Grinden läser nu båda formerna, och regressionstestet låser att den +distribuerade formen faktiskt spärrar på servern. + +## 3. Utanför plattformen — med avsikt + +| FGS | Gräns | +| --- | --- | +| ODIS/GFF-körning, verktygsversioner inom 14 dagar | Tillverkarens verktyg och versionspolicy. ALVA dokumenterar diagnosens resultat (loggar, foton, mätvärden som evidens) men kör inte tillverkardiagnos. Verktygsversion kan antecknas i mätvärdets beskrivning; ett eget versionsfält bokförs som kandidat till nästa schemarevision | +| DISS-registrering, SAGA2-claimskapande | Tillverkarsystem bakom återförsäljaravtal. ALVA bär eskaleringsspåret och det kompletta bevispaket en SAGA2-claim (eller en extern garantigivares claim) kräver — den skapar inte claimen. Det är samma gräns som fakturamodellen: underlag, inte betalningsflöde | +| §15 Avslagsorsakerna | Var och en motsvarar ett grindkrav eller en evidensregel som redan spärrar; listan är med andra ord en beskrivning av vad grinden finns till för | +| Juridiska ramar (EU-garantiregler, reklamationsrätt) | Reglerar avtal mellan parter, inte verkstadens dokumentation. Noterbart: att garantin inte får villkoras av märkesbunden service är ett skäl att historikkravet formuleras som *kontrollerad*, aldrig som *utförd hos auktoriserad verkstad* | + +## 4. Verifiering + +Betalar- och eskaleringskraven mutationsprövade i grindtesterna +(öppnad spärrar/besvarad släpper, referensmatchning, betalare med och +utan namn/godkännande, claim ur betalare respektive arbetsorder, den +distribuerade paketformens regression). Klientens och serverns +regelpaket låsta mot varandra av befintligt likhetstest; +delningsfiltrets tillåtelselista täcker de nya händelsetyperna via +schematestet. Full kontrollslinga i commit-historiken. + +--- + +*ALVA-DOC-0011 · FGS-1.0 ⇄ ALVA · ALVA bär bevispaketet — tillverkarsystemen ersätts inte* diff --git a/felsokning/services/gemensam/grind.mjs b/felsokning/services/gemensam/grind.mjs index b6a9aee..490b95f 100644 --- a/felsokning/services/gemensam/grind.mjs +++ b/felsokning/services/gemensam/grind.mjs @@ -210,6 +210,24 @@ export function grinda(handelser, metodik, sprak = STANDARD) { krav(`sparr_${frageId}`, nyckel, arJakande(svaret?.svar), "grind.sparr.ej_uppfyllt"); } + // Teknisk eskalering (FGS-1.0 §8, DISS-mönstret): en öppnad förfrågan + // till tillverkare eller garantigivare utan dokumenterat svar spärrar + // avslutet. Bulletinerna kräver att svaret inväntas innan ytterligare + // åtgärder görs — ett ärende som stängs med öppen eskalering har inte + // hela orsakskedjan dokumenterad, och garantianspråket kan ogiltigas. + { + const oppna = new Map(); + for (const h of av(handelser, "eskalering")) { + const nyckel = String(h.referens ?? "").trim(); + if (h.status === "oppnad") oppna.set(nyckel, h); + else if (oppna.has(nyckel)) oppna.delete(nyckel); + else if (nyckel === "" && oppna.size === 1) oppna.clear(); + } + krav("eskalering", "grind.eskalering", oppna.size === 0, "grind.eskalering.detalj", { + referens: [...oppna.keys()].map((r) => r || "—").join(", "), + }); + } + // Evidensnivån måste överstiga E0 — annars avslutas ett ärende utan // att någonting alls har dokumenterats. krav( @@ -231,15 +249,33 @@ export function grindaArendetyp(handelser, regelpaket, sprak = STANDARD) { // när typerna hette "Garanti" ska få garantins extra krav även efter // att de bytt språk — se ARENDETYP_ARV. const typ = arendetypNu(av(handelser, "arendetyp_satt").at(-1)?.arendetyp); - const regler = regelpaket?.arendetyper?.[typ]?.krav ?? []; + // Paketet finns i två former: den distribuerade filens rika form + // (arendetypRegler med id/rubrik/detaljVidBrist) och den slimmade + // (arendetyper[typ].krav). Servern matade tidigare in den rika formen + // i en funktion som bara läste den slimmade — varje ärendetypskrav + // blev en tyst no-op på servern, och spärren fanns bara som råd i + // klienten. Det är exakt C-2-mönstret, återuppstått genom ett format. + const regler = [ + ...(regelpaket?.arendetyper?.[typ]?.krav ?? []), + ...(regelpaket?.arendetypRegler?.[typ] ?? []).map((r) => ({ typ: r.krav, rubrik: r.rubrik, detalj: r.detaljVidBrist })), + ]; const hinder = []; + const betalareReferens = () => av(handelser, "betalare").some((h) => String(h.referens ?? "").trim()); const uppfyllt = { miltal: () => av(handelser, "matarstallning").length > 0, historik: () => av(handelser, "historik_kontrollerad").some((h) => h.kontrollerad), foto: () => av(handelser, "foto").length > 0, - claim: () => harReferens(handelser, ["ao_claim", "claim"]), - skadenummer: () => harReferens(handelser, ["ao_skadenummer", "skadenummer"]), + // Referensen kan komma ur den skannade arbetsordern ELLER ur en + // registrerad betalare — alla verkstäder skannar inte (FGS-1.0 §4). + claim: () => harReferens(handelser, ["ao_claim", "claim"]) || betalareReferens(), + skadenummer: () => harReferens(handelser, ["ao_skadenummer", "skadenummer"]) || betalareReferens(), + // FGS-1.0 §1/§18: betalaren fastställd — spår och namn — innan + // ärendet kan stängas i de spår där någon annan än kunden betalar. + betalare: () => av(handelser, "betalare").some((h) => String(h.namn ?? "").trim() && String(h.spar ?? "").trim()), + // Goodwill och externa garantigivare kräver godkännande/claim + // authorization INNAN reparationen ersätts (FGS-1.0 §12). + godkannande: () => av(handelser, "betalare").some((h) => String(h.godkannande ?? "").trim()), }; for (const regel of regler) { @@ -260,6 +296,7 @@ export function grindaArendetyp(handelser, regelpaket, sprak = STANDARD) { id: `arendetyp_${krav}`, nyckel: regel.rubrik ? undefined : "grind.arendetyp.krav", rubrik: regel.rubrik ?? t(sprak, "grind.arendetyp.krav", { krav }), + ...(regel.detalj ? { detalj: regel.detalj } : {}), }); } } diff --git a/felsokning/services/gemensam/handelser.mjs b/felsokning/services/gemensam/handelser.mjs index 0812dd6..87738f5 100644 --- a/felsokning/services/gemensam/handelser.mjs +++ b/felsokning/services/gemensam/handelser.mjs @@ -54,6 +54,14 @@ export const HÄNDELSESCHEMA = { resultat: ["symptomet_borta", "kvarstar", "delvis", "ej_verifierbar"], beskrivning: "text", }, + // FGS-1.0: betalarspåret, eskaleringen och reservdelen är händelser i + // loggen — inte fritext i en kommentar — så att grinden kan räkna på dem. + betalare: { + spar: ["fabriksgaranti", "vagnskadegaranti", "forsakring", "extern_garanti", "leasing", "goodwill", "kund"], + namn: "text", + }, + eskalering: { status: ["oppnad", "besvarad"], beskrivning: "text" }, + reservdel: { artikelnummer: "text", beskrivning: "text" }, kommentar: { text: "text" }, kategori_byte: { kategori: "text" }, inaktivitet_forklarad: { text: "text", minuter: "tal" }, @@ -91,6 +99,9 @@ export const VALFRIA_FÄLT = { matarstallning: ["undantag", "bilagaId", "bilagaHash", "dataUrl"], kontroll_utford: ["resultat", "undantag"], + betalare: ["referens", "godkannande"], + eskalering: ["referens", "kanal"], + reservdel: ["serienummer", "batch", "sparad"], // `kalla` bär härkomsten för värden som kommit in via ett // diagnosprotokoll (ALVA-SPEC-020). Fältet måste vara deklarerat: // integrationen fungerade tidigare bara därför att schemat var öppet. diff --git a/felsokning/services/gemensam/sprak/de.mjs b/felsokning/services/gemensam/sprak/de.mjs index 125c286..46a5344 100644 --- a/felsokning/services/gemensam/sprak/de.mjs +++ b/felsokning/services/gemensam/sprak/de.mjs @@ -54,6 +54,8 @@ export const DE = { "grind.arendetyp.krav": "Anforderung für diese Vorgangsart: {krav}", "grind.sakerhet": "Konfidenzniveau innerhalb dessen, was die Evidenz trägt", "grind.sakerhet.detalj": "Die angegebene Konfidenz ({niva}) übersteigt, was die Evidenz trägt ({tak}). Evidenz ergänzen oder das Niveau senken — ehrliche Unsicherheit ist Information.", + "grind.eskalering": "Technische Eskalationen beantwortet", + "grind.eskalering.detalj": "Eine Eskalation ist offen ({referens}). Die Antwort abwarten und dokumentieren, bevor der Vorgang abgeschlossen wird.", // ---- Abschlussfeststellung (ALVA-RULE-200) ------------------------------ diff --git a/felsokning/services/gemensam/sprak/en.mjs b/felsokning/services/gemensam/sprak/en.mjs index 2bcd226..472e92d 100644 --- a/felsokning/services/gemensam/sprak/en.mjs +++ b/felsokning/services/gemensam/sprak/en.mjs @@ -81,6 +81,8 @@ export const EN = { "grind.arendetyp.krav": "Requirement for this case type: {krav}", "grind.sakerhet": "Confidence level within what the evidence supports", "grind.sakerhet.detalj": "The stated confidence ({niva}) exceeds what the evidence supports ({tak}). Add supporting evidence, or lower the confidence — honest uncertainty is information.", + "grind.eskalering": "Technical escalations answered", + "grind.eskalering.detalj": "An escalation is open ({referens}). Await the answer and document it before the case is closed.", // ---- The closing statement (ALVA-RULE-200) ------------------------------ diff --git a/felsokning/services/gemensam/sprak/es.mjs b/felsokning/services/gemensam/sprak/es.mjs index 9302dc8..cebc9c2 100644 --- a/felsokning/services/gemensam/sprak/es.mjs +++ b/felsokning/services/gemensam/sprak/es.mjs @@ -53,6 +53,8 @@ export const ES = { "grind.arendetyp.krav": "Requisito para este tipo de expediente: {krav}", "grind.sakerhet": "Nivel de confianza dentro de lo que sustenta la evidencia", "grind.sakerhet.detalj": "La confianza indicada ({niva}) supera lo que sustenta la evidencia ({tak}). Añadir evidencia que la sustente, o bajar el nivel — la incertidumbre honesta es información.", + "grind.eskalering": "Escalaciones técnicas respondidas", + "grind.eskalering.detalj": "Hay una escalación abierta ({referens}). Esperar la respuesta y documentarla antes de cerrar el expediente.", // ---- La conclusión (ALVA-RULE-200) -------------------------------------- diff --git a/felsokning/services/gemensam/sprak/fr.mjs b/felsokning/services/gemensam/sprak/fr.mjs index 080876c..273b585 100644 --- a/felsokning/services/gemensam/sprak/fr.mjs +++ b/felsokning/services/gemensam/sprak/fr.mjs @@ -54,6 +54,8 @@ export const FR = { "grind.arendetyp.krav": "Exigence pour ce type de dossier : {krav}", "grind.sakerhet": "Niveau de confiance dans les limites de ce que les preuves soutiennent", "grind.sakerhet.detalj": "La confiance indiquée ({niva}) dépasse ce que les preuves soutiennent ({tak}). Compléter les preuves, ou abaisser le niveau — une incertitude honnête est une information.", + "grind.eskalering": "Escalades techniques traitées", + "grind.eskalering.detalj": "Une escalade est ouverte ({referens}). Attendre la réponse et la documenter avant de clôturer le dossier.", // ---- La conclusion (ALVA-RULE-200) -------------------------------------- diff --git a/felsokning/services/gemensam/sprak/it.mjs b/felsokning/services/gemensam/sprak/it.mjs index 1974ee3..1427205 100644 --- a/felsokning/services/gemensam/sprak/it.mjs +++ b/felsokning/services/gemensam/sprak/it.mjs @@ -53,6 +53,8 @@ export const IT = { "grind.arendetyp.krav": "Requisito per questo tipo di pratica: {krav}", "grind.sakerhet": "Livello di confidenza entro quanto sostiene l’evidenza", "grind.sakerhet.detalj": "La confidenza indicata ({niva}) supera quanto sostiene l’evidenza ({tak}). Integrare l’evidenza, oppure abbassare il livello — l’incertezza onesta è informazione.", + "grind.eskalering": "Escalation tecniche risolte", + "grind.eskalering.detalj": "Un’escalation è aperta ({referens}). Attendere la risposta e documentarla prima di chiudere la pratica.", // ---- La conclusione (ALVA-RULE-200) ------------------------------------- diff --git a/felsokning/services/gemensam/sprak/nl.mjs b/felsokning/services/gemensam/sprak/nl.mjs index 229da32..ce9b89c 100644 --- a/felsokning/services/gemensam/sprak/nl.mjs +++ b/felsokning/services/gemensam/sprak/nl.mjs @@ -53,6 +53,8 @@ export const NL = { "grind.arendetyp.krav": "Eis voor dit zaaktype: {krav}", "grind.sakerhet": "Betrouwbaarheidsniveau binnen wat het bewijs draagt", "grind.sakerhet.detalj": "De opgegeven betrouwbaarheid ({niva}) overstijgt wat het bewijs draagt ({tak}). Het bewijs aanvullen, of het niveau verlagen — eerlijke onzekerheid is informatie.", + "grind.eskalering": "Technische escalaties beantwoord", + "grind.eskalering.detalj": "Er staat een escalatie open ({referens}). Wacht het antwoord af en documenteer het voordat de zaak wordt afgesloten.", // ---- De eindconclusie (ALVA-RULE-200) ----------------------------------- diff --git a/felsokning/services/gemensam/sprak/pl.mjs b/felsokning/services/gemensam/sprak/pl.mjs index 91f31cd..34067e6 100644 --- a/felsokning/services/gemensam/sprak/pl.mjs +++ b/felsokning/services/gemensam/sprak/pl.mjs @@ -55,6 +55,8 @@ export const PL = { "grind.arendetyp.krav": "Wymóg dla tego rodzaju sprawy: {krav}", "grind.sakerhet": "Poziom pewności w granicach tego, co potwierdzają dowody", "grind.sakerhet.detalj": "Podany poziom pewności ({niva}) przekracza to, co potwierdzają dowody ({tak}). Uzupełnić dowody lub obniżyć poziom — uczciwa niepewność to informacja.", + "grind.eskalering": "Eskalacje techniczne z odpowiedzią", + "grind.eskalering.detalj": "Eskalacja jest otwarta ({referens}). Poczekać na odpowiedź i udokumentować ją przed zamknięciem sprawy.", // ---- Wniosek końcowy (ALVA-RULE-200) ------------------------------------ diff --git a/felsokning/services/gemensam/sprak/pt.mjs b/felsokning/services/gemensam/sprak/pt.mjs index 3af7793..8ef6ad6 100644 --- a/felsokning/services/gemensam/sprak/pt.mjs +++ b/felsokning/services/gemensam/sprak/pt.mjs @@ -53,6 +53,8 @@ export const PT = { "grind.arendetyp.krav": "Requisito para este tipo de processo: {krav}", "grind.sakerhet": "Nível de confiança dentro do que a evidência sustenta", "grind.sakerhet.detalj": "A confiança indicada ({niva}) excede o que a evidência sustenta ({tak}). Completar a evidência, ou baixar o nível — a incerteza honesta é informação.", + "grind.eskalering": "Escalonamentos técnicos respondidos", + "grind.eskalering.detalj": "Há um escalonamento em aberto ({referens}). Aguardar a resposta e documentá-la antes de encerrar o processo.", // ---- A conclusão (ALVA-RULE-200) ---------------------------------------- diff --git a/felsokning/services/gemensam/sprak/ro.mjs b/felsokning/services/gemensam/sprak/ro.mjs index eccb156..ed51685 100644 --- a/felsokning/services/gemensam/sprak/ro.mjs +++ b/felsokning/services/gemensam/sprak/ro.mjs @@ -59,6 +59,8 @@ export const RO = { "grind.arendetyp.krav": "Cerință pentru acest tip de caz: {krav}", "grind.sakerhet": "Nivel de încredere în limitele a ceea ce susțin dovezile", "grind.sakerhet.detalj": "Încrederea indicată ({niva}) depășește ceea ce susțin dovezile ({tak}). Completează dovezile sau coboară nivelul — incertitudinea onestă este informație.", + "grind.eskalering": "Escaladări tehnice cu răspuns", + "grind.eskalering.detalj": "O escaladare este deschisă ({referens}). Așteaptă răspunsul și documentează-l înainte de închiderea cazului.", // ---- Concluzia finală (ALVA-RULE-200) ----------------------------------- diff --git a/felsokning/services/gemensam/sprak/sv.mjs b/felsokning/services/gemensam/sprak/sv.mjs index 3a6b0ec..5630139 100644 --- a/felsokning/services/gemensam/sprak/sv.mjs +++ b/felsokning/services/gemensam/sprak/sv.mjs @@ -52,6 +52,8 @@ export const SV = { "grind.arendetyp.krav": "Krav för ärendetypen: {krav}", "grind.sakerhet": "Konfidensnivån inom vad underlaget bär", "grind.sakerhet.detalj": "Angiven konfidens ({niva}) överstiger vad underlaget bär ({tak}). Komplettera underlaget, eller sänk nivån — ärlig osäkerhet är information.", + "grind.eskalering": "Tekniska eskaleringar besvarade", + "grind.eskalering.detalj": "En eskalering är öppen ({referens}). Invänta svaret och dokumentera det innan ärendet avslutas.", // ---- Slutsatsen (ALVA-RULE-200) ----------------------------------------- diff --git a/felsokning/services/gemensam/version.mjs b/felsokning/services/gemensam/version.mjs index 83dbebf..94f8bdd 100644 --- a/felsokning/services/gemensam/version.mjs +++ b/felsokning/services/gemensam/version.mjs @@ -21,6 +21,20 @@ // TÜV-härdningen ändrade vad loggen garanterar. export const UTGAVOR = [ + { + version: "3.6", + datum: "2026-08-06", + commit: "4dea6d2", + rubrik: "Betalarspåret: beviskraven följer den som betalar", + andringar: [ + "FGS-1.0 (garantistandarden) inarbetad: betalaren är en händelse i loggen — spår, namn, claim-/skadereferens och godkännande — och grinden kräver den för de ärendetyper där någon annan än kunden betalar. Tre nya ärendetyper: vagnskadegaranti, förlängd garanti och leasing/fleet, med egna regelpaketskrav.", + "Eskaleringsspärren (DISS-mönstret): en öppnad teknisk förfrågan till tillverkare eller garantigivare utan dokumenterat svar spärrar avslutet, matchad per referens. Bulletinernas \"invänta svar\" är nu en grindregel, inte ett råd.", + "Ett verkligt fynd på vägen: serverns ärendetypskrav var en tyst no-op — servern matade in den distribuerade paketformen i en funktion som bara läste den slimmade, så garanti- och försäkringskraven fanns bara som råd i klienten. C-2-mönstret, återuppstånden genom ett format. Grinden läser nu båda formerna, med regressionstest.", + "Claim- och skadenummer kan komma ur den registrerade betalaren, inte bara ur skannad arbetsorder. Goodwill och externa garantigivare kräver dokumenterat godkännande före avslut.", + "Reservdelshändelsen: artikelnummer, serienummer, batch och markering att den demonterade delen sparas i väntan på garantibeslut. Betalare, eskalering och reservdel är interna i delningsfiltret — kunden ser åtgärden, inte förhandlingen.", + "Kartläggningen av hela FGS-1.0 mot plattformen bokförd i ALVA-DOC-0011, med ärlig gräns: ODIS/DISS/SAGA2 är tillverkarsystem — ALVA bär bevispaketet och eskaleringsspåret, den ersätter dem inte.", + ], + }, { version: "3.5", datum: "2026-08-06", diff --git a/felsokning/services/plattform/ecm-regler.json b/felsokning/services/plattform/ecm-regler.json index b4293b0..ee3faa3 100644 --- a/felsokning/services/plattform/ecm-regler.json +++ b/felsokning/services/plattform/ecm-regler.json @@ -1,5 +1,5 @@ { - "version": "2.0", + "version": "2.1", "beskrivning": "ECM Knowledge Library — serverdistribuerat regelpaket. Uppdateras utan appändring: redigera filen (eller montera en ConfigMap över den i klustret) och starta om tjänsten. Klienten hämtar paketet vid inloggning och faller tillbaka till sitt inbyggda standardpaket offline.", "arendetypRegler": { "Warranty": [ @@ -19,7 +19,13 @@ "id": "garanti_claim", "rubrik": "Claim or warranty number recorded", "krav": "claim", - "detaljVidBrist": "State the claim or warranty number (read from the work order)." + "detaljVidBrist": "State the claim or warranty number — from the scanned work order or the recorded payer." + }, + { + "id": "garanti_betalare", + "rubrik": "Payer established", + "krav": "betalare", + "detaljVidBrist": "Record the payer — track, name and reference — before closing." } ], "Goodwill": [ @@ -34,6 +40,12 @@ "rubrik": "Service history checked", "krav": "historik", "detaljVidBrist": "Goodwill cases require a checked service history." + }, + { + "id": "goodwill_godkannande", + "rubrik": "Grantor's approval recorded", + "krav": "godkannande", + "detaljVidBrist": "Goodwill requires the grantor's documented approval before closing." } ], "Insurance": [ @@ -48,6 +60,12 @@ "rubrik": "Photographic evidence present", "krav": "foto", "detaljVidBrist": "Insurance cases require photographic documentation." + }, + { + "id": "forsakring_betalare", + "rubrik": "Payer established", + "krav": "betalare", + "detaljVidBrist": "Record the insurer — name and claim reference — before closing." } ], "Complaint": [ @@ -65,6 +83,66 @@ "krav": "miltal", "detaljVidBrist": "Used-vehicle warranty cases require a documented odometer reading." } + ], + "Body damage warranty": [ + { + "id": "vagnskada_betalare", + "rubrik": "Payer established", + "krav": "betalare", + "detaljVidBrist": "Record the warranty administrator — name and reference — before closing." + }, + { + "id": "vagnskada_miltal", + "rubrik": "Mileage documented", + "krav": "miltal", + "detaljVidBrist": "Body damage warranty cases require a documented odometer reading." + }, + { + "id": "vagnskada_bildbevis", + "rubrik": "Photographic evidence present", + "krav": "foto", + "detaljVidBrist": "Body damage warranty cases require photographs of the damage." + } + ], + "Extended warranty": [ + { + "id": "forlangd_betalare", + "rubrik": "Payer established", + "krav": "betalare", + "detaljVidBrist": "Record the warranty provider — name and reference — before closing." + }, + { + "id": "forlangd_claim", + "rubrik": "Claim authorization recorded", + "krav": "claim", + "detaljVidBrist": "External warranties require a claim or authorization number before repair is reimbursed." + }, + { + "id": "forlangd_miltal", + "rubrik": "Mileage documented", + "krav": "miltal", + "detaljVidBrist": "Extended warranty cases require a documented odometer reading." + }, + { + "id": "forlangd_historik", + "rubrik": "Service history checked", + "krav": "historik", + "detaljVidBrist": "Extended warranty terms often condition cover on service history — check and document it." + } + ], + "Leasing or fleet": [ + { + "id": "leasing_betalare", + "rubrik": "Payer established", + "krav": "betalare", + "detaljVidBrist": "Record the leasing or fleet company — name and reference — before closing." + }, + { + "id": "leasing_miltal", + "rubrik": "Mileage documented", + "krav": "miltal", + "detaljVidBrist": "Leasing and fleet cases require a documented odometer reading." + } ] }, "undantagsorsaker": [ diff --git a/felsokning/services/plattform/integrationstest.sh b/felsokning/services/plattform/integrationstest.sh index 5eb6558..051b791 100755 --- a/felsokning/services/plattform/integrationstest.sh +++ b/felsokning/services/plattform/integrationstest.sh @@ -214,8 +214,8 @@ kontroll "statistiken är organisationsknuten data" "$(echo "$STAT" | falt '.ors # 10b. ECM Knowledge Library: regelpaketet serveras till inloggade klienter REGLER=$(curl -s "$BAS/api/ecm/regler" -H "Authorization: Bearer $TOKEN_J") -kontroll "regelpaketet serveras" "$(echo "$REGLER" | falt .version)" "2.0" -kontroll "regelpaketet innehåller garantiregler" "$(echo "$REGLER" | falt '.arendetypRegler.Warranty.length')" "3" +kontroll "regelpaketet serveras" "$(echo "$REGLER" | falt .version)" "2.1" +kontroll "regelpaketet innehåller garantiregler" "$(echo "$REGLER" | falt '.arendetypRegler.Warranty.length')" "4" # 10c. Publikt kundgodkännande — den enda skrivande publika vägen # Ärendets ursprungliga delningskod saknar registrerad nivå och får diff --git a/felsokning/services/plattform/server.mjs b/felsokning/services/plattform/server.mjs index fb313ca..b2e59ce 100644 --- a/felsokning/services/plattform/server.mjs +++ b/felsokning/services/plattform/server.mjs @@ -386,7 +386,10 @@ export const DELBART_PARTNER = [...DELBART_KUND, "hypotes"]; // Aldrig utanför organisationen: arbetsledning, arbetsmaterial och // underlag som kan läsas som konstateranden. -export const ENDAST_INTERNT = ["kategori_byte", "hypotes", "ai_svar", "ansvarig_satt", "arbetsorder_skannad"]; +// Betalare, eskalering och reservdel är interna: claimreferenser, +// fabrikskorrespondens och serienummer hör till garantiakten, inte till +// kundens delningslänk — kunden ser åtgärden, inte förhandlingen (FGS-1.0). +export const ENDAST_INTERNT = ["kategori_byte", "hypotes", "ai_svar", "ansvarig_satt", "arbetsorder_skannad", "betalare", "eskalering", "reservdel"]; export function synligaTyper(niva) { if (niva === "intern") return null; // full insyn — ingen filtrering