quotes.prisma 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. model Quote {
  2. id String @id @default(cuid())
  3. quoteNumber String?
  4. title String
  5. description String?
  6. status String @default("draft")
  7. validUntil DateTime?
  8. subtotal Float @default(0)
  9. taxRate Float @default(0)
  10. taxAmount Float @default(0)
  11. taxInclusive Boolean @default(false)
  12. /// See ServiceRecord.taxComponents.
  13. taxComponents Json?
  14. discountType String?
  15. discountValue Float @default(0)
  16. discountAmount Float @default(0)
  17. totalAmount Float @default(0)
  18. notes String?
  19. /// The warranty the customer is being offered, stated before they accept.
  20. /// Same meaning as on ServiceRecord, and copied there on conversion. No
  21. /// expiry: the period starts with the work, which a quote has no date for.
  22. warrantyStatus String?
  23. warrantyMonths Int?
  24. warrantyMileage Int?
  25. warrantyNotes String?
  26. publicToken String?
  27. sharedAt DateTime?
  28. viewCount Int @default(0)
  29. lastViewedAt DateTime?
  30. customerMessage String?
  31. convertedToId String?
  32. createdAt DateTime @default(now())
  33. updatedAt DateTime @updatedAt
  34. /// Most recent time this quote reached the customer, by email or share
  35. /// link. Exists so an unlock is spent when the quote is issued again; the
  36. /// status alone cannot show that, because re-sending an accepted quote does
  37. /// not change it.
  38. sentAt DateTime?
  39. /// When the workshop dismissed the customer's answer from its list of
  40. /// responses. An acceptance keeps its status when dismissed, because it is
  41. /// the customer's agreement and may be what holds the quote locked, so the
  42. /// list needs its own record of it. Cleared whenever the customer answers
  43. /// again.
  44. responseDismissedAt DateTime?
  45. /// See ServiceRecord.editUnlockedAt.
  46. editUnlockedAt DateTime?
  47. editUnlockedById String?
  48. userId String
  49. user User @relation(fields: [userId], references: [id], onDelete: Cascade)
  50. organizationId String?
  51. organization Organization? @relation(fields: [organizationId], references: [id], onDelete: Cascade)
  52. customerId String?
  53. customer Customer? @relation(fields: [customerId], references: [id], onDelete: SetNull)
  54. /// The stored set this quote is about, when it came out of the tire hotel.
  55. /// SetNull rather than Cascade: a quote already sent is a document the
  56. /// customer holds, and deleting the set must not erase it.
  57. tireSetId String?
  58. tireSet TireSet? @relation(fields: [tireSetId], references: [id], onDelete: SetNull)
  59. vehicleId String?
  60. vehicle Vehicle? @relation(fields: [vehicleId], references: [id], onDelete: SetNull)
  61. inspectionId String?
  62. inspection Inspection? @relation(fields: [inspectionId], references: [id], onDelete: SetNull)
  63. partItems QuotePart[]
  64. laborItems QuoteLabor[]
  65. attachments QuoteAttachment[]
  66. @@index([organizationId])
  67. @@map("quotes")
  68. }
  69. model QuoteAttachment {
  70. id String @id @default(cuid())
  71. fileName String
  72. fileUrl String
  73. fileType String
  74. fileSize Int
  75. category String @default("image")
  76. description String?
  77. includeInInvoice Boolean @default(true)
  78. createdAt DateTime @default(now())
  79. quoteId String
  80. quote Quote @relation(fields: [quoteId], references: [id], onDelete: Cascade)
  81. @@index([quoteId])
  82. @@map("quote_attachments")
  83. }
  84. model QuotePart {
  85. id String @id @default(cuid())
  86. partNumber String?
  87. name String
  88. quantity Float @default(1)
  89. /// Unit of measure snapshotted from InventoryPart at pick time; null for
  90. /// free-text lines and legacy rows.
  91. unit String?
  92. /// Internal purchase cost — never shown to customers (quote PDF/share
  93. /// render unitPrice only)
  94. unitCost Float @default(0)
  95. markupPercent Float @default(0)
  96. unitPrice Float @default(0)
  97. total Float @default(0)
  98. excluded Boolean @default(false)
  99. /// Optional link to the stocked part this line was picked from. Carried into
  100. /// ServicePart on conversion so the job deducts the right inventory item.
  101. inventoryPartId String?
  102. quoteId String
  103. quote Quote @relation(fields: [quoteId], references: [id], onDelete: Cascade)
  104. @@index([quoteId])
  105. @@map("quote_parts")
  106. }
  107. model QuoteLabor {
  108. id String @id @default(cuid())
  109. description String
  110. hours Float @default(0)
  111. rate Float @default(0)
  112. total Float @default(0)
  113. pricingType String @default("hourly")
  114. excluded Boolean @default(false)
  115. quoteId String
  116. quote Quote @relation(fields: [quoteId], references: [id], onDelete: Cascade)
  117. @@index([quoteId])
  118. @@map("quote_labor")
  119. }