vehicles.prisma 10.0 KB

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