vehicles.prisma 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  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. dueMileage Int?
  86. isCompleted Boolean @default(false)
  87. /// Per-reminder notification channels, chosen when creating the reminder
  88. notifyInApp Boolean @default(true)
  89. notifyEmail Boolean @default(false)
  90. /// Set by the due-reminder cron once notified; cleared when dueDate changes
  91. notifiedAt DateTime?
  92. createdAt DateTime @default(now())
  93. updatedAt DateTime @updatedAt
  94. /// A reminder relates to a vehicle, a customer, or just the workshop
  95. /// (both null). organizationId is the canonical scope for all three.
  96. vehicleId String?
  97. vehicle Vehicle? @relation(fields: [vehicleId], references: [id], onDelete: Cascade)
  98. customerId String?
  99. customer Customer? @relation(fields: [customerId], references: [id], onDelete: Cascade)
  100. organizationId String?
  101. organization Organization? @relation(fields: [organizationId], references: [id], onDelete: Cascade)
  102. @@index([vehicleId])
  103. @@index([customerId])
  104. @@index([organizationId])
  105. @@map("reminders")
  106. }
  107. model VehicleFinding {
  108. id String @id @default(cuid())
  109. description String
  110. severity String @default("needs_work")
  111. status String @default("open")
  112. notes String?
  113. imageUrls String[] @default([])
  114. createdAt DateTime @default(now())
  115. updatedAt DateTime @updatedAt
  116. vehicleId String
  117. vehicle Vehicle @relation(fields: [vehicleId], references: [id], onDelete: Cascade)
  118. serviceRecordId String?
  119. serviceRecord ServiceRecord? @relation("FindingDiscoveredIn", fields: [serviceRecordId], references: [id], onDelete: SetNull)
  120. resolvedServiceRecordId String?
  121. resolvedServiceRecord ServiceRecord? @relation("FindingResolvedIn", fields: [resolvedServiceRecordId], references: [id], onDelete: SetNull)
  122. /// The customer concern this finding answers, if it answers one.
  123. ///
  124. /// Nullable because plenty of findings are things nobody asked about: the
  125. /// perished bush somebody noticed while the car was on the lift. Those are
  126. /// worth just as much, they just are not part of the story the customer
  127. /// started. SetNull rather than Cascade because a finding is a fact about
  128. /// the vehicle and outlives the concern that prompted the look.
  129. concernId String?
  130. concern ServiceConcern? @relation(fields: [concernId], references: [id], onDelete: SetNull)
  131. @@index([vehicleId])
  132. @@index([serviceRecordId])
  133. @@index([concernId])
  134. @@map("vehicle_findings")
  135. }
  136. /// What a vehicle registry, or the workshop itself, last said about when the
  137. /// vehicle's periodic roadworthiness inspection (EU-kontroll, MOT, APK, HU,
  138. /// contrôle technique, and so on) is due. One row per vehicle. The
  139. /// organisation is on the row so "what is due for this workshop" is one
  140. /// indexed query and the isolation is explicit rather than inherited.
  141. model VehicleInspectionStatus {
  142. id String @id @default(cuid())
  143. vehicleId String @unique
  144. vehicle Vehicle @relation(fields: [vehicleId], references: [id], onDelete: Cascade)
  145. organizationId String
  146. organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
  147. /// Next periodic inspection deadline, when known.
  148. dueAt DateTime?
  149. /// Last approved periodic inspection, when known.
  150. lastAt DateTime?
  151. /// Connector id ("vegvesen"), or "manual" / "inspection" for the workshop's own entries.
  152. source String
  153. /// When a registry last confirmed the row. Null for manual entries.
  154. checkedAt DateTime?
  155. /// False when the registry answered but had no such vehicle.
  156. found Boolean @default(true)
  157. /// Whether the registry lists the vehicle as currently registered, when it says.
  158. registered Boolean?
  159. /// Last lookup failure, cleared on success. Never a plate or VIN.
  160. lastError String?
  161. /// Whatever else the registry returned and the app has no column for yet:
  162. /// approved tyre sizes, weights, vehicle class, first registration.
  163. extras Json?
  164. createdAt DateTime @default(now())
  165. updatedAt DateTime @updatedAt
  166. @@index([organizationId, dueAt])
  167. @@index([organizationId, checkedAt])
  168. @@map("vehicle_inspection_statuses")
  169. }
  170. /// One "remind these customers" action, created only by a person confirming
  171. /// a reviewed list. Never created by a timer.
  172. model InspectionReminderCampaign {
  173. id String @id @default(cuid())
  174. organizationId String
  175. organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
  176. createdById String
  177. /// Client-generated, so a double submit creates one campaign, not two.
  178. idempotencyToken String @unique
  179. /// Deadline window the list was built from, in days.
  180. windowDays Int
  181. channel String
  182. subject String?
  183. body String
  184. /// "sent" once rows exist; "dry_run" in demo mode, where nothing goes out.
  185. status String @default("sent")
  186. recipientCount Int @default(0)
  187. createdAt DateTime @default(now())
  188. sends InspectionReminderSend[]
  189. @@index([organizationId, createdAt])
  190. @@map("inspection_reminder_campaigns")
  191. }
  192. /// One customer told once about one vehicle's deadline. The unique pair of
  193. /// vehicle and deadline is the guarantee that no deadline is ever reminded
  194. /// twice, whatever the code above it does.
  195. model InspectionReminderSend {
  196. id String @id @default(cuid())
  197. campaignId String
  198. campaign InspectionReminderCampaign @relation(fields: [campaignId], references: [id], onDelete: Cascade)
  199. organizationId String
  200. organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
  201. vehicleId String
  202. vehicle Vehicle @relation(fields: [vehicleId], references: [id], onDelete: Cascade)
  203. customerId String
  204. customer Customer @relation(fields: [customerId], references: [id], onDelete: Cascade)
  205. /// The deadline as it stood when the reminder went out.
  206. dueAt DateTime
  207. channel String
  208. recipient String
  209. /// Booking link secret; the link is /b/<token>.
  210. token String @unique
  211. expiresAt DateTime
  212. scheduledMessageId String? @unique
  213. scheduledMessage ScheduledMessage? @relation(fields: [scheduledMessageId], references: [id], onDelete: SetNull)
  214. /// Set when the customer booked from the link.
  215. bookedAt DateTime?
  216. bookedServiceRecordId String?
  217. bookedServiceRequestId String?
  218. cancelledAt DateTime?
  219. createdAt DateTime @default(now())
  220. @@unique([vehicleId, dueAt])
  221. @@index([organizationId, createdAt])
  222. @@index([campaignId])
  223. @@map("inspection_reminder_sends")
  224. }