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]>
46 lines
1.9 KiB
TypeScript
46 lines
1.9 KiB
TypeScript
/**
|
|
* Bestandsabgleich mit ApiCore als Sicherheitsnetz unter den Sofort-Wegen.
|
|
*
|
|
* Normalerweise geht eine Buchung direkt nach der Zahlung/Stornierung an
|
|
* ApiCore, und eine Entnahme in ApiCore kommt per Webhook sofort an. Dieser
|
|
* Lauf faengt alles ab, was dabei liegen blieb: ApiCore war nicht erreichbar,
|
|
* ein Webhook ging verloren, der Server wurde mitten im Senden neu gestartet.
|
|
*
|
|
* 1. offene Buchungen senden (mit Wartezeiten 1, 5, 15, 60 Minuten),
|
|
* 2. ALLE verknuepften Produkte gegen ApiCore abgleichen.
|
|
*
|
|
* Aufruf: pnpm stock:apicore-sync
|
|
* Im Cluster laeuft er alle 10 Minuten als CronJob (k8s/07-backup-cronjob.yaml).
|
|
*
|
|
* Exit-Code 1, wenn ApiCore nicht erreichbar war oder Buchungen abgelehnt
|
|
* hat: dann soll der Job als fehlgeschlagen sichtbar sein, nicht nur in
|
|
* einer Logzeile verschwinden.
|
|
*/
|
|
import { prisma } from "../src/lib/prisma";
|
|
import { deliverPendingStockEvents } from "../src/lib/apicore-stock/delivery";
|
|
import { reconcileApiCoreStock } from "../src/lib/apicore-stock/reconcile";
|
|
|
|
async function main(): Promise<number> {
|
|
const delivered = await deliverPendingStockEvents();
|
|
console.log(
|
|
`[apicore-stock] Buchungen: ${delivered.sent} bestaetigt, ${delivered.rejected} abgelehnt, ${delivered.failed} nicht zugestellt${delivered.error ? ` (${delivered.error})` : ""}`
|
|
);
|
|
const reconciled = await reconcileApiCoreStock();
|
|
console.log(
|
|
`[apicore-stock] Abgleich: ${reconciled.updated} Produkte aktualisiert, ${reconciled.missing} in ApiCore nicht gefunden${reconciled.error ? ` (${reconciled.error})` : ""}`
|
|
);
|
|
const failed = delivered.failed > 0 || delivered.rejected > 0 || Boolean(reconciled.error);
|
|
return failed ? 1 : 0;
|
|
}
|
|
|
|
main()
|
|
.then(async (code) => {
|
|
await prisma.$disconnect();
|
|
process.exit(code);
|
|
})
|
|
.catch(async (error: unknown) => {
|
|
console.error("[apicore-stock] Lauf abgebrochen:", error);
|
|
await prisma.$disconnect();
|
|
process.exit(1);
|
|
});
|