Versandkosten-System (Pflichtenheft Punkt 9) + kritischen Warenkorb/Checkout-Bug behoben

Neu:
- Produkte können eigene Versandkosten je Land (DE/AT/CH/LU) bekommen, gelten nur bei
  Einzelkauf (genau 1 Stück von 1 Produkt)
- Versandkosten-Staffel nach Warenwert je Land, von VanVan im Adminbereich bearbeitbar
  (neuer Bereich "Versandkosten-Staffel"), greift bei mehreren Produkten/Stück
- Versandkostenfrei ab 75 € gilt immer, unabhängig von Einzelkauf/Staffel
- Checkout berechnet und zeigt jetzt die echten Versandkosten live beim Länderwechsel an

Kritischer Bugfix (unabhängig vom neuen Feature entdeckt):
- Warenkorb- und Checkout-Skripte haben "import" innerhalb von define:vars-Skripten benutzt,
  was Astro als IIFE rendert (kein ES-Modul) — das hat in der echten Live-Version die ganze
  Zeit zu "Cannot use import statement outside a module" geführt, wodurch Warenkorb und
  Checkout praktisch nie funktioniert haben. Behoben durch Trennung: define:vars-Skript setzt
  nur Werte auf window, separates <script type="module"> mit den echten Imports liest sie aus.
  Mit echtem End-to-End-Test verifiziert (Artikel in Warenkorb legen, Land wechseln, Summe
  prüfen) statt nur Screenshot.
This commit is contained in:
qcigano
2026-08-02 17:56:21 +02:00
parent 49cf12fd1c
commit 094edaaf39
12 changed files with 282 additions and 40 deletions
+5
View File
@@ -29,6 +29,11 @@ export interface Product {
lieferzeit?: string;
badges: Badge[];
bilder?: string[]; // Pfade zu Produktfotos, von VanVan im Admin-Bereich hochgeladen
// Eigene Versandkosten je Land — gelten NUR, wenn genau 1 Stück dieses einen Produkts
// gekauft wird (Einzelkauf). Bei mehreren Produkten/Stückzahlen greift stattdessen die
// Versandkosten-Staffel (siehe src/data/versand.ts). Optional, damit Bestandsprodukte ohne
// diese Angabe nicht kaputtgehen — dann greift automatisch die Staffel als Rückfalllösung.
versand?: { de?: number; at?: number; ch?: number; lu?: number };
}
const modules = import.meta.glob("/src/content/products/*.json", { eager: true }) as Record<
+73
View File
@@ -0,0 +1,73 @@
// Versandkosten-Logik. Regeln (mit dem Nutzer am 02.08.2026 abgestimmt):
// 1. Warenkorb-Gesamtwert >= 75 € → Versand immer kostenlos, egal wie viele Produkte/Stück.
// 2. Genau 1 Stück von genau 1 Produkt im Warenkorb → der am Produkt selbst hinterlegte
// Versandpreis für das Zielland gilt (falls hinterlegt, sonst Fallback auf die Staffel).
// 3. Alles andere (mehrere Produkte und/oder mehrere Stück) → Versandkosten-Staffel nach
// Gesamt-Warenwert für das Zielland (src/content/versandstaffel.json), von VanVan im
// Adminbereich bearbeitbar.
import type { Product } from "./products";
export type CountryCode = "de" | "at" | "ch" | "lu";
export interface VersandStufe {
abWarenwert: number;
kosten: number;
}
export interface Versandstaffel {
de: VersandStufe[];
at: VersandStufe[];
ch: VersandStufe[];
lu: VersandStufe[];
}
// @ts-ignore -- JSON-Import, zur Build-Zeit von Astro/Vite aufgelöst
import versandstaffelRaw from "../content/versandstaffel.json";
export const versandstaffel = versandstaffelRaw as Versandstaffel;
export const FREE_SHIPPING_FROM = 75;
function tierLookup(land: CountryCode, warenwert: number): number {
const stufen = versandstaffel[land] ?? [];
const sortiert = [...stufen].sort((a, b) => a.abWarenwert - b.abWarenwert);
let kosten = sortiert[0]?.kosten ?? 0;
for (const s of sortiert) {
if (warenwert >= s.abWarenwert) kosten = s.kosten;
}
return kosten;
}
export interface MinimalCartItem {
slug: string;
preis: number;
menge: number;
}
/**
* Berechnet die Versandkosten für einen Warenkorb in ein bestimmtes Land.
* `alleProdukte` wird gebraucht, um bei Einzelkäufen den produktspezifischen Versandpreis
* nachzuschlagen (die Cart-Items selbst kennen nur slug/preis/menge, keine Versanddaten).
*/
export function berechneVersandkosten(
land: CountryCode,
items: MinimalCartItem[],
alleProdukte: Product[]
): number {
if (items.length === 0) return 0;
const warenwert = items.reduce((sum, i) => sum + i.preis * i.menge, 0);
if (warenwert >= FREE_SHIPPING_FROM) return 0;
const gesamtmenge = items.reduce((sum, i) => sum + i.menge, 0);
const istEinzelkauf = items.length === 1 && gesamtmenge === 1;
if (istEinzelkauf) {
const produkt = alleProdukte.find((p) => p.slug === items[0].slug);
const eigenerPreis = produkt?.versand?.[land];
if (typeof eigenerPreis === "number") return eigenerPreis;
// Kein eigener Versandpreis hinterlegt -> Staffel als Rückfalllösung
}
return tierLookup(land, warenwert);
}