Commit Graph
2 Commits
Author SHA1 Message Date
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 dbf146c19f test: Geldpfade absichern und Go-Live-Pruefung ehrlich machen
Die beiden Dateien, in denen Geld und Lagerbestand zusammenlaufen, hatten
bisher keinen einzigen Test. Dazu meldete die Go-Live-Pruefung gruene
Haken fuer Werte, die gar nicht gesetzt waren.

Tests fuer api/checkout/route.ts (10 Faelle)
  Torwaechter (fremder Origin, Turnstile, Rate-Limit), deaktiviertes
  Produkt, Preis 0, ausverkaufte Ware, Sitzungslebensdauer — und die
  beiden Regressionen: scheitert die Stripe-Sitzung, wird die Bestellung
  aufgeloest und OHNE orderNumber geantwortet; laesst sich die
  Sitzungs-ID nicht speichern, wird die Sitzung geschlossen. Per
  Mutationstest geprueft, dass der zweite Fall ohne den Fix rot wird.

Tests fuer api/checkout/webhook/route.ts (12 Faelle)
  Signaturpruefung, verzoegerte Zahlung (SEPA), Auslieferung samt
  Rechnung und Anhang, mehrfach zugestelltes Ereignis (Gutschein und
  Rechnungsnummer duerfen sich nicht wiederholen), Zahlung auf eine
  stornierte Bestellung, Betragsabweichung, abgelaufene Sitzung,
  fehlgeschlagener Einzug, volle und teilweise Erstattung, Rueckbuchung.

Datenbank und Stripe sind in beiden Dateien vollstaendig ersetzt.

Go-Live-Pruefung: falsch-positive Haken beseitigt
  Regeln, die einen Wert voraussetzen, bestanden bei fehlendem Wert
  einfach — eine leere Zeichenkette enthaelt nun einmal kein
  "example.com". Der Bericht meldete deshalb "NEXT_PUBLIC_SITE_URL ist
  die echte Domain" als bestanden, direkt neben dem Blocker
  "NEXT_PUBLIC_SITE_URL gesetzt". Ohne jeden Stripe-Schluessel galt
  sogar der Live-Modus als bestaetigt. Das ist die gefaehrlichste
  Fehlmeldung, die eine solche Pruefung machen kann: sie beruhigt ueber
  genau den Punkt, der offen ist. mustWhenSet/shouldWhenSet lassen
  Folgeregeln bei fehlendem Wert entfallen.

  Ausserdem sagt der Bericht jetzt ausdruecklich, dass er die Umgebung
  prueft, in der er LAEUFT. Lokal meldet er Blocker, die in der
  ConfigMap des Clusters laengst erledigt sind; wer das ein paarmal
  erlebt, liest ueber den Bericht hinweg. Das README nennt den Aufruf
  im Pod.

tsc: 0 Fehler. vitest: 316/316 gruen (zuvor 294). eslint: 0 Warnungen.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-09-18 17:50:38 +02:00