feat: add PurchaseOrder Prisma model + migration
This commit is contained in:
@@ -0,0 +1,18 @@
|
|||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "purchase_orders" (
|
||||||
|
"id" UUID NOT NULL,
|
||||||
|
"vendor_id" UUID NOT NULL,
|
||||||
|
"order_date" TIMESTAMPTZ(6) NOT NULL,
|
||||||
|
"expected_date" TIMESTAMPTZ(6) NOT NULL,
|
||||||
|
"received_at" TIMESTAMPTZ(6),
|
||||||
|
"received_by" UUID,
|
||||||
|
"created_at" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
|
||||||
|
CONSTRAINT "purchase_orders_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "purchase_orders" ADD CONSTRAINT "purchase_orders_vendor_id_fkey" FOREIGN KEY ("vendor_id") REFERENCES "vendors"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "purchase_orders" ADD CONSTRAINT "purchase_orders_received_by_fkey" FOREIGN KEY ("received_by") REFERENCES "employees"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
+56
-21
@@ -51,14 +51,17 @@ model Request {
|
|||||||
// possible future normalization, not built yet. `managerId` is a nullable
|
// possible future normalization, not built yet. `managerId` is a nullable
|
||||||
// self-relation: top-level employees (no manager) have `managerId = null`.
|
// self-relation: top-level employees (no manager) have `managerId = null`.
|
||||||
model Employee {
|
model Employee {
|
||||||
id String @id @default(uuid()) @db.Uuid
|
id String @id @default(uuid()) @db.Uuid
|
||||||
name String
|
name String
|
||||||
department String
|
department String
|
||||||
managerId String? @map("manager_id") @db.Uuid
|
managerId String? @map("manager_id") @db.Uuid
|
||||||
manager Employee? @relation("EmployeeManager", fields: [managerId], references: [id])
|
manager Employee? @relation("EmployeeManager", fields: [managerId], references: [id])
|
||||||
reports Employee[] @relation("EmployeeManager")
|
reports Employee[] @relation("EmployeeManager")
|
||||||
requests Request[]
|
requests Request[]
|
||||||
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(6)
|
// Back-relation for PurchaseOrder.receivedById — an Employee can be the
|
||||||
|
// receiver on multiple POs over time.
|
||||||
|
purchaseOrdersReceived PurchaseOrder[]
|
||||||
|
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(6)
|
||||||
|
|
||||||
@@map("employees")
|
@@map("employees")
|
||||||
}
|
}
|
||||||
@@ -130,24 +133,56 @@ model License {
|
|||||||
// (OEM, e.g. "Dell") — Vendor is the reseller/distributor actually bought
|
// (OEM, e.g. "Dell") — Vendor is the reseller/distributor actually bought
|
||||||
// from (e.g. "FPT Trading"), a different concept that isn't always the same
|
// from (e.g. "FPT Trading"), a different concept that isn't always the same
|
||||||
// company. `licenses` is the back-relation from the License.vendor ->
|
// company. `licenses` is the back-relation from the License.vendor ->
|
||||||
// License.vendorId retrofit (same doc section) — PurchaseOrder is a future
|
// License.vendorId retrofit (same doc section). `purchaseOrders` is the
|
||||||
// FK from this entity, not built yet. taxCode/notes are nullable — not
|
// back-relation from PurchaseOrder.vendorId (see "## PurchaseOrder (gộp
|
||||||
// every vendor record has a known VN tax code or extra notes at creation
|
// Receipt) (chốt 2026-08-02)"). taxCode/notes are nullable — not every
|
||||||
// time.
|
// vendor record has a known VN tax code or extra notes at creation time.
|
||||||
model Vendor {
|
model Vendor {
|
||||||
id String @id @default(uuid()) @db.Uuid
|
id String @id @default(uuid()) @db.Uuid
|
||||||
name String
|
name String
|
||||||
contactEmail String @map("contact_email")
|
contactEmail String @map("contact_email")
|
||||||
contactPhone String @map("contact_phone")
|
contactPhone String @map("contact_phone")
|
||||||
address String
|
address String
|
||||||
taxCode String? @map("tax_code")
|
taxCode String? @map("tax_code")
|
||||||
notes String?
|
notes String?
|
||||||
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[]
|
||||||
|
|
||||||
@@map("vendors")
|
@@map("vendors")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Purchasing document — see docs/asset-license-management.md's "##
|
||||||
|
// PurchaseOrder (gộp Receipt) (chốt 2026-08-02)". Deliberately absorbs what
|
||||||
|
// would otherwise be a separate `Receipt` entity: receiving is just part of
|
||||||
|
// this row's own state (`receivedAt`/`receivedBy`), not a separate table to
|
||||||
|
// reconcile against. No `status` field — status is inferred from linked
|
||||||
|
// `Request`s (Ordered -> Received -> Delivered), not stored here, to avoid
|
||||||
|
// two sources of truth. No cost/price fields — stakeholder said explicitly
|
||||||
|
// PurchaseOrder doesn't need to track money, only "which asset/license
|
||||||
|
// belongs to which PO". Receiving is all-or-nothing per PO (no partial
|
||||||
|
// receipt tracking) — a deliberate simplification, see doc for the
|
||||||
|
// stakeholder-accepted tradeoff. `receivedAt`/`receivedBy` are both
|
||||||
|
// nullable and must be co-null/co-set together (both-null = not yet
|
||||||
|
// received) — enforced at the Zod layer (src/lib/purchase-orders.ts), not a
|
||||||
|
// DB constraint, same approach as other cross-field invariants in this
|
||||||
|
// codebase (e.g. Assignment's assetItemId/licenseId XOR, not built yet).
|
||||||
|
// purchaseOrderId back-references on AssetItem/License/Request (traceability,
|
||||||
|
// same doc section) are NOT part of this change — out of scope for this task.
|
||||||
|
model PurchaseOrder {
|
||||||
|
id String @id @default(uuid()) @db.Uuid
|
||||||
|
vendorId String @map("vendor_id") @db.Uuid
|
||||||
|
vendor Vendor @relation(fields: [vendorId], references: [id])
|
||||||
|
orderDate DateTime @map("order_date") @db.Timestamptz(6)
|
||||||
|
expectedDate DateTime @map("expected_date") @db.Timestamptz(6)
|
||||||
|
receivedAt DateTime? @map("received_at") @db.Timestamptz(6)
|
||||||
|
receivedById String? @map("received_by") @db.Uuid
|
||||||
|
receivedBy Employee? @relation(fields: [receivedById], references: [id])
|
||||||
|
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(6)
|
||||||
|
|
||||||
|
@@map("purchase_orders")
|
||||||
|
}
|
||||||
|
|
||||||
// Minimal internal auth accounts — NOT company SSO (deliberately ruled out,
|
// Minimal internal auth accounts — NOT company SSO (deliberately ruled out,
|
||||||
// see CLAUDE.md / auth decision notes). A handful of trusted IT staff
|
// see CLAUDE.md / auth decision notes). A handful of trusted IT staff
|
||||||
// accounts, created by hand via scripts/create-user.ts, no self-registration.
|
// accounts, created by hand via scripts/create-user.ts, no self-registration.
|
||||||
|
|||||||
Reference in New Issue
Block a user