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]>
This commit is contained in:
Nick.RosenloecherandClaude Opus 5.5 committed 2026-10-16 18:35:04 +02:00
1 parent 3ddf0bfa73
commit 1d88bac52e
30 files changed
+1152 -394

No files matched your search

+23 -6
View File
@@ -24,6 +24,8 @@ import { vatRateForProduct } from "@/lib/vat";
import { getLocale } from "@/i18n/locale";
import { expandCheckoutItems, type CheckoutProduct } from "@/lib/sponsorship/checkout-expand";
import { getOffersByProductIds } from "@/lib/sponsorship/offers";
import { loadHoneyCandidates } from "@/lib/sponsorship/honey-candidates";
import { isCreamy } from "@/lib/sponsorship/honey-selection";
import { reservedJarsByProduct } from "@/lib/sponsorship-subscription/reservation";
class OutOfStockError extends Error {}
@@ -231,9 +233,16 @@ export async function POST(request: Request) {
const productIds = [...new Set(input.items.map((item) => item.productId))];
const locale = await getLocale();
const offersByProduct = await getOffersByProductIds(productIds, locale);
const honeyIds = [...offersByProduct.values()]
.map((offer) => offer.honeyProduct?.id)
.filter((id): id is string => Boolean(id) && !productIds.includes(id as string));
// Honig fuer Pakete waehlt der Checkout selbst, im Moment der Bestellung
// (lib/sponsorship/honey-selection.ts) — dafuer braucht er alle Kandidaten
// mit freiem Bestand und deren Produktdaten (Charge, Gewicht, Steuersatz).
// Cremiger Honig kommt nie in ein Paket, seine Produktdaten braucht es
// deshalb nicht.
const needsHoney = [...offersByProduct.values()].some((offer) => offer.honeyJars > 0);
const honeyCandidates = needsHoney ? await loadHoneyCandidates() : [];
const honeyIds = honeyCandidates
.filter((candidate) => !isCreamy(candidate.consistency) && !productIds.includes(candidate.id))
.map((candidate) => candidate.id);
const products = await getProductsByIds(productIds);
const honeyProducts = honeyIds.length > 0 ? await getProductsByIds(honeyIds) : [];
@@ -290,6 +299,7 @@ export async function POST(request: Request) {
cartItems: input.items,
products: productMap,
offers: offersByProduct,
honeyCandidates,
vatRateFor: (product) => vatRateForProduct(product, process.env),
locale,
now: new Date(),
@@ -329,9 +339,16 @@ export async function POST(request: Request) {
// Fuer Abos reservierte Glaeser duerfen weder einzeln noch im Paket
// verkauft werden (lib/sponsorship-subscription/reservation.ts). Vor der
// Transaktion gelesen: die Reservierung aendert sich nur mit Abos, nicht
// durch gleichzeitige Bestellungen — die verbindliche Bestandspruefung
// bleibt das bedingte Update in der Transaktion.
// Transaktion gelesen, wie bisher: die verbindliche Bestandspruefung bleibt
// das bedingte Update in der Transaktion. Seit die Abo-Glaeser automatisch
// auf dem gerade gewaehlten Honig liegen, kann eine gleichzeitige Bestellung
// die Reservierung zwar auf einen anderen Honig verschieben; das bedingte
// Update haelt aber den Honig, auf dem sie laut dieser Momentaufnahme
// liegt, ueber ihrem Anteil. Die Reservierung in die Serializable-
// Transaktion zu ziehen, wuerde jede Bestellung mit jeder anderen
// Honigbuchung kollidieren lassen — bei Andrang deutlich mehr Abbrueche
// fuer einen Randfall, den im schlimmsten Fall eine zurueckgestellte
// Abo-Lieferung in der Aufmerksamkeitsliste auffaengt.
const reservedByProduct = await reservedJarsByProduct(expanded.reserve.map((line) => line.productId));
// Gutscheincode nur auf den Warenwert anwenden, nicht auf den Versand —