feat(db): reshape user and inventory schema
This commit is contained in:
@@ -1,66 +1,111 @@
|
||||
-- CreateEnum
|
||||
CREATE TYPE "UserRole" AS ENUM ('ADMIN', 'MANAGER', 'STAFF', 'VIEWER');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "UserStatus" AS ENUM ('INVITED', 'ACTIVE', 'SUSPENDED', 'DISABLED');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "PersonDepartment" AS ENUM ('IT', 'ENGINEERING', 'LOGISTICS', 'TRAFFIC', 'DRIVER', 'ADMINISTRATION', 'SALES', 'OTHER');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "ItemStatus" AS ENUM ('AVAILABLE', 'ASSIGNED', 'RESERVED', 'IN_REPAIR', 'BROKEN', 'STOLEN', 'DISPOSED');
|
||||
CREATE TYPE "ItemTrackingType" AS ENUM ('QUANTITY', 'SERIALIZED');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "MovementType" AS ENUM ('IN', 'OUT', 'ASSIGNMENT', 'RETURN', 'ADJUSTMENT', 'DELETED');
|
||||
CREATE TYPE "ItemStatus" AS ENUM ('ACTIVE', 'DISCONTINUED', 'ARCHIVED');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "AssetStatus" AS ENUM ('AVAILABLE', 'ASSIGNED', 'IN_REPAIR', 'BROKEN', 'LOST', 'STOLEN', 'DISPOSED', 'RETIRED');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "AssignmentStatus" AS ENUM ('OPEN', 'PARTIALLY_RETURNED', 'RETURNED', 'CANCELLED');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "InventoryMovementType" AS ENUM ('RECEIPT', 'ISSUE', 'ASSIGNMENT', 'RETURN', 'ADJUSTMENT', 'STATUS_CHANGE', 'DISPOSAL', 'INITIAL_LOAD');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "InventoryMovementReason" AS ENUM ('PURCHASE', 'MANUAL_ENTRY', 'EMPLOYEE_ASSIGNMENT', 'EMPLOYEE_RETURN', 'INVENTORY_CORRECTION', 'DAMAGE', 'REPAIR', 'REPAIR_RETURN', 'LOSS', 'THEFT', 'DISPOSAL', 'INITIAL_LOAD', 'OTHER');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "StockAlertStatus" AS ENUM ('OPEN', 'ACKNOWLEDGED', 'RESOLVED');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "StockAlertTrigger" AS ENUM ('BELOW_MINIMUM', 'OUT_OF_STOCK');
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "User" (
|
||||
"id" TEXT NOT NULL,
|
||||
"id" UUID NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"email" TEXT NOT NULL,
|
||||
"password" TEXT NOT NULL,
|
||||
"emailNormalized" TEXT NOT NULL,
|
||||
"passwordHash" TEXT,
|
||||
"role" "UserRole" NOT NULL DEFAULT 'STAFF',
|
||||
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||
"status" "UserStatus" NOT NULL DEFAULT 'INVITED',
|
||||
"deletedAt" TIMESTAMP(3),
|
||||
"invitedAt" TIMESTAMP(3),
|
||||
"activatedAt" TIMESTAMP(3),
|
||||
"passwordChangedAt" TIMESTAMP(3),
|
||||
"lastLoginAt" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "UserInvitation" (
|
||||
"id" UUID NOT NULL,
|
||||
"userId" UUID NOT NULL,
|
||||
"tokenHash" TEXT NOT NULL,
|
||||
"invitedById" UUID NOT NULL,
|
||||
"email" TEXT NOT NULL,
|
||||
"expiresAt" TIMESTAMP(3) NOT NULL,
|
||||
"acceptedAt" TIMESTAMP(3),
|
||||
"revokedAt" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "UserInvitation_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Person" (
|
||||
"id" TEXT NOT NULL,
|
||||
"id" UUID NOT NULL,
|
||||
"firstName" TEXT NOT NULL,
|
||||
"lastName" TEXT NOT NULL,
|
||||
"department" "PersonDepartment",
|
||||
"email" TEXT,
|
||||
"phone" TEXT,
|
||||
"userId" TEXT,
|
||||
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||
"userId" UUID,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
"deletedAt" TIMESTAMP(3),
|
||||
|
||||
CONSTRAINT "Person_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Category" (
|
||||
"id" TEXT NOT NULL,
|
||||
"id" UUID NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"description" TEXT,
|
||||
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
"deletedAt" TIMESTAMP(3),
|
||||
|
||||
CONSTRAINT "Category_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Item" (
|
||||
"id" TEXT NOT NULL,
|
||||
"id" UUID NOT NULL,
|
||||
"sku" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"description" TEXT,
|
||||
"categoryId" TEXT NOT NULL,
|
||||
"trackingType" "ItemTrackingType" NOT NULL,
|
||||
"status" "ItemStatus" NOT NULL DEFAULT 'ACTIVE',
|
||||
"categoryId" UUID NOT NULL,
|
||||
"stock" INTEGER NOT NULL DEFAULT 0,
|
||||
"minStock" INTEGER,
|
||||
"maxStock" INTEGER,
|
||||
"targetStock" INTEGER,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
"deletedAt" TIMESTAMP(3),
|
||||
@@ -70,29 +115,38 @@ CREATE TABLE "Item" (
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Asset" (
|
||||
"id" TEXT NOT NULL,
|
||||
"itemId" TEXT,
|
||||
"id" UUID NOT NULL,
|
||||
"assetTag" TEXT,
|
||||
"serialNumber" TEXT NOT NULL,
|
||||
"itemId" UUID NOT NULL,
|
||||
"status" "AssetStatus" NOT NULL DEFAULT 'AVAILABLE',
|
||||
"manufacturer" TEXT,
|
||||
"model" TEXT,
|
||||
"deliveryNote" TEXT,
|
||||
"status" "ItemStatus" NOT NULL DEFAULT 'AVAILABLE',
|
||||
"invoiceNumber" TEXT,
|
||||
"purchaseDate" TIMESTAMP(3),
|
||||
"purchasePrice" DECIMAL(12,2),
|
||||
"warrantyEndsAt" TIMESTAMP(3),
|
||||
"notes" TEXT,
|
||||
"retiredAt" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
"deletedAt" TIMESTAMP(3),
|
||||
|
||||
CONSTRAINT "Asset_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Assignment" (
|
||||
"id" TEXT NOT NULL,
|
||||
"quantity" INTEGER,
|
||||
"id" UUID NOT NULL,
|
||||
"personId" UUID NOT NULL,
|
||||
"status" "AssignmentStatus" NOT NULL DEFAULT 'OPEN',
|
||||
"assignedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"dueAt" TIMESTAMP(3),
|
||||
"closedAt" TIMESTAMP(3),
|
||||
"notes" TEXT,
|
||||
"itemId" TEXT,
|
||||
"assetId" TEXT,
|
||||
"personId" TEXT,
|
||||
"assignmentDate" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"returnDate" TIMESTAMP(3),
|
||||
"createdBy" TEXT NOT NULL,
|
||||
"createdById" UUID NOT NULL,
|
||||
"closedById" UUID,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
@@ -100,29 +154,135 @@ CREATE TABLE "Assignment" (
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Movement" (
|
||||
"id" TEXT NOT NULL,
|
||||
"type" "MovementType" NOT NULL DEFAULT 'IN',
|
||||
CREATE TABLE "AssignmentStockLine" (
|
||||
"id" UUID NOT NULL,
|
||||
"assignmentId" UUID NOT NULL,
|
||||
"itemId" UUID NOT NULL,
|
||||
"quantity" INTEGER NOT NULL,
|
||||
"details" TEXT,
|
||||
"returnedQuantity" INTEGER NOT NULL DEFAULT 0,
|
||||
"notes" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "AssignmentStockLine_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "AssignmentStockReturn" (
|
||||
"id" UUID NOT NULL,
|
||||
"assignmentLineId" UUID NOT NULL,
|
||||
"quantity" INTEGER NOT NULL,
|
||||
"returnedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"receivedById" UUID NOT NULL,
|
||||
"notes" TEXT,
|
||||
"itemId" TEXT,
|
||||
"assetId" TEXT,
|
||||
"previousStock" INTEGER,
|
||||
"newStock" INTEGER,
|
||||
"personId" TEXT,
|
||||
"assignmentId" TEXT,
|
||||
"userId" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "Movement_pkey" PRIMARY KEY ("id")
|
||||
CONSTRAINT "AssignmentStockReturn_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "AssignmentAssetLine" (
|
||||
"id" UUID NOT NULL,
|
||||
"assignmentId" UUID NOT NULL,
|
||||
"assetId" UUID NOT NULL,
|
||||
"assignedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"returnedAt" TIMESTAMP(3),
|
||||
"returnedById" UUID,
|
||||
"returnStatus" "AssetStatus",
|
||||
"notes" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "AssignmentAssetLine_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "InventoryMovement" (
|
||||
"id" UUID NOT NULL,
|
||||
"type" "InventoryMovementType" NOT NULL,
|
||||
"reason" "InventoryMovementReason" NOT NULL,
|
||||
"assignmentId" UUID,
|
||||
"reference" TEXT,
|
||||
"details" TEXT,
|
||||
"notes" TEXT,
|
||||
"performedById" UUID NOT NULL,
|
||||
"occurredAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "InventoryMovement_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "StockMovementLine" (
|
||||
"id" UUID NOT NULL,
|
||||
"movementId" UUID NOT NULL,
|
||||
"itemId" UUID NOT NULL,
|
||||
"stockDelta" INTEGER NOT NULL,
|
||||
"previousStock" INTEGER NOT NULL,
|
||||
"newStock" INTEGER NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "StockMovementLine_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "AssetMovementLine" (
|
||||
"id" UUID NOT NULL,
|
||||
"movementId" UUID NOT NULL,
|
||||
"assetId" UUID NOT NULL,
|
||||
"previousStatus" "AssetStatus",
|
||||
"newStatus" "AssetStatus" NOT NULL,
|
||||
"notes" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "AssetMovementLine_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "StockAlert" (
|
||||
"id" UUID NOT NULL,
|
||||
"itemId" UUID NOT NULL,
|
||||
"trigger" "StockAlertTrigger" NOT NULL,
|
||||
"status" "StockAlertStatus" NOT NULL DEFAULT 'OPEN',
|
||||
"availableStock" INTEGER NOT NULL,
|
||||
"minimumStock" INTEGER NOT NULL,
|
||||
"suggestedPurchase" INTEGER,
|
||||
"triggeredAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"acknowledgedAt" TIMESTAMP(3),
|
||||
"acknowledgedById" UUID,
|
||||
"resolvedAt" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "StockAlert_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
|
||||
CREATE UNIQUE INDEX "User_emailNormalized_key" ON "User"("emailNormalized");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Person_email_key" ON "Person"("email");
|
||||
CREATE INDEX "User_status_idx" ON "User"("status");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "User_deletedAt_idx" ON "User"("deletedAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "User_createdAt_idx" ON "User"("createdAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "UserInvitation_tokenHash_key" ON "UserInvitation"("tokenHash");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "UserInvitation_userId_idx" ON "UserInvitation"("userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "UserInvitation_expiresAt_idx" ON "UserInvitation"("expiresAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "UserInvitation_acceptedAt_idx" ON "UserInvitation"("acceptedAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "UserInvitation_revokedAt_idx" ON "UserInvitation"("revokedAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Person_userId_key" ON "Person"("userId");
|
||||
@@ -131,61 +291,127 @@ CREATE UNIQUE INDEX "Person_userId_key" ON "Person"("userId");
|
||||
CREATE INDEX "Person_lastName_firstName_idx" ON "Person"("lastName", "firstName");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Person_department_idx" ON "Person"("department");
|
||||
CREATE INDEX "Person_department_deletedAt_idx" ON "Person"("department", "deletedAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Person_deletedAt_idx" ON "Person"("deletedAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Category_name_key" ON "Category"("name");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Category_name_idx" ON "Category"("name");
|
||||
CREATE INDEX "Category_deletedAt_idx" ON "Category"("deletedAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Item_name_key" ON "Item"("name");
|
||||
CREATE UNIQUE INDEX "Item_sku_key" ON "Item"("sku");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Item_categoryId_idx" ON "Item"("categoryId");
|
||||
CREATE INDEX "Item_categoryId_status_idx" ON "Item"("categoryId", "status");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Item_trackingType_status_idx" ON "Item"("trackingType", "status");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Item_name_idx" ON "Item"("name");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Item_deletedAt_idx" ON "Item"("deletedAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Asset_assetTag_key" ON "Asset"("assetTag");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Asset_serialNumber_key" ON "Asset"("serialNumber");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Asset_serialNumber_idx" ON "Asset"("serialNumber");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Asset_itemId_idx" ON "Asset"("itemId");
|
||||
CREATE INDEX "Asset_itemId_status_idx" ON "Asset"("itemId", "status");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Asset_status_idx" ON "Asset"("status");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Assignment_assetId_key" ON "Assignment"("assetId");
|
||||
CREATE INDEX "Asset_createdAt_idx" ON "Asset"("createdAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Assignment_itemId_idx" ON "Assignment"("itemId");
|
||||
CREATE INDEX "Asset_deletedAt_idx" ON "Asset"("deletedAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Assignment_assetId_idx" ON "Assignment"("assetId");
|
||||
CREATE INDEX "Assignment_personId_status_idx" ON "Assignment"("personId", "status");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Assignment_personId_idx" ON "Assignment"("personId");
|
||||
CREATE INDEX "Assignment_personId_assignedAt_idx" ON "Assignment"("personId", "assignedAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Assignment_createdBy_idx" ON "Assignment"("createdBy");
|
||||
CREATE INDEX "Assignment_status_assignedAt_idx" ON "Assignment"("status", "assignedAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Movement_itemId_idx" ON "Movement"("itemId");
|
||||
CREATE INDEX "Assignment_dueAt_idx" ON "Assignment"("dueAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Movement_assetId_idx" ON "Movement"("assetId");
|
||||
CREATE INDEX "Assignment_createdById_createdAt_idx" ON "Assignment"("createdById", "createdAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Movement_personId_idx" ON "Movement"("personId");
|
||||
CREATE INDEX "AssignmentStockLine_assignmentId_idx" ON "AssignmentStockLine"("assignmentId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Movement_type_idx" ON "Movement"("type");
|
||||
CREATE INDEX "AssignmentStockLine_itemId_createdAt_idx" ON "AssignmentStockLine"("itemId", "createdAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Movement_userId_idx" ON "Movement"("userId");
|
||||
CREATE INDEX "AssignmentStockReturn_assignmentLineId_returnedAt_idx" ON "AssignmentStockReturn"("assignmentLineId", "returnedAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "AssignmentStockReturn_receivedById_returnedAt_idx" ON "AssignmentStockReturn"("receivedById", "returnedAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "AssignmentAssetLine_assignmentId_idx" ON "AssignmentAssetLine"("assignmentId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "AssignmentAssetLine_assetId_assignedAt_idx" ON "AssignmentAssetLine"("assetId", "assignedAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "AssignmentAssetLine_returnedAt_idx" ON "AssignmentAssetLine"("returnedAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "InventoryMovement_type_occurredAt_idx" ON "InventoryMovement"("type", "occurredAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "InventoryMovement_reason_occurredAt_idx" ON "InventoryMovement"("reason", "occurredAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "InventoryMovement_assignmentId_idx" ON "InventoryMovement"("assignmentId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "InventoryMovement_performedById_occurredAt_idx" ON "InventoryMovement"("performedById", "occurredAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "InventoryMovement_occurredAt_idx" ON "InventoryMovement"("occurredAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "StockMovementLine_movementId_idx" ON "StockMovementLine"("movementId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "StockMovementLine_itemId_createdAt_idx" ON "StockMovementLine"("itemId", "createdAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "AssetMovementLine_assetId_createdAt_idx" ON "AssetMovementLine"("assetId", "createdAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "AssetMovementLine_movementId_assetId_key" ON "AssetMovementLine"("movementId", "assetId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "StockAlert_itemId_status_idx" ON "StockAlert"("itemId", "status");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "StockAlert_status_triggeredAt_idx" ON "StockAlert"("status", "triggeredAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "StockAlert_trigger_triggeredAt_idx" ON "StockAlert"("trigger", "triggeredAt");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "UserInvitation" ADD CONSTRAINT "UserInvitation_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "UserInvitation" ADD CONSTRAINT "UserInvitation_invitedById_fkey" FOREIGN KEY ("invitedById") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Person" ADD CONSTRAINT "Person_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
@@ -194,31 +420,335 @@ ALTER TABLE "Person" ADD CONSTRAINT "Person_userId_fkey" FOREIGN KEY ("userId")
|
||||
ALTER TABLE "Item" ADD CONSTRAINT "Item_categoryId_fkey" FOREIGN KEY ("categoryId") REFERENCES "Category"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Asset" ADD CONSTRAINT "Asset_itemId_fkey" FOREIGN KEY ("itemId") REFERENCES "Item"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
ALTER TABLE "Asset" ADD CONSTRAINT "Asset_itemId_fkey" FOREIGN KEY ("itemId") REFERENCES "Item"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Assignment" ADD CONSTRAINT "Assignment_itemId_fkey" FOREIGN KEY ("itemId") REFERENCES "Item"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
ALTER TABLE "Assignment" ADD CONSTRAINT "Assignment_personId_fkey" FOREIGN KEY ("personId") REFERENCES "Person"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Assignment" ADD CONSTRAINT "Assignment_assetId_fkey" FOREIGN KEY ("assetId") REFERENCES "Asset"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
ALTER TABLE "Assignment" ADD CONSTRAINT "Assignment_createdById_fkey" FOREIGN KEY ("createdById") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Assignment" ADD CONSTRAINT "Assignment_personId_fkey" FOREIGN KEY ("personId") REFERENCES "Person"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE "Assignment" ADD CONSTRAINT "Assignment_closedById_fkey" FOREIGN KEY ("closedById") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Assignment" ADD CONSTRAINT "Assignment_createdBy_fkey" FOREIGN KEY ("createdBy") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
ALTER TABLE "AssignmentStockLine" ADD CONSTRAINT "AssignmentStockLine_assignmentId_fkey" FOREIGN KEY ("assignmentId") REFERENCES "Assignment"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Movement" ADD CONSTRAINT "Movement_itemId_fkey" FOREIGN KEY ("itemId") REFERENCES "Item"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
ALTER TABLE "AssignmentStockLine" ADD CONSTRAINT "AssignmentStockLine_itemId_fkey" FOREIGN KEY ("itemId") REFERENCES "Item"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Movement" ADD CONSTRAINT "Movement_assetId_fkey" FOREIGN KEY ("assetId") REFERENCES "Asset"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
ALTER TABLE "AssignmentStockReturn" ADD CONSTRAINT "AssignmentStockReturn_assignmentLineId_fkey" FOREIGN KEY ("assignmentLineId") REFERENCES "AssignmentStockLine"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Movement" ADD CONSTRAINT "Movement_personId_fkey" FOREIGN KEY ("personId") REFERENCES "Person"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
ALTER TABLE "AssignmentStockReturn" ADD CONSTRAINT "AssignmentStockReturn_receivedById_fkey" FOREIGN KEY ("receivedById") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Movement" ADD CONSTRAINT "Movement_assignmentId_fkey" FOREIGN KEY ("assignmentId") REFERENCES "Assignment"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
ALTER TABLE "AssignmentAssetLine" ADD CONSTRAINT "AssignmentAssetLine_assignmentId_fkey" FOREIGN KEY ("assignmentId") REFERENCES "Assignment"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Movement" ADD CONSTRAINT "Movement_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
ALTER TABLE "AssignmentAssetLine" ADD CONSTRAINT "AssignmentAssetLine_assetId_fkey" FOREIGN KEY ("assetId") REFERENCES "Asset"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "AssignmentAssetLine" ADD CONSTRAINT "AssignmentAssetLine_returnedById_fkey" FOREIGN KEY ("returnedById") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "InventoryMovement" ADD CONSTRAINT "InventoryMovement_assignmentId_fkey" FOREIGN KEY ("assignmentId") REFERENCES "Assignment"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "InventoryMovement" ADD CONSTRAINT "InventoryMovement_performedById_fkey" FOREIGN KEY ("performedById") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "StockMovementLine" ADD CONSTRAINT "StockMovementLine_movementId_fkey" FOREIGN KEY ("movementId") REFERENCES "InventoryMovement"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "StockMovementLine" ADD CONSTRAINT "StockMovementLine_itemId_fkey" FOREIGN KEY ("itemId") REFERENCES "Item"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "AssetMovementLine" ADD CONSTRAINT "AssetMovementLine_movementId_fkey" FOREIGN KEY ("movementId") REFERENCES "InventoryMovement"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "AssetMovementLine" ADD CONSTRAINT "AssetMovementLine_assetId_fkey" FOREIGN KEY ("assetId") REFERENCES "Asset"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "StockAlert" ADD CONSTRAINT "StockAlert_itemId_fkey" FOREIGN KEY ("itemId") REFERENCES "Item"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "StockAlert" ADD CONSTRAINT "StockAlert_acknowledgedById_fkey" FOREIGN KEY ("acknowledgedById") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
|
||||
-- =====================================================
|
||||
-- USER INVITATION / ACTIVATION
|
||||
-- =====================================================
|
||||
|
||||
ALTER TABLE "User"
|
||||
ADD CONSTRAINT "User_invited_without_password"
|
||||
CHECK (
|
||||
"status" <> 'INVITED'
|
||||
OR "passwordHash" IS NULL
|
||||
);
|
||||
|
||||
ALTER TABLE "User"
|
||||
ADD CONSTRAINT "User_active_requires_password"
|
||||
CHECK (
|
||||
"status" <> 'ACTIVE'
|
||||
OR "passwordHash" IS NOT NULL
|
||||
);
|
||||
|
||||
ALTER TABLE "User"
|
||||
ADD CONSTRAINT "User_active_requires_activation_date"
|
||||
CHECK (
|
||||
"status" <> 'ACTIVE'
|
||||
OR "activatedAt" IS NOT NULL
|
||||
);
|
||||
|
||||
ALTER TABLE "User"
|
||||
ADD CONSTRAINT "User_activation_date_after_invitation"
|
||||
CHECK (
|
||||
"activatedAt" IS NULL
|
||||
OR "invitedAt" IS NULL
|
||||
OR "activatedAt" >= "invitedAt"
|
||||
);
|
||||
|
||||
ALTER TABLE "User"
|
||||
ADD CONSTRAINT "User_password_changed_after_invitation"
|
||||
CHECK (
|
||||
"passwordChangedAt" IS NULL
|
||||
OR "invitedAt" IS NULL
|
||||
OR "passwordChangedAt" >= "invitedAt"
|
||||
);
|
||||
|
||||
ALTER TABLE "UserInvitation"
|
||||
ADD CONSTRAINT "UserInvitation_expiry_after_creation"
|
||||
CHECK ("expiresAt" > "createdAt");
|
||||
|
||||
ALTER TABLE "UserInvitation"
|
||||
ADD CONSTRAINT "UserInvitation_accepted_or_revoked"
|
||||
CHECK (
|
||||
"acceptedAt" IS NULL
|
||||
OR "revokedAt" IS NULL
|
||||
);
|
||||
|
||||
ALTER TABLE "UserInvitation"
|
||||
ADD CONSTRAINT "UserInvitation_accepted_after_creation"
|
||||
CHECK (
|
||||
"acceptedAt" IS NULL
|
||||
OR "acceptedAt" >= "createdAt"
|
||||
);
|
||||
|
||||
ALTER TABLE "UserInvitation"
|
||||
ADD CONSTRAINT "UserInvitation_revoked_after_creation"
|
||||
CHECK (
|
||||
"revokedAt" IS NULL
|
||||
OR "revokedAt" >= "createdAt"
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX "UserInvitation_active_user_key"
|
||||
ON "UserInvitation" ("userId")
|
||||
WHERE "acceptedAt" IS NULL
|
||||
AND "revokedAt" IS NULL;
|
||||
|
||||
|
||||
-- =====================================================
|
||||
-- ITEM STOCK
|
||||
-- =====================================================
|
||||
|
||||
ALTER TABLE "Item"
|
||||
ADD CONSTRAINT "Item_stock_non_negative"
|
||||
CHECK ("stock" >= 0);
|
||||
|
||||
ALTER TABLE "Item"
|
||||
ADD CONSTRAINT "Item_min_stock_non_negative"
|
||||
CHECK (
|
||||
"minStock" IS NULL
|
||||
OR "minStock" >= 0
|
||||
);
|
||||
|
||||
ALTER TABLE "Item"
|
||||
ADD CONSTRAINT "Item_target_stock_non_negative"
|
||||
CHECK (
|
||||
"targetStock" IS NULL
|
||||
OR "targetStock" >= 0
|
||||
);
|
||||
|
||||
ALTER TABLE "Item"
|
||||
ADD CONSTRAINT "Item_target_not_below_minimum"
|
||||
CHECK (
|
||||
"minStock" IS NULL
|
||||
OR "targetStock" IS NULL
|
||||
OR "targetStock" >= "minStock"
|
||||
);
|
||||
|
||||
ALTER TABLE "Item"
|
||||
ADD CONSTRAINT "Item_serialized_stock_zero"
|
||||
CHECK (
|
||||
"trackingType" <> 'SERIALIZED'
|
||||
OR "stock" = 0
|
||||
);
|
||||
|
||||
|
||||
-- =====================================================
|
||||
-- ASSET DATA
|
||||
-- =====================================================
|
||||
|
||||
ALTER TABLE "Asset"
|
||||
ADD CONSTRAINT "Asset_purchase_price_non_negative"
|
||||
CHECK (
|
||||
"purchasePrice" IS NULL
|
||||
OR "purchasePrice" >= 0
|
||||
);
|
||||
|
||||
ALTER TABLE "Asset"
|
||||
ADD CONSTRAINT "Asset_warranty_date_valid"
|
||||
CHECK (
|
||||
"warrantyEndsAt" IS NULL
|
||||
OR "purchaseDate" IS NULL
|
||||
OR "warrantyEndsAt" >= "purchaseDate"
|
||||
);
|
||||
|
||||
ALTER TABLE "Asset"
|
||||
ADD CONSTRAINT "Asset_retired_date_valid"
|
||||
CHECK (
|
||||
"retiredAt" IS NULL
|
||||
OR "retiredAt" >= "createdAt"
|
||||
);
|
||||
|
||||
|
||||
-- =====================================================
|
||||
-- ASSIGNMENTS
|
||||
-- =====================================================
|
||||
|
||||
ALTER TABLE "Assignment"
|
||||
ADD CONSTRAINT "Assignment_due_date_valid"
|
||||
CHECK (
|
||||
"dueAt" IS NULL
|
||||
OR "dueAt" >= "assignedAt"
|
||||
);
|
||||
|
||||
ALTER TABLE "Assignment"
|
||||
ADD CONSTRAINT "Assignment_closed_date_valid"
|
||||
CHECK (
|
||||
"closedAt" IS NULL
|
||||
OR "closedAt" >= "assignedAt"
|
||||
);
|
||||
|
||||
|
||||
-- =====================================================
|
||||
-- QUANTITY ASSIGNMENTS
|
||||
-- =====================================================
|
||||
|
||||
ALTER TABLE "AssignmentStockLine"
|
||||
ADD CONSTRAINT "AssignmentStockLine_quantity_positive"
|
||||
CHECK ("quantity" > 0);
|
||||
|
||||
ALTER TABLE "AssignmentStockLine"
|
||||
ADD CONSTRAINT "AssignmentStockLine_returned_non_negative"
|
||||
CHECK ("returnedQuantity" >= 0);
|
||||
|
||||
ALTER TABLE "AssignmentStockLine"
|
||||
ADD CONSTRAINT "AssignmentStockLine_returned_not_greater"
|
||||
CHECK ("returnedQuantity" <= "quantity");
|
||||
|
||||
ALTER TABLE "AssignmentStockReturn"
|
||||
ADD CONSTRAINT "AssignmentStockReturn_quantity_positive"
|
||||
CHECK ("quantity" > 0);
|
||||
|
||||
|
||||
-- =====================================================
|
||||
-- SERIALIZED ASSET ASSIGNMENTS
|
||||
-- =====================================================
|
||||
|
||||
ALTER TABLE "AssignmentAssetLine"
|
||||
ADD CONSTRAINT "AssignmentAssetLine_return_date_valid"
|
||||
CHECK (
|
||||
"returnedAt" IS NULL
|
||||
OR "returnedAt" >= "assignedAt"
|
||||
);
|
||||
|
||||
ALTER TABLE "AssignmentAssetLine"
|
||||
ADD CONSTRAINT "AssignmentAssetLine_return_data_consistent"
|
||||
CHECK (
|
||||
(
|
||||
"returnedAt" IS NULL
|
||||
AND "returnedById" IS NULL
|
||||
AND "returnStatus" IS NULL
|
||||
)
|
||||
OR
|
||||
(
|
||||
"returnedAt" IS NOT NULL
|
||||
AND "returnedById" IS NOT NULL
|
||||
AND "returnStatus" IS NOT NULL
|
||||
)
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX "AssignmentAssetLine_active_asset_key"
|
||||
ON "AssignmentAssetLine" ("assetId")
|
||||
WHERE "returnedAt" IS NULL;
|
||||
|
||||
|
||||
-- =====================================================
|
||||
-- STOCK MOVEMENTS
|
||||
-- =====================================================
|
||||
|
||||
ALTER TABLE "StockMovementLine"
|
||||
ADD CONSTRAINT "StockMovementLine_stock_consistency"
|
||||
CHECK (
|
||||
"newStock" = "previousStock" + "stockDelta"
|
||||
);
|
||||
|
||||
ALTER TABLE "StockMovementLine"
|
||||
ADD CONSTRAINT "StockMovementLine_previous_stock_non_negative"
|
||||
CHECK ("previousStock" >= 0);
|
||||
|
||||
ALTER TABLE "StockMovementLine"
|
||||
ADD CONSTRAINT "StockMovementLine_new_stock_non_negative"
|
||||
CHECK ("newStock" >= 0);
|
||||
|
||||
ALTER TABLE "StockMovementLine"
|
||||
ADD CONSTRAINT "StockMovementLine_delta_not_zero"
|
||||
CHECK ("stockDelta" <> 0);
|
||||
|
||||
|
||||
-- =====================================================
|
||||
-- STOCK ALERTS
|
||||
-- =====================================================
|
||||
|
||||
ALTER TABLE "StockAlert"
|
||||
ADD CONSTRAINT "StockAlert_available_stock_non_negative"
|
||||
CHECK ("availableStock" >= 0);
|
||||
|
||||
ALTER TABLE "StockAlert"
|
||||
ADD CONSTRAINT "StockAlert_minimum_stock_non_negative"
|
||||
CHECK ("minimumStock" >= 0);
|
||||
|
||||
ALTER TABLE "StockAlert"
|
||||
ADD CONSTRAINT "StockAlert_suggested_purchase_non_negative"
|
||||
CHECK (
|
||||
"suggestedPurchase" IS NULL
|
||||
OR "suggestedPurchase" >= 0
|
||||
);
|
||||
|
||||
ALTER TABLE "StockAlert"
|
||||
ADD CONSTRAINT "StockAlert_acknowledgement_consistent"
|
||||
CHECK (
|
||||
(
|
||||
"acknowledgedAt" IS NULL
|
||||
AND "acknowledgedById" IS NULL
|
||||
)
|
||||
OR
|
||||
(
|
||||
"acknowledgedAt" IS NOT NULL
|
||||
AND "acknowledgedById" IS NOT NULL
|
||||
)
|
||||
);
|
||||
|
||||
ALTER TABLE "StockAlert"
|
||||
ADD CONSTRAINT "StockAlert_resolution_date_valid"
|
||||
CHECK (
|
||||
"resolvedAt" IS NULL
|
||||
OR "resolvedAt" >= "triggeredAt"
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX "StockAlert_active_item_trigger_key"
|
||||
ON "StockAlert" ("itemId", "trigger")
|
||||
WHERE "status" IN ('OPEN', 'ACKNOWLEDGED');
|
||||
+511
-117
@@ -14,6 +14,10 @@ datasource db {
|
||||
provider = "postgresql"
|
||||
}
|
||||
|
||||
// ======================================================
|
||||
// USERS
|
||||
// ======================================================
|
||||
|
||||
enum UserRole {
|
||||
ADMIN
|
||||
MANAGER
|
||||
@@ -21,20 +25,91 @@ enum UserRole {
|
||||
VIEWER
|
||||
}
|
||||
|
||||
model User {
|
||||
id String @id @default(uuid())
|
||||
name String
|
||||
email String @unique
|
||||
password String
|
||||
role UserRole @default(STAFF)
|
||||
isActive Boolean @default(true)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
movements Movement[]
|
||||
assignments Assignment[]
|
||||
person Person?
|
||||
enum UserStatus {
|
||||
INVITED
|
||||
ACTIVE
|
||||
SUSPENDED
|
||||
DISABLED
|
||||
}
|
||||
|
||||
model User {
|
||||
id String @id @default(uuid(7)) @db.Uuid
|
||||
|
||||
name String
|
||||
email String
|
||||
emailNormalized String @unique
|
||||
|
||||
/**
|
||||
* Nulo mientras el usuario no haya aceptado la invitación.
|
||||
*/
|
||||
passwordHash String?
|
||||
|
||||
role UserRole @default(STAFF)
|
||||
status UserStatus @default(INVITED)
|
||||
|
||||
deletedAt DateTime?
|
||||
|
||||
invitedAt DateTime?
|
||||
activatedAt DateTime?
|
||||
passwordChangedAt DateTime?
|
||||
lastLoginAt DateTime?
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
person Person?
|
||||
|
||||
createdAssignments Assignment[] @relation("AssignmentCreatedBy")
|
||||
closedAssignments Assignment[] @relation("AssignmentClosedBy")
|
||||
|
||||
receivedStockReturns AssignmentStockReturn[]
|
||||
receivedAssetReturns AssignmentAssetLine[] @relation("AssetReturnedBy")
|
||||
|
||||
movements InventoryMovement[]
|
||||
|
||||
acknowledgedStockAlerts StockAlert[] @relation("StockAlertAcknowledgedBy")
|
||||
|
||||
sentInvitations UserInvitation[] @relation("UserInvitationInvitedBy")
|
||||
invitations UserInvitation[]
|
||||
|
||||
@@index([status])
|
||||
@@index([deletedAt])
|
||||
@@index([createdAt])
|
||||
}
|
||||
|
||||
model UserInvitation {
|
||||
id String @id @default(uuid(7)) @db.Uuid
|
||||
|
||||
userId String @db.Uuid
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: Cascade)
|
||||
|
||||
/**
|
||||
* Hash del token de invitación.
|
||||
* Nunca guardar el token plano.
|
||||
*/
|
||||
tokenHash String @unique
|
||||
|
||||
invitedById String @db.Uuid
|
||||
invitedBy User @relation("UserInvitationInvitedBy", fields: [invitedById], references: [id], onDelete: Restrict, onUpdate: Cascade)
|
||||
|
||||
email String
|
||||
|
||||
expiresAt DateTime
|
||||
acceptedAt DateTime?
|
||||
revokedAt DateTime?
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@index([userId])
|
||||
@@index([expiresAt])
|
||||
@@index([acceptedAt])
|
||||
@@index([revokedAt])
|
||||
}
|
||||
|
||||
// ======================================================
|
||||
// PEOPLE
|
||||
// ======================================================
|
||||
|
||||
enum PersonDepartment {
|
||||
IT
|
||||
ENGINEERING
|
||||
@@ -47,139 +122,458 @@ enum PersonDepartment {
|
||||
}
|
||||
|
||||
model Person {
|
||||
id String @id @default(uuid())
|
||||
firstName String
|
||||
lastName String
|
||||
department PersonDepartment?
|
||||
email String? @unique
|
||||
phone String?
|
||||
userId String? @unique
|
||||
user User? @relation(fields: [userId], references: [id], onDelete: SetNull, onUpdate: Cascade)
|
||||
isActive Boolean @default(true)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
id String @id @default(uuid(7)) @db.Uuid
|
||||
firstName String
|
||||
lastName String
|
||||
department PersonDepartment?
|
||||
|
||||
email String?
|
||||
phone String?
|
||||
|
||||
userId String? @unique @db.Uuid
|
||||
user User? @relation(fields: [userId], references: [id], onDelete: SetNull, onUpdate: Cascade)
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime?
|
||||
|
||||
assignments Assignment[]
|
||||
movements Movement[]
|
||||
|
||||
@@index([lastName, firstName])
|
||||
@@index([department])
|
||||
@@index([department, deletedAt])
|
||||
@@index([deletedAt])
|
||||
}
|
||||
|
||||
model Category {
|
||||
id String @id @default(uuid())
|
||||
name String @unique
|
||||
description String?
|
||||
isActive Boolean @default(true)
|
||||
items Item[]
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
// ======================================================
|
||||
// CATALOG
|
||||
// ======================================================
|
||||
|
||||
@@index([name])
|
||||
enum ItemTrackingType {
|
||||
QUANTITY
|
||||
SERIALIZED
|
||||
}
|
||||
|
||||
enum ItemStatus {
|
||||
AVAILABLE
|
||||
ASSIGNED
|
||||
RESERVED
|
||||
IN_REPAIR
|
||||
BROKEN
|
||||
STOLEN
|
||||
DISPOSED
|
||||
ACTIVE
|
||||
DISCONTINUED
|
||||
ARCHIVED
|
||||
}
|
||||
|
||||
model Category {
|
||||
id String @id @default(uuid(7)) @db.Uuid
|
||||
name String @unique
|
||||
description String?
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime?
|
||||
|
||||
items Item[]
|
||||
|
||||
@@index([deletedAt])
|
||||
}
|
||||
|
||||
model Item {
|
||||
id String @id @default(uuid())
|
||||
name String @unique
|
||||
id String @id @default(uuid(7)) @db.Uuid
|
||||
sku String @unique
|
||||
name String
|
||||
description String?
|
||||
categoryId String
|
||||
category Category @relation(fields: [categoryId], references: [id])
|
||||
stock Int @default(0)
|
||||
minStock Int?
|
||||
maxStock Int?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime?
|
||||
movements Movement[]
|
||||
assignments Assignment[]
|
||||
assets Asset[]
|
||||
|
||||
@@index([categoryId])
|
||||
trackingType ItemTrackingType
|
||||
status ItemStatus @default(ACTIVE)
|
||||
|
||||
categoryId String @db.Uuid
|
||||
category Category @relation(fields: [categoryId], references: [id], onDelete: Restrict, onUpdate: Cascade)
|
||||
|
||||
/**
|
||||
* Solo se utiliza para artículos QUANTITY.
|
||||
* Para artículos SERIALIZED, las existencias se obtienen
|
||||
* contando los activos AVAILABLE.
|
||||
*/
|
||||
stock Int @default(0)
|
||||
|
||||
/**
|
||||
* Umbral de alerta.
|
||||
* QUANTITY:
|
||||
* Se compara contra Item.stock.
|
||||
* SERIALIZED:
|
||||
* Se compara contra número de Asset AVAILABLE.
|
||||
*/
|
||||
minStock Int?
|
||||
|
||||
/**
|
||||
* Nivel deseado tras reposición.
|
||||
* Compra sugerida:
|
||||
* targetStock - stock disponible.
|
||||
*/
|
||||
targetStock Int?
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime?
|
||||
|
||||
assets Asset[]
|
||||
|
||||
assignmentStockLines AssignmentStockLine[]
|
||||
stockMovementLines StockMovementLine[]
|
||||
|
||||
stockAlerts StockAlert[]
|
||||
|
||||
@@index([categoryId, status])
|
||||
@@index([trackingType, status])
|
||||
@@index([name])
|
||||
@@index([deletedAt])
|
||||
}
|
||||
|
||||
// ======================================================
|
||||
// SERIALIZED ASSETS
|
||||
// ======================================================
|
||||
|
||||
enum AssetStatus {
|
||||
AVAILABLE
|
||||
ASSIGNED
|
||||
IN_REPAIR
|
||||
BROKEN
|
||||
LOST
|
||||
STOLEN
|
||||
DISPOSED
|
||||
RETIRED
|
||||
}
|
||||
|
||||
model Asset {
|
||||
id String @id @default(uuid())
|
||||
itemId String?
|
||||
item Item? @relation(fields: [itemId], references: [id])
|
||||
serialNumber String @unique
|
||||
deliveryNote String?
|
||||
status ItemStatus @default(AVAILABLE)
|
||||
notes String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
movements Movement[]
|
||||
assignment Assignment?
|
||||
id String @id @default(uuid(7)) @db.Uuid
|
||||
|
||||
@@index([serialNumber])
|
||||
@@index([itemId])
|
||||
/**
|
||||
* Identificador interno visible.
|
||||
* Ejemplos:
|
||||
* IT-000001
|
||||
* LAP-000042
|
||||
* MON-000117
|
||||
*/
|
||||
assetTag String? @unique
|
||||
|
||||
/**
|
||||
* Número de serie del fabricante.
|
||||
* Puede ser nulo.
|
||||
*/
|
||||
serialNumber String @unique
|
||||
|
||||
itemId String @db.Uuid
|
||||
item Item @relation(fields: [itemId], references: [id], onDelete: Restrict, onUpdate: Cascade)
|
||||
|
||||
status AssetStatus @default(AVAILABLE)
|
||||
|
||||
manufacturer String?
|
||||
model String?
|
||||
|
||||
deliveryNote String?
|
||||
invoiceNumber String?
|
||||
|
||||
purchaseDate DateTime?
|
||||
purchasePrice Decimal? @db.Decimal(12, 2)
|
||||
|
||||
warrantyEndsAt DateTime?
|
||||
|
||||
notes String?
|
||||
|
||||
retiredAt DateTime?
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
deletedAt DateTime?
|
||||
|
||||
assignmentLines AssignmentAssetLine[]
|
||||
movementLines AssetMovementLine[]
|
||||
|
||||
@@index([itemId, status])
|
||||
@@index([status])
|
||||
@@index([createdAt])
|
||||
@@index([deletedAt])
|
||||
}
|
||||
|
||||
// ======================================================
|
||||
// ASSIGNMENTS
|
||||
// ======================================================
|
||||
|
||||
enum AssignmentStatus {
|
||||
OPEN
|
||||
PARTIALLY_RETURNED
|
||||
RETURNED
|
||||
CANCELLED
|
||||
}
|
||||
|
||||
model Assignment {
|
||||
id String @id @default(uuid())
|
||||
quantity Int?
|
||||
notes String?
|
||||
itemId String?
|
||||
item Item? @relation(fields: [itemId], references: [id], onDelete: SetNull, onUpdate: Cascade)
|
||||
assetId String? @unique
|
||||
asset Asset? @relation(fields: [assetId], references: [id], onDelete: SetNull, onUpdate: Cascade)
|
||||
personId String?
|
||||
person Person? @relation(fields: [personId], references: [id], onDelete: Cascade, onUpdate: Cascade)
|
||||
assignmentDate DateTime @default(now())
|
||||
returnDate DateTime?
|
||||
createdBy String
|
||||
createdUser User @relation(fields: [createdBy], references: [id])
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
movement Movement[]
|
||||
id String @id @default(uuid(7)) @db.Uuid
|
||||
|
||||
@@index([itemId])
|
||||
@@index([assetId])
|
||||
@@index([personId])
|
||||
@@index([createdBy])
|
||||
personId String @db.Uuid
|
||||
person Person @relation(fields: [personId], references: [id], onDelete: Restrict, onUpdate: Cascade)
|
||||
|
||||
status AssignmentStatus @default(OPEN)
|
||||
|
||||
assignedAt DateTime @default(now())
|
||||
dueAt DateTime?
|
||||
closedAt DateTime?
|
||||
|
||||
notes String?
|
||||
|
||||
createdById String @db.Uuid
|
||||
createdBy User @relation("AssignmentCreatedBy", fields: [createdById], references: [id], onDelete: Restrict, onUpdate: Cascade)
|
||||
|
||||
closedById String? @db.Uuid
|
||||
closedBy User? @relation("AssignmentClosedBy", fields: [closedById], references: [id], onDelete: Restrict, onUpdate: Cascade)
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
stockLines AssignmentStockLine[]
|
||||
assetLines AssignmentAssetLine[]
|
||||
movements InventoryMovement[]
|
||||
|
||||
@@index([personId, status])
|
||||
@@index([personId, assignedAt])
|
||||
@@index([status, assignedAt])
|
||||
@@index([dueAt])
|
||||
@@index([createdById, createdAt])
|
||||
}
|
||||
|
||||
enum MovementType {
|
||||
IN
|
||||
OUT
|
||||
// ======================================================
|
||||
// QUANTITY ASSIGNMENTS
|
||||
// ======================================================
|
||||
|
||||
model AssignmentStockLine {
|
||||
id String @id @default(uuid(7)) @db.Uuid
|
||||
|
||||
assignmentId String @db.Uuid
|
||||
assignment Assignment @relation(fields: [assignmentId], references: [id], onDelete: Restrict, onUpdate: Cascade)
|
||||
|
||||
itemId String @db.Uuid
|
||||
item Item @relation(fields: [itemId], references: [id], onDelete: Restrict, onUpdate: Cascade)
|
||||
|
||||
quantity Int
|
||||
returnedQuantity Int @default(0)
|
||||
|
||||
notes String?
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
returns AssignmentStockReturn[]
|
||||
|
||||
@@index([assignmentId])
|
||||
@@index([itemId, createdAt])
|
||||
}
|
||||
|
||||
model AssignmentStockReturn {
|
||||
id String @id @default(uuid(7)) @db.Uuid
|
||||
|
||||
assignmentLineId String @db.Uuid
|
||||
assignmentLine AssignmentStockLine @relation(fields: [assignmentLineId], references: [id], onDelete: Restrict, onUpdate: Cascade)
|
||||
|
||||
quantity Int
|
||||
|
||||
returnedAt DateTime @default(now())
|
||||
|
||||
receivedById String @db.Uuid
|
||||
receivedBy User @relation(fields: [receivedById], references: [id], onDelete: Restrict, onUpdate: Cascade)
|
||||
|
||||
notes String?
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@index([assignmentLineId, returnedAt])
|
||||
@@index([receivedById, returnedAt])
|
||||
}
|
||||
|
||||
// ======================================================
|
||||
// SERIALIZED ASSET ASSIGNMENTS
|
||||
// ======================================================
|
||||
|
||||
model AssignmentAssetLine {
|
||||
id String @id @default(uuid(7)) @db.Uuid
|
||||
|
||||
assignmentId String @db.Uuid
|
||||
assignment Assignment @relation(fields: [assignmentId], references: [id], onDelete: Restrict, onUpdate: Cascade)
|
||||
|
||||
assetId String @db.Uuid
|
||||
asset Asset @relation(fields: [assetId], references: [id], onDelete: Restrict, onUpdate: Cascade)
|
||||
|
||||
assignedAt DateTime @default(now())
|
||||
returnedAt DateTime?
|
||||
|
||||
returnedById String? @db.Uuid
|
||||
returnedBy User? @relation("AssetReturnedBy", fields: [returnedById], references: [id], onDelete: Restrict, onUpdate: Cascade)
|
||||
|
||||
returnStatus AssetStatus?
|
||||
|
||||
notes String?
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
/**
|
||||
* La unicidad de asignación activa se protege
|
||||
* mediante índice único parcial en PostgreSQL.
|
||||
*/
|
||||
@@index([assignmentId])
|
||||
@@index([assetId, assignedAt])
|
||||
@@index([returnedAt])
|
||||
}
|
||||
|
||||
// ======================================================
|
||||
// INVENTORY MOVEMENTS
|
||||
// ======================================================
|
||||
|
||||
enum InventoryMovementType {
|
||||
RECEIPT
|
||||
ISSUE
|
||||
ASSIGNMENT
|
||||
RETURN
|
||||
ADJUSTMENT
|
||||
DELETED
|
||||
STATUS_CHANGE
|
||||
DISPOSAL
|
||||
INITIAL_LOAD
|
||||
}
|
||||
|
||||
model Movement {
|
||||
id String @id @default(uuid())
|
||||
type MovementType @default(IN)
|
||||
quantity Int
|
||||
details String?
|
||||
notes String?
|
||||
itemId String?
|
||||
item Item? @relation(fields: [itemId], references: [id], onDelete: SetNull, onUpdate: Cascade)
|
||||
assetId String?
|
||||
asset Asset? @relation(fields: [assetId], references: [id], onDelete: SetNull, onUpdate: Cascade)
|
||||
previousStock Int?
|
||||
newStock Int?
|
||||
personId String?
|
||||
person Person? @relation(fields: [personId], references: [id], onDelete: SetNull, onUpdate: Cascade)
|
||||
assignmentId String?
|
||||
assignment Assignment? @relation(fields: [assignmentId], references: [id], onDelete: SetNull, onUpdate: Cascade)
|
||||
userId String
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@index([itemId])
|
||||
@@index([assetId])
|
||||
@@index([personId])
|
||||
@@index([type])
|
||||
@@index([userId])
|
||||
enum InventoryMovementReason {
|
||||
PURCHASE
|
||||
MANUAL_ENTRY
|
||||
EMPLOYEE_ASSIGNMENT
|
||||
EMPLOYEE_RETURN
|
||||
INVENTORY_CORRECTION
|
||||
DAMAGE
|
||||
REPAIR
|
||||
REPAIR_RETURN
|
||||
LOSS
|
||||
THEFT
|
||||
DISPOSAL
|
||||
INITIAL_LOAD
|
||||
OTHER
|
||||
}
|
||||
|
||||
model InventoryMovement {
|
||||
id String @id @default(uuid(7)) @db.Uuid
|
||||
|
||||
type InventoryMovementType
|
||||
reason InventoryMovementReason
|
||||
|
||||
assignmentId String? @db.Uuid
|
||||
assignment Assignment? @relation(fields: [assignmentId], references: [id], onDelete: Restrict, onUpdate: Cascade)
|
||||
|
||||
reference String?
|
||||
|
||||
details String?
|
||||
notes String?
|
||||
|
||||
performedById String @db.Uuid
|
||||
performedBy User @relation(fields: [performedById], references: [id], onDelete: Restrict, onUpdate: Cascade)
|
||||
|
||||
occurredAt DateTime @default(now())
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
stockLines StockMovementLine[]
|
||||
assetLines AssetMovementLine[]
|
||||
|
||||
@@index([type, occurredAt])
|
||||
@@index([reason, occurredAt])
|
||||
@@index([assignmentId])
|
||||
@@index([performedById, occurredAt])
|
||||
@@index([occurredAt])
|
||||
}
|
||||
|
||||
// ======================================================
|
||||
// QUANTITY MOVEMENTS
|
||||
// ======================================================
|
||||
|
||||
model StockMovementLine {
|
||||
id String @id @default(uuid(7)) @db.Uuid
|
||||
|
||||
movementId String @db.Uuid
|
||||
movement InventoryMovement @relation(fields: [movementId], references: [id], onDelete: Cascade, onUpdate: Cascade)
|
||||
|
||||
itemId String @db.Uuid
|
||||
item Item @relation(fields: [itemId], references: [id], onDelete: Restrict, onUpdate: Cascade)
|
||||
|
||||
/**
|
||||
* Positivo: entrada/devolución/ajuste positivo.
|
||||
* Negativo: salida/asignación/ajuste negativo.
|
||||
*/
|
||||
stockDelta Int
|
||||
|
||||
previousStock Int
|
||||
newStock Int
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@index([movementId])
|
||||
@@index([itemId, createdAt])
|
||||
}
|
||||
|
||||
// ======================================================
|
||||
// SERIALIZED ASSET MOVEMENTS
|
||||
// ======================================================
|
||||
|
||||
model AssetMovementLine {
|
||||
id String @id @default(uuid(7)) @db.Uuid
|
||||
|
||||
movementId String @db.Uuid
|
||||
movement InventoryMovement @relation(fields: [movementId], references: [id], onDelete: Cascade, onUpdate: Cascade)
|
||||
|
||||
assetId String @db.Uuid
|
||||
asset Asset @relation(fields: [assetId], references: [id], onDelete: Restrict, onUpdate: Cascade)
|
||||
|
||||
previousStatus AssetStatus?
|
||||
newStatus AssetStatus
|
||||
|
||||
notes String?
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@unique([movementId, assetId])
|
||||
@@index([assetId, createdAt])
|
||||
}
|
||||
|
||||
// ======================================================
|
||||
// STOCK ALERTS
|
||||
// ======================================================
|
||||
|
||||
enum StockAlertStatus {
|
||||
OPEN
|
||||
ACKNOWLEDGED
|
||||
RESOLVED
|
||||
}
|
||||
|
||||
enum StockAlertTrigger {
|
||||
BELOW_MINIMUM
|
||||
OUT_OF_STOCK
|
||||
}
|
||||
|
||||
model StockAlert {
|
||||
id String @id @default(uuid(7)) @db.Uuid
|
||||
|
||||
itemId String @db.Uuid
|
||||
item Item @relation(fields: [itemId], references: [id], onDelete: Restrict, onUpdate: Cascade)
|
||||
|
||||
trigger StockAlertTrigger
|
||||
status StockAlertStatus @default(OPEN)
|
||||
|
||||
availableStock Int
|
||||
minimumStock Int
|
||||
suggestedPurchase Int?
|
||||
|
||||
triggeredAt DateTime @default(now())
|
||||
|
||||
acknowledgedAt DateTime?
|
||||
|
||||
acknowledgedById String? @db.Uuid
|
||||
acknowledgedBy User? @relation("StockAlertAcknowledgedBy", fields: [acknowledgedById], references: [id], onDelete: SetNull, onUpdate: Cascade)
|
||||
|
||||
resolvedAt DateTime?
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@index([itemId, status])
|
||||
@@index([status, triggeredAt])
|
||||
@@index([trigger, triggeredAt])
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user