vehicles.prisma 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  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. purchaseDate DateTime?
  15. purchasePrice Float?
  16. imageUrl String?
  17. isArchived Boolean @default(false)
  18. archiveReason String?
  19. /// Set when the customer said in the portal that the vehicle is no longer theirs.
  20. soldReportedAt DateTime?
  21. maintenanceDismissed Boolean @default(false)
  22. maintenanceDismissedAt DateTime?
  23. aiMessages AiGeneratedMessage[]
  24. createdAt DateTime @default(now())
  25. updatedAt DateTime @updatedAt
  26. userId String
  27. user User @relation(fields: [userId], references: [id], onDelete: Cascade)
  28. organizationId String?
  29. organization Organization? @relation(fields: [organizationId], references: [id], onDelete: Cascade)
  30. customerId String?
  31. customer Customer? @relation(fields: [customerId], references: [id], onDelete: SetNull)
  32. serviceRecords ServiceRecord[]
  33. notes Note[]
  34. fuelLogs FuelLog[]
  35. reminders Reminder[]
  36. quotes Quote[]
  37. recurringInvoices RecurringInvoice[]
  38. inspections Inspection[]
  39. serviceRequests ServiceRequest[]
  40. findings VehicleFinding[]
  41. scheduledMessages ScheduledMessage[]
  42. tireSets TireSet[]
  43. inspectionStatus VehicleInspectionStatus?
  44. inspectionReminders InspectionReminderSend[]
  45. importBatchId String?
  46. importBatch ImportBatch? @relation(fields: [importBatchId], references: [id], onDelete: SetNull)
  47. @@index([organizationId])
  48. @@index([importBatchId])
  49. @@map("vehicles")
  50. }
  51. model Note {
  52. id String @id @default(cuid())
  53. title String
  54. content String
  55. isPinned Boolean @default(false)
  56. createdAt DateTime @default(now())
  57. updatedAt DateTime @updatedAt
  58. vehicleId String
  59. vehicle Vehicle @relation(fields: [vehicleId], references: [id], onDelete: Cascade)
  60. @@index([vehicleId])
  61. @@map("notes")
  62. }
  63. model FuelLog {
  64. id String @id @default(cuid())
  65. date DateTime @default(now())
  66. mileage Int
  67. gallons Float
  68. pricePerGallon Float
  69. totalCost Float
  70. isFillUp Boolean @default(true)
  71. station String?
  72. notes String?
  73. createdAt DateTime @default(now())
  74. updatedAt DateTime @updatedAt
  75. vehicleId String
  76. vehicle Vehicle @relation(fields: [vehicleId], references: [id], onDelete: Cascade)
  77. @@index([vehicleId])
  78. @@map("fuel_logs")
  79. }
  80. model Reminder {
  81. id String @id @default(cuid())
  82. title String
  83. description String?
  84. dueDate DateTime?
  85. /// True when the time of day in dueDate was chosen, not the noon a
  86. /// day-only reminder is stored at
  87. hasDueTime Boolean @default(false)
  88. dueMileage Int?
  89. isCompleted Boolean @default(false)
  90. /// Per-reminder notification channels, chosen when creating the reminder
  91. notifyInApp Boolean @default(true)
  92. notifyEmail Boolean @default(false)
  93. /// Set by the due-reminder cron once notified; cleared when dueDate changes
  94. notifiedAt DateTime?
  95. createdAt DateTime @default(now())
  96. updatedAt DateTime @updatedAt
  97. /// A reminder relates to a vehicle, a customer, or just the workshop
  98. /// (both null). organizationId is the canonical scope for all three.
  99. vehicleId String?
  100. vehicle Vehicle? @relation(fields: [vehicleId], references: [id], onDelete: Cascade)
  101. customerId String?
  102. customer Customer? @relation(fields: [customerId], references: [id], onDelete: Cascade)
  103. organizationId String?
  104. organization Organization? @relation(fields: [organizationId], references: [id], onDelete: Cascade)
  105. @@index([vehicleId])
  106. @@index([customerId])
  107. @@index([organizationId])
  108. @@map("reminders")
  109. }
  110. model VehicleFinding {
  111. id String @id @default(cuid())
  112. description String
  113. severity String @default("needs_work")
  114. status String @default("open")
  115. notes String?
  116. imageUrls String[] @default([])
  117. createdAt DateTime @default(now())
  118. updatedAt DateTime @updatedAt
  119. vehicleId String
  120. vehicle Vehicle @relation(fields: [vehicleId], references: [id], onDelete: Cascade)
  121. serviceRecordId String?
  122. serviceRecord ServiceRecord? @relation("FindingDiscoveredIn", fields: [serviceRecordId], references: [id], onDelete: SetNull)
  123. resolvedServiceRecordId String?
  124. resolvedServiceRecord ServiceRecord? @relation("FindingResolvedIn", fields: [resolvedServiceRecordId], references: [id], onDelete: SetNull)
  125. /// The customer concern this finding answers, if it answers one.
  126. ///
  127. /// Nullable because plenty of findings are things nobody asked about: the
  128. /// perished bush somebody noticed while the car was on the lift. Those are
  129. /// worth just as much, they just are not part of the story the customer
  130. /// started. SetNull rather than Cascade because a finding is a fact about
  131. /// the vehicle and outlives the concern that prompted the look.
  132. concernId String?
  133. concern ServiceConcern? @relation(fields: [concernId], references: [id], onDelete: SetNull)
  134. @@index([vehicleId])
  135. @@index([serviceRecordId])
  136. @@index([concernId])
  137. @@map("vehicle_findings")
  138. }
  139. /// What a vehicle registry, or the workshop itself, last said about when the
  140. /// vehicle's periodic roadworthiness inspection (EU-kontroll, MOT, APK, HU,
  141. /// contrôle technique, and so on) is due. One row per vehicle. The
  142. /// organisation is on the row so "what is due for this workshop" is one
  143. /// indexed query and the isolation is explicit rather than inherited.
  144. model VehicleInspectionStatus {
  145. id String @id @default(cuid())
  146. vehicleId String @unique
  147. vehicle Vehicle @relation(fields: [vehicleId], references: [id], onDelete: Cascade)
  148. organizationId String
  149. organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
  150. /// Next periodic inspection deadline, when known.
  151. dueAt DateTime?
  152. /// Last approved periodic inspection, when known.
  153. lastAt DateTime?
  154. /// Connector id ("vegvesen"), or "manual" / "inspection" for the workshop's own entries.
  155. source String
  156. /// When a registry last confirmed the row. Null for manual entries.
  157. checkedAt DateTime?
  158. /// False when the registry answered but had no such vehicle.
  159. found Boolean @default(true)
  160. /// Whether the registry lists the vehicle as currently registered, when it says.
  161. registered Boolean?
  162. /// Last lookup failure, cleared on success. Never a plate or VIN.
  163. lastError String?
  164. /// Whatever else the registry returned and the app has no column for yet:
  165. /// approved tyre sizes, weights, vehicle class, first registration.
  166. extras Json?
  167. createdAt DateTime @default(now())
  168. updatedAt DateTime @updatedAt
  169. @@index([organizationId, dueAt])
  170. @@index([organizationId, checkedAt])
  171. @@map("vehicle_inspection_statuses")
  172. }
  173. /// One "remind these customers" action, created only by a person confirming
  174. /// a reviewed list. Never created by a timer.
  175. model InspectionReminderCampaign {
  176. id String @id @default(cuid())
  177. organizationId String
  178. organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
  179. createdById String
  180. /// Client-generated, so a double submit creates one campaign, not two.
  181. idempotencyToken String @unique
  182. /// Deadline window the list was built from, in days.
  183. windowDays Int
  184. channel String
  185. subject String?
  186. body String
  187. /// "sent" once rows exist; "dry_run" in demo mode, where nothing goes out.
  188. status String @default("sent")
  189. recipientCount Int @default(0)
  190. createdAt DateTime @default(now())
  191. sends InspectionReminderSend[]
  192. @@index([organizationId, createdAt])
  193. @@map("inspection_reminder_campaigns")
  194. }
  195. /// One customer told once about one vehicle's deadline. The unique pair of
  196. /// vehicle and deadline is the guarantee that no deadline is ever reminded
  197. /// twice, whatever the code above it does.
  198. model InspectionReminderSend {
  199. id String @id @default(cuid())
  200. campaignId String
  201. campaign InspectionReminderCampaign @relation(fields: [campaignId], references: [id], onDelete: Cascade)
  202. organizationId String
  203. organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
  204. vehicleId String
  205. vehicle Vehicle @relation(fields: [vehicleId], references: [id], onDelete: Cascade)
  206. customerId String
  207. customer Customer @relation(fields: [customerId], references: [id], onDelete: Cascade)
  208. /// The deadline as it stood when the reminder went out.
  209. dueAt DateTime
  210. channel String
  211. recipient String
  212. /// Booking link secret; the link is /b/<token>.
  213. token String @unique
  214. expiresAt DateTime
  215. scheduledMessageId String? @unique
  216. scheduledMessage ScheduledMessage? @relation(fields: [scheduledMessageId], references: [id], onDelete: SetNull)
  217. /// Set when the customer booked from the link.
  218. bookedAt DateTime?
  219. bookedServiceRecordId String?
  220. bookedServiceRequestId String?
  221. cancelledAt DateTime?
  222. createdAt DateTime @default(now())
  223. @@unique([vehicleId, dueAt])
  224. @@index([organizationId, createdAt])
  225. @@index([campaignId])
  226. @@map("inspection_reminder_sends")
  227. }