feat: Produkte aus ApiCore synchronisieren
Der Shop konnte Ernten und Chargen bereits aus ApiCore uebernehmen, den Produktkatalog aber nicht - jedes Glas musste im Admin ein zweites Mal von Hand angelegt werden. Neuer Knopf unter /admin/produkte zieht die Produkte und traegt anschliessend die passende Charge am Produkt nach, wo ApiCore die Abfuellung einem Produkt zugeordnet hat. Genau diese Verknuepfung fuettert die Herkunftsseite hinter dem QR-Code auf dem Glas. ApiCore fuehrt ueberhaupt keine Verkaufspreise. Importierte Produkte entstehen deshalb als deaktivierte Entwuerfe mit Preis 0, den nur ein Mensch setzen kann. Damit daraus nie eine Gratis-Bestellung wird, lehnen die Aktivierung im Admin und der Checkout ein Produkt ohne Preis jetzt ausdruecklich ab, und die Produktliste kennzeichnet solche Entwuerfe. Ein bereits vorhandenes Produkt wird uebersprungen, nie ueberschrieben: genau die drei Felder, die ApiCore liefert (Name, Beschreibung, Gewicht), zieht der Admin vor der Freischaltung von Hand nach - ein Update wuerde diese Arbeit bei jedem weiteren Lauf wegwerfen. Ein in ApiCore ausgelaufenes Produkt wird nicht automatisch deaktiviert, sondern nur gemeldet, damit der Restbestand abverkauft werden kann. Nebenbei behoben: die beiden Produktformulare und die Bestelluebersicht haben den ?error=-Parameter aus fail() nie ausgegeben - abgelehnte Eingaben landeten wortlos wieder auf dem Formular. Der Login gegen ApiCore ist jetzt von den Abfragen getrennt (openApiCoreSession), damit ein Sync-Lauf mit einer einzigen Anmeldung auskommt statt mit dreien. Die Feld-Abbildung liegt neu in lib/apicore-mapping.ts und ist damit testbar, statt hinter "use server" zu verschwinden. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
1 parent
c61ae92390
commit
78c19f1b73
15 files changed
+908
-226
No files matched your search
@@ -39,6 +39,7 @@ Regionale Imkerei-Website und Shop für Wabenhain (Altenmünster) — MVP-Stufe.
|
||||
- Rechtliches: Impressum, Datenschutz, AGB, Widerrufsbelehrung, Versand und Zahlung (**Platzhaltertexte, rechtlich prüfen lassen**)
|
||||
- **Kundenkonto** unter `/konto`: Registrierung mit **Pflicht-E-Mail-Verifizierung** (siehe „Sicherheit" unten) und Login (Passwort-Hash + signiertes Session-Cookie), optionale **Zwei-Faktor-Authentifizierung** (TOTP-Authenticator-App + Backup-Codes, `/konto/zwei-faktor`), Konto-Sperre nach Fehlversuchen, "Alle Geräte abmelden", "Passwort vergessen"-Link mit E-Mail-Reset, Bestellhistorie mit Versand-/Abholstatus, "Bestellung erneut auslösen", Adressverwaltung (mehrere Adressen, Standardadresse), **Wunschliste**, Rechnungsansicht als HTML und PDF-Download (`/rechnung/[nummer]`), Newsletter-Verwaltung mit Double-Opt-In
|
||||
- **Admin-Backend** unter `/admin` (Zugriff über das normale Kundenkonto-Login + Rolle `ADMIN`, siehe „Admin-Zugang" unten): Dashboard mit Kennzahlen, Top-Produkten, Umsatz nach Kategorie, **Umsatzverlauf der letzten 14 Tage**, **Stammkund:innen-Zähler** und **meistgenutzten Gutscheinen**, Bestellliste mit Detailseite (`/admin/bestellungen/[id]`: vollständige Adresse, Artikel, Zahlung, Rabatt, Rechnungs-PDF) inkl. Status-Änderung (setzt automatisch Versand-/Abholdatum **und verschickt eine Versand-/Abholbereit-E-Mail**), Produkte/Ernten/Chargen jeweils mit Anlegen- und Bearbeiten-Formularen, Firmenanfragen-Inbox mit Statuspflege, **Benutzerverwaltung** (`/admin/benutzer`, siehe „Admin-Zugang" unten), **Versandkosten-Verwaltung** (`/admin/versand`, siehe „Versandkosten" unten), **Gutschein-Verwaltung** (`/admin/gutscheine`), **Bewertungsmoderation** (`/admin/bewertungen`: freischalten oder löschen), **Audit-Log** (`/admin/audit-log`, siehe „Sicherheit" unten)
|
||||
- **ApiCore-Synchronisierung**: Ernten, Chargen und der Produktkatalog lassen sich per Knopfdruck aus dem Imkerei-Verwaltungssystem ApiCore übernehmen, statt dieselben Daten zweimal einzutippen — jeweils in einen Entwurfszustand, den erst ein Mensch freigibt, und ohne je etwas zu überschreiben, was im Admin bewusst gesetzt wurde (siehe „ApiCore-Synchronisierung" unten)
|
||||
- **Transaktions-E-Mails** (Bestellbestätigung, Versand-/Abholbereit-Benachrichtigung, E-Mail-Verifizierung, Newsletter-Opt-in, Passwort-Reset) über SMTP, siehe Abschnitt „E-Mail-Versand" unten
|
||||
- `sitemap.xml`, `robots.txt`, OpenGraph-Metadaten, Favicon (`app/icon.svg`) mit dem echten Wabenhain-Logo
|
||||
- **Barrierefreiheit (WCAG AA)**: die Honigfarbe gibt es jetzt als zwei kontraststarke Varianten (`text-honey-dark` für helle, `text-honey-light` für dunkle Abschnitte wie `bg-forest`) statt eines einzelnen Tons, der auf keinem der beiden Hintergründe zuverlässig 4.5:1 erreichte; dieselbe Kontrastprüfung wurde site-weit für alle `text-ink/NN`-Texttöne nachgezogen (Admin-Bereich, Kundenkonto, Checkout, Rechnungen, Produktkarten, Geschmacksskala — 190+ Stellen in 50 Dateien) sowie für die Bildergalerie auf der Produktseite korrigiert (Vorschaubild-Buttons hatten einen `aria-label`, der den sichtbaren Bildtext überschrieben statt eingeschlossen hat). Lighthouse-Accessibility-Audit steht jetzt bei 100/100 ohne offene Verstöße auf Start-, Shop- und Produktseite.
|
||||
@@ -189,6 +190,28 @@ Registrierung, Newsletter-Anmeldung und Firmenanfrage sind optional durch [Cloud
|
||||
|
||||
Die serverseitige Prüfung läuft über `src/lib/turnstile.ts` gegen die Cloudflare-Siteverify-API; das Widget selbst ist `src/components/turnstile-widget.tsx`.
|
||||
|
||||
## ApiCore-Synchronisierung
|
||||
|
||||
ApiCore ist das separate Imkerei-Verwaltungssystem (NestJS-Backend mit eigener Datenbank), in dem Völker, Ernten, Schleuderungen und Abfüllungen gepflegt werden. Wabenhain kann drei Datenarten daraus übernehmen, damit dieselbe Charge nicht zweimal von Hand eingetippt wird:
|
||||
|
||||
| ApiCore | Wabenhain | Knopf |
|
||||
|---------|-----------|-------|
|
||||
| `Harvest` (Ernte) | `Harvest` | `/admin/ernten` → „ApiCore-Synchronisierung" |
|
||||
| `BottlingBatch` (Abfüllung) | `Batch` (Charge) | derselbe Knopf, läuft direkt nach den Ernten |
|
||||
| `Product` (Produktkatalog) | `Product` | `/admin/produkte` → „ApiCore-Synchronisierung" |
|
||||
|
||||
**Grundregel: ApiCore liefert die Rohdaten, Wabenhain trifft die Verkaufsentscheidungen.** Ein Sync überschreibt deshalb nie etwas, das im Admin bewusst gesetzt wurde:
|
||||
|
||||
- **Ernten** entstehen als unveröffentlichte Entwürfe. Eine bereits veröffentlichte, früher synchronisierte Ernte bleibt unangetastet — sonst würde ein erneuter Lauf eine Freigabeentscheidung stillschweigend zurücknehmen.
|
||||
- **Produkte** entstehen als deaktivierte Entwürfe **ohne Preis**: ApiCore führt überhaupt keine Verkaufspreise, das ist reine Betriebsdatenhaltung. Den Preis kann nur ein Mensch setzen. Solange er fehlt, lässt sich das Produkt nicht aktiv schalten (`admin/actions/products.ts`), und der Checkout lehnt es zusätzlich ab (`api/checkout/route.ts`) — ein importiertes Produkt kann also nie versehentlich für 0 € verkauft werden. Ein bereits vorhandenes Produkt wird übersprungen, nie aktualisiert: genau die drei Felder, die ApiCore liefert (Name, Beschreibung, Gewicht), zieht der Admin vor der Freischaltung von Hand nach, und ein Update würde diese Arbeit bei jedem weiteren Lauf wegwerfen. Soll ein Produkt wirklich frisch aus ApiCore kommen, löscht man es in Wabenhain und synchronisiert erneut.
|
||||
- **Chargen** werden ausschließlich neu angelegt, nie aktualisiert. Ein Update würde `remainingGlasses` überschreiben — das ist Wabenhains eigener, durch echte Verkäufe veränderter Lagerbestand.
|
||||
- Wo ApiCore eine Abfüllung einem Produkt zuordnet, trägt der Produkt-Sync die passende **Charge am Produkt** nach (und darüber die Ernte), aber nur solange dort noch keine Charge steht. Genau diese Verknüpfung füttert die Herkunftsseite hinter dem QR-Code auf dem Glas.
|
||||
- Ein in ApiCore ausgelaufenes Produkt (`DISCONTINUED`) wird **nicht** automatisch deaktiviert: solange Gläser im Regal stehen, ist „nicht mehr produziert" kein Grund, den Abverkauf zu stoppen. Der Fall wird stattdessen im Ergebnis gemeldet.
|
||||
|
||||
Wiederholte Läufe sind gefahrlos: Ernten und Produkte werden über einen stabilen Schlüssel `apicore-<ApiCore-ID>` wiedererkannt (`Harvest.slug` bzw. `Product.slug`), Chargen über die ApiCore-Losnummer als `Batch.code`.
|
||||
|
||||
**Einrichtung:** `APICORE_API_URL`, `APICORE_SYNC_EMAIL` und `APICORE_SYNC_PASSWORD` in `.env` setzen (siehe `.env.example`). ApiCore kennt keinen API-Key-Mechanismus, nur E-Mail/Passwort-Login — deshalb dort ein **eigenes, MFA-freies Service-Konto** anlegen statt einen echten Admin-Zugang einzutragen. Es braucht ausschließlich Leserechte: `harvests:read`, `products:read`, `extractions:read`, `bottling:read` und optional `apiaries:read` (nur für die Standortnamen an den Ernten). Fehlt eine Berechtigung, meldet der Sync das konkret als HTTP 403 statt still nichts zu tun. Details und die Zuordnung Feld für Feld: `src/lib/apicore-client.ts`, `src/lib/apicore-mapping.ts` und `src/app/admin/actions/apicore-sync.ts`.
|
||||
|
||||
## Vollständiger Docker-Betrieb
|
||||
|
||||
```bash
|
||||
|
||||
Reference in new issue
Block a user