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]>
This commit is contained in:
Nick SzaunigandClaude Opus 5.5 committed 2026-10-05 21:16:54 +02:00
1 parent 641afa1ae4
commit 93b589ff04
39 files changed
+1400 -37

No files matched your search

+2
View File
@@ -93,6 +93,8 @@ data:
VAT_RATE_PERCENT_HONIG: "7"
VAT_RATE_PERCENT_KERZEN: "19"
VAT_RATE_PERCENT_PROPOLIS: "19"
# Weitere Bienenprodukte (Kategorie SONSTIGES, z. B. Salben, Wachstuecher).
VAT_RATE_PERCENT_SONSTIGES: "19"
# Threads fuer das Umrechnen hochgeladener Videos (Admin -> Inhalte ->
# Hero-Video). NICHT ohne Not erhoehen: ffmpeg teilt sich das Speicherlimit
+6
View File
@@ -109,6 +109,12 @@ stringData:
# Konfigurationsfehler. APICORE_API_URL steht in 01-configmap.yaml.
APICORE_API_KEY: ""
# Secret des ApiCore-Webhooks "stock.changed" (ApiCore -> Automatisierung ->
# Webhooks, Ziel https://<shop>/api/apicore/webhook). Damit kommen
# Entnahmen aus ApiCore sofort im Shop an; leer = nur der CronJob gleicht
# ab (alle 10 Minuten).
APICORE_WEBHOOK_SECRET: ""
# DHL-Versandmarken (Admin -> Bestellung -> "DHL-Versandmarke"), siehe
# .env.example fuer die vollstaendige Einrichtung. Leer = der Knopf meldet
# "noch nicht konfiguriert", der Rest des Shops ist unberuehrt. Ob Sandbox
+52
View File
@@ -248,3 +248,55 @@ spec:
# Derselbe Wert wie beim App-Container: der Lauf laedt
# denselben Prisma-Client samt Treiber.
memory: 512Mi
---
# Bestandsabgleich mit ApiCore (scripts/apicore-stock-sync.ts): sendet
# liegengebliebene Verkaeufe/Rueckbuchungen an ApiCore und gleicht alle
# verknuepften Produkte gegen den ApiCore-Restbestand ab. Sicherheitsnetz
# unter dem Sofort-Versand und dem Webhook "stock.changed" — scheitert der
# Lauf (ApiCore nicht erreichbar, Buchung abgelehnt), ist der Job rot.
apiVersion: batch/v1
kind: CronJob
metadata:
name: wabenhain-apicore-stock
namespace: wabenhain
spec:
schedule: "*/10 * * * *"
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 5
startingDeadlineSeconds: 300
jobTemplate:
spec:
backoffLimit: 1
# Ein Lauf darf den naechsten nicht blockieren.
activeDeadlineSeconds: 540
template:
spec:
restartPolicy: OnFailure
securityContext:
runAsUser: 1001
runAsGroup: 1001
containers:
- name: apicore-stock
# DASSELBE Image und derselbe Tag wie in 05-app-deployment.yaml.
image: git.encry.de/nick.rosenloecher/wabenhain:latest
imagePullPolicy: IfNotPresent
# Braucht DATABASE_URL, DATABASE_PROVIDER, APICORE_API_URL und
# APICORE_API_KEY — dieselben Quellen wie die App.
envFrom:
- configMapRef:
name: wabenhain-config
- secretRef:
name: wabenhain-secrets
command: ["/bin/sh", "-c"]
args:
- |
set -eu
node scripts/prisma-provider.mjs
exec tsx scripts/apicore-stock-sync.ts
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
memory: 512Mi