feat: add MaintenanceRecord Prisma model + migration
New entity tracking repair/warranty/preventive maintenance jobs for an AssetItem. Reuses Vendor (nullable) for the repair location, no cost field (mirrors PurchaseOrder). "At most 1 open record per AssetItem" invariant left to the application layer, mirroring Assignment.
This commit is contained in:
@@ -0,0 +1,23 @@
|
|||||||
|
-- CreateEnum
|
||||||
|
CREATE TYPE "MaintenanceType" AS ENUM ('repair', 'warranty', 'preventive');
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "maintenance_records" (
|
||||||
|
"id" UUID NOT NULL,
|
||||||
|
"asset_item_id" UUID NOT NULL,
|
||||||
|
"type" "MaintenanceType" NOT NULL,
|
||||||
|
"vendor_id" UUID,
|
||||||
|
"issue_description" TEXT NOT NULL,
|
||||||
|
"resolution_notes" TEXT,
|
||||||
|
"start_date" DATE NOT NULL,
|
||||||
|
"end_date" DATE,
|
||||||
|
"created_at" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
|
||||||
|
CONSTRAINT "maintenance_records_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "maintenance_records" ADD CONSTRAINT "maintenance_records_asset_item_id_fkey" FOREIGN KEY ("asset_item_id") REFERENCES "asset_items"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "maintenance_records" ADD CONSTRAINT "maintenance_records_vendor_id_fkey" FOREIGN KEY ("vendor_id") REFERENCES "vendors"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
@@ -125,6 +125,11 @@ model AssetItem {
|
|||||||
// multiple Assignment rows over its lifetime (historical + at most one
|
// multiple Assignment rows over its lifetime (historical + at most one
|
||||||
// active, see Assignment model's comment), not built until this task.
|
// active, see Assignment model's comment), not built until this task.
|
||||||
assignments Assignment[]
|
assignments Assignment[]
|
||||||
|
// Back-relation for MaintenanceRecord.assetItemId — an AssetItem can have
|
||||||
|
// multiple MaintenanceRecord rows over its lifetime (historical + at most
|
||||||
|
// one open, see MaintenanceRecord model's comment), not built until this
|
||||||
|
// task.
|
||||||
|
maintenanceRecords MaintenanceRecord[]
|
||||||
|
|
||||||
@@map("asset_items")
|
@@map("asset_items")
|
||||||
}
|
}
|
||||||
@@ -194,6 +199,10 @@ model Vendor {
|
|||||||
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(6)
|
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(6)
|
||||||
licenses License[]
|
licenses License[]
|
||||||
purchaseOrders PurchaseOrder[]
|
purchaseOrders PurchaseOrder[]
|
||||||
|
// Back-relation for MaintenanceRecord.vendorId — a Vendor can perform
|
||||||
|
// multiple repairs/warranty jobs over time, same reuse rationale as
|
||||||
|
// PurchaseOrder.vendorId (see MaintenanceRecord model's comment).
|
||||||
|
maintenanceRecords MaintenanceRecord[]
|
||||||
|
|
||||||
@@map("vendors")
|
@@map("vendors")
|
||||||
}
|
}
|
||||||
@@ -417,3 +426,50 @@ model User {
|
|||||||
|
|
||||||
@@map("users")
|
@@map("users")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fixed, closed set of 3 values tied to the domain (see docs/
|
||||||
|
// asset-license-management.md's "## MaintenanceRecord (spec BA + stakeholder,
|
||||||
|
// chốt 2026-08-04)" section) — native Postgres enum, same rationale as
|
||||||
|
// RequestType/AssetItemStatus/LicenseType/AssetCategoryValueGroup/
|
||||||
|
// AssetAttributeDataType above.
|
||||||
|
enum MaintenanceType {
|
||||||
|
repair
|
||||||
|
warranty
|
||||||
|
preventive
|
||||||
|
}
|
||||||
|
|
||||||
|
// Records a repair/warranty/preventive-maintenance job for a specific
|
||||||
|
// AssetItem — see docs/asset-license-management.md's "## MaintenanceRecord
|
||||||
|
// (spec BA + stakeholder, chốt 2026-08-04)" section. Only applies to
|
||||||
|
// AssetItem (hardware) — License has no physical-repair concept, so unlike
|
||||||
|
// Assignment there's no XOR here: assetItemId is the single, always-required
|
||||||
|
// target. `vendorId` reuses the existing Vendor entity (same "external
|
||||||
|
// counterparty" concept as PurchaseOrder.vendorId) and is nullable — null
|
||||||
|
// means an internal repair with no outside vendor involved. No `cost` field,
|
||||||
|
// per the same precedent as PurchaseOrder (stakeholder explicitly said no
|
||||||
|
// money tracking there). No separate `status` field either — "open" vs
|
||||||
|
// "done" is derived from `endDate` (null = still being worked on, set = done),
|
||||||
|
// same precedent as PurchaseOrder.receivedAt not duplicating state. `endDate
|
||||||
|
// >= startDate` and "at most one OPEN (endDate IS NULL) MaintenanceRecord per
|
||||||
|
// AssetItem" are both business invariants enforced at the application layer
|
||||||
|
// (src/lib/maintenance-records.ts, not built in this task) — NOT DB
|
||||||
|
// constraints, per the same convention as Assignment's XOR / "at most one
|
||||||
|
// active" invariant (see Assignment model's comment above for the mirrored
|
||||||
|
// trade-off). No FK to User for "who logged this" — no existing precedent in
|
||||||
|
// this codebase ties a business entity to User (User is login-only), see
|
||||||
|
// doc for this explicit call.
|
||||||
|
model MaintenanceRecord {
|
||||||
|
id String @id @default(uuid()) @db.Uuid
|
||||||
|
assetItemId String @map("asset_item_id") @db.Uuid
|
||||||
|
assetItem AssetItem @relation(fields: [assetItemId], references: [id])
|
||||||
|
type MaintenanceType
|
||||||
|
vendorId String? @map("vendor_id") @db.Uuid
|
||||||
|
vendor Vendor? @relation(fields: [vendorId], references: [id])
|
||||||
|
issueDescription String @map("issue_description")
|
||||||
|
resolutionNotes String? @map("resolution_notes")
|
||||||
|
startDate DateTime @map("start_date") @db.Date
|
||||||
|
endDate DateTime? @map("end_date") @db.Date
|
||||||
|
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(6)
|
||||||
|
|
||||||
|
@@map("maintenance_records")
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user