From ae5acbed92ec107657d0dc02dd9e23378e5e3ccf Mon Sep 17 00:00:00 2001
From: Claude
Date: Thu, 6 Aug 2026 15:15:35 +0000
Subject: [PATCH] =?UTF-8?q?Fotograferad=20m=C3=A4tarst=C3=A4llning,=20vers?=
=?UTF-8?q?ionshistorik=20och=20tysk=20kravbild=20i=20foten?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---- Mätarställningen ska fotograferas ----------------------------------
Klienten krävde redan fotot: värdefältet visas först efter bilden. Men
GRINDEN gjorde det inte, och en regel som bara finns i gränssnittet är en
vana — inte en spärr. Det är samma förhållande som QUALITET C-2 gällde,
fast åt andra hållet: här var servern svagare, vilket är värre, eftersom
servern är den auktoritativa.
Skillnaden är hela uppgiftens bevisvärde. En inskriven siffra är
teknikerns påstående om vad som stod på mätaren; ett foto visar vad som
stod där. Mätarställningen avgör garanti- och försäkringsfrågor i
efterhand och är den enda uppgift i ärendet någon kan ha ett intresse av
att skriva fel. Evidensgraderingen följer nu med: fotograferad ger E2,
inskriven ger E1 och märks som inskriven i rapporten.
Undantag finns och måste finnas — slocknad display, oåtkomlig timräknare
— men kräver motivering, som det nekade historiksvaret.
---- Versionen räknades aldrig upp --------------------------------------
Systemet påstod "ALVA 1.0" efter tre revisioner, en fakturamodul och en
stängd portal. Versionen sa alltså ingenting om vad man fick.
Historiken är HÄRLEDD UR VAD SOM FAKTISKT LEVERERATS och varje post pekar
på sin commit. En utgåva som inte går att peka på hör inte hemma i listan
— en påhittad ändringslogg är samma obefogade säkerhet som produkten
finns för att undvika. Första siffran höjs bara när GARANTIERNA ändras,
vilket hänt två gånger: 2.0 när fakturan blev ett härlett dokument, 3.0
när TÜV-härdningen ändrade vad loggen garanterar. Nuvarande: 3.1.
Och ett fynd på vägen: ritningsstämpeln läste versionen ur den AKTUELLA
konstanten, så ett ärende stängt under 1.0 visade 3.1 — precis det
stämpelns egen beskrivning säger att den inte ska göra. Avslutshändelsen
bär numera versionen, och stämpeln läser den därifrån. Äldre ärenden
säger uttryckligen "ej registrerad" i stället för att låna dagens siffra.
---- Tysk kravbild i foten ----------------------------------------------
Listan är tysk därför att § 5 DDG är den strängaste och mest utkrävbara i
EU: klarar man den klarar man de andra marknaderna på köpet.
Två fällor som är aktuella just nu, och bägge ser fortfarande ut som
upplysningar:
ODR-LÄNKEN SKA BORT. Förordning 524/2013 upphävdes genom förordning
(EU) 2024/3228 och plattformen stängdes 20 juli 2025. Länken har legat
i tiotusentals fotnoter sedan 2016 och pekar numera ingenstans. Foten
saknar den medvetet.
§ 5 TMG HETER § 5 DDG sedan 14 maj 2024.
Modulen hittar inte på uppgifter. Ett Impressum med påhittad adress är
inte ett halvfärdigt Impressum utan ett vilseledande, och skadan större
än den tomma rutans. Osatta fält redovisas som en åtgärdslista med
rättslig grund, så driften kan spärra en driftsättning på listan i
stället för att upptäcka bristen när brevet kommer.
460 enhetstester · genomgången 4/4 · typkontroll ren.
Co-Authored-By: Claude Opus 5
Claude-Session: https://claude.ai/code/session_012EQg3rJsrQ1ZNTvkzmQAtt
---
felsokning/app/e2e/genomgang.mjs | 2 +
felsokning/app/src/App.tsx | 6 +
felsokning/app/src/alva/system.ts | 6 +-
.../src/felsokning/__tests__/grind.test.ts | 52 ++++-
felsokning/app/src/felsokning/domain.ts | 12 +-
felsokning/app/src/felsokning/ecm.ts | 16 +-
felsokning/app/src/pages/alva/Impressum.tsx | 138 ++++++++++++
felsokning/app/src/pages/alva/Ram.tsx | 42 +++-
felsokning/app/src/pages/alva/Utgavor.tsx | 75 +++++++
.../app/src/pages/felsokning/ArendeSida.tsx | 11 +-
.../services/gemensam/foretagsuppgifter.mjs | 198 ++++++++++++++++++
felsokning/services/gemensam/grind.mjs | 35 +++-
felsokning/services/gemensam/handelser.mjs | 5 +-
felsokning/services/gemensam/version.mjs | 134 ++++++++++++
14 files changed, 714 insertions(+), 18 deletions(-)
create mode 100644 felsokning/app/src/pages/alva/Impressum.tsx
create mode 100644 felsokning/app/src/pages/alva/Utgavor.tsx
create mode 100644 felsokning/services/gemensam/foretagsuppgifter.mjs
create mode 100644 felsokning/services/gemensam/version.mjs
diff --git a/felsokning/app/e2e/genomgang.mjs b/felsokning/app/e2e/genomgang.mjs
index 3370afc..b6d4b1b 100644
--- a/felsokning/app/e2e/genomgang.mjs
+++ b/felsokning/app/e2e/genomgang.mjs
@@ -484,6 +484,8 @@ try {
"/alva/portal/garantier",
"/alva/portal/forsakring",
"/alva/portal/support",
+ "/alva/impressum",
+ "/alva/utgavor",
]) {
await sida.goto(`${BAS}/#${vag}`, { waitUntil: "networkidle" });
await sida.waitForTimeout(400);
diff --git a/felsokning/app/src/App.tsx b/felsokning/app/src/App.tsx
index 9145cb6..ebc0623 100644
--- a/felsokning/app/src/App.tsx
+++ b/felsokning/app/src/App.tsx
@@ -30,6 +30,8 @@ import AlvaAnalys from "./pages/alva/Analys";
import AlvaFakturor from "./pages/alva/Fakturor";
import AlvaIntegration from "./pages/alva/Integration";
import AlvaGarantier from "./pages/alva/Garantier";
+import AlvaImpressum from "./pages/alva/Impressum";
+import AlvaUtgavor from "./pages/alva/Utgavor";
import AlvaForsakring from "./pages/alva/Forsakring";
import AlvaSupport from "./pages/alva/Support";
import { Portalvakt } from "./pages/alva/Portalvakt";
@@ -69,6 +71,10 @@ function AppContent() {
} />
} />
} />
+ {/* Lagstadgad information. Nås från foten på varje sida — det är
+ en del av kravet, inte en placering. */}
+ } />
+ } />
{/* Portalen är stängd utan giltig plattformssession — se
Portalvakt. Utan konfigurerad plattform finns ingen session
att kräva, och vyerna är då märkta som demonstration. */}
diff --git a/felsokning/app/src/alva/system.ts b/felsokning/app/src/alva/system.ts
index 274660b..a47b79c 100644
--- a/felsokning/app/src/alva/system.ts
+++ b/felsokning/app/src/alva/system.ts
@@ -161,7 +161,11 @@ export const DOKUMENTKLASSER = [
// inte Pro Max. En version är ett tillstånd av systemet, inte en
// marknadsföringshändelse.
-export const PLATTFORMSVERSION = "ALVA 1.0";
+// Versionen bor i services/gemensam/version.mjs tillsammans med
+// historiken. Den stod tidigare som en sträng här och räknades aldrig
+// upp: systemet påstod "ALVA 1.0" efter tre revisioner, en fakturamodul
+// och en stängd portal, och versionen sa alltså ingenting om vad man fick.
+export { PLATTFORMSVERSION, UTGAVOR, NUVARANDE } from "../../../services/gemensam/version.mjs";
// ---- Rutnät -----------------------------------------------------------
//
diff --git a/felsokning/app/src/felsokning/__tests__/grind.test.ts b/felsokning/app/src/felsokning/__tests__/grind.test.ts
index 983db54..f029e4c 100644
--- a/felsokning/app/src/felsokning/__tests__/grind.test.ts
+++ b/felsokning/app/src/felsokning/__tests__/grind.test.ts
@@ -20,8 +20,10 @@ function komplettLogg(extra: Record[] = []) {
return [
{ typ: "objekt_identifierat", objekt: { identifierare: "ABC123" } },
{ typ: "historik_kontrollerad", kontrollerad: true },
- { typ: "matarstallning", lage: "ingaende", varde: "14 200" },
- { typ: "matarstallning", lage: "utgaende", varde: "14 205" },
+ // Fotot är en del av kravet, inte en utsmyckning: en inskriven siffra
+ // är teknikerns påstående om vad som stod på mätaren.
+ { typ: "matarstallning", lage: "ingaende", varde: "14 200", bilagaId: "bil-in" },
+ { typ: "matarstallning", lage: "utgaende", varde: "14 205", bilagaId: "bil-ut" },
{ typ: "reproducering", status: "ja", beskrivning: "Reproducerat vid 88 km/h." },
{ typ: "felorsak", avvikelse: "Obalans", orsaker: ["Normalt slitage"], underlag: ["Mätvärde"], sakerhet: "hog" },
{ typ: "atgard_utford", beskrivning: "Balanserade hjulen.", utford: true },
@@ -450,3 +452,49 @@ describe("grinden räknar rätt sorts evidens per kontrolltyp", () => {
expect(hinder).toContain("metodik_kontroller");
});
});
+
+// ---- Mätarställningen ska vara fotograferad ----------------------------
+//
+// Klienten krävde redan fotot. Grinden gjorde det inte, och en regel som
+// bara finns i gränssnittet är en vana — inte en spärr. Det är samma
+// förhållande som C-2 gällde, fast åt andra hållet: här var det servern
+// som var svagare, vilket är värre, eftersom servern är den auktoritativa.
+describe("mätarställningen ska vara fotograferad, inte bara inskriven", () => {
+ const utanFoto = () =>
+ komplettLogg().map((h) => (h.typ === "matarstallning" ? { ...h, bilagaId: undefined } : h));
+
+ it("en inskriven siffra utan foto spärrar avslutet", () => {
+ const hinder = grinda(utanFoto(), GENERISK).map((h) => h.id);
+ expect(hinder).toContain("matarstallning_ingaende");
+ expect(hinder).toContain("matarstallning_utgaende");
+ });
+
+ it("hindret säger vad som saknas och vad man gör åt det", () => {
+ const h = grinda(utanFoto(), GENERISK).find((x) => x.id === "matarstallning_ingaende");
+ expect(h.detalj).toMatch(/fotografera/i);
+ });
+
+ it("ett dataUrl-foto duger lika bra som en bilaga", () => {
+ // Offline-läget lagrar bilden inline tills den synkats.
+ const inline = komplettLogg().map((h) =>
+ h.typ === "matarstallning" ? { ...h, bilagaId: undefined, dataUrl: "data:image/png;base64,AA" } : h,
+ );
+ expect(grinda(inline, GENERISK)).toEqual([]);
+ });
+
+ it("ett motiverat undantag ersätter fotot — trasig display, oåtkomlig timräknare", () => {
+ const undantag = komplettLogg().map((h) =>
+ h.typ === "matarstallning"
+ ? { ...h, bilagaId: undefined, undantag: "Displayen är slocknad, avläst via diagnosverktyg." }
+ : h,
+ );
+ expect(grinda(undantag, GENERISK)).toEqual([]);
+ });
+
+ it("ett tomt undantag räcker inte — då vore det en väg runt kravet", () => {
+ const tomt = komplettLogg().map((h) =>
+ h.typ === "matarstallning" ? { ...h, bilagaId: undefined, undantag: " " } : h,
+ );
+ expect(grinda(tomt, GENERISK).map((h) => h.id)).toContain("matarstallning_ingaende");
+ });
+});
diff --git a/felsokning/app/src/felsokning/domain.ts b/felsokning/app/src/felsokning/domain.ts
index 970e612..afbd1f8 100644
--- a/felsokning/app/src/felsokning/domain.ts
+++ b/felsokning/app/src/felsokning/domain.ts
@@ -229,7 +229,17 @@ export type Handelse =
*/
orsakFastställd?: boolean;
}
- | { typ: "arende_avslutat"; signatur?: string };
+ | {
+ typ: "arende_avslutat";
+ signatur?: string;
+ /**
+ * Versionen som gällde vid avslutet. Ritningsstämpeln läste den ur
+ * den aktuella konstanten, så ett ärende stängt under 1.0 visade
+ * 3.1 — precis det stämpelns egen beskrivning säger att den inte
+ * ska göra.
+ */
+ plattformsversion?: string;
+ };
export interface LoggPost {
id: string;
diff --git a/felsokning/app/src/felsokning/ecm.ts b/felsokning/app/src/felsokning/ecm.ts
index 45dd766..666d639 100644
--- a/felsokning/app/src/felsokning/ecm.ts
+++ b/felsokning/app/src/felsokning/ecm.ts
@@ -119,7 +119,21 @@ export function evidensposter(arende: Arende): Evidenspost[] {
`${h.beskrivning} = ${h.varde}${h.enhet ? ` ${h.enhet}` : ""} (${märkning})`,
);
}
- if (h.typ === "matarstallning" && !h.undantag) lagg(post, "mätarställning", "E2", `${h.lage === "ingaende" ? "In" : "Ut"}: ${h.varde}`);
+ if (h.typ === "matarstallning" && !h.undantag) {
+ // Ett foto på mätaren är E2 — något som visar vad som stod där.
+ // En inskriven siffra utan foto är teknikerns påstående om vad som
+ // stod där, alltså E1. Skillnaden syns i rapporten, eftersom
+ // mätarställningen är det som avgör garanti- och försäkringsfrågor
+ // i efterhand och den enda uppgift någon kan ha ett intresse av att
+ // skriva fel.
+ const fotograferad = Boolean(h.bilagaId || h.dataUrl);
+ lagg(
+ post,
+ "mätarställning",
+ fotograferad ? "E2" : "E1",
+ `${h.lage === "ingaende" ? "In" : "Ut"}: ${h.varde}${fotograferad ? "" : " (inskriven, ej fotograferad)"}`,
+ );
+ }
if (h.typ === "arbetsorder_skannad") lagg(post, "dokument", "E5", `Arbetsorder, ${h.falt.length} fält`);
if (h.typ === "observation") lagg(post, "observation", "E1", h.text);
if (h.typ === "kontroll_utford" && !h.undantag) lagg(post, "kontroll", "E1", h.text);
diff --git a/felsokning/app/src/pages/alva/Impressum.tsx b/felsokning/app/src/pages/alva/Impressum.tsx
new file mode 100644
index 0000000..c59bdda
--- /dev/null
+++ b/felsokning/app/src/pages/alva/Impressum.tsx
@@ -0,0 +1,138 @@
+// ALVA-SPEC-050 · Impressum och lagstadgad information.
+//
+// Sidan visar två saker: uppgifterna, och — så länge de inte är satta —
+// exakt vilka som fattas och med vilken rättslig grund.
+//
+// Att fylla rutorna med påhittade uppgifter vore inte ett halvfärdigt
+// Impressum utan ett vilseledande, och skadan större än den tomma rutans.
+// Därför står det som saknas som en åtgärdslista i stället.
+
+import {
+ AVSKAFFAT,
+ FALT,
+ HANDLINGAR,
+ saknade,
+} from "../../../../services/gemensam/foretagsuppgifter.mjs";
+import { Block, Etikett, FARG, Rubrik, Tabell } from "@/alva/komponenter";
+import { Ram } from "./Ram";
+
+interface Falt {
+ id: string;
+ rubrik: string;
+ grund: string;
+ kravs: boolean;
+ hjalp: string;
+}
+interface Handling {
+ id: string;
+ rubrik: string;
+ grund: string;
+ kravs: boolean;
+ nar: string;
+}
+
+// Driftens uppgifter. Sätts vid driftsättning; tomt betyder tomt.
+const UPPGIFTER: Record = {};
+
+export default function Impressum() {
+ const fattas = saknade(UPPGIFTER) as { id: string; rubrik: string; grund: string }[];
+
+ return (
+
+
+ Legal
+
+ Impressum
+
+
+ Angaben gemäß § 5 DDG. The requirement set is German because it is the strictest and the most
+ enforceable in the EU — meeting it meets the other markets as a by-product, and spares a
+ second review per country.
+
+
+ {fattas.length > 0 && (
+
+
+ The operator’s details have not been configured. They are shown as outstanding rather
+ than filled with placeholder text: an Impressum with an invented address is not an
+ incomplete Impressum but a misleading one, and the harm is greater than an empty field.
+
+ [
+
+ {f.rubrik}
+ ,
+
+ {f.grund}
+ ,
+ ])}
+ />
+
+ )}
+
+
+ [
+
+ {f.rubrik}
+ {!f.kravs && (
+
+ conditional
+
+ )}
+ ,
+
+ {UPPGIFTER[f.id] || "— not set"}
+ ,
+
+ {f.grund}
+ ,
+
+ {f.hjalp}
+ ,
+ ])}
+ />
+
+
+
+ [
+
+ {h.rubrik}
+ ,
+
+ {h.grund}
+ ,
+
+ {h.nar}
+ ,
+ ])}
+ />
+
+
+ {/* Den här rutan är sidans egentliga behållning. Två hänvisningar
+ som legat i tiotusentals fotnoter är numera fel, och bägge ser
+ fortfarande ut som upplysningar. */}
+
+ [
+
+ {a.rubrik}
+ ,
+
+ {a.grund}
+ ,
+
+ {a.text}
+ ,
+ ])}
+ />
+
+
+
+ );
+}
diff --git a/felsokning/app/src/pages/alva/Ram.tsx b/felsokning/app/src/pages/alva/Ram.tsx
index 92e81d8..bcecfb1 100644
--- a/felsokning/app/src/pages/alva/Ram.tsx
+++ b/felsokning/app/src/pages/alva/Ram.tsx
@@ -108,13 +108,43 @@ export function Ram({ children, portal = false }: { children: ReactNode; portal?
{children}
+ {/* Foten bär det som en näringsidkare måste hålla synligt. Listan
+ är byggd mot tyskt krav — § 5 DDG är den strängaste och mest
+ utkrävbara i EU, och den som klarar den klarar de andra på
+ köpet. Se services/gemensam/foretagsuppgifter.mjs.
+
+ Här ligger MEDVETET ingen länk till EU:s ODR-plattform. Den
+ upphävdes genom förordning (EU) 2024/3228 och stängdes den
+ 20 juli 2025; en kvarliggande länk är en död hänvisning som ser
+ ut som en lagstadgad upplysning. */}
- skicka({ typ: "arende_avslutat", signatur: anvandare })}>
+ skicka({ typ: "arende_avslutat", signatur: anvandare, plattformsversion: PLATTFORMSVERSION })}>
Avsluta felsökning
{!kanAvslutas && }
@@ -1423,7 +1424,7 @@ function GuideFlik({
setVisaOverlamning(true)}>
Lämna över arbete
- skicka({ typ: "arende_avslutat", signatur: anvandare })}>
+ skicka({ typ: "arende_avslutat", signatur: anvandare, plattformsversion: PLATTFORMSVERSION })}>
Avsluta felsökning
@@ -2697,7 +2698,11 @@ function Ritningsstampel({ arende }: { arende: Arende }) {
["Beteckning", arendebeteckning(arende.nummer)],
["Regelpaket", paket.version],
["Evidensmodell", `ECM ${ECM_VERSION}`],
- ["Plattform", PLATTFORMSVERSION],
+ // Versionen VID AVSLUTET, inte dagens. Stämpeln läste tidigare den
+ // aktuella konstanten, så ett ärende stängt under 1.0 visade 3.1 —
+ // vilket är exakt det stämpelns beskrivning ovan säger att den inte
+ // ska göra. Ett öppet ärende visar dagens, eftersom det är sant då.
+ ["Plattform", avslutad ? versionVidAvslut(avslutad.handelse) : PLATTFORMSVERSION],
["Status", avslutad ? "Closed" : "In progress"],
["Utgåva", avslutad ? new Date(avslutad.tidpunkt).toISOString().slice(0, 10) : "—"],
];
diff --git a/felsokning/services/gemensam/foretagsuppgifter.mjs b/felsokning/services/gemensam/foretagsuppgifter.mjs
new file mode 100644
index 0000000..3cc2204
--- /dev/null
+++ b/felsokning/services/gemensam/foretagsuppgifter.mjs
@@ -0,0 +1,198 @@
+// ALVA-SPEC-050 · Företagsuppgifter och lagstadgad information.
+//
+// ---- Varför tyskt krav som golv ----------------------------------------
+//
+// Tyskland har den strängaste kravbilden på vad en näringsidkare måste
+// hålla synligt: Impressumspflicht enligt § 5 DDG är namngiven,
+// utkrävbar och avmahnbar på ett sätt som saknar motsvarighet i övriga
+// EU. Bygger man för den listan uppfyller man i praktiken de andra
+// marknadernas krav på köpet, och man slipper en andra genomgång per
+// land. Det är därför listan nedan är tysk och inte svensk.
+//
+// ---- Vad modulen INTE gör -----------------------------------------------
+//
+// Den hittar inte på uppgifter. Ett Impressum med påhittad adress eller
+// påhittat organisationsnummer är inte ett halvfärdigt Impressum utan ett
+// vilseledande, och skadan är större än den tomma rutans.
+//
+// Osatta fält märks därför som osatta, och `saknade()` returnerar dem så
+// att driften kan spärra en driftsättning på listan i stället för att
+// upptäcka den när brevet kommer.
+//
+// ---- Två fällor som är aktuella just nu ---------------------------------
+//
+// ODR-LÄNKEN SKA BORT. Förordning (EU) 524/2013 upphävdes genom
+// förordning (EU) 2024/3228 och plattformen stängdes 20 juli 2025.
+// Länken till ec.europa.eu/odr har legat i tiotusentals fotnoter sedan
+// 2016 och pekar numera ingenstans. Den som låtit den ligga kvar visar
+// upp en död hänvisning som en lagstadgad upplysning.
+//
+// § 5 TMG HETER § 5 DDG. Telemediengesetz ersattes av
+// Digitale-Dienste-Gesetz den 14 maj 2024. Innehållet i plikten är
+// oförändrat; hänvisningen är det inte.
+
+/** Fälten, med sin rättsliga grund. `kravs` styr vad `saknade()` larmar om. */
+export const FALT = [
+ {
+ id: "foretagsnamn",
+ rubrik: "Firma",
+ grund: "§ 5 Abs. 1 Nr. 1 DDG",
+ kravs: true,
+ hjalp: "Fullständigt firmanamn inklusive bolagsform (GmbH, AB, Ltd).",
+ },
+ {
+ id: "adress",
+ rubrik: "Anschrift",
+ grund: "§ 5 Abs. 1 Nr. 1 DDG",
+ kravs: true,
+ hjalp: "Gatuadress. En postbox räcker inte — adressen ska gå att delge på.",
+ },
+ {
+ id: "foretradare",
+ rubrik: "Vertretungsberechtigte",
+ grund: "§ 5 Abs. 1 Nr. 1 DDG",
+ kravs: true,
+ hjalp: "Behörig företrädare, t.ex. verkställande direktör eller Geschäftsführer.",
+ },
+ {
+ id: "epost",
+ rubrik: "E-Mail",
+ grund: "§ 5 Abs. 1 Nr. 2 DDG",
+ kravs: true,
+ hjalp: "En adress som läses. Ett kontaktformulär ersätter den inte.",
+ },
+ {
+ id: "telefon",
+ rubrik: "Telefon",
+ grund: "§ 5 Abs. 1 Nr. 2 DDG",
+ kravs: true,
+ hjalp: "Snabb elektronisk kontakt ska vara möjlig; telefon är det som normalt godtas.",
+ },
+ {
+ id: "register",
+ rubrik: "Registergericht und -nummer",
+ grund: "§ 5 Abs. 1 Nr. 4 DDG",
+ kravs: true,
+ hjalp: "Handelsregister och HRB-nummer, eller motsvarande register i hemlandet.",
+ },
+ {
+ id: "momsnummer",
+ rubrik: "Umsatzsteuer-Identifikationsnummer",
+ grund: "§ 27a UStG",
+ kravs: true,
+ hjalp: "VAT-nummer, om ett sådant finns.",
+ },
+ {
+ id: "tillsyn",
+ rubrik: "Aufsichtsbehörde",
+ grund: "§ 5 Abs. 1 Nr. 3 DDG",
+ kravs: false,
+ hjalp: "Endast om verksamheten kräver tillstånd. Diagnostikverktyg gör normalt inte det.",
+ },
+ {
+ id: "ansvarig_innehall",
+ rubrik: "Inhaltlich Verantwortlicher",
+ grund: "§ 18 Abs. 2 MStV",
+ kravs: false,
+ hjalp: "Krävs för journalistiskt-redaktionellt innehåll. En produktwebbplats behöver det sällan.",
+ },
+ {
+ id: "dataskyddsombud",
+ rubrik: "Datenschutzbeauftragter",
+ grund: "Art. 37 DSGVO",
+ kravs: false,
+ hjalp: "Krävs vid regelbunden och systematisk övervakning i stor omfattning, eller känsliga uppgifter i stor omfattning.",
+ },
+];
+
+/**
+ * Handlingar som ska finnas och nås från varje sida.
+ *
+ * `nar` säger när kravet inträder — flera av dem är nya, och en fot som
+ * byggdes för två år sedan saknar dem utan att någon märkt det.
+ */
+export const HANDLINGAR = [
+ {
+ id: "impressum",
+ rubrik: "Impressum",
+ grund: "§ 5 DDG (ersatte § 5 TMG den 14 maj 2024)",
+ kravs: true,
+ nar: "Alltid, för kommersiella digitala tjänster.",
+ },
+ {
+ id: "dataskydd",
+ rubrik: "Datenschutzerklärung",
+ grund: "Art. 13–14 DSGVO",
+ kravs: true,
+ nar: "Alltid när personuppgifter behandlas.",
+ },
+ {
+ id: "villkor",
+ rubrik: "Allgemeine Geschäftsbedingungen",
+ grund: "§§ 305 ff. BGB",
+ kravs: true,
+ nar: "När standardvillkor används.",
+ },
+ {
+ id: "tillganglighet",
+ rubrik: "Erklärung zur Barrierefreiheit",
+ grund: "BFSG (Barrierefreiheitsstärkungsgesetz), i kraft 28 juni 2025",
+ kravs: true,
+ nar: "Tjänster till konsument. Mikroföretag som endast tillhandahåller tjänster är undantagna.",
+ },
+ {
+ id: "tvistlosning",
+ rubrik: "Verbraucherstreitbeilegung",
+ grund: "§ 36 VSBG",
+ kravs: true,
+ nar: "Besked om man deltar i tvistlösning inför konsumentskiljenämnd — även ett nej ska stå.",
+ },
+ {
+ id: "visselblasare",
+ rubrik: "Hinweisgeberkanal",
+ grund: "HinSchG",
+ kravs: false,
+ nar: "Från 50 anställda.",
+ },
+];
+
+/**
+ * Vad som INTE ska stå i foten längre.
+ *
+ * En upphävd hänvisning är inte harmlös: den ser ut som en upplysning och
+ * pekar ingenstans.
+ */
+export const AVSKAFFAT = [
+ {
+ id: "odr",
+ rubrik: "Länk till EU:s ODR-plattform",
+ grund: "Förordning (EU) 524/2013 upphävd genom förordning (EU) 2024/3228",
+ text:
+ "Plattformen togs ur drift den 20 juli 2025 och tog inte emot nya ärenden efter 20 mars 2025. Länken ska tas bort helt, inte bytas ut.",
+ },
+ {
+ id: "tmg",
+ rubrik: 'Hänvisning till "§ 5 TMG"',
+ grund: "Telemediengesetz ersatt av Digitale-Dienste-Gesetz 14 maj 2024",
+ text: "Plikten är oförändrad, men paragrafen heter § 5 DDG. Enklast är att inte hänvisa till paragrafen alls.",
+ },
+];
+
+/**
+ * Vilka obligatoriska fält som saknas.
+ *
+ * Driften kan spärra en driftsättning på den här listan i stället för att
+ * upptäcka bristen när ett avmahnungsbrev kommer.
+ */
+export function saknade(uppgifter = {}) {
+ return FALT.filter((f) => f.kravs && !String(uppgifter[f.id] ?? "").trim()).map((f) => ({
+ id: f.id,
+ rubrik: f.rubrik,
+ grund: f.grund,
+ }));
+}
+
+/** Är uppgifterna kompletta nog för att visas som ett riktigt Impressum? */
+export function komplett(uppgifter = {}) {
+ return saknade(uppgifter).length === 0;
+}
diff --git a/felsokning/services/gemensam/grind.mjs b/felsokning/services/gemensam/grind.mjs
index 06267f7..f17c3c1 100644
--- a/felsokning/services/gemensam/grind.mjs
+++ b/felsokning/services/gemensam/grind.mjs
@@ -53,11 +53,40 @@ export function grinda(handelser, metodik) {
historik ? "Nekad historikkontroll kräver en motivering." : "Ingen historikkontroll dokumenterad.",
);
+ // Mätarställningen ska vara FOTOGRAFERAD, inte bara inskriven.
+ //
+ // Skillnaden är hela dess bevisvärde. En inskriven siffra är teknikerns
+ // påstående om vad som stod på mätaren; ett foto visar vad som stod
+ // där. Mätarställningen är samtidigt det som avgör garanti- och
+ // försäkringsfrågor i efterhand, och den enda uppgift i ärendet som
+ // någon kan ha ett intresse av att skriva fel.
+ //
+ // Klienten krävde redan fotot — men bara klienten, och en regel som
+ // bara finns i gränssnittet är en vana, inte en spärr. Det är samma
+ // förhållande som QUALITET C-2 gällde, fast åt andra hållet: här var
+ // det servern som var svagare, vilket är värre, eftersom servern är
+ // den som är auktoritativ.
+ //
+ // Undantag finns och måste finnas — en trasig display, en timräknare
+ // som sitter oåtkomligt, en spegling som inte går att fota bort. Men
+ // undantaget kräver en motivering, av samma skäl som ett nekat
+ // historiksvar gör det.
for (const [lage, rubrik] of [
- ["ingaende", "Ingående mätarställning dokumenterad"],
- ["utgaende", "Utgående mätarställning dokumenterad"],
+ ["ingaende", "Ingående mätarställning fotograferad"],
+ ["utgaende", "Utgående mätarställning fotograferad"],
]) {
- krav(`matarstallning_${lage}`, rubrik, av(handelser, "matarstallning").some((h) => h.lage === lage));
+ const poster = av(handelser, "matarstallning").filter((h) => h.lage === lage);
+ const dokumenterad = poster.length > 0;
+ const fotograferad = poster.some((h) => h.bilagaId || h.dataUrl);
+ const motiverat = poster.some((h) => (h.undantag ?? "").trim().length > 0);
+ krav(
+ `matarstallning_${lage}`,
+ rubrik,
+ dokumenterad && (fotograferad || motiverat),
+ !dokumenterad
+ ? "Ingen mätarställning dokumenterad."
+ : "Mätarställningen är inskriven men inte fotograferad. Fotografera mätaren eller ange varför det inte går.",
+ );
}
krav(
diff --git a/felsokning/services/gemensam/handelser.mjs b/felsokning/services/gemensam/handelser.mjs
index e4e0744..afbeec2 100644
--- a/felsokning/services/gemensam/handelser.mjs
+++ b/felsokning/services/gemensam/handelser.mjs
@@ -103,7 +103,10 @@ export const VALFRIA_FÄLT = {
kundbeslut: ["kommentar", "kontaktperson"],
atgard_utford: ["delar", "motivering"],
slutsats: ["atgardsval", "orsakFastställd"],
- arende_avslutat: ["signatur"],
+ // Versionen som gällde VID AVSLUTET. Ritningsstämpeln läste den ur
+ // den aktuella konstanten, så ett ärende stängt under 1.0 visade 3.1 —
+ // precis det stämpelns egen beskrivning säger att den inte ska göra.
+ arende_avslutat: ["signatur", "plattformsversion"],
};
/**
diff --git a/felsokning/services/gemensam/version.mjs b/felsokning/services/gemensam/version.mjs
new file mode 100644
index 0000000..feb2fa2
--- /dev/null
+++ b/felsokning/services/gemensam/version.mjs
@@ -0,0 +1,134 @@
+// ALVA-SPEC-002 · Utgåvor.
+//
+// ---- Varför versionen är en egen modul ---------------------------------
+//
+// Den stod som en sträng i gränssnittskoden och räknades aldrig upp.
+// Systemet påstod "ALVA 1.0" efter att kvalitetsgrinden flyttats till
+// servern, schemat stängts, faktureringen byggts, portalen stängts och
+// tre revisioner härdats. Versionen sa alltså ingenting om vad man fick.
+//
+// Historiken nedan är HÄRLEDD UR VAD SOM FAKTISKT LEVERERATS, inte
+// skriven för att se ut som en produkt med lång historia. Varje post
+// pekar på den commit där arbetet finns. En utgåva som inte går att peka
+// på hör inte hemma i listan — en påhittad ändringslogg är samma sorts
+// obefogade säkerhet som produkten finns för att undvika.
+//
+// ---- Vad som gör en utgåva stor -----------------------------------------
+//
+// Andra siffran räcker för ny funktion. Första siffran höjs bara när
+// GARANTIERNA ändras — vad systemet lovar om ett underlag. Det har hänt
+// två gånger: när fakturan blev ett härlett dokument i systemet, och när
+// TÜV-härdningen ändrade vad loggen garanterar.
+
+export const UTGAVOR = [
+ {
+ version: "3.1",
+ datum: "2026-08-06",
+ commit: "a94c228",
+ rubrik: "Regelverk, försäkringsunderlag och support i ärendet",
+ andringar: [
+ "Garantiregimer för EU och USA med källa och kontrolldatum per post — och skiljelinjen mellan lagstadgat och avtalat, som förväxlas åt bägge håll.",
+ "Försäkringsvillkor som underlag för ett samtal. Modulen avgör ingenting och kan inte avgöra något.",
+ "Felanmälan i varje ärende, med härlett sammanhang. Ett regnr kan inte smugglas med.",
+ "Mätarställningen ska vara fotograferad, inte bara inskriven — kravet fanns bara i gränssnittet och var därmed en vana, inte en spärr.",
+ ],
+ },
+ {
+ version: "3.0",
+ datum: "2026-08-06",
+ commit: "b147694",
+ rubrik: "TÜV-revision och härdning av integritetsgarantierna",
+ stor: true,
+ andringar: [
+ "Händelsenyckeln skopad till ärendet: en organisation kan inte längre hindra en annan från att skriva historik.",
+ "Mätkedjan slås upp i mätdonsregistret — E4 var tidigare ett påstående klienten gjorde om sig själv.",
+ "Personnycklarna kuverteras under en huvudnyckel utanför databasen.",
+ "Gallringen verkställs av ett eget jobb i stället för att bara vara ett datum ingen läste.",
+ "Åtkomstloggen och raderingsregistret är append-only.",
+ ],
+ },
+ {
+ version: "2.1",
+ datum: "2026-08-06",
+ commit: "3b83fc9",
+ rubrik: "Portalen stängd på riktigt",
+ andringar: [
+ "Inloggningen autentiserar mot plattformen och portalen är oåtkomlig utan giltig session.",
+ "Fakturavyn läser organisationens egna fakturor; ett misslyckat anrop visar felet, inte exemplet.",
+ ],
+ },
+ {
+ version: "2.0",
+ datum: "2026-08-06",
+ commit: "b457543",
+ rubrik: "Fakturering i systemet",
+ stor: true,
+ andringar: [
+ "Fakturan härleds ur organisationens tillstånd i stället för att skrivas in.",
+ "Utfärdad faktura är oföränderlig; betalning är en händelse och statusen en projektion.",
+ "Nummerserie utan luckor. Ingen betalleverantör är inblandad.",
+ ],
+ },
+ {
+ version: "1.3",
+ datum: "2026-08-05",
+ commit: "6676262",
+ rubrik: "Egen typografi och läsbarhet på telefon",
+ andringar: [
+ "Inbäddad typografi enligt ALVA-SPEC-001 — inga externa hämtningar.",
+ "Ritningsstämpel på rapporten, räta hörn, tätare spärrning.",
+ ],
+ },
+ {
+ version: "1.2",
+ datum: "2026-08-05",
+ commit: "86d553b",
+ rubrik: "Sviten körs faktiskt",
+ andringar: [
+ "ALVA:s tester lades in i CI. De hade passerat bara när någon körde dem för hand — ett test som ingen kör är dokumentation, inte en spärr.",
+ "Genomgången blev ett incheckat test.",
+ ],
+ },
+ {
+ version: "1.1",
+ datum: "2026-08-05",
+ commit: "31727a4",
+ rubrik: "Leverantörsrevision 2 och härdning",
+ andringar: [
+ "Händelseschemat stängt: okända fält avvisas i stället för att lagras.",
+ "Kvalitetsregeln finns på ett ställe — klienten frågar grinden i stället för att ha en egen åsikt.",
+ ],
+ },
+ {
+ version: "1.0",
+ datum: "2026-08-04",
+ commit: "590fab3",
+ rubrik: "ALVA-metoden",
+ andringar: [
+ "Analys · Lokalisering · Verifiering · Åtgärd som metod, inte som assistent.",
+ "Kvalitetsgrinden serverauktoritativ. Avslutssats enligt ALVA-RULE-200.",
+ ],
+ },
+];
+
+/** Nuvarande utgåva. Enda stället siffran finns. */
+export const NUVARANDE = UTGAVOR[0];
+
+/** Så versionen skrivs i gränssnittet och i ritningsstämpeln. */
+export const PLATTFORMSVERSION = `ALVA ${NUVARANDE.version}`;
+
+/**
+ * Versionen som gällde när ett ärende stängdes.
+ *
+ * Ritningsstämpeln ska visa vad som gällde VID AVSLUTET, inte vad som
+ * gäller den dag rapporten läses — det står i stämpelns egen beskrivning,
+ * och gällde ändå inte för versionen, som lästes ur den aktuella
+ * konstanten. Ett ärende stängt under 1.0 visade 3.1.
+ *
+ * Avslutshändelsen bär numera versionen. Äldre ärenden saknar den, och då
+ * sägs det uttryckligen i stället för att dagens siffra lånas ut.
+ */
+export function versionVidAvslut(avslutshandelse) {
+ const v = avslutshandelse?.plattformsversion;
+ return typeof v === "string" && v.trim() ? v : "Ej registrerad (stängt före ALVA 3.1)";
+}