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]>
Wurde eine Retoure erstattet und die Bestellung danach auf "STORNIERT"
gesetzt, buchten beide Schritte die Ware zurueck. Bei einer Testbestellung
stieg der Bestand so von 105 auf 106 Glaeser.
- Stornieren bucht nur noch zurueck, wenn keine Retoure erstattet ist
(needsRestockOnCancel in lib/inventory.ts)
- Retoure einer bereits stornierten Bestellung laesst sich nicht mehr
erstatten, auch nicht im Wettlauf (Bedingung im atomaren Claim)
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Viele Aktionen gaben keine Rueckmeldung (Retoure annehmen, Archiv,
Bewertungen, Adressen, Newsletter, Kundensperre ...) oder meldeten Erfolg,
obwohl die zugehoerige E-Mail nie rausging.
- lib/flash: Meldung als kurzlebiges Cookie; Toast im Root-Layout
(FlashToast). Ersetzt ?message=/?error= in der URL — darueber liess sich
bisher beliebiger Text als Systemmeldung im Wabenhain-Layout anzeigen
(Phishing-Hebel). Server Components leiten ueber /api/hinweis mit festen
Codes und festen Zielen weiter.
- fail()/succeed() zentral und asynchron; ein Quelltext-Test stellt sicher,
dass jeder Aufruf mit await/return erfolgt (sonst liefe eine Action nach
einem Fehler weiter).
- sendMail meldet, ob die Mail angenommen wurde; Aktionen mit Mail zeigen
ehrlich an, wenn der Versand scheitert (z.B. "Link erneut senden").
- Jede Server Action meldet jetzt Erfolg oder den Grund des Abbruchs.
- SubmitFeedback: geklickter Knopf zeigt sofort einen Ladezustand und ist
bis zur Antwort gegen Doppelklick gesperrt.
- 2FA: nach dem Einrichten zurueck ins Profil statt auf die Seite mit
"Deaktivieren".
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
- Checkout: Gutscheincode-Nutzungslimit wird jetzt innerhalb derselben
Serializable-Transaktion wie die Bestellanlage frisch nachgeprueft, statt
nur einmalig vorab. Zwei gleichzeitige Checkouts mit demselben
maxUses:1-Code konnten sich sonst beide durchsetzen.
- Admin/Retouren: Erstattungsstatus wird jetzt atomar (bedingtes Update,
ANGENOMMEN -> ERSTATTET) VOR dem Stripe-Aufruf gesetzt statt danach. Ein
Doppelklick auf "Erstatten" konnte sonst zwei echte Stripe-Erstattungen
ausloesen. Bei einem fehlschlagenden Stripe-Aufruf wird der Status wieder
zurueckgesetzt, damit erneut versucht werden kann.
Beide Luecken kamen aus dem Security-Assessment dieser Session.
Co-Authored-By: Claude Sonnet 5 <[email protected]>