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
|
||||
}
|
||||
|
||||
enum ItemTrackingType {
|
||||
SERIALIZED
|
||||
BATCH
|
||||
}
|
||||
|
||||
enum ItemCondition {
|
||||
NEW
|
||||
REFURBISHED
|
||||
USED
|
||||
DAMAGED
|
||||
}
|
||||
|
||||
enum MovementType {
|
||||
RECEIVED
|
||||
ISSUED
|
||||
RETURNED
|
||||
DISPOSED
|
||||
TRANSFERRED
|
||||
}
|
||||
|
||||
enum LocationType {
|
||||
WAREHOUSE
|
||||
TECHNICIAN
|
||||
SUBSCRIBER
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// MODELS
|
||||
// =============================================================================
|
||||
@@ -401,6 +427,8 @@ model User {
|
||||
createdJobOrders JobOrder[] @relation("JobOrderCreatedBy")
|
||||
/// Technician profiles for this user (one per tenant, enforced by @@unique([tenantId, userId]))
|
||||
technicianProfiles TechnicianProfile[]
|
||||
/// Stock movements performed/recorded by this user
|
||||
recordedMovements StockMovement[] @relation("MovementPerformedBy")
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
@@ -855,3 +883,73 @@ model JobTypeRate {
|
||||
/// RLS-ready index — always present on tenant-scoped models
|
||||
@@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