Commit Graph
232 Commits
Author SHA1 Message Date
Nick.RosenloecherandClaude Opus 5.5 c4b0212004 chore(k8s): Wabenhain auf Build sha-1d88bac (automatische Honigwahl)
Docker Image bauen und veroeffentlichen / lint (push) Canceled after 0s
Docker Image bauen und veroeffentlichen / build-and-push (push) Canceled after 0s
Docker Image bauen und veroeffentlichen / test (push) Canceled after 3m27s
Docker Image bauen und veroeffentlichen / typecheck (push) Canceled after 3m38s
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-16 19:07:40 +02:00
Nick.RosenloecherandClaude Opus 5.5 1d88bac52e feat: Patenschafts-Honig automatisch waehlen (nie cremig, neueste Ernte)
Docker Image bauen und veroeffentlichen / typecheck (push) Successful in 2m18s
Docker Image bauen und veroeffentlichen / test (push) Successful in 5m32s
Docker Image bauen und veroeffentlichen / lint (push) Successful in 3m41s
Docker Image bauen und veroeffentlichen / build-and-push (push) Canceled after 20m38s
Pakete, Abo-Reservierung und Abo-Lieferungen nehmen immer den aktiven,
nicht cremigen Honig der neuesten Ernte mit genug freiem Bestand, bei
gleichem Jahrgang den mit dem meisten Bestand. Feste Honig-Zuordnung
im Admin entfaellt (Spalte bleibt, wird nicht mehr genutzt).

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-16 18:35:04 +02:00
Nick.RosenloecherandClaude Opus 5.5 3ddf0bfa73 chore: kurze Namen fuer die Abo-Angebote im Einrichtungsskript
Docker Image bauen und veroeffentlichen / typecheck (push) Successful in 2m26s
Docker Image bauen und veroeffentlichen / test (push) Successful in 5m17s
Docker Image bauen und veroeffentlichen / lint (push) Successful in 4m13s
Docker Image bauen und veroeffentlichen / build-and-push (push) Successful in 8m55s
Live angelegt am 2026-10-16 (inaktiv, Stripe-Preise price_1URDJE..., price_1URDJF...).

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-16 17:46:04 +02:00
Nick.RosenloecherandClaude Opus 5.5 f786601309 chore(k8s): Wabenhain auf Build sha-ed17d14 (Bienenpatenschaft), Einrichtungsskript
Docker Image bauen und veroeffentlichen / typecheck (push) Successful in 2m21s
Docker Image bauen und veroeffentlichen / lint (push) Successful in 2m27s
Docker Image bauen und veroeffentlichen / test (push) Successful in 3m19s
Docker Image bauen und veroeffentlichen / build-and-push (push) Successful in 7m44s
Ausgerollt am 2026-10-16, Migrationen und Seed beim Start gelaufen.
scripts/sponsorship-bootstrap.ts verknuepft den Paket-Honig und legt die
Abo-Angebote inaktiv samt Stripe-Preis an.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-16 17:26:30 +02:00
Nick.RosenloecherandClaude Opus 5.5 e3b0eb7b05 chore(stripe): Livekonto fuer das Patenschafts-Abo eingerichtet
Docker Image bauen und veroeffentlichen / typecheck (push) Successful in 2m28s
Docker Image bauen und veroeffentlichen / lint (push) Successful in 2m37s
Docker Image bauen und veroeffentlichen / test (push) Successful in 3m27s
Docker Image bauen und veroeffentlichen / build-and-push (push) Successful in 9m3s
Webhook um Abo-Ereignisse ergaenzt, Kundenportal nur fuer die Zahlungsart
angelegt (ID in der ConfigMap), Zahlungsmethoden-Domains registriert.
Dashboard-Schritte ohne API in EINRICHTUNG.md.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-16 16:43:30 +02:00
Nick.RosenloecherandClaude Opus 5.5 ed17d14e46 fix: CI-Zeitgrenze fuer Urkundentests, Cookie-Banner mobil, Hydration im Warenkorb-Effekt
Docker Image bauen und veroeffentlichen / typecheck (push) Successful in 3m4s
Docker Image bauen und veroeffentlichen / lint (push) Successful in 3m13s
Docker Image bauen und veroeffentlichen / test (push) Successful in 4m4s
Docker Image bauen und veroeffentlichen / build-and-push (push) Successful in 13m1s
Die Layout-Matrix der Urkunde lief auf dem CI-Runner in die 5-Sekunden-
Grenze; Tests bekommen 30 s. Die Cookie-Knoepfe stapeln sich unter 430 px,
statt ineinanderzulaufen. Der Tropfen im Warenkorb-Effekt wird immer
gerendert und nur bei reduzierter Bewegung nicht animiert.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-16 16:06:33 +02:00
Nick SzaunigandClaude Opus 5.5 310c5ecf8c fix: Kuendigungslimits zaehlen nur Fehlversuche, kuerzere Kontingent-Reservierung
Docker Image bauen und veroeffentlichen / typecheck (push) Successful in 3m18s
Docker Image bauen und veroeffentlichen / test (push) Failing after 4m28s
Docker Image bauen und veroeffentlichen / lint (push) Successful in 2m15s
Docker Image bauen und veroeffentlichen / build-and-push (push) Skipped
Passende Kuendigungen werden nicht mehr von fremden Fehlversuchen
ausgesperrt. Offene Abo-Abschluesse blockieren einen Platz nur noch 35
Minuten, die Stripe-Sitzung laeuft nach 32 Minuten ab.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-16 15:13:48 +02:00
Nick SzaunigandClaude Opus 5.5 515fca1c16 fix: Befunde aus Sicherheits- und Korrektheitsreview der Bienenpatenschaft
Docker Image bauen und veroeffentlichen / typecheck (push) Successful in 2m57s
Docker Image bauen und veroeffentlichen / lint (push) Successful in 3m7s
Docker Image bauen und veroeffentlichen / build-and-push (push) Canceled after 0s
Docker Image bauen und veroeffentlichen / test (push) Canceled after 1m22s
- Oeffentliche Kuendigung: exakte Identifikation, Limits je Vertrag und
  Adresse, Bestaetigung nur an die Kontoadresse mit Ruecknahme-Link (HMAC)
- Vorschau: Body-Grenze beim Lesen, Parallelitaets- und Minutenlimit, Cache
- Rabatte gelten nicht fuer Patenschaftspositionen
- Abo aktiv erst nach bestaetigter Zahlung, offene Abschluesse zaehlen
  zum Kontingent und werden nach 24 h aufgeraeumt, Sitzung 58 Minuten
- Konto-Loeschung nur ohne laufendes Abo, Urkunden-Dateien werden entfernt
- Abo-Lieferungen mit Zahlungszeile (DHL-Marke moeglich)
- Shop zeigt freien Bestand nach Abo-Reservierung
- Storno/volle Erstattung widerruft die Patenschaft (Status WIDERRUFEN)
- Umsatzsteuer der Patenschaft per VAT_RATE_PERCENT_PATENSCHAFT (19 %)
- Digitale Bestellungen archivieren sich, Kalendertage in Europe/Berlin,
  Monatsenden begrenzt, Datenauskunft um Patenschaften ergaenzt

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-16 15:09:32 +02:00
Nick SzaunigandClaude Opus 5.5 f227aeb8f9 feat: Bienenpatenschaft mit Urkunde, Abo, Medaillen und 3D-Konto
Setzt Teilprojekte 1-5 der Spec 2026-10-16 um:
- Urkunden-Engine (lib/certificate): ein Layout fuer PDF, Vorschau und
  3D-Textur, Vorlage classic@1, eingebettete OFL-Schriften, Testmatrix
- Einmal-Patenschaft ueber Traegerprodukte (Kategorie PATENSCHAFT),
  Paket wird im Checkout in Patenschaft (Regelsatz) und Honig aufgeteilt,
  Kontopflicht, rein digitale Bestellungen (FulfillmentMethod DIGITAL)
- Ausstellung nach Zahlung idempotent, Wiederholung im App-Prozess
  (instrumentation + background-jobs), Ablage in CERTIFICATE_DIR
- Verkaufsseite /patenschaft mit Konfigurator und Live-Vorschau,
  Warenkorb mit Schluessel je Position
- Kundenkonto: 3D-Ansicht von Urkunde und Medaille, Downloads, ZIP,
  Uebertragung per befristetem Einmal-Link, Treuestufen bis VIP
- Admin: Angebote, Patenschaften, Druck, Korrektur mit Revisionen,
  Abos, Erntefenster, Deckung, SEPA-Frist, Treuestufen
- Abo ueber Stripe Billing: Lieferung erst nach bezahlten Monaten,
  SEPA-Neukunden-Frist und Rueckbuchungsschutz, Honigreservierung,
  Kuendigungsbutton, Monatsrechnungen, Stripe-Einrichtungsskript

Startangebote werden inaktiv angelegt; Livebetrieb erst nach Freigabe.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-16 14:07:30 +02:00
Nick SzaunigandClaude Opus 5.5 dcb578f062 chore(deps): fontkit, three, react-three und fflate fuer die Bienenpatenschaft
Feste, mindestens zwei Wochen alte Versionen. fontkit misst die Schriften
der Urkunde, three/@react-three rendern die 3D-Ansicht im Konto, fflate
packt den Download aller Dateien.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-16 12:55:58 +02:00
Nick SzaunigandClaude Opus 5.5 db50bcf381 docs: Ist-Stand des Stripe-Livekontos in der Patenschafts-Spec
Docker Image bauen und veroeffentlichen / lint (push) Successful in 2m16s
Docker Image bauen und veroeffentlichen / typecheck (push) Successful in 1m56s
Docker Image bauen und veroeffentlichen / test (push) Successful in 2m16s
Docker Image bauen und veroeffentlichen / build-and-push (push) Successful in 7m51s
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-16 10:13:12 +02:00
Nick SzaunigandClaude Opus 5.5 86f2145115 docs: Bienenpatenschaft mit SEPA-Schutz, Stripe-Einrichtung und Abo-Stufen je Monat
Docker Image bauen und veroeffentlichen / lint (push) Canceled after 0s
Docker Image bauen und veroeffentlichen / typecheck (push) Canceled after 0s
Docker Image bauen und veroeffentlichen / test (push) Canceled after 0s
Docker Image bauen und veroeffentlichen / build-and-push (push) Canceled after 0s
Abo-Stufen 1 bis 24 je bezahltem Monat, danach VIP. SEPA fuer Abos mit
Neukunden-Frist, Verzug und Sperre nach Rueckbuchung. Stripe-Einrichtung
erst im Testmodus per Skript, Livemodus nur nach Freigabe.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-16 10:04:27 +02:00
Nick SzaunigandClaude Opus 5.5 4bcf90a8d9 docs: Bienenpatenschaft um Abo, Kontopflicht, 3D-Ansicht, Medaillen und Uebertragung erweitern
Docker Image bauen und veroeffentlichen / lint (push) Successful in 2m0s
Docker Image bauen und veroeffentlichen / typecheck (push) Successful in 1m48s
Docker Image bauen und veroeffentlichen / test (push) Successful in 2m8s
Docker Image bauen und veroeffentlichen / build-and-push (push) Failing after 17m57s
Teilt den Umfang in fuenf Teilprojekte mit eigener Planung. Abo mit
Lieferung erst nach bezahlten Monaten, Honigkontingent und
Reservierung, Kuendigungsbutton; Medaillen je Patenschaft, Stammkunden-
und Abo-Stufen bis VIP; befristete Uebertragungslinks.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-16 09:03:17 +02:00
Nick SzaunigandClaude Opus 5.5 96c101804d docs: README, k8s/README und EINRICHTUNG auf Stand main (2026-10-16)
Docker Image bauen und veroeffentlichen / lint (push) Successful in 1m41s
Docker Image bauen und veroeffentlichen / typecheck (push) Successful in 1m42s
Docker Image bauen und veroeffentlichen / test (push) Successful in 2m40s
Docker Image bauen und veroeffentlichen / build-and-push (push) Successful in 8m21s
README: Abschnitt Projektstand (Issues #34, #33), veraltete Aussagen zu
Platzhaltern und Rechnungsnummern korrigiert, CI-Gate, Rollout per set
image, Client-IP hinter Cloudflare, VAT_RATE_PERCENT_SONSTIGES, Teststand.
k8s/README: Image-Pinning, Abweichungen Repo/Cluster, Sicherung auf der
Storage Box, SMB 3.1.1 mit seal. EINRICHTUNG: APICORE_WEBHOOK_SECRET,
ERP_EXPORT_TOKEN, Abschnitt Neuen Build ausrollen. .env.example:
DATABASE_POOL_SIZE.

Refs #32

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-16 08:01:07 +02:00
Nick SzaunigandClaude Opus 5.5 24df111495 docs: Spezifikation Bienenpatenschaft mit automatischer Urkunde
Docker Image bauen und veroeffentlichen / lint (push) Canceled after 0s
Docker Image bauen und veroeffentlichen / typecheck (push) Canceled after 0s
Docker Image bauen und veroeffentlichen / test (push) Canceled after 0s
Docker Image bauen und veroeffentlichen / build-and-push (push) Canceled after 0s
Ersetzt den bisherigen Implementierungsauftrag nach den verbindlichen
Vorgaben vom 2026-10-16: drei Pakete, keine Besuche oder laufenden
Inhalte, Urkunde als deterministisch erzeugte PDF mit Vorschau aus
derselben Layoutberechnung, idempotente Erzeugung nach Zahlung,
Revisionen und Druckablauf im Admin.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-16 07:45:54 +02:00
Nick SzaunigandClaude Opus 5.5 35e7c84767 fix(ci): typecheck erzeugt die Next-Routentypen selbst
Docker Image bauen und veroeffentlichen / typecheck (push) Successful in 1m53s
Docker Image bauen und veroeffentlichen / lint (push) Successful in 2m3s
Docker Image bauen und veroeffentlichen / test (push) Successful in 1m53s
Docker Image bauen und veroeffentlichen / build-and-push (push) Successful in 5m44s
In der CI fehlt .next/types; tsc fand die globalen PageProps/LayoutProps
nicht (TS2304). "next typegen" vor tsc erzeugt sie ohne Build.
Lokal mit geloeschtem .next/types geprueft.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-14 09:10:13 +02:00
Nick SzaunigandClaude Opus 5.5 85d1a46f64 ci: Qualitaets-Gate vor dem Image-Build
Docker Image bauen und veroeffentlichen / typecheck (push) Failing after 1m56s
Docker Image bauen und veroeffentlichen / lint (push) Successful in 2m17s
Docker Image bauen und veroeffentlichen / test (push) Successful in 3m10s
Docker Image bauen und veroeffentlichen / build-and-push (push) Skipped
Der Workflow baute bisher bei jedem Push auf main ungeprueft ein Image.
Jetzt laufen lint (inkl. pnpm audit ab high), typecheck und test auf dem
isolierten Runner (ci-isolated, ohne Docker-Socket und Clusternetz);
build-and-push startet nur, wenn alle drei gruen sind. Muster und
Runner-Pruefskript aus dem VyCept-Repo (docs/BETRIEB.md 11b).

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-14 09:02:45 +02:00
Nick SzaunigandClaude Opus 5.5 835323e466 fix(deps): High-/Critical-Advisories schliessen, typecheck-Script
pnpm audit --prod meldete 1 critical und 8 high:
- next 16.3.5 -> 16.3.8 (critical), eslint-config-next mitgezogen
- nodemailer 9 -> 10.1.0 (Bruch nur: Node >= 20; Image laeuft auf Node 22)
- sharp >= 0.35.5 und source-map-js >= 1.2.2 per Override (transitiv ueber next)
- mariadb >= 3.5.4 (direkt und per Override)

Neues Script "typecheck" (Prisma-Clients erzeugen, tsc --noEmit) fuer das CI-Gate.
Lokal geprueft: lint, typecheck, 674 Tests, next build gruen; audit ohne high.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-14 09:02:44 +02:00
Nick SzaunigandClaude Opus 5.5 63d90ae8b3 chore(k8s): Wabenhain auf Build sha-0d254db (Client-IP-Haertung)
Docker Image bauen und veroeffentlichen / build-and-push (push) Successful in 7m47s
Live seit 2026-10-12: Rate-Limits je Besucher (CF-Connecting-IP nur von
Cloudflare-Edges und nur oeffentlich), IPv6 je /64, X-Real-IP nur ausserhalb
der Produktion.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-12 16:03:20 +02:00
Nick SzaunigandClaude Opus 5.5 0d254db867 fix: Client-IP-Ermittlung härten (CF-Connecting-IP, X-Real-IP)
Docker Image bauen und veroeffentlichen / build-and-push (push) Successful in 4m59s
CF-Connecting-IP wird nur noch als öffentliche Unicast-Adresse akzeptiert
(kein 0/8, 10/8, 100.64/10, 127/8, 169.254/16, 172.16/12, 192.168/16,
240/4 "Pseudo IPv4", 255.255.255.255, ::, ::1, fc00::/7, fe80::/10 sowie
IPv4-gemappte Formen); sonst gilt die Gegenstelle aus X-Forwarded-For.

Der X-Real-IP-Fallback gilt nur noch außerhalb von Produktion. In
Produktion ohne X-Forwarded-For wird "unknown" geliefert.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-12 11:19:21 +02:00
Nick SzaunigandClaude Opus 5.5 97f0e033e0 fix(k8s): Ingress nur noch ueber Cloudflare erreichbar
Docker Image bauen und veroeffentlichen / build-and-push (push) Canceled after 0s
Middleware cloudflare-only (ipAllowList: Cloudflare-IPs und private Netze)
vor die HTTPS-Umleitung gesetzt, wie live im Cluster seit 2026-10-12.
Direkte Zugriffe auf den Origin an Cloudflare vorbei bekommen 403.
Die Middleware selbst liegt in rke2-cluster/security.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-12 11:14:59 +02:00
Nick SzaunigandClaude Opus 5.5 5a8ff27090 fix(security): Rate-Limits zaehlen IPv6 je /64-Netz
Docker Image bauen und veroeffentlichen / build-and-push (push) Successful in 6m47s
Seit CF-Connecting-IP gilt, sieht der Limiter echte IPv6-Adressen. Ein
Anschluss bekommt meist ein ganzes /64 und koennte durch Adresswechsel
jedes Limit umgehen (Login, 2FA, Checkout). getClientIp liefert IPv6 deshalb
als /64-Praefix, IPv4-gemappte Adressen wie IPv4 (wie VyCept, #94).

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-12 09:59:09 +02:00
Nick SzaunigandClaude Opus 5.5 946745c064 fix(security): Rate-Limits je Besucher statt je Cloudflare-Edge
Docker Image bauen und veroeffentlichen / build-and-push (push) Canceled after 0s
wabenhain.com laeuft ueber Cloudflare. Der letzte X-Forwarded-For-Eintrag
ist dann die Cloudflare-Edge, die sich viele Besucher teilen: Login-, 2FA-
und Checkout-Limits trafen alle Besucher derselben Edge gemeinsam, ein
einzelner konnte andere aussperren.

resolveClientIp nimmt CF-Connecting-IP, wenn die Gegenstelle eine
Cloudflare-Edge ist; sonst wie bisher den letzten Eintrag (beim
Direktzugriff ist CF-Connecting-IP faelschbar und zaehlt nicht).

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-12 09:54:01 +02:00
Nick SzaunigandClaude Opus 5.5 dfa5c97491 fix(k8s): Wabenhain-Image per Tag und Digest pinnen
Docker Image bauen und veroeffentlichen / build-and-push (push) Canceled after 0s
Sicherheitsreview: ein Tag ist in der Registry ueberschreibbar. Tag plus
Digest bleibt lesbar und unveraenderlich; Cluster auf denselben Stand
gebracht, kubectl diff leer.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-12 09:41:34 +02:00
Nick SzaunigandClaude Opus 5.5 eda2c11fe3 chore(k8s): Wabenhain auf Build sha-4827c8f (E-Rechnung, Storno, CRM-Export)
Docker Image bauen und veroeffentlichen / build-and-push (push) Canceled after 0s
Ausgerollt am 2026-10-12 nach Sicherung wabenhain-db-backup-pre-einvoice;
Migration 20261012010000_cancellation_invoices angewendet.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-12 09:39:59 +02:00
Nick SzaunigandClaude Opus 5.5 4827c8fa61 fix(invoice): Befunde aus dem Sicherheitsreview
Docker Image bauen und veroeffentlichen / build-and-push (push) Successful in 9m45s
- E-Rechnung: in XML verbotene Steuerzeichen aus Namen/Anschriften entfernen,
  sonst waere das eingebettete XML bei einem einzigen Zeichen ungueltig.
- Rechnungskopie: genau eine Adresse; eine Liste ([email protected],[email protected]) wird beim
  Speichern abgelehnt und nie an nodemailer gegeben.
- Hinweis bei fehlgeschlagener Stornorechnung korrigiert (ein erneutes
  Stornieren ist nicht moeglich, die Stripe-Erstattung holt sie nach).

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-12 06:57:29 +02:00
Nick SzaunigandClaude Opus 5.5 2995890b07 chore(k8s): App-Deployment auf festen Build gepinnt, wie im Cluster
Docker Image bauen und veroeffentlichen / build-and-push (push) Canceled after 0s
Image per Digest statt :latest, imagePullPolicy IfNotPresent, Pod- und
Container-securityContext (runAsNonRoot 1001, seccomp RuntimeDefault, keine
Rechteausweitung, alle Capabilities entfernt). kubectl diff gegen den Cluster
ist leer. Neue Builds per kubectl set image ...:sha-<commit>.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-12 06:48:00 +02:00
Nick SzaunigandClaude Opus 5.5 4b15ca0b8d fix(invoice): Rechnung ohne PDF/A statt Fehler, wenn Schriften fehlen
Docker Image bauen und veroeffentlichen / build-and-push (push) Canceled after 0s
Fehlt assets/fonts im Container, entsteht das PDF mit Standardschriften
und eingebettetem XML, nur nicht als PDF/A; der Fehler steht laut im Log.
Eine Rechnung darf nie an den Schriften scheitern.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-12 06:24:25 +02:00
Nick SzaunigandClaude Opus 5.5 051eedaf7f feat(erp): Kunden und Firmenanfragen fuer das CRM in ERPNext
Zwei nur lesende Endpunkte mit ERP_EXPORT_TOKEN:
- GET /api/erp/kunden: Kundenkonten (ohne Admins, ohne Passwort/2FA) mit
  Standardadresse; ERPNext fuehrt sie als Privatkunden.
- GET /api/erp/anfragen: Firmenanfragen; ERPNext fuehrt die Firma als
  B2B-Kunden mit Ansprechpartner und je Anfrage eine Chance.
Seitenweise ueber den Cursor "nach"; ERPNext holt jeweils die ganze Liste
und erkennt so auch geloeschte Konten.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-12 06:22:48 +02:00
Nick SzaunigandClaude Opus 5.5 4d1bcbbdb7 feat(invoice): Stornorechnung und Rechnungskopie an Admin-Adresse
Rechnungskopie: unter Einstellungen > Firma > "Rechnungskopie an" laesst sich
eine Adresse hinterlegen; jede Rechnung und Stornorechnung geht zusaetzlich
als eigene Mail mit derselben E-Rechnung dorthin (lib/invoice-mail.ts).

Stornorechnung: eine Rechnung wird nie geloescht. Bei Stornierung im Admin,
erstatteter Retoure oder voller Erstattung bei Stripe entsteht genau eine
Stornorechnung ST-JJJJ-NNNN (eigener lueckenloser Nummernkreis) mit allen
Positionen und Bezug auf die Rechnung, als E-Rechnung (Gutschrift 381) an
die Kundschaft und als Kopie. Gleichzeitige Ausloeser (Admin-Storno und
Stripe-Webhook derselben Erstattung) enden bei einer Stornorechnung.
Bestellungen mit Stornorechnung lassen sich nicht wieder oeffnen.

ERP-Export: GET /api/erp/rechnungen?art=storno liefert Stornorechnungen mit
Bezug (cancels), Rechnungen tragen cancelledBy; PDFs auch fuer ST-Nummern.
Admin-Bestelldetail zeigt die Stornorechnung mit Download.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-12 06:19:42 +02:00
Nick SzaunigandClaude Opus 5.5 508ff24aa7 feat(invoice): Rechnungen als E-Rechnung (ZUGFeRD/Factur-X EN 16931)
Jedes Rechnungs-PDF ist jetzt eine hybride E-Rechnung: PDF/A-3b mit
eingebettetem factur-x.xml (CII D16B, Profil EN 16931). Netto und Steuer je
Satz kommen unveraendert aus der Aufstellung des PDFs, Versand und Rabatt
stehen als Zu- und Abschlag je Satz; XML und PDF tragen dieselben Summen.

- lib/einvoice/cii.ts: XML-Erzeugung (Rechnung 380, Stornorechnung 381 mit
  Bezug), ohne USt-IdNr. oder bei Positionen ohne Satz nur PDF mit Warnung
- lib/einvoice/facturx-pdf.ts: PDF/A-3b, Factur-X-XMP mit Erweiterungsschema,
  eingebettete Liberation-Schriften (gleiche Laufweiten wie Helvetica/Times)
- invoice-pdf: Darstellung als Stornorechnung (negative Betraege, Bezug)
- Geprueft mit XSD, CEN-Schematron EN 16931 1.3.16 und Mustang 2.26
  (veraPDF: PDF/A-3b konform, XML valid)

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-12 05:32:44 +02:00
Nick SzaunigandClaude Opus 5.5 f28e5e9d84 feat(k8s): Sicherung und Hintergrund-Jobs lauffaehig machen
- Werkzeug-Image wabenhain-tools (Dockerfile "AS tools", Muster wie
  artista-modelcamp-tools): Im App-Image fehlten tsconfig.json und Pakete
  wie stripe, Bestandsabgleich und Bestellfreigabe brachen mit
  "Cannot find module" ab. Die CI baut es neben dem App-Image.
- Datenbank-Sicherung: PV auf der Storage Box (Customer/Wabenhain), der
  Unterordner db-backup entsteht per subPath. Vorher blieb der PVC ohne
  StorageClass haengen.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-10 08:15:51 +02:00
Nick SzaunigandClaude Opus 5.5 b246c9ff4e feat(erp): read-only invoice export for ERPNext
ERPNext (collab.vycept.com) now picks up every shop invoice with its PDF,
books it as a Wabenhain sales invoice and matches it to the Stripe payment
via the PaymentIntent, so fees, revenue and the invoice document sit on one
booking.

- GET /api/erp/rechnungen?seit=<ISO>&limit=<1-200>: invoices oldest first,
  with order, payment intent, customer address (no phone, no notes), items
  and the VAT split per rate exactly as printed (lib/vat.ts), including the
  shipping and discount share per rate, so ERPNext books the same cents.
- GET /api/erp/rechnungen/<RE-YYYY-NNNN>/pdf: the stored invoice PDF.
- Bearer token ERP_EXPORT_TOKEN (min. 32 chars, timing-safe compare);
  without it the export is off. Failed attempts are rate limited.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-10 04:39:11 +02:00
Nick SzaunigandClaude Opus 5.5 7698d4cef3 chore(k8s): CPU- und Speicher-Reservierung entfernen
Requests stehen explizit auf 0 statt zu fehlen, weil Kubernetes einen
fehlenden Request auf das Limit setzt und dann noch mehr reserviert.
Die Limits bleiben als Obergrenze.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-10 04:08:40 +02:00
Nick SzaunigandClaude Opus 5.5 93b589ff04 feat(stock): keep shop stock in sync with ApiCore, write sales back
Docker Image bauen und veroeffentlichen / build-and-push (push) Successful in 3m40s
ApiCore now owns the remaining stock per batch; the shop sells from it
and reports every paid sale and every return back.

- Outbox apicore_stock_events, written in the same transaction as the
  status change: paid -> SALE, cancelled-after-payment / refunded return
  -> RETURN onto the batches of the sale, reopened paid order -> SALE.
  Whether a return is due is decided by the open sales in the outbox,
  so unpaid orders never book and nothing is booked twice.
- Delivery right after the response (next/server after()) plus CronJob
  wabenhain-apicore-stock every 10 minutes with 1/5/15/60 min backoff;
  ApiCore deduplicates by event id. Rejected bookings stop and show up
  on the dashboard.
- Reconcile: shop stock = ApiCore remaining - unconfirmed bookings -
  reservations of unpaid orders, in a serializable transaction like the
  checkout. Triggered after bookings, by the signed ApiCore webhook
  stock.changed (ids only, numbers are fetched) and by the sync button.
- Manufactured goods (candles, propolis products, ...): one shop product
  per ApiCore article via Product.apicoreProductId, drafts created by the
  sync, existing products linkable in the product form; new category
  SONSTIGES. Stock field is read-only for linked products.
- OrderItem.apicoreAllocations keeps which lots were sold (recalls).

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-05 21:16:54 +02:00
Nick SzaunigandClaude Opus 5.5 641afa1ae4 fix(harvests): allow several harvests of the same type per year
Docker Image bauen und veroeffentlichen / build-and-push (push) Successful in 2m15s
The ApiCore sync failed with "Unique constraint failed on the constraint:
harvests_year_type_key" as soon as ApiCore had two harvests of the same
honey type in one year, which is normal (several extractions per season).
Every bottling of that harvest was then skipped too.

- Drop the (year, type) unique index. The slug is now the only unique,
  public identifier: /ernten/<slug>, e.g. bluetenhonig-2026, then
  bluetenhonig-2026-07-14 for a further harvest of the same type.
- New Harvest.apicoreHarvestId as the sync key, backfilled from the old
  "apicore-<id>" slugs; the sync gives those rows a readable slug.
- Slugs are generated by one shared slugify() that normalizes Unicode
  (decomposed umlauts) and writes out umlauts. The migration repairs
  manual slugs created by the old umlaut bug ("fr-htracht-2026").
- Slug collisions are resolved by the database unique index, so parallel
  writers cannot produce duplicates; known slugs are skipped up front to
  keep the logs free of handled P2002 errors.
- Old URLs /ernten/<year>/<type> answer with 308 to the new address.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-02 04:25:02 +02:00
Nick SzaunigandClaude Opus 5.5 f1c0da4673 feat(admin): archive tabs for business inquiries and reviews
Inquiries: completed and rejected inquiries move to an "Archiv" tab;
resetting the status brings them back. Reviews: new "Ablehnen" action
(Review.rejectedAt) instead of only deleting; the archive shows decided
reviews in two columns, accepted and rejected, and either decision can
be reversed. Pending counters in the sidebar and dashboard exclude
rejected reviews, and the product page tells the author their review was
not published instead of "still being checked".

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-02 04:13:07 +02:00
Nick SzaunigandClaude Opus 5.5 866d3726d1 fix(apicore-sync): explain that HTTP 403 can mean an IP allowlist block
Docker Image bauen und veroeffentlichen / build-and-push (push) Successful in 2m46s
ApiCore API keys can now be limited to source addresses; a 403 then
usually means this server's address is not on the key's allowlist.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-01 02:42:23 +02:00
Nick SzaunigandClaude Opus 5.5 5c6d99d0a3 docs(einrichtung): ApiCore sync uses an API key, not a service account
The setup guide still listed APICORE_SYNC_EMAIL/APICORE_SYNC_PASSWORD,
which the client no longer reads since 3bc0693.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-01 01:09:53 +02:00
Nick SzaunigandClaude Opus 5.5 f71bcb2b67 feat(inhalte): text placeholder {{USt-IdNr}} for the VAT ID
Docker Image bauen und veroeffentlichen / build-and-push (push) Successful in 2m53s
Admin texts can now contain the placeholder {{USt-IdNr}}. Wherever
content is read (lib/content.ts), it is replaced with the VAT ID
maintained under Admin > Einstellungen > Firmendaten, so every text
follows a change automatically. The placeholder is matched case- and
space-insensitively; an empty VAT ID inserts nothing, and unknown
placeholders stay visible so typos show up. Images, videos, select
fields and the source field itself are never rewritten.

The default imprint text uses the placeholder instead of a hard-coded
number, and the settings page lists the available placeholders.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-01 00:59:04 +02:00
Nick SzaunigandClaude Opus 5.5 77bdd9958c feat(rechnung): branded invoice with VAT ID and per-rate grouping
The invoice PDF and the invoice page now share one branded layout:
logo (uploaded brand logo, otherwise the drawn honeycomb mark) and
wordmark in the header, honey accents, a forest-green table header,
a meta box with invoice number, date, order number and VAT ID, and a
footer with address, contact, VAT ID and page numbers on every page.

A mixed cart (e.g. honey at 7 % and a beeswax candle at 19 %) lists
its items in one block per VAT rate with a subtotal each. Next to the
totals, a VAT table shows net, VAT and gross per rate plus a sum row.
Long invoices continue on further pages with the table header repeated.

The VAT identification number is a new company field
(company.vatId), editable in Admin > Inhalte, defaulting to the
number already stated in the imprint.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-01 00:49:59 +02:00
Nick SzaunigandClaude Opus 5.5 1daef79026 feat(cart): open the cart drawer only after the drip has run down
Docker Image bauen und veroeffentlichen / build-and-push (push) Successful in 2m20s
The drawer used to slide in 180 ms after the honey drop landed and
covered the glow and the drip on the cart icon. It now waits until the
drip has finished (about 1.4 s). Without a visible flight (reduced
motion, icon off-screen) it still opens almost at once. The drip
timing lives in cart-flight.tsx so both files share one source.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-01 00:34:38 +02:00
Nick SzaunigandClaude Opus 5.5 15a457f66e feat(ernten): show only the remaining jars per batch
Docker Image bauen und veroeffentlichen / build-and-push (push) Successful in 4m6s
The batch list on a harvest page no longer states the batch's total
size ("12 von 300 Gläsern verfügbar"). It now reads "Es sind noch 12
Gläser verfügbar", with dedicated wording for a single jar and for a
sold-out batch, which reads scarcer and nudges customers to buy.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-01 00:23:20 +02:00
Nick SzaunigandClaude Opus 5.5 55ae867f68 fix(cart): keep the cart icon unchanged while it glows
The landing glow was a larger, filled basket with a thicker stroke and
a blurred halo, so the icon looked bigger during the effect. The glow
is now an exact copy of the icon (same size, same stroke width, no
fill, no scaling) that only changes colour to honey for a moment.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-30 23:57:52 +02:00
Nick SzaunigandClaude Opus 5.5 e19827d6af feat(cart): honey glow and drip when the drop lands in the cart
After the flying honey drop reaches the header cart icon, the basket
now lights up twice in honey colour with a warm halo, and a small
honey drop gathers at its lower edge, stretches and runs down. With
reduced motion only a single calm glow remains, without the drip.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-30 23:45:54 +02:00
Nick SzaunigandClaude Opus 5.5 3107a02588 feat(konto): settings page for security and privacy
Adds /konto/einstellungen, which gathers two-factor authentication,
"sign out on all devices", the Erntepost consent, the data export
(Art. 15 GDPR) and account deletion (Art. 17 GDPR) in one place.
The account overview now links there through a "Settings" card
instead of listing these blocks inline. Server actions are unchanged
apart from redirecting failures and revalidating the new page.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-30 21:20:37 +02:00
Nick SzaunigandClaude Opus 5.5 3bc0693e4d refactor(apicore-sync): API key and shop feed only, no password login
The sync now talks to ApiCore exclusively through the secured shop
interface: GET /shop-sync with an API key as bearer token. Removed:

- the email/password service-account login (and its MFA handling),
- the fallback to ApiCore's general read endpoints (/harvests,
  /products, /extractions, /bottling-batches, /apiaries).

A missing interface (404) or a rejected key (401/403) now stops the run
with a specific message instead of falling back. APICORE_SYNC_EMAIL and
APICORE_SYNC_PASSWORD are gone from .env.example, README and the k8s
secret template; the system status needs APICORE_API_KEY.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-28 06:05:11 +02:00
Nick SzaunigandClaude Opus 5.5 55be1fe881 feat(apicore-sync): authenticate with an ApiCore API key
APICORE_API_KEY replaces the service-account login: the key is sent as
the bearer token directly, needs no password and no MFA exemption, may
only read the shop feed and can be revoked in ApiCore at any time. A
malformed key is rejected with a clear message before any request.
Email/password stays as fallback for ApiCore versions without keys.

The system status treats the key or the legacy login as configured.
README, .env.example and the k8s secret template describe the key.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-28 01:24:26 +02:00
Nick SzaunigandClaude Opus 5.5 5642f0c4af feat(apicore-sync): read from ApiCore's dedicated shop feed
The sync now fetches everything from ApiCore's GET /shop-sync in one call:
an allowlisted feed (harvest facts, article master data, bottlings) that
never contains internal notes and needs only the single permission
"shop-sync:read" instead of read access to harvests, extractions etc.

- Contract version is checked; an unknown version stops the run with a
  clear message and writes nothing. Extra fields are ignored.
- One consistent snapshot, processed only after it arrived completely: a
  failed fetch writes nothing (no more half state of new harvests and old
  batches); the batch-load-error path is gone.
- Falls back to the previous per-resource endpoints when ApiCore answers
  404, so both packages can be deployed independently.
- .env.example documents the reduced permission.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-27 20:30:33 +02:00
Nick SzaunigandClaude Opus 5.5 d38f4303ff feat(apicore-sync): field-level ownership between ApiCore and the shop
ApiCore stays the source of truth for beekeeping facts, Wabenhain for
everything sales-related; both remain separate packages talking only over
ApiCore's HTTP API.

- Batches: lot number, fill date, best-before date and filled jar count
  now follow ApiCore on every run (before: created once, corrections never
  arrived). remainingGlasses - the shop's own stock - is never written; if
  it exceeds a corrected fill quantity, the run reports it instead of
  silently adjusting.
- Batches are matched by the new Batch.apicoreBottlingId instead of the lot
  number, so correcting a lot number in ApiCore no longer creates a second
  batch. Existing synced batches are adopted once via their lot number.
- Harvests: date and year always follow ApiCore, also when published or
  edited; type, location and text stay as maintained in the admin.
- Products stay entirely shop-owned after creation (weight drives the
  legally required unit price, so it is never changed automatically).
- Admin panel: batch update count and stock-conflict notice; intro text
  states which system owns which fields.

Migration is additive (nullable unique column).

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-27 20:14:35 +02:00