inventory.prisma 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. model LaborPreset {
  2. id String @id @default(cuid())
  3. name String
  4. description String?
  5. isArchived Boolean @default(false)
  6. createdAt DateTime @default(now())
  7. updatedAt DateTime @updatedAt
  8. userId String
  9. organizationId String
  10. organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
  11. items LaborPresetItem[]
  12. parts LaborPresetPart[]
  13. @@index([organizationId])
  14. @@map("labor_presets")
  15. }
  16. model LaborPresetItem {
  17. id String @id @default(cuid())
  18. description String
  19. hours Float @default(0)
  20. rate Float @default(0)
  21. pricingType String @default("hourly")
  22. sortOrder Int @default(0)
  23. presetId String
  24. preset LaborPreset @relation(fields: [presetId], references: [id], onDelete: Cascade)
  25. @@index([presetId])
  26. @@map("labor_preset_items")
  27. }
  28. model LaborPresetPart {
  29. id String @id @default(cuid())
  30. name String
  31. partNumber String?
  32. quantity Float @default(1)
  33. /// Unit of measure snapshotted from InventoryPart at pick time.
  34. unit String?
  35. /// Category snapshotted from InventoryPart at pick time; copied onto the
  36. /// part line when the preset is applied.
  37. category String?
  38. unitPrice Float @default(0)
  39. inventoryPartId String?
  40. sortOrder Int @default(0)
  41. presetId String
  42. preset LaborPreset @relation(fields: [presetId], references: [id], onDelete: Cascade)
  43. @@index([presetId])
  44. @@map("labor_preset_parts")
  45. }
  46. model InventoryPart {
  47. id String @id @default(cuid())
  48. partNumber String?
  49. barcode String?
  50. name String
  51. description String?
  52. category String?
  53. quantity Float @default(0)
  54. minQuantity Float @default(0)
  55. /// Unit of measure for `quantity` (e.g. "pcs", "l", "kg"). Free text,
  56. /// snapshotted onto part lines at pick time so invoices keep the unit the
  57. /// part had when it was billed.
  58. unit String?
  59. unitCost Float @default(0)
  60. sellPrice Float @default(0)
  61. supplier String?
  62. supplierPhone String?
  63. supplierEmail String?
  64. supplierUrl String?
  65. imageUrl String?
  66. location String?
  67. isArchived Boolean @default(false)
  68. /// Set when a low-stock alert has been sent for the current dip below
  69. /// `minQuantity`, and cleared once stock climbs back above it. This
  70. /// hysteresis is what stops a part that sits at zero from re-alerting on
  71. /// every check — it alerts once per descent, not once per run.
  72. lowStockAlertedAt DateTime?
  73. createdAt DateTime @default(now())
  74. updatedAt DateTime @updatedAt
  75. userId String
  76. user User @relation(fields: [userId], references: [id], onDelete: Cascade)
  77. organizationId String?
  78. organization Organization? @relation(fields: [organizationId], references: [id], onDelete: Cascade)
  79. gallery StoredImage[] @relation("InventoryPartImages")
  80. movements StockMovement[]
  81. @@unique([organizationId, barcode])
  82. @@index([organizationId])
  83. @@map("inventory_parts")
  84. }
  85. /// Append-only audit trail of every change to InventoryPart.quantity.
  86. ///
  87. /// Written inside the same transaction as the stock mutation itself, so the
  88. /// ledger can never drift from the balance it describes. `quantityAfter` is
  89. /// captured from the atomic `UPDATE ... RETURNING`, giving an exact running
  90. /// balance even under concurrent writes.
  91. model StockMovement {
  92. id String @id @default(cuid())
  93. /// Signed change in stock units (fractional allowed, e.g. 0.5 l).
  94. /// Negative = consumed, positive = restocked.
  95. delta Float
  96. /// Part balance immediately after this movement was applied.
  97. quantityAfter Float
  98. /// Why the stock moved — see StockMovementReason in src/features/inventory/Lib/stockMovement.ts
  99. reason String
  100. note String?
  101. inventoryPartId String
  102. inventoryPart InventoryPart @relation(fields: [inventoryPartId], references: [id], onDelete: Cascade)
  103. /// The job the part was used on. Kept on SetNull so history survives
  104. /// deletion of the service record — "where was this used" outlives the job.
  105. serviceRecordId String?
  106. serviceRecord ServiceRecord? @relation(fields: [serviceRecordId], references: [id], onDelete: SetNull)
  107. /// Denormalised so history still reads correctly after a service record is
  108. /// deleted and serviceRecordId goes null.
  109. serviceRecordLabel String?
  110. userId String?
  111. user User? @relation(fields: [userId], references: [id], onDelete: SetNull)
  112. organizationId String
  113. organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
  114. createdAt DateTime @default(now())
  115. @@index([inventoryPartId, createdAt(sort: Desc)])
  116. @@index([organizationId, createdAt(sort: Desc)])
  117. @@index([serviceRecordId])
  118. @@map("stock_movements")
  119. }
  120. model StoredImage {
  121. id String @id @default(cuid())
  122. url String
  123. fileName String?
  124. description String?
  125. sortOrder Int @default(0)
  126. createdAt DateTime @default(now())
  127. inventoryPartId String?
  128. inventoryPart InventoryPart? @relation("InventoryPartImages", fields: [inventoryPartId], references: [id], onDelete: Cascade)
  129. /// Condition photos taken when a tire is measured. Second parent on this
  130. /// model — an image belongs to exactly one of them.
  131. tireMeasurementId String?
  132. tireMeasurement TireMeasurement? @relation("TireMeasurementImages", fields: [tireMeasurementId], references: [id], onDelete: Cascade)
  133. @@index([inventoryPartId])
  134. @@index([tireMeasurementId])
  135. @@map("stored_images")
  136. }