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