feat(04-01): InventoryItem and StockMovement schema with enums
- Add ItemTrackingType (SERIALIZED/BATCH), ItemCondition, MovementType, LocationType enums - Add InventoryItem model with dual tracking (serialized by serial number, batch by quantity) - Add StockMovement model as immutable movement ledger (no updatedAt) - Add recordedMovements relation on User model - Indexes on tenantId, itemType, trackingType, inventoryItemId, movementType Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -142,6 +142,32 @@ enum CompensationModel {
|
|||||||
HYBRID
|
HYBRID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum ItemTrackingType {
|
||||||
|
SERIALIZED
|
||||||
|
BATCH
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ItemCondition {
|
||||||
|
NEW
|
||||||
|
REFURBISHED
|
||||||
|
USED
|
||||||
|
DAMAGED
|
||||||
|
}
|
||||||
|
|
||||||
|
enum MovementType {
|
||||||
|
RECEIVED
|
||||||
|
ISSUED
|
||||||
|
RETURNED
|
||||||
|
DISPOSED
|
||||||
|
TRANSFERRED
|
||||||
|
}
|
||||||
|
|
||||||
|
enum LocationType {
|
||||||
|
WAREHOUSE
|
||||||
|
TECHNICIAN
|
||||||
|
SUBSCRIBER
|
||||||
|
}
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// MODELS
|
// MODELS
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
@@ -401,6 +427,8 @@ model User {
|
|||||||
createdJobOrders JobOrder[] @relation("JobOrderCreatedBy")
|
createdJobOrders JobOrder[] @relation("JobOrderCreatedBy")
|
||||||
/// Technician profiles for this user (one per tenant, enforced by @@unique([tenantId, userId]))
|
/// Technician profiles for this user (one per tenant, enforced by @@unique([tenantId, userId]))
|
||||||
technicianProfiles TechnicianProfile[]
|
technicianProfiles TechnicianProfile[]
|
||||||
|
/// Stock movements performed/recorded by this user
|
||||||
|
recordedMovements StockMovement[] @relation("MovementPerformedBy")
|
||||||
|
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
@@ -855,3 +883,73 @@ model JobTypeRate {
|
|||||||
/// RLS-ready index — always present on tenant-scoped models
|
/// RLS-ready index — always present on tenant-scoped models
|
||||||
@@index([tenantId])
|
@@index([tenantId])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An InventoryItem represents a piece of ISP equipment (router, ONU, cable, etc.).
|
||||||
|
/// Serialized items have unique serial numbers and are tracked individually.
|
||||||
|
/// Batch items (consumables like cables, connectors) are tracked by type+quantity.
|
||||||
|
/// Stock levels are NEVER stored — always derived from movement history.
|
||||||
|
model InventoryItem {
|
||||||
|
id String @id @default(uuid())
|
||||||
|
tenantId String
|
||||||
|
/// Human-readable name (e.g., "Huawei HG8145V5 ONU")
|
||||||
|
name String
|
||||||
|
/// Category type (e.g., "Router", "ONU", "Cable", "Connector")
|
||||||
|
itemType String
|
||||||
|
/// Brand/model identifier for serialized items (optional for batch)
|
||||||
|
model String?
|
||||||
|
/// Unique serial number — required for SERIALIZED, null for BATCH
|
||||||
|
serialNumber String?
|
||||||
|
trackingType ItemTrackingType
|
||||||
|
/// Purchase cost per unit (2 decimal places)
|
||||||
|
purchaseCost Decimal? @db.Decimal(10, 2)
|
||||||
|
purchaseDate DateTime?
|
||||||
|
warrantyExpiry DateTime?
|
||||||
|
/// Soft-delete: inactive items are not shown in active lists
|
||||||
|
isActive Boolean @default(true)
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
|
movements StockMovement[]
|
||||||
|
|
||||||
|
/// Serial numbers must be unique within a tenant (only enforced for non-null values)
|
||||||
|
@@unique([tenantId, serialNumber])
|
||||||
|
/// RLS-ready index — always present on tenant-scoped models
|
||||||
|
@@index([tenantId])
|
||||||
|
@@index([tenantId, itemType])
|
||||||
|
@@index([tenantId, trackingType])
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A StockMovement is an immutable record of inventory movement.
|
||||||
|
/// Stock levels are derived by aggregating movements — no mutable quantity columns.
|
||||||
|
/// RECEIVED movements auto-post journal entries (DR 1200 Equipment Inventory, CR 2010 AP).
|
||||||
|
model StockMovement {
|
||||||
|
id String @id @default(uuid())
|
||||||
|
tenantId String
|
||||||
|
/// The inventory item this movement affects
|
||||||
|
inventoryItemId String
|
||||||
|
inventoryItem InventoryItem @relation(fields: [inventoryItemId], references: [id])
|
||||||
|
movementType MovementType
|
||||||
|
/// Quantity moved — always 1 for SERIALIZED items, variable for BATCH
|
||||||
|
quantity Int @default(1)
|
||||||
|
/// Condition of the item at time of movement
|
||||||
|
condition ItemCondition?
|
||||||
|
/// Source location (null for RECEIVED — items come from external supplier)
|
||||||
|
fromLocationType LocationType?
|
||||||
|
fromLocationId String?
|
||||||
|
/// Destination location (null for DISPOSED — items leave the system)
|
||||||
|
toLocationType LocationType?
|
||||||
|
toLocationId String?
|
||||||
|
notes String?
|
||||||
|
/// Journal entry created for RECEIVED movements (DR 1200, CR 2010)
|
||||||
|
journalEntryId String?
|
||||||
|
/// The user who recorded this movement
|
||||||
|
performedById String
|
||||||
|
performedBy User @relation("MovementPerformedBy", fields: [performedById], references: [id])
|
||||||
|
/// Immutable — no updatedAt. Movements cannot be modified, only new movements added.
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
|
||||||
|
/// RLS-ready index — always present on tenant-scoped models
|
||||||
|
@@index([tenantId])
|
||||||
|
@@index([inventoryItemId])
|
||||||
|
@@index([tenantId, movementType])
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user