quotes.prisma 4.3 KB

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