feat(04-01): InventoryService, API routes, migration, and 13 passing tests
- InventoryService: registerItem, recordMovement, getStockLevels, getItemMovements, listItems - RECEIVED movements auto-post JE (DR 1200 Equipment Inventory, CR 2010 AP) - Stock levels derived from movement aggregation (no mutable quantity column) - All 5 movement types validated with type-specific rules - API routes: POST/GET items, GET item detail, POST/GET movements, GET stock-levels - CASL: OFFICE_STAFF gets manage Inventory permission - Migration applied: add_inventory_models (4 enums, 2 tables) - 13 tests: registration, all movement types, stock derivation, JE posting, history Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
-- CreateEnum
|
||||
CREATE TYPE "ItemTrackingType" AS ENUM ('SERIALIZED', 'BATCH');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "ItemCondition" AS ENUM ('NEW', 'REFURBISHED', 'USED', 'DAMAGED');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "MovementType" AS ENUM ('RECEIVED', 'ISSUED', 'RETURNED', 'DISPOSED', 'TRANSFERRED');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "LocationType" AS ENUM ('WAREHOUSE', 'TECHNICIAN', 'SUBSCRIBER');
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "InventoryItem" (
|
||||
"id" TEXT NOT NULL,
|
||||
"tenantId" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"itemType" TEXT NOT NULL,
|
||||
"model" TEXT,
|
||||
"serialNumber" TEXT,
|
||||
"trackingType" "ItemTrackingType" NOT NULL,
|
||||
"purchaseCost" DECIMAL(10,2),
|
||||
"purchaseDate" TIMESTAMP(3),
|
||||
"warrantyExpiry" TIMESTAMP(3),
|
||||
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "InventoryItem_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "StockMovement" (
|
||||
"id" TEXT NOT NULL,
|
||||
"tenantId" TEXT NOT NULL,
|
||||
"inventoryItemId" TEXT NOT NULL,
|
||||
"movementType" "MovementType" NOT NULL,
|
||||
"quantity" INTEGER NOT NULL DEFAULT 1,
|
||||
"condition" "ItemCondition",
|
||||
"fromLocationType" "LocationType",
|
||||
"fromLocationId" TEXT,
|
||||
"toLocationType" "LocationType",
|
||||
"toLocationId" TEXT,
|
||||
"notes" TEXT,
|
||||
"journalEntryId" TEXT,
|
||||
"performedById" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "StockMovement_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "InventoryItem_tenantId_idx" ON "InventoryItem"("tenantId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "InventoryItem_tenantId_itemType_idx" ON "InventoryItem"("tenantId", "itemType");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "InventoryItem_tenantId_trackingType_idx" ON "InventoryItem"("tenantId", "trackingType");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "InventoryItem_tenantId_serialNumber_key" ON "InventoryItem"("tenantId", "serialNumber");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "StockMovement_tenantId_idx" ON "StockMovement"("tenantId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "StockMovement_inventoryItemId_idx" ON "StockMovement"("inventoryItemId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "StockMovement_tenantId_movementType_idx" ON "StockMovement"("tenantId", "movementType");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "StockMovement" ADD CONSTRAINT "StockMovement_inventoryItemId_fkey" FOREIGN KEY ("inventoryItemId") REFERENCES "InventoryItem"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "StockMovement" ADD CONSTRAINT "StockMovement_performedById_fkey" FOREIGN KEY ("performedById") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
Reference in New Issue
Block a user