feat(raw-materials): wax and propolis lots consumed by production batches
- RawMaterialLot (beeswax, propolis) in grams with origin (apiary, harvest); stock in the same movement ledger: withdrawals, inventory counts, and the new movement type CONSUMPTION. - Production batches take inputs (raw lot + grams); batch and consumption are one transaction, never more than a lot has left. Deleting a mistaken batch gives the material back as its own movement. - Traceability both ways: batch -> inputs, raw lot -> usedIn. - Movement belongs to exactly one of bottling / production / raw lot (CHECK); raw material changes do not trigger the shop webhook. - Permissions raw-material:*, granted to roles that maintain production batches. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
1 parent
41c2af7284
commit
e73c8b2cb4
15 files changed
+986
-32
No files matched your search
+106
@@ -0,0 +1,106 @@
|
||||
-- Rohstoffe (Wachs, Propolis) mit eigenen Chargen und Bestand; Herstellungs-
|
||||
-- chargen verbrauchen sie (ProductionInput + Bewegung CONSUMPTION). Damit
|
||||
-- reicht die Rueckverfolgung vom Rohstoff ueber die Charge bis zur Kundin.
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "RawMaterialKind" AS ENUM ('BEESWAX', 'PROPOLIS');
|
||||
|
||||
-- AlterEnum
|
||||
ALTER TYPE "BatchMovementType" ADD VALUE 'CONSUMPTION';
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "BatchMovement" ADD COLUMN "rawMaterialLotId" TEXT;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "RawMaterialLot" (
|
||||
"id" TEXT NOT NULL,
|
||||
"kind" "RawMaterialKind" NOT NULL,
|
||||
"lotNumber" TEXT NOT NULL,
|
||||
"date" TIMESTAMP(3) NOT NULL,
|
||||
"quantity" INTEGER NOT NULL,
|
||||
"apiaryId" TEXT,
|
||||
"harvestId" TEXT,
|
||||
"notes" TEXT,
|
||||
"userId" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "RawMaterialLot_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "ProductionInput" (
|
||||
"id" TEXT NOT NULL,
|
||||
"productionBatchId" TEXT NOT NULL,
|
||||
"rawMaterialLotId" TEXT NOT NULL,
|
||||
"quantity" INTEGER NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "ProductionInput_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "RawMaterialLot_lotNumber_key" ON "RawMaterialLot"("lotNumber");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "RawMaterialLot_kind_idx" ON "RawMaterialLot"("kind");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "ProductionInput_productionBatchId_idx" ON "ProductionInput"("productionBatchId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "ProductionInput_rawMaterialLotId_idx" ON "ProductionInput"("rawMaterialLotId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "BatchMovement_rawMaterialLotId_idx" ON "BatchMovement"("rawMaterialLotId");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "RawMaterialLot" ADD CONSTRAINT "RawMaterialLot_apiaryId_fkey" FOREIGN KEY ("apiaryId") REFERENCES "Apiary"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "RawMaterialLot" ADD CONSTRAINT "RawMaterialLot_harvestId_fkey" FOREIGN KEY ("harvestId") REFERENCES "Harvest"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "RawMaterialLot" ADD CONSTRAINT "RawMaterialLot_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "ProductionInput" ADD CONSTRAINT "ProductionInput_productionBatchId_fkey" FOREIGN KEY ("productionBatchId") REFERENCES "ProductionBatch"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "ProductionInput" ADD CONSTRAINT "ProductionInput_rawMaterialLotId_fkey" FOREIGN KEY ("rawMaterialLotId") REFERENCES "RawMaterialLot"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "BatchMovement" ADD CONSTRAINT "BatchMovement_rawMaterialLotId_fkey" FOREIGN KEY ("rawMaterialLotId") REFERENCES "RawMaterialLot"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
|
||||
-- Eine Bewegung gehoert jetzt zu genau einer von drei Chargenarten.
|
||||
ALTER TABLE "BatchMovement" DROP CONSTRAINT "BatchMovement_exactly_one_batch";
|
||||
ALTER TABLE "BatchMovement" ADD CONSTRAINT "BatchMovement_exactly_one_batch"
|
||||
CHECK (num_nonnulls("bottlingBatchId", "productionBatchId", "rawMaterialLotId") = 1);
|
||||
ALTER TABLE "RawMaterialLot" ADD CONSTRAINT "RawMaterialLot_quantity_positive" CHECK ("quantity" > 0);
|
||||
ALTER TABLE "ProductionInput" ADD CONSTRAINT "ProductionInput_quantity_positive" CHECK ("quantity" > 0);
|
||||
|
||||
-- Permissions (catalog sync grants only brand-new rows to ADMIN): roles that
|
||||
-- may maintain production batches may maintain raw materials, readers read.
|
||||
INSERT INTO "Permission" (id, key, resource, action, "labelDe", "groupDe", "sortOrder")
|
||||
VALUES
|
||||
(gen_random_uuid()::text, 'raw-material:read', 'raw-material', 'read', 'Ansehen', 'Rohstoffe', 1630),
|
||||
(gen_random_uuid()::text, 'raw-material:create', 'raw-material', 'create', 'Anlegen', 'Rohstoffe', 1631),
|
||||
(gen_random_uuid()::text, 'raw-material:update', 'raw-material', 'update', 'Bearbeiten', 'Rohstoffe', 1632),
|
||||
(gen_random_uuid()::text, 'raw-material:delete', 'raw-material', 'delete', 'Löschen', 'Rohstoffe', 1633),
|
||||
(gen_random_uuid()::text, 'raw-material:withdraw', 'raw-material', 'withdraw', 'Entnehmen und Inventur', 'Rohstoffe', 1634)
|
||||
ON CONFLICT (key) DO NOTHING;
|
||||
|
||||
INSERT INTO "RolePermission" ("roleId", "permissionId", "grantedAt")
|
||||
SELECT DISTINCT rp."roleId", p.id, now()
|
||||
FROM "RolePermission" rp
|
||||
JOIN "Permission" existing ON existing.id = rp."permissionId" AND existing.key = 'production-batch:create'
|
||||
JOIN "Permission" p ON p.key IN ('raw-material:create', 'raw-material:update', 'raw-material:delete', 'raw-material:withdraw')
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
INSERT INTO "RolePermission" ("roleId", "permissionId", "grantedAt")
|
||||
SELECT DISTINCT rp."roleId", p.id, now()
|
||||
FROM "RolePermission" rp
|
||||
JOIN "Permission" existing ON existing.id = rp."permissionId" AND existing.key = 'production-batch:read'
|
||||
JOIN "Permission" p ON p.key = 'raw-material:read'
|
||||
ON CONFLICT DO NOTHING;
|
||||
@@ -122,6 +122,7 @@ model User {
|
||||
bottlingBatches BottlingBatch[]
|
||||
productionBatches ProductionBatch[]
|
||||
batchMovements BatchMovement[]
|
||||
rawMaterialLots RawMaterialLot[]
|
||||
notifications Notification[]
|
||||
queenEvaluations QueenEvaluation[]
|
||||
webhookSubscriptions WebhookSubscription[]
|
||||
@@ -239,6 +240,7 @@ model Apiary {
|
||||
colonyMoves ColonyApiaryHistory[]
|
||||
harvests Harvest[]
|
||||
treatments Treatment[]
|
||||
rawMaterialLots RawMaterialLot[]
|
||||
|
||||
@@index([status])
|
||||
}
|
||||
@@ -761,6 +763,7 @@ model Harvest {
|
||||
|
||||
colonies HarvestColony[]
|
||||
extractions ExtractionHarvest[]
|
||||
rawMaterialLots RawMaterialLot[]
|
||||
|
||||
@@index([apiaryId, date])
|
||||
}
|
||||
@@ -1097,15 +1100,73 @@ model ProductionBatch {
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
movements BatchMovement[]
|
||||
// Woraus die Charge hergestellt wurde (Rueckverfolgung Rohstoff -> Ware).
|
||||
inputs ProductionInput[]
|
||||
|
||||
@@index([productId])
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Rohstoffe (Wachs, Propolis) und ihr Verbrauch in der Herstellung
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
enum RawMaterialKind {
|
||||
BEESWAX
|
||||
PROPOLIS
|
||||
}
|
||||
|
||||
// Eine Rohstoff-Charge, z. B. "Wachs 2026-01: 4200 g" aus dem Entdeckeln
|
||||
// oder "Propolis 2026-02: 180 g" vom Abkratzen der Gitter. Menge in Gramm;
|
||||
// der Rest folgt wie bei allen Chargen aus dem Bewegungsbuch.
|
||||
model RawMaterialLot {
|
||||
id String @id @default(cuid())
|
||||
kind RawMaterialKind
|
||||
lotNumber String @unique
|
||||
// Gewinnungsdatum
|
||||
date DateTime
|
||||
// Gewonnene Menge in Gramm - Ausgangsmenge fuer den Rest.
|
||||
quantity Int
|
||||
// Herkunft, optional: Stand und/oder Ernte, aus der der Rohstoff stammt.
|
||||
apiaryId String?
|
||||
apiary Apiary? @relation(fields: [apiaryId], references: [id])
|
||||
harvestId String?
|
||||
harvest Harvest? @relation(fields: [harvestId], references: [id])
|
||||
notes String?
|
||||
userId String?
|
||||
user User? @relation(fields: [userId], references: [id])
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
movements BatchMovement[]
|
||||
usedIn ProductionInput[]
|
||||
|
||||
@@index([kind])
|
||||
}
|
||||
|
||||
// Verbrauch einer Rohstoff-Charge in einer Herstellungscharge. Die
|
||||
// Abbuchung selbst steht als CONSUMPTION im Bewegungsbuch des Rohstoffs.
|
||||
model ProductionInput {
|
||||
id String @id @default(cuid())
|
||||
productionBatchId String
|
||||
productionBatch ProductionBatch @relation(fields: [productionBatchId], references: [id], onDelete: Cascade)
|
||||
rawMaterialLotId String
|
||||
rawMaterialLot RawMaterialLot @relation(fields: [rawMaterialLotId], references: [id], onDelete: Restrict)
|
||||
// Verbrauchte Menge in Gramm.
|
||||
quantity Int
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@index([productionBatchId])
|
||||
@@index([rawMaterialLotId])
|
||||
}
|
||||
|
||||
enum BatchMovementType {
|
||||
SHOP_SALE
|
||||
SHOP_RETURN
|
||||
WITHDRAWAL
|
||||
CORRECTION
|
||||
// Rohstoff in einer Herstellungscharge verbraucht (minus) bzw. beim
|
||||
// Loeschen einer irrtuemlich angelegten Charge zurueckgegeben (plus).
|
||||
CONSUMPTION
|
||||
}
|
||||
|
||||
enum WithdrawalReason {
|
||||
@@ -1126,6 +1187,8 @@ model BatchMovement {
|
||||
bottlingBatch BottlingBatch? @relation(fields: [bottlingBatchId], references: [id], onDelete: Restrict)
|
||||
productionBatchId String?
|
||||
productionBatch ProductionBatch? @relation(fields: [productionBatchId], references: [id], onDelete: Restrict)
|
||||
rawMaterialLotId String?
|
||||
rawMaterialLot RawMaterialLot? @relation(fields: [rawMaterialLotId], references: [id], onDelete: Restrict)
|
||||
type BatchMovementType
|
||||
// Mit Vorzeichen: minus = raus, plus = rein.
|
||||
quantity Int
|
||||
@@ -1140,6 +1203,7 @@ model BatchMovement {
|
||||
|
||||
@@index([bottlingBatchId])
|
||||
@@index([productionBatchId])
|
||||
@@index([rawMaterialLotId])
|
||||
@@index([shopRequestId])
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user