Betalarspåret: beviskraven följer den som betalar — ALVA 3.6

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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012EQg3rJsrQ1ZNTvkzmQAtt
This commit is contained in:
Claude
2026-08-07 08:55:02 +00:00
parent 4dea6d2818
commit 28bfc29a5c
22 changed files with 530 additions and 16 deletions
@@ -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);
@@ -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([]);
});
});
+42
View File
@@ -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<Betalarspar, string> = {
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}` : ""}`
+36 -5
View File
@@ -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<ComplianceKravTyp, (arende: Arende) => 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,
@@ -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({
</Panel>
)}
{/* 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")) && (
<BetalarePanel arende={arende} skicka={skicka} />
)}
{/* Teknisk eskalering (DISS-mönstret): en öppnad förfrågan utan
dokumenterat svar spärrar avslutet. */}
<EskaleringPanel arende={arende} skicka={skicka} />
{!kanAvslutas && <Avslutshinder hinder={hinder} />}
<div className="grid grid-cols-2 gap-2">
<StorKnapp variant="sekundar" onClick={() => 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<Betalarspar>("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 (
<Panel rubrik="Payer and claim — who reimburses the work">
{senaste?.typ === "betalare" && (
<p className="mb-2 text-[13px]">
<span className="font-semibold text-[#4D5662]">Recorded:</span> {BETALARSPAR_LABEL[senaste.spar]} {senaste.namn}
{senaste.referens ? ` (ref ${senaste.referens})` : ""}
{senaste.godkannande ? ` — approval ${senaste.godkannande}` : ""}
</p>
)}
<div className="mb-2 grid gap-2 sm:grid-cols-2">
<label className="block">
<span className="mb-2 block text-[11px] font-semibold uppercase tracking-[0.08em] text-[#4D5662]">Track</span>
<select
value={spar}
onChange={(e) => setSpar(e.target.value as Betalarspar)}
className="w-full border border-[#4D5662] bg-white px-2 py-2 text-[13px] focus:outline-none"
>
{(Object.keys(BETALARSPAR_LABEL) as Betalarspar[]).map((k) => (
<option key={k} value={k}>
{BETALARSPAR_LABEL[k]}
</option>
))}
</select>
</label>
<TextFalt label="Payer name" varde={namn} satt={setNamn} platshallare="e.g. the manufacturer, insurer or warranty provider" />
<TextFalt label="Claim / policy / damage number" varde={referens} satt={setReferens} platshallare="Claim, policy or damage reference" />
<TextFalt label="Approval reference" varde={godkannande} satt={setGodkannande} platshallare="Authorization — goodwill and external warranties" />
</div>
<StorKnapp
variant="sekundar"
disabled={!namn.trim()}
onClick={() => {
skicka({
typ: "betalare",
spar,
namn: namn.trim(),
...(referens.trim() ? { referens: referens.trim() } : {}),
...(godkannande.trim() ? { godkannande: godkannande.trim() } : {}),
});
setNamn("");
setReferens("");
setGodkannande("");
}}
>
Record payer
</StorKnapp>
</Panel>
);
}
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<string, string>();
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 (
<Panel rubrik="Technical escalation — manufacturer or warranty provider">
{oppna.size > 0 && (
<p className="mb-2 border-l-2 border-[#8A5A00] pl-4 text-[13px]">
<span className="font-semibold text-[#8A5A00]">Open, awaiting answer:</span>{" "}
{[...oppna.entries()].map(([r, b]) => (r ? `${r}${b}` : b)).join(" · ")}
</p>
)}
<div className="mb-2 grid gap-2 sm:grid-cols-3">
<TextFalt label="Description" varde={text} satt={setText} platshallare="What was asked, or what the answer says" />
<TextFalt label="Channel" varde={kanal} satt={setKanal} platshallare="e.g. DISS, technical support" />
<TextFalt label="Reference" varde={referens} satt={setReferens} platshallare="Ticket or query number" />
</div>
<div className="grid grid-cols-2 gap-2">
<StorKnapp variant="sekundar" disabled={!text.trim()} onClick={() => skickaMed("oppnad")}>
Open escalation
</StorKnapp>
<StorKnapp variant="sekundar" disabled={!text.trim() || oppna.size === 0} onClick={() => skickaMed("besvarad")}>
Document the answer
</StorKnapp>
</div>
</Panel>
);
}
/**
* Varför avslut är spärrat — i klartext, på skärmen, medan arbetet pågår.
*
+75
View File
@@ -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 ComplaintCauseCorrection | 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*
+40 -3
View File
@@ -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 } : {}),
});
}
}
@@ -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.
@@ -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) ------------------------------
@@ -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) ------------------------------
@@ -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) --------------------------------------
@@ -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) --------------------------------------
@@ -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 levidenza",
"grind.sakerhet.detalj": "La confidenza indicata ({niva}) supera quanto sostiene levidenza ({tak}). Integrare levidenza, oppure abbassare il livello — lincertezza onesta è informazione.",
"grind.eskalering": "Escalation tecniche risolte",
"grind.eskalering.detalj": "Unescalation è aperta ({referens}). Attendere la risposta e documentarla prima di chiudere la pratica.",
// ---- La conclusione (ALVA-RULE-200) -------------------------------------
@@ -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) -----------------------------------
@@ -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) ------------------------------------
@@ -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) ----------------------------------------
@@ -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) -----------------------------------
@@ -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) -----------------------------------------
+14
View File
@@ -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",
+80 -2
View File
@@ -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": [
@@ -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
+4 -1
View File
@@ -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