tire-hotel.prisma 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. /// A physical building or room holding tire storage. Most workshops have one;
  2. /// multi-site operators have several.
  3. model TireWarehouse {
  4. id String @id @default(cuid())
  5. name String
  6. address String?
  7. notes String?
  8. /// Preselected when checking tires in. Exactly one per organization.
  9. isDefault Boolean @default(false)
  10. isArchived Boolean @default(false)
  11. organizationId String
  12. organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
  13. userId String
  14. user User @relation(fields: [userId], references: [id], onDelete: Cascade)
  15. locations TireLocation[]
  16. createdAt DateTime @default(now())
  17. updatedAt DateTime @updatedAt
  18. @@index([organizationId])
  19. @@map("tire_warehouses")
  20. }
  21. /// One addressable storage slot: a shelf, a rack level, a pallet, a floor
  22. /// square. The workshop names its own geography, so `zone`/`rack`/`shelf` are
  23. /// free text rather than a fixed hierarchy — "Room B / Rack 4 / Shelf 2" and
  24. /// "Container 1 / -- / Pallet 7" both fit.
  25. model TireLocation {
  26. id String @id @default(cuid())
  27. /// Short human code used on labels and when directing a technician,
  28. /// e.g. "B-04-2". Unique within a warehouse.
  29. code String
  30. zone String?
  31. rack String?
  32. shelf String?
  33. position String?
  34. /// How many individual tires fit here. The shelf overview divides stored
  35. /// tires by this to show how full the location is.
  36. capacity Int @default(8)
  37. notes String?
  38. isArchived Boolean @default(false)
  39. warehouseId String
  40. warehouse TireWarehouse @relation(fields: [warehouseId], references: [id], onDelete: Cascade)
  41. organizationId String
  42. organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
  43. tireSets TireSet[]
  44. movesFrom TireMovement[] @relation("TireMovementFrom")
  45. movesTo TireMovement[] @relation("TireMovementTo")
  46. createdAt DateTime @default(now())
  47. updatedAt DateTime @updatedAt
  48. @@unique([warehouseId, code])
  49. @@index([organizationId])
  50. @@index([warehouseId, isArchived])
  51. @@map("tire_locations")
  52. }
  53. /// A group of tires stored together for one vehicle. Usually four, but the
  54. /// count is explicit so a pair or a five-wheel set works the same way.
  55. model TireSet {
  56. id String @id @default(cuid())
  57. /// Sequential per organization, shown to staff and printed on labels.
  58. reference String?
  59. /// summer | winter | all_season | other — see TIRE_SEASONS in
  60. /// src/features/tire-hotel/Lib/tireConstants.ts
  61. season String @default("summer")
  62. /// Studded winter tires are legal in some markets and banned in others, so
  63. /// this is a flag on the season rather than a season of its own.
  64. studded Boolean @default(false)
  65. brand String?
  66. model String?
  67. /// Free text so every regional notation fits: "225/45R17", "P225/45R17 94V",
  68. /// "31x10.5R15".
  69. size String?
  70. /// DOT / TIN week-year code used to judge tire age.
  71. dotCode String?
  72. loadSpeedIndex String?
  73. /// Mounted on rims, which changes both handling and what a shelf holds.
  74. withRims Boolean @default(false)
  75. rimType String?
  76. /// Tire pressure monitoring sensors present — they need care on refit.
  77. hasTpms Boolean @default(false)
  78. /// Individual tires in this set. Drives every capacity calculation.
  79. quantity Int @default(4)
  80. /// stored | released | disposed — see TIRE_SET_STATUSES
  81. status String @default("stored")
  82. notes String?
  83. /// Where it physically sits. Null once released to the customer.
  84. locationId String?
  85. location TireLocation? @relation(fields: [locationId], references: [id], onDelete: SetNull)
  86. vehicleId String?
  87. vehicle Vehicle? @relation(fields: [vehicleId], references: [id], onDelete: SetNull)
  88. customerId String?
  89. customer Customer? @relation(fields: [customerId], references: [id], onDelete: SetNull)
  90. organizationId String
  91. organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
  92. userId String
  93. user User @relation(fields: [userId], references: [id], onDelete: Cascade)
  94. checkedInAt DateTime?
  95. checkedOutAt DateTime?
  96. measurements TireMeasurement[]
  97. movements TireMovement[]
  98. treatments TireTreatment[]
  99. attachments TireSetAttachment[]
  100. quotes Quote[]
  101. serviceRecords ServiceRecord[]
  102. createdAt DateTime @default(now())
  103. updatedAt DateTime @updatedAt
  104. @@unique([organizationId, reference])
  105. @@index([organizationId, status])
  106. @@index([locationId])
  107. @@index([vehicleId])
  108. @@index([customerId])
  109. @@map("tire_sets")
  110. }
  111. /// Condition of one tire at one moment. Kept as history rather than current
  112. /// state so wear between seasons is visible, and so a damage dispute has a
  113. /// dated record behind it.
  114. /// A photo or document held against a stored set: a picture of a kerbed rim,
  115. /// a damage report, the customer's written agreement to store them.
  116. ///
  117. /// Deliberately the same shape as ServiceAttachment, because that is where
  118. /// these end up when the set is billed. Copying a row between two models with
  119. /// the same fields cannot lose anything in translation, and the invoice PDF
  120. /// already knows how to render one.
  121. model TireSetAttachment {
  122. id String @id @default(cuid())
  123. fileName String
  124. fileUrl String
  125. fileType String
  126. fileSize Int
  127. /// What the technician wants said about it, e.g. "kerbing on the near side".
  128. /// Travels onto the invoice as the caption under the image.
  129. description String?
  130. /// Whether it should reach the customer's invoice when this set is billed.
  131. /// On by default: a photo taken at check-in is usually taken to be shown.
  132. includeInInvoice Boolean @default(true)
  133. sortOrder Int @default(0)
  134. tireSetId String
  135. tireSet TireSet @relation(fields: [tireSetId], references: [id], onDelete: Cascade)
  136. organizationId String
  137. organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
  138. uploadedById String?
  139. uploadedBy User? @relation(fields: [uploadedById], references: [id], onDelete: SetNull)
  140. createdAt DateTime @default(now())
  141. @@index([tireSetId])
  142. @@index([organizationId])
  143. @@map("tire_set_attachments")
  144. }
  145. model TireMeasurement {
  146. id String @id @default(cuid())
  147. /// front_left | front_right | rear_left | rear_right | spare | unspecified
  148. position String @default("unspecified")
  149. /// Always stored in millimetres. The UI converts to 32nds of an inch for
  150. /// workshops on imperial units, so one number means the same thing in
  151. /// every market.
  152. treadDepthMm Float?
  153. /// Always stored in bar. Converted to psi/kPa for display.
  154. pressureBar Float?
  155. /// good | fair | replace — see TIRE_CONDITIONS
  156. condition String @default("good")
  157. damage String?
  158. notes String?
  159. tireSetId String
  160. tireSet TireSet @relation(fields: [tireSetId], references: [id], onDelete: Cascade)
  161. /// Groups the four readings taken during one check-in or inspection so the
  162. /// history reads as events rather than loose numbers.
  163. movementId String?
  164. movement TireMovement? @relation(fields: [movementId], references: [id], onDelete: SetNull)
  165. measuredById String?
  166. measuredBy User? @relation(fields: [measuredById], references: [id], onDelete: SetNull)
  167. images StoredImage[] @relation("TireMeasurementImages")
  168. measuredAt DateTime @default(now())
  169. @@index([tireSetId, measuredAt(sort: Desc)])
  170. @@index([movementId])
  171. @@map("tire_measurements")
  172. }
  173. /// Every physical handling of a set: arrival, departure, a move between
  174. /// shelves, disposal. This is the storage audit trail — "where has this been
  175. /// and who touched it" has to outlive the set's current row.
  176. model TireMovement {
  177. id String @id @default(cuid())
  178. /// check_in | check_out | relocate | dispose — see TIRE_MOVEMENT_TYPES
  179. type String
  180. /// Denormalised location codes so history still reads correctly after a
  181. /// shelf is renamed or deleted.
  182. fromCode String?
  183. toCode String?
  184. fromLocationId String?
  185. fromLocation TireLocation? @relation("TireMovementFrom", fields: [fromLocationId], references: [id], onDelete: SetNull)
  186. toLocationId String?
  187. toLocation TireLocation? @relation("TireMovementTo", fields: [toLocationId], references: [id], onDelete: SetNull)
  188. note String?
  189. tireSetId String
  190. tireSet TireSet @relation(fields: [tireSetId], references: [id], onDelete: Cascade)
  191. organizationId String
  192. organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
  193. performedById String?
  194. performedBy User? @relation(fields: [performedById], references: [id], onDelete: SetNull)
  195. measurements TireMeasurement[]
  196. createdAt DateTime @default(now())
  197. @@index([tireSetId, createdAt(sort: Desc)])
  198. @@index([organizationId, createdAt(sort: Desc)])
  199. @@map("tire_movements")
  200. }
  201. /// Work a set needs before it goes back on the car, or before it goes on the
  202. /// shelf: washing, balancing, a TPMS service.
  203. ///
  204. /// A task with a state rather than a flag on the set, because the tire
  205. /// department's real question is "what is left to do on this one", and a
  206. /// boolean cannot answer that once someone has started. Recording who
  207. /// finished it also settles the "was this washed?" conversation at pickup.
  208. model TireTreatment {
  209. id String @id @default(cuid())
  210. /// wash_tires | wash_rims | balance | tpms_service | new_valves | repair
  211. /// — see TREATMENT_TYPES
  212. type String
  213. /// pending | done | skipped — see TREATMENT_STATUSES
  214. status String @default("pending")
  215. notes String?
  216. tireSetId String
  217. tireSet TireSet @relation(fields: [tireSetId], references: [id], onDelete: Cascade)
  218. completedAt DateTime?
  219. completedById String?
  220. completedBy User? @relation(fields: [completedById], references: [id], onDelete: SetNull)
  221. organizationId String
  222. organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
  223. createdAt DateTime @default(now())
  224. updatedAt DateTime @updatedAt
  225. /// One row per kind of work per set: asking for a wash twice is the same
  226. /// request, not two.
  227. @@unique([tireSetId, type])
  228. @@index([organizationId, status])
  229. @@map("tire_treatments")
  230. }