feat(stock): batch movement ledger, production batches and shop write-back API
One remaining quantity per batch, kept in ApiCore and always derived: initial quantity (jarCount / produced quantity) plus the signed movements in the new BatchMovement ledger, which is never deleted. - Production batches for MANUFACTURED articles (candles, propolis products, ...); articles get a kind and a shop category. - Withdrawals (farm shop, own use, breakage, sample) and inventory corrections on bottlings and production batches, never more than the remaining stock; every write locks the bottling or the article row. - Shop bookings via POST /shop-sync/movements (new scope shop-sync:write): idempotent per externalId, articles allocated oldest best-before first, returns mirrored onto the batches of the sale, invalid entries rejected one by one. - GET /shop-sync/stock and remainingCount in the shop feed; webhook stock.changed after every change (ids only, receivers fetch numbers). - Bottlings and production batches with movements cannot be deleted. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
1 parent
85f05dc541
commit
04f93e954a
28 files changed
+2445
-35
No files matched your search
@@ -120,6 +120,8 @@ model User {
|
||||
colonyMoves ColonyApiaryHistory[]
|
||||
auditLogs AuditLog[]
|
||||
bottlingBatches BottlingBatch[]
|
||||
productionBatches ProductionBatch[]
|
||||
batchMovements BatchMovement[]
|
||||
notifications Notification[]
|
||||
queenEvaluations QueenEvaluation[]
|
||||
webhookSubscriptions WebhookSubscription[]
|
||||
@@ -979,6 +981,22 @@ enum ProductStatus {
|
||||
DISCONTINUED
|
||||
}
|
||||
|
||||
// Wo der Bestand eines Artikels liegt: Honig je Abfuellung (BottlingBatch),
|
||||
// alles andere (Kerzen, Propolis-Produkte, ...) je Herstellungscharge
|
||||
// (ProductionBatch). Nicht umstellbar, sobald Chargen existieren.
|
||||
enum ProductKind {
|
||||
BOTTLED_HONEY
|
||||
MANUFACTURED
|
||||
}
|
||||
|
||||
// Vorgabe fuer die Kategorie des Produkts im Webshop (Wabenhain).
|
||||
enum ShopCategory {
|
||||
HONIG
|
||||
KERZEN
|
||||
PROPOLIS
|
||||
SONSTIGES
|
||||
}
|
||||
|
||||
model Product {
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
@@ -988,10 +1006,13 @@ model Product {
|
||||
weight Float?
|
||||
salesUnit String?
|
||||
status ProductStatus @default(ACTIVE)
|
||||
kind ProductKind @default(BOTTLED_HONEY)
|
||||
shopCategory ShopCategory @default(HONIG)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
bottlingBatches BottlingBatch[]
|
||||
bottlingBatches BottlingBatch[]
|
||||
productionBatches ProductionBatch[]
|
||||
|
||||
@@index([status])
|
||||
}
|
||||
@@ -1046,10 +1067,106 @@ model BottlingBatch {
|
||||
user User? @relation(fields: [userId], references: [id])
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
movements BatchMovement[]
|
||||
|
||||
@@index([honeyBatchId])
|
||||
@@index([productId])
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Herstellungschargen und Bestandsbewegungen (Bestandsabgleich mit dem Shop,
|
||||
// docs/superpowers/specs/2026-10-02-bestandsabgleich-design.md)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Gegenstueck zur Abfuellung fuer Artikel der Art MANUFACTURED (Kerzen,
|
||||
// Propolis-Tinkturen, Salben, ...). Spaeter Ziel eines Herstellungslaufs, der
|
||||
// Rohstoff-Chargen verbraucht.
|
||||
model ProductionBatch {
|
||||
id String @id @default(cuid())
|
||||
productId String
|
||||
product Product @relation(fields: [productId], references: [id])
|
||||
lotNumber String @unique
|
||||
date DateTime
|
||||
// Hergestellte Stueckzahl - Ausgangsmenge fuer den Rest.
|
||||
quantity Int
|
||||
bestBeforeDate DateTime?
|
||||
notes String?
|
||||
userId String?
|
||||
user User? @relation(fields: [userId], references: [id])
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
movements BatchMovement[]
|
||||
|
||||
@@index([productId])
|
||||
}
|
||||
|
||||
enum BatchMovementType {
|
||||
SHOP_SALE
|
||||
SHOP_RETURN
|
||||
WITHDRAWAL
|
||||
CORRECTION
|
||||
}
|
||||
|
||||
enum WithdrawalReason {
|
||||
HOFLADEN
|
||||
EIGENBEDARF
|
||||
BRUCH
|
||||
PROBE
|
||||
SONSTIGES
|
||||
}
|
||||
|
||||
// Bewegungsbuch: jede Bestandsaenderung einer Charge, nie geloescht (Fehler
|
||||
// werden gegengebucht). Rest = Ausgangsmenge + Summe aller quantity.
|
||||
// Genau eine der beiden Chargen-Spalten ist gesetzt (CHECK-Constraint in der
|
||||
// Migration).
|
||||
model BatchMovement {
|
||||
id String @id @default(cuid())
|
||||
bottlingBatchId String?
|
||||
bottlingBatch BottlingBatch? @relation(fields: [bottlingBatchId], references: [id], onDelete: Restrict)
|
||||
productionBatchId String?
|
||||
productionBatch ProductionBatch? @relation(fields: [productionBatchId], references: [id], onDelete: Restrict)
|
||||
type BatchMovementType
|
||||
// Mit Vorzeichen: minus = raus, plus = rein.
|
||||
quantity Int
|
||||
reason WithdrawalReason?
|
||||
notes String?
|
||||
shopRequestId String?
|
||||
shopRequest ShopStockRequest? @relation(fields: [shopRequestId], references: [id], onDelete: Restrict)
|
||||
userId String?
|
||||
user User? @relation(fields: [userId], references: [id])
|
||||
apiKeyId String?
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@index([bottlingBatchId])
|
||||
@@index([productionBatchId])
|
||||
@@index([shopRequestId])
|
||||
}
|
||||
|
||||
enum ShopStockRequestKind {
|
||||
SALE
|
||||
RETURN
|
||||
}
|
||||
|
||||
// Eine Buchungsanfrage des Shops. externalId (vom Shop vergeben) macht jede
|
||||
// Anfrage idempotent: dieselbe Anfrage zweimal bucht nur einmal.
|
||||
model ShopStockRequest {
|
||||
id String @id @default(cuid())
|
||||
externalId String @unique
|
||||
kind ShopStockRequestKind
|
||||
// Ziel: eine Abfuellung (Honig) ODER ein Artikel (MANUFACTURED, FIFO).
|
||||
bottlingBatchId String?
|
||||
productId String?
|
||||
quantity Int
|
||||
reversesExternalId String?
|
||||
apiKeyId String?
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
movements BatchMovement[]
|
||||
|
||||
@@index([reversesExternalId])
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Inventar (Abschnitt 4.22) – v1.x Modul 24
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Reference in new issue
Block a user