vehicles.prisma 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. model Vehicle {
  2. id String @id @default(cuid())
  3. make String
  4. model String
  5. year Int
  6. vin String?
  7. licensePlate String?
  8. color String?
  9. mileage Int @default(0)
  10. fuelType String? @default("gasoline")
  11. transmission String? @default("automatic")
  12. engineSize String?
  13. engineCode String?
  14. /// German type approval key from the registration certificate: HSN is the
  15. /// four-digit manufacturer code in field 2.1, TSN the three-character type
  16. /// code at the start of field 2.2. Parts catalogues and insurers look a car
  17. /// up by the pair, since Germany has no public plate registry.
  18. hsn String?
  19. tsn String?
  20. purchaseDate DateTime?
  21. purchasePrice Float?
  22. imageUrl String?
  23. /// sedan | hatchback | estate | suv | van | pickup | motorcycle | boat: which
  24. /// line drawing the condition map is marked on. Null until somebody picks
  25. /// one, and the map then guesses from the workshop's trade.
  26. bodyType String?
  27. isArchived Boolean @default(false)
  28. archiveReason String?
  29. /// Set when the customer said in the portal that the vehicle is no longer theirs.
  30. soldReportedAt DateTime?
  31. maintenanceDismissed Boolean @default(false)
  32. maintenanceDismissedAt DateTime?
  33. aiMessages AiGeneratedMessage[]
  34. aiChats AiChat[]
  35. createdAt DateTime @default(now())
  36. updatedAt DateTime @updatedAt
  37. userId String
  38. user User @relation(fields: [userId], references: [id], onDelete: Cascade)
  39. organizationId String?
  40. organization Organization? @relation(fields: [organizationId], references: [id], onDelete: Cascade)
  41. customerId String?
  42. customer Customer? @relation(fields: [customerId], references: [id], onDelete: SetNull)
  43. serviceRecords ServiceRecord[]
  44. notes Note[]
  45. fuelLogs FuelLog[]
  46. reminders Reminder[]
  47. quotes Quote[]
  48. recurringInvoices RecurringInvoice[]
  49. inspections Inspection[]
  50. serviceRequests ServiceRequest[]
  51. findings VehicleFinding[]
  52. scheduledMessages ScheduledMessage[]
  53. tireSets TireSet[]
  54. inspectionStatus VehicleInspectionStatus?
  55. inspectionReminders InspectionReminderSend[]
  56. conditionMarks ConditionMark[]
  57. importBatchId String?
  58. importBatch ImportBatch? @relation(fields: [importBatchId], references: [id], onDelete: SetNull)
  59. @@index([organizationId])
  60. @@index([importBatchId])
  61. @@map("vehicles")
  62. }
  63. model Note {
  64. id String @id @default(cuid())
  65. title String
  66. content String
  67. isPinned Boolean @default(false)
  68. createdAt DateTime @default(now())
  69. updatedAt DateTime @updatedAt
  70. vehicleId String
  71. vehicle Vehicle @relation(fields: [vehicleId], references: [id], onDelete: Cascade)
  72. @@index([vehicleId])
  73. @@map("notes")
  74. }
  75. model FuelLog {
  76. id String @id @default(cuid())
  77. date DateTime @default(now())
  78. mileage Int
  79. gallons Float
  80. pricePerGallon Float
  81. totalCost Float
  82. isFillUp Boolean @default(true)
  83. station String?
  84. notes String?
  85. createdAt DateTime @default(now())
  86. updatedAt DateTime @updatedAt
  87. vehicleId String
  88. vehicle Vehicle @relation(fields: [vehicleId], references: [id], onDelete: Cascade)
  89. @@index([vehicleId])
  90. @@map("fuel_logs")
  91. }
  92. model Reminder {
  93. id String @id @default(cuid())
  94. title String
  95. description String?
  96. dueDate DateTime?
  97. /// True when the time of day in dueDate was chosen, not the noon a
  98. /// day-only reminder is stored at
  99. hasDueTime Boolean @default(false)
  100. dueMileage Int?
  101. isCompleted Boolean @default(false)
  102. /// Per-reminder notification channels, chosen when creating the reminder
  103. notifyInApp Boolean @default(true)
  104. notifyEmail Boolean @default(false)
  105. /// Set by the due-reminder cron once notified; cleared when dueDate changes
  106. notifiedAt DateTime?
  107. createdAt DateTime @default(now())
  108. updatedAt DateTime @updatedAt
  109. /// A reminder relates to a vehicle, a customer, or just the workshop
  110. /// (both null). organizationId is the canonical scope for all three.
  111. vehicleId String?
  112. vehicle Vehicle? @relation(fields: [vehicleId], references: [id], onDelete: Cascade)
  113. customerId String?
  114. customer Customer? @relation(fields: [customerId], references: [id], onDelete: Cascade)
  115. organizationId String?
  116. organization Organization? @relation(fields: [organizationId], references: [id], onDelete: Cascade)
  117. @@index([vehicleId])
  118. @@index([customerId])
  119. @@index([organizationId])
  120. @@map("reminders")
  121. }
  122. model VehicleFinding {
  123. id String @id @default(cuid())
  124. description String
  125. severity String @default("needs_work")
  126. status String @default("open")
  127. notes String?
  128. imageUrls String[] @default([])
  129. createdAt DateTime @default(now())
  130. updatedAt DateTime @updatedAt
  131. vehicleId String
  132. vehicle Vehicle @relation(fields: [vehicleId], references: [id], onDelete: Cascade)
  133. serviceRecordId String?
  134. serviceRecord ServiceRecord? @relation("FindingDiscoveredIn", fields: [serviceRecordId], references: [id], onDelete: SetNull)
  135. resolvedServiceRecordId String?
  136. resolvedServiceRecord ServiceRecord? @relation("FindingResolvedIn", fields: [resolvedServiceRecordId], references: [id], onDelete: SetNull)
  137. /// The customer concern this finding answers, if it answers one.
  138. ///
  139. /// Nullable because plenty of findings are things nobody asked about: the
  140. /// perished bush somebody noticed while the car was on the lift. Those are
  141. /// worth just as much, they just are not part of the story the customer
  142. /// started. SetNull rather than Cascade because a finding is a fact about
  143. /// the vehicle and outlives the concern that prompted the look.
  144. concernId String?
  145. concern ServiceConcern? @relation(fields: [concernId], references: [id], onDelete: SetNull)
  146. /// A diagnostic trouble code, when the finding is one: normalised and
  147. /// upper case, such as P0420 (`^[PCBU][0-3][0-9A-F]{3}$`).
  148. faultCode String?
  149. /// stored, pending, permanent or history, when the scan tool said.
  150. faultStatus String?
  151. /// The control unit the code was read from ("Engine", "ABS"), when known.
  152. faultModule String?
  153. /// The meaning as it was when the code was added, in the workshop's
  154. /// language. Kept on the finding so prints, history and search never wait
  155. /// on the code service and survive a disconnect.
  156. faultMeaning String?
  157. /// Where that meaning came from: generic, manufacturer or report.
  158. faultMeaningSource String?
  159. /// The diagnostic report the code was read from. SetNull: the finding is
  160. /// a fact about the vehicle and outlives the file.
  161. sourceAttachmentId String?
  162. sourceAttachment ServiceAttachment? @relation(fields: [sourceAttachmentId], references: [id], onDelete: SetNull)
  163. @@index([vehicleId])
  164. @@index([serviceRecordId])
  165. @@index([concernId])
  166. @@index([vehicleId, faultCode])
  167. @@index([sourceAttachmentId])
  168. @@map("vehicle_findings")
  169. }
  170. /// What a vehicle registry, or the workshop itself, last said about when the
  171. /// vehicle's periodic roadworthiness inspection (EU-kontroll, MOT, APK, HU,
  172. /// contrôle technique, and so on) is due. One row per vehicle. The
  173. /// organisation is on the row so "what is due for this workshop" is one
  174. /// indexed query and the isolation is explicit rather than inherited.
  175. model VehicleInspectionStatus {
  176. id String @id @default(cuid())
  177. vehicleId String @unique
  178. vehicle Vehicle @relation(fields: [vehicleId], references: [id], onDelete: Cascade)
  179. organizationId String
  180. organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
  181. /// Next periodic inspection deadline, when known.
  182. dueAt DateTime?
  183. /// Last approved periodic inspection, when known.
  184. lastAt DateTime?
  185. /// Connector id ("vegvesen"), or "manual" / "inspection" for the workshop's own entries.
  186. source String
  187. /// When a registry last confirmed the row. Null for manual entries.
  188. checkedAt DateTime?
  189. /// False when the registry answered but had no such vehicle.
  190. found Boolean @default(true)
  191. /// Whether the registry lists the vehicle as currently registered, when it says.
  192. registered Boolean?
  193. /// Last lookup failure, cleared on success. Never a plate or VIN.
  194. lastError String?
  195. /// Whatever else the registry returned and the app has no column for yet:
  196. /// approved tyre sizes, weights, vehicle class, first registration.
  197. extras Json?
  198. createdAt DateTime @default(now())
  199. updatedAt DateTime @updatedAt
  200. @@index([organizationId, dueAt])
  201. @@index([organizationId, checkedAt])
  202. @@map("vehicle_inspection_statuses")
  203. }
  204. /// One "remind these customers" action, created only by a person confirming
  205. /// a reviewed list. Never created by a timer.
  206. model InspectionReminderCampaign {
  207. id String @id @default(cuid())
  208. organizationId String
  209. organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
  210. createdById String
  211. /// Client-generated, so a double submit creates one campaign, not two.
  212. idempotencyToken String @unique
  213. /// Deadline window the list was built from, in days.
  214. windowDays Int
  215. channel String
  216. subject String?
  217. body String
  218. /// "sent" once rows exist; "dry_run" in demo mode, where nothing goes out.
  219. status String @default("sent")
  220. recipientCount Int @default(0)
  221. createdAt DateTime @default(now())
  222. sends InspectionReminderSend[]
  223. @@index([organizationId, createdAt])
  224. @@map("inspection_reminder_campaigns")
  225. }
  226. /// One customer told once about one vehicle's deadline. The unique pair of
  227. /// vehicle and deadline is the guarantee that no deadline is ever reminded
  228. /// twice, whatever the code above it does.
  229. model InspectionReminderSend {
  230. id String @id @default(cuid())
  231. campaignId String
  232. campaign InspectionReminderCampaign @relation(fields: [campaignId], references: [id], onDelete: Cascade)
  233. organizationId String
  234. organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
  235. vehicleId String
  236. vehicle Vehicle @relation(fields: [vehicleId], references: [id], onDelete: Cascade)
  237. customerId String
  238. customer Customer @relation(fields: [customerId], references: [id], onDelete: Cascade)
  239. /// The deadline as it stood when the reminder went out.
  240. dueAt DateTime
  241. channel String
  242. recipient String
  243. /// Booking link secret; the link is /b/<token>.
  244. token String @unique
  245. expiresAt DateTime
  246. scheduledMessageId String? @unique
  247. scheduledMessage ScheduledMessage? @relation(fields: [scheduledMessageId], references: [id], onDelete: SetNull)
  248. /// Set when the customer booked from the link.
  249. bookedAt DateTime?
  250. bookedServiceRecordId String?
  251. bookedServiceRequestId String?
  252. cancelledAt DateTime?
  253. createdAt DateTime @default(now())
  254. @@unique([vehicleId, dueAt])
  255. @@index([organizationId, createdAt])
  256. @@index([campaignId])
  257. @@map("inspection_reminder_sends")
  258. }