autopilot-core-sandbox 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (822) hide show
  1. package/README.md +53 -0
  2. package/dist/Provider.d.ts +21 -0
  3. package/dist/Provider.d.ts.map +1 -0
  4. package/dist/Provider.js +61 -0
  5. package/dist/Provider.js.map +1 -0
  6. package/dist/dist/Provider.d.ts +21 -0
  7. package/dist/dist/Provider.d.ts.map +1 -0
  8. package/dist/dist/Provider.js +61 -0
  9. package/dist/dist/Provider.js.map +1 -0
  10. package/dist/dist/index.d.ts +45 -0
  11. package/dist/dist/index.d.ts.map +1 -0
  12. package/dist/dist/index.js +61 -0
  13. package/dist/dist/index.js.map +1 -0
  14. package/dist/dist/interfaces/index.d.ts +1238 -0
  15. package/dist/dist/interfaces/index.d.ts.map +1 -0
  16. package/dist/dist/interfaces/index.js +3 -0
  17. package/dist/dist/interfaces/index.js.map +1 -0
  18. package/dist/dist/migrations/20260608222757_remote_schema.sql +5666 -0
  19. package/dist/dist/migrations/20260611174826_add_job_item_recurring_id_to_job_item.sql +7 -0
  20. package/dist/dist/migrations/20260625224612_add_dashboard_guided_tour_dismissed_to_settings_user.sql +5 -0
  21. package/dist/dist/migrations/20260627031433_support_chat_tables.sql +115 -0
  22. package/dist/dist/migrations/20260627031434_support_chat_rls.sql +37 -0
  23. package/dist/dist/migrations/20260704170209_add_estimate_scheduling.sql +121 -0
  24. package/dist/dist/migrations/20260704170210_add_estimate_scheduling_rls.sql +42 -0
  25. package/dist/dist/migrations/20260704235034_add_estimate_reminder_settings.sql +11 -0
  26. package/dist/dist/migrations/20260707003825_blocked_number_table.sql +32 -0
  27. package/dist/dist/migrations/20260707003826_blocked_number_rls.sql +22 -0
  28. package/dist/dist/migrations/20260709191055_sms_opt_out_table.sql +38 -0
  29. package/dist/dist/migrations/20260709191056_sms_opt_out_rls.sql +21 -0
  30. package/dist/dist/migrations/20260709214507_job_time_entry_table.sql +70 -0
  31. package/dist/dist/migrations/20260709214508_job_time_entry_rls.sql +21 -0
  32. package/dist/dist/migrations/20260711185956_phone_call_table.sql +99 -0
  33. package/dist/dist/migrations/20260711185957_phone_call_rls.sql +25 -0
  34. package/dist/dist/migrations/20260712014705_ai_voice_assistant_settings.sql +71 -0
  35. package/dist/dist/migrations/20260712014706_ai_voice_assistant_settings_rls.sql +14 -0
  36. package/dist/dist/migrations/20260712014707_ai_prompt_templates.sql +28 -0
  37. package/dist/dist/migrations/20260712014708_ai_prompt_templates_rls.sql +5 -0
  38. package/dist/dist/migrations/20260712014709_ai_prompt_templates_seed.sql +77 -0
  39. package/dist/dist/migrations/20260715011550_reminder_dispatch_table.sql +47 -0
  40. package/dist/dist/migrations/20260715011551_reminder_dispatch_rls.sql +22 -0
  41. package/dist/dist/migrations/20260715192923_dedup_sequence_contact_add_unique_constraint.sql +27 -0
  42. package/dist/dist/migrations/20260716194420_add_employee_location_tracking.sql +146 -0
  43. package/dist/dist/migrations/20260716194421_add_employee_location_tracking_rls.sql +154 -0
  44. package/dist/dist/migrations/20260716194422_get_client_map_last_completed_jobs.sql +28 -0
  45. package/dist/dist/migrations/20260717144535_add_blast_email_invalid_skipped_count.sql +2 -0
  46. package/dist/dist/migrations/20260717162900_create_blast_email_chunk.sql +36 -0
  47. package/dist/dist/migrations/20260717162901_add_blast_email_chunk_rls.sql +5 -0
  48. package/dist/dist/migrations/20260717162902_add_blast_email_chunk_progress_columns.sql +3 -0
  49. package/dist/dist/migrations/20260717162903_add_blast_email_bounce_tracking_columns.sql +4 -0
  50. package/dist/dist/migrations/20260717162904_create_email_hard_bounced.sql +23 -0
  51. package/dist/dist/migrations/20260717162905_add_email_hard_bounced_rls.sql +5 -0
  52. package/dist/dist/migrations/20260717164837_add_sequence_contact_sent_at.sql +19 -0
  53. package/dist/dist/migrations/20260717170920_add_sequence_contact_claim_index.sql +12 -0
  54. package/dist/dist/migrations/20260719215139_create_outbound_event.sql +59 -0
  55. package/dist/dist/migrations/20260719215140_create_outbound_event_rls.sql +16 -0
  56. package/dist/dist/migrations/20260719215141_create_webhook_delivery.sql +99 -0
  57. package/dist/dist/migrations/20260719215142_create_webhook_delivery_rls.sql +11 -0
  58. package/dist/dist/migrations/20260719215143_outbound_event_triggers.sql +219 -0
  59. package/dist/dist/migrations/20260719215144_create_api_key.sql +34 -0
  60. package/dist/dist/migrations/20260719215146_create_api_key_rls.sql +12 -0
  61. package/dist/dist/migrations/20260719215147_webhook_subscription_add_secret_active.sql +28 -0
  62. package/dist/dist/migrations/20260719215148_api_aggregate_create_functions.sql +479 -0
  63. package/dist/dist/migrations/20260721174837_webhook_subscription_generic_endpoints.sql +41 -0
  64. package/dist/dist/migrations/20260721185631_webhook_outbox_retention.sql +87 -0
  65. package/dist/dist/migrations/20260721190304_create_qb_sync_task.sql +120 -0
  66. package/dist/dist/migrations/20260721190306_qb_sync_task_rls.sql +8 -0
  67. package/dist/dist/migrations/20260721190307_qb_mapping_orphan_fks.sql +29 -0
  68. package/dist/dist/migrations/20260721190308_qb_task_retention.sql +90 -0
  69. package/dist/dist/migrations/20260721234401_create_permission_role.sql +83 -0
  70. package/dist/dist/migrations/20260721234402_permission_role_rls.sql +22 -0
  71. package/dist/dist/migrations/20260721234403_user_role_add_permission_role_id.sql +12 -0
  72. package/dist/dist/migrations/20260721234404_seed_permission_roles.sql +100 -0
  73. package/dist/dist/migrations/20260721234405_backfill_user_role_permission_role_id.sql +20 -0
  74. package/dist/dist/migrations/20260722193800_permission_role_add_manage_employees.sql +56 -0
  75. package/dist/dist/migrations/20260723025606_staging_new_employee_company_read_rls.sql +10 -0
  76. package/dist/dist/migrations/20260723134328_create_google_lsa_integration.sql +1016 -0
  77. package/dist/dist/migrations/20260723134329_google_lsa_integration_rls.sql +205 -0
  78. package/dist/dist/migrations/20260723150640_google_lsa_hardening.sql +169 -0
  79. package/dist/dist/migrations/20260723150641_google_lsa_hardening_rls.sql +12 -0
  80. package/dist/dist/migrations/20260724230721_google_lsa_facebook_exclusivity_attach.sql +104 -0
  81. package/dist/dist/migrations/20260724230722_google_lsa_facebook_exclusivity_attach_rls.sql +20 -0
  82. package/dist/dist/migrations/20260725172630_add_message_pagination_and_search.sql +265 -0
  83. package/dist/dist/migrations/20260725172631_add_message_pagination_and_search_rls.sql +72 -0
  84. package/dist/dist/migrations/20260725172632_optimize_message_thread_pagination_search.sql +245 -0
  85. package/dist/dist/migrations/20260725172633_harden_message_search_and_photo_pagination.sql +256 -0
  86. package/dist/dist/migrations/20260725172634_fix_message_thread_line_filter.sql +250 -0
  87. package/dist/dist/migrations/20260725174948_google_lsa_attach_bing_exclusivity_guard.sql +23 -0
  88. package/dist/dist/migrations/20260725200430_client_portal_settings.sql +41 -0
  89. package/dist/dist/migrations/20260725200431_client_portal_settings_rls.sql +21 -0
  90. package/dist/dist/migrations/20260725200433_client_portal_auth_tables.sql +81 -0
  91. package/dist/dist/migrations/20260725200434_client_portal_auth_rls.sql +18 -0
  92. package/dist/dist/migrations/20260725200435_portal_claim_login_attempt.sql +36 -0
  93. package/dist/dist/migrations/20260725200436_portal_appointment_request.sql +39 -0
  94. package/dist/dist/migrations/20260725200437_portal_appointment_request_rls.sql +10 -0
  95. package/dist/dist/migrations/20260725210000_google_reserve_integration.sql +85 -0
  96. package/dist/dist/migrations/20260725210001_google_reserve_integration_rls.sql +10 -0
  97. package/dist/dist/migrations/20260725220000_add_analytics_query_indexes.sql +47 -0
  98. package/dist/dist/migrations/20260725230000_onboarding_preload_data.sql +1074 -0
  99. package/dist/dist/migrations/20260725230001_onboarding_preload_data_rls.sql +40 -0
  100. package/dist/dist/migrations/20260725230002_add_pricebook_query_indexes.sql +13 -0
  101. package/dist/dist/migrations/20260726000000_facebook_ads_integration.sql +1538 -0
  102. package/dist/dist/migrations/20260726000001_facebook_ads_integration_rls.sql +356 -0
  103. package/dist/dist/migrations/20260726000002_facebook_ads_attach_lsa_exclusivity_guard.sql +25 -0
  104. package/dist/dist/migrations/20260726000003_facebook_ads_attach_bing_exclusivity_guard.sql +22 -0
  105. package/dist/dist/migrations/20260726010000_create_bing_ads_integration.sql +1112 -0
  106. package/dist/dist/migrations/20260726010001_bing_ads_integration_rls.sql +227 -0
  107. package/dist/dist/migrations/20260726220000_add_document_theme_settings.sql +11 -0
  108. package/dist/dist/migrations/20260726220001_update_document_theme_comments.sql +7 -0
  109. package/dist/dist/migrations/20260727210319_evolve_tier_for_pricing.sql +68 -0
  110. package/dist/dist/migrations/20260727210320_create_billing_addon.sql +85 -0
  111. package/dist/dist/migrations/20260727210321_create_billing_addon_rls.sql +21 -0
  112. package/dist/dist/migrations/20260728213930_create_usage_metering.sql +189 -0
  113. package/dist/dist/migrations/20260728213931_create_usage_metering_rls.sql +32 -0
  114. package/dist/dist/migrations/20260729004305_marketing_charge_txn_functions.sql +242 -0
  115. package/dist/dist/migrations/20260729004306_marketing_charge_txn_functions_rls.sql +37 -0
  116. package/dist/dist/migrations/20260729005524_update_deduct_from_balance_errcode.sql +42 -0
  117. package/dist/dist/migrations/20260730193601_fix_deduct_from_balance_lost_update.sql +46 -0
  118. package/dist/dist/migrations/20260730193602_add_blast_sms_chunk_reaper_columns.sql +27 -0
  119. package/dist/dist/migrations/20260731011415_create_google_calendar_connection.sql +84 -0
  120. package/dist/dist/migrations/20260731011416_google_calendar_connection_rls.sql +23 -0
  121. package/dist/dist/migrations/20260731011417_create_google_calendar_sync.sql +157 -0
  122. package/dist/dist/migrations/20260731011418_google_calendar_sync_rls.sql +26 -0
  123. package/dist/dist/migrations/20260731011419_event_outbound_triggers.sql +28 -0
  124. package/dist/dist/migrations/20260731011420_add_google_calendar_addon_key.sql +24 -0
  125. package/dist/dist/migrations/20260731011421_gcal_task_retention.sql +109 -0
  126. package/dist/dist/migrations/20260731234245_create_estimate_proposal.sql +157 -0
  127. package/dist/dist/migrations/20260731234247_estimate_proposal_rls.sql +46 -0
  128. package/dist/dist/migrations/20260731234248_add_proposal_estimate_settings.sql +17 -0
  129. package/dist/dist/migrations/20260731234249_add_estimate_proposals_addon_key.sql +25 -0
  130. package/dist/dist/migrations/20260731234250_approve_estimate_proposal_function.sql +133 -0
  131. package/dist/dist/migrations/20260801010609_save_estimate_proposal_function.sql +154 -0
  132. package/dist/dist/migrations/20260801173554_add_hide_autopilot_branding_setting.sql +6 -0
  133. package/dist/dist/migrations/20260806230917_add_client_billing_address_columns.sql +9 -0
  134. package/dist/dist/migrations/20260806230944_api_create_client_billing_address.sql +93 -0
  135. package/dist/dist/migrations/20260807014702_create_parent_id_company_id_keys.sql +20 -0
  136. package/dist/dist/migrations/20260807014703_create_contract_template.sql +30 -0
  137. package/dist/dist/migrations/20260807014704_create_contract.sql +233 -0
  138. package/dist/dist/migrations/20260807014705_create_contract_signature.sql +39 -0
  139. package/dist/dist/migrations/20260807014706_create_item_contract_requirement.sql +38 -0
  140. package/dist/dist/migrations/20260807014707_create_settings_contract.sql +23 -0
  141. package/dist/dist/migrations/20260807014708_contract_rls.sql +74 -0
  142. package/dist/dist/migrations/20260807014709_sign_contract_function.sql +92 -0
  143. package/dist/dist/migrations/20260807014710_create_settings_rental.sql +34 -0
  144. package/dist/dist/migrations/20260807014711_create_saved_location.sql +56 -0
  145. package/dist/dist/migrations/20260807014712_create_rental_task_option.sql +52 -0
  146. package/dist/dist/migrations/20260807014713_create_asset.sql +90 -0
  147. package/dist/dist/migrations/20260807014714_add_job_schedule_asset_fkeys.sql +39 -0
  148. package/dist/dist/migrations/20260807014715_rental_rls.sql +48 -0
  149. package/dist/dist/migrations/20260807014716_add_job_schedule_any_time.sql +7 -0
  150. package/dist/dist/migrations/20260807014717_add_job_schedule_target_location.sql +48 -0
  151. package/dist/dist/migrations/20260807014718_job_schedule_completion_guard.sql +53 -0
  152. package/dist/dist/migrations/20260807014719_add_job_schedule_list_sort_key.sql +7 -0
  153. package/dist/dist/migrations/20260807014720_add_item_duration.sql +11 -0
  154. package/dist/dist/migrations/20260807014721_add_settings_booking_enforce_rental_availability.sql +9 -0
  155. package/dist/dist/migrations/20260807014722_add_job_unsigned_rental_agreement.sql +10 -0
  156. package/dist/dist/migrations/20260807014723_add_item_size.sql +12 -0
  157. package/dist/dist/migrations/20260809193533_add_booking_path_button_labels.sql +11 -0
  158. package/dist/dist/migrations/20260809231721_add_route_distance_cache.sql +37 -0
  159. package/dist/dist/migrations/20260809231722_route_distance_cache_rls.sql +15 -0
  160. package/dist/dist/migrations/20260809231723_apply_rental_route_plan_function.sql +275 -0
  161. package/dist/dist/migrations/20260810171550_add_settings_job_default_job_types.sql +9 -0
  162. package/dist/dist/migrations/20260810212430_backfill_rental_task_option_sort_order.sql +61 -0
  163. package/dist/dist/migrations/20260811224317_add_settings_user_dnd_hint_dismissed.sql +9 -0
  164. package/dist/dist/migrations/20260813174503_repair_user_role_email_drift.sql +59 -0
  165. package/dist/dist/migrations/20260813174504_sync_user_role_email_trigger.sql +30 -0
  166. package/dist/dist/migrations/20260813175210_add_event_list_sort_key.sql +5 -0
  167. package/dist/dist/migrations/20260814172222_add_client_auto_bill_on_completion.sql +6 -0
  168. package/dist/dist/migrations/20260816160252_create_automation_rule.sql +99 -0
  169. package/dist/dist/migrations/20260816160253_automation_rule_rls.sql +19 -0
  170. package/dist/dist/migrations/20260816160254_create_settings_automation.sql +36 -0
  171. package/dist/dist/migrations/20260816160255_settings_automation_rls.sql +18 -0
  172. package/dist/dist/migrations/20260816160256_create_automation_event.sql +71 -0
  173. package/dist/dist/migrations/20260816160257_automation_event_rls.sql +12 -0
  174. package/dist/dist/migrations/20260816160258_create_automation_run.sql +129 -0
  175. package/dist/dist/migrations/20260816160259_automation_run_rls.sql +19 -0
  176. package/dist/dist/migrations/20260816160300_add_client_created_via.sql +25 -0
  177. package/dist/dist/migrations/20260816160301_add_estimate_sent_at.sql +39 -0
  178. package/dist/dist/migrations/20260816160302_add_message_automation_ref.sql +10 -0
  179. package/dist/dist/migrations/20260816160303_automation_event_triggers.sql +236 -0
  180. package/dist/dist/migrations/20260816160304_automation_retention.sql +142 -0
  181. package/dist/dist/migrations/20260816202404_add_automation_center_addon_key.sql +26 -0
  182. package/dist/dist/migrations/20260816225501_widen_automation_rule_trigger_action_checks.sql +25 -0
  183. package/dist/dist/migrations/20260816225502_widen_automation_event_type_check.sql +18 -0
  184. package/dist/dist/migrations/20260816225503_add_invoice_id_to_automation_tables.sql +18 -0
  185. package/dist/dist/migrations/20260816225504_add_job_rescheduled_capture_trigger.sql +51 -0
  186. package/dist/dist/migrations/20260816225505_add_lead_converted_capture_trigger.sql +26 -0
  187. package/dist/dist/migrations/20260816225506_fix_booking_request_import_guard.sql +27 -0
  188. package/dist/dist/migrations/20260816225507_widen_outbound_event_for_automation.sql +38 -0
  189. package/dist/dist/migrations/20260817002626_add_user_last_login_platform_columns.sql +14 -0
  190. package/dist/dist/migrations/20260817221703_create_deletion.sql +34 -0
  191. package/dist/dist/migrations/20260817221707_deletion_rls.sql +21 -0
  192. package/dist/dist/migrations/20260817221708_add_soft_delete_columns.sql +82 -0
  193. package/dist/dist/migrations/20260817221709_trash_functions.sql +434 -0
  194. package/dist/dist/migrations/20260817221710_outbound_event_soft_delete.sql +55 -0
  195. package/dist/dist/migrations/20260817221711_automation_soft_delete_events.sql +77 -0
  196. package/dist/dist/migrations/20260817221712_fix_estimate_address_cascade.sql +14 -0
  197. package/dist/dist/migrations/20260817232630_trash_batch_function.sql +67 -0
  198. package/dist/dist/migrations/20260819170204_create_email_spam_complaint.sql +23 -0
  199. package/dist/dist/migrations/20260819170205_add_email_spam_complaint_rls.sql +5 -0
  200. package/dist/dist/migrations/20260819170206_email_unsubscribed_upsert_constraints.sql +14 -0
  201. package/dist/dist/migrations/20260819180151_get_suppressed_emails_function.sql +23 -0
  202. package/dist/dist/migrations/20260819191143_is_deleted_backfill_guard.sql +29 -0
  203. package/dist/dist/migrations/20260819191144_trash_functions_no_dual_write.sql +319 -0
  204. package/dist/dist/migrations/20260819191145_client_map_jobs_deleted_at.sql +33 -0
  205. package/dist/dist/migrations/20260819191147_booking_declined_deleted_at.sql +28 -0
  206. package/dist/dist/migrations/20260819191148_onboarding_preload_no_address_is_deleted.sql +1019 -0
  207. package/dist/dist/migrations/20260820222756_add_settings_job_hide_task_timer.sql +2 -0
  208. package/dist/dist/migrations/20260824234151_add_blast_chunk_recipients.sql +13 -0
  209. package/dist/dist/migrations/20260824234152_add_blast_pause_cancel_columns.sql +10 -0
  210. package/dist/dist/migrations/20260825014725_get_job_ids_in_range.sql +42 -0
  211. package/dist/dist/migrations/20260825194452_add_failed_job_status.sql +15 -0
  212. package/dist/dist/migrations/20260825194457_remove_take_to_rental_task_options.sql +24 -0
  213. package/dist/dist/migrations/20260825194458_remove_take_to_job_schedules.sql +12 -0
  214. package/dist/dist/migrations/20260825194459_rework_apply_rental_route_plan_no_take_to.sql +168 -0
  215. package/dist/dist/migrations/20260825194500_drop_job_schedule_target_location.sql +24 -0
  216. package/dist/dist/migrations/20260825194501_add_job_schedule_size.sql +17 -0
  217. package/dist/dist/migrations/20260825233001_add_job_schedule_id_company_id_key.sql +11 -0
  218. package/dist/dist/migrations/20260825233002_create_route_plan.sql +52 -0
  219. package/dist/dist/migrations/20260825233003_create_route_plan_stop.sql +159 -0
  220. package/dist/dist/migrations/20260825233004_route_plan_rls.sql +32 -0
  221. package/dist/dist/migrations/20260825233005_apply_rental_route_plan_v2.sql +351 -0
  222. package/dist/dist/migrations/20260826150934_facebook_ads_lead_ingestion.sql +1711 -0
  223. package/dist/dist/migrations/20260826151141_facebook_ads_lead_ingestion_rls_v2.sql +43 -0
  224. package/dist/dist/migrations/20260826234722_facebook_messenger_ingestion.sql +703 -0
  225. package/dist/dist/migrations/20260826234723_facebook_messenger_ingestion_rls.sql +80 -0
  226. package/dist/dist/migrations/20260826234724_widen_automations_messenger.sql +46 -0
  227. package/dist/dist/migrations/20260827100001_add_route_plan_minutes.sql +10 -0
  228. package/dist/dist/migrations/20260827100002_add_route_plan_stop_leg_minutes.sql +9 -0
  229. package/dist/dist/migrations/20260827100003_apply_rental_route_plan_v3.sql +341 -0
  230. package/dist/dist/migrations/20260827230346_apply_rental_route_plan_v4.sql +366 -0
  231. package/dist/dist/migrations/20260829090911_create_fleet_schema.sql +3698 -0
  232. package/dist/dist/migrations/20260829090912_fleet_rls.sql +1011 -0
  233. package/dist/dist/migrations/20260831102212_add_online_booking_address_display.sql +8 -0
  234. package/dist/dist/providers/AddressProvider.d.ts +9 -0
  235. package/dist/dist/providers/AddressProvider.d.ts.map +1 -0
  236. package/dist/dist/providers/AddressProvider.js +207 -0
  237. package/dist/dist/providers/AddressProvider.js.map +1 -0
  238. package/dist/dist/providers/AutomationsProvider.d.ts +11 -0
  239. package/dist/dist/providers/AutomationsProvider.d.ts.map +1 -0
  240. package/dist/dist/providers/AutomationsProvider.js +170 -0
  241. package/dist/dist/providers/AutomationsProvider.js.map +1 -0
  242. package/dist/dist/providers/BlockedNumbersProvider.d.ts +13 -0
  243. package/dist/dist/providers/BlockedNumbersProvider.d.ts.map +1 -0
  244. package/dist/dist/providers/BlockedNumbersProvider.js +71 -0
  245. package/dist/dist/providers/BlockedNumbersProvider.js.map +1 -0
  246. package/dist/dist/providers/CategoryProvider.d.ts +8 -0
  247. package/dist/dist/providers/CategoryProvider.d.ts.map +1 -0
  248. package/dist/dist/providers/CategoryProvider.js +95 -0
  249. package/dist/dist/providers/CategoryProvider.js.map +1 -0
  250. package/dist/dist/providers/ClientProvider.d.ts +39 -0
  251. package/dist/dist/providers/ClientProvider.d.ts.map +1 -0
  252. package/dist/dist/providers/ClientProvider.js +253 -0
  253. package/dist/dist/providers/ClientProvider.js.map +1 -0
  254. package/dist/dist/providers/CompanyProvider.d.ts +7 -0
  255. package/dist/dist/providers/CompanyProvider.d.ts.map +1 -0
  256. package/dist/dist/providers/CompanyProvider.js +114 -0
  257. package/dist/dist/providers/CompanyProvider.js.map +1 -0
  258. package/dist/dist/providers/DashboardProvider.d.ts +78 -0
  259. package/dist/dist/providers/DashboardProvider.d.ts.map +1 -0
  260. package/dist/dist/providers/DashboardProvider.js +568 -0
  261. package/dist/dist/providers/DashboardProvider.js.map +1 -0
  262. package/dist/dist/providers/DiscountProvider.d.ts +8 -0
  263. package/dist/dist/providers/DiscountProvider.d.ts.map +1 -0
  264. package/dist/dist/providers/DiscountProvider.js +92 -0
  265. package/dist/dist/providers/DiscountProvider.js.map +1 -0
  266. package/dist/dist/providers/EmployeesProvider.d.ts +6 -0
  267. package/dist/dist/providers/EmployeesProvider.d.ts.map +1 -0
  268. package/dist/dist/providers/EmployeesProvider.js +185 -0
  269. package/dist/dist/providers/EmployeesProvider.js.map +1 -0
  270. package/dist/dist/providers/EstimatesProvider.d.ts +16 -0
  271. package/dist/dist/providers/EstimatesProvider.d.ts.map +1 -0
  272. package/dist/dist/providers/EstimatesProvider.js +251 -0
  273. package/dist/dist/providers/EstimatesProvider.js.map +1 -0
  274. package/dist/dist/providers/EventsProvider.d.ts +4 -0
  275. package/dist/dist/providers/EventsProvider.d.ts.map +1 -0
  276. package/dist/dist/providers/EventsProvider.js +24 -0
  277. package/dist/dist/providers/EventsProvider.js.map +1 -0
  278. package/dist/dist/providers/FilesProvider.d.ts +17 -0
  279. package/dist/dist/providers/FilesProvider.d.ts.map +1 -0
  280. package/dist/dist/providers/FilesProvider.js +201 -0
  281. package/dist/dist/providers/FilesProvider.js.map +1 -0
  282. package/dist/dist/providers/InvoicesProvider.d.ts +13 -0
  283. package/dist/dist/providers/InvoicesProvider.d.ts.map +1 -0
  284. package/dist/dist/providers/InvoicesProvider.js +207 -0
  285. package/dist/dist/providers/InvoicesProvider.js.map +1 -0
  286. package/dist/dist/providers/ItemProvider.d.ts +9 -0
  287. package/dist/dist/providers/ItemProvider.d.ts.map +1 -0
  288. package/dist/dist/providers/ItemProvider.js +201 -0
  289. package/dist/dist/providers/ItemProvider.js.map +1 -0
  290. package/dist/dist/providers/JobSourceProvider.d.ts +8 -0
  291. package/dist/dist/providers/JobSourceProvider.d.ts.map +1 -0
  292. package/dist/dist/providers/JobSourceProvider.js +75 -0
  293. package/dist/dist/providers/JobSourceProvider.js.map +1 -0
  294. package/dist/dist/providers/JobTypeProvider.d.ts +8 -0
  295. package/dist/dist/providers/JobTypeProvider.d.ts.map +1 -0
  296. package/dist/dist/providers/JobTypeProvider.js +75 -0
  297. package/dist/dist/providers/JobTypeProvider.js.map +1 -0
  298. package/dist/dist/providers/JobsProvider.d.ts +35 -0
  299. package/dist/dist/providers/JobsProvider.d.ts.map +1 -0
  300. package/dist/dist/providers/JobsProvider.js +863 -0
  301. package/dist/dist/providers/JobsProvider.js.map +1 -0
  302. package/dist/dist/providers/MessagesProvider.d.ts +33 -0
  303. package/dist/dist/providers/MessagesProvider.d.ts.map +1 -0
  304. package/dist/dist/providers/MessagesProvider.js +415 -0
  305. package/dist/dist/providers/MessagesProvider.js.map +1 -0
  306. package/dist/dist/providers/PaymentProvider.d.ts +9 -0
  307. package/dist/dist/providers/PaymentProvider.d.ts.map +1 -0
  308. package/dist/dist/providers/PaymentProvider.js +48 -0
  309. package/dist/dist/providers/PaymentProvider.js.map +1 -0
  310. package/dist/dist/providers/PermissionRolesProvider.d.ts +5 -0
  311. package/dist/dist/providers/PermissionRolesProvider.d.ts.map +1 -0
  312. package/dist/dist/providers/PermissionRolesProvider.js +30 -0
  313. package/dist/dist/providers/PermissionRolesProvider.js.map +1 -0
  314. package/dist/dist/providers/PhoneNumberProvider.d.ts +12 -0
  315. package/dist/dist/providers/PhoneNumberProvider.d.ts.map +1 -0
  316. package/dist/dist/providers/PhoneNumberProvider.js +184 -0
  317. package/dist/dist/providers/PhoneNumberProvider.js.map +1 -0
  318. package/dist/dist/providers/ProfileProvider.d.ts +6 -0
  319. package/dist/dist/providers/ProfileProvider.d.ts.map +1 -0
  320. package/dist/dist/providers/ProfileProvider.js +71 -0
  321. package/dist/dist/providers/ProfileProvider.js.map +1 -0
  322. package/dist/dist/providers/RentalsProvider.d.ts +28 -0
  323. package/dist/dist/providers/RentalsProvider.d.ts.map +1 -0
  324. package/dist/dist/providers/RentalsProvider.js +438 -0
  325. package/dist/dist/providers/RentalsProvider.js.map +1 -0
  326. package/dist/dist/providers/SettingsBookingProvider.d.ts +9 -0
  327. package/dist/dist/providers/SettingsBookingProvider.d.ts.map +1 -0
  328. package/dist/dist/providers/SettingsBookingProvider.js +163 -0
  329. package/dist/dist/providers/SettingsBookingProvider.js.map +1 -0
  330. package/dist/dist/providers/SettingsProvider.d.ts +8 -0
  331. package/dist/dist/providers/SettingsProvider.d.ts.map +1 -0
  332. package/dist/dist/providers/SettingsProvider.js +86 -0
  333. package/dist/dist/providers/SettingsProvider.js.map +1 -0
  334. package/dist/dist/providers/TagsProvider.d.ts +12 -0
  335. package/dist/dist/providers/TagsProvider.d.ts.map +1 -0
  336. package/dist/dist/providers/TagsProvider.js +210 -0
  337. package/dist/dist/providers/TagsProvider.js.map +1 -0
  338. package/dist/dist/providers/TimeCardProvider.d.ts +8 -0
  339. package/dist/dist/providers/TimeCardProvider.d.ts.map +1 -0
  340. package/dist/dist/providers/TimeCardProvider.js +93 -0
  341. package/dist/dist/providers/TimeCardProvider.js.map +1 -0
  342. package/dist/dist/providers/TrashProvider.d.ts +13 -0
  343. package/dist/dist/providers/TrashProvider.d.ts.map +1 -0
  344. package/dist/dist/providers/TrashProvider.js +98 -0
  345. package/dist/dist/providers/TrashProvider.js.map +1 -0
  346. package/dist/dist/providers/dashboardAnalytics.d.ts +9 -0
  347. package/dist/dist/providers/dashboardAnalytics.d.ts.map +1 -0
  348. package/dist/dist/providers/dashboardAnalytics.js +41 -0
  349. package/dist/dist/providers/dashboardAnalytics.js.map +1 -0
  350. package/dist/dist/providers/dashboardAnalytics.test.d.ts +2 -0
  351. package/dist/dist/providers/dashboardAnalytics.test.d.ts.map +1 -0
  352. package/dist/dist/providers/dashboardAnalytics.test.js +111 -0
  353. package/dist/dist/providers/dashboardAnalytics.test.js.map +1 -0
  354. package/dist/dist/server-safe/BlockedNumberFunctions.d.ts +11 -0
  355. package/dist/dist/server-safe/BlockedNumberFunctions.d.ts.map +1 -0
  356. package/dist/dist/server-safe/BlockedNumberFunctions.js +16 -0
  357. package/dist/dist/server-safe/BlockedNumberFunctions.js.map +1 -0
  358. package/dist/dist/server-safe/EmployeeFunctions.d.ts +14 -0
  359. package/dist/dist/server-safe/EmployeeFunctions.d.ts.map +1 -0
  360. package/dist/dist/server-safe/EmployeeFunctions.js +70 -0
  361. package/dist/dist/server-safe/EmployeeFunctions.js.map +1 -0
  362. package/dist/dist/server-safe/EmployeeLocationFunctions.d.ts +7 -0
  363. package/dist/dist/server-safe/EmployeeLocationFunctions.d.ts.map +1 -0
  364. package/dist/dist/server-safe/EmployeeLocationFunctions.js +47 -0
  365. package/dist/dist/server-safe/EmployeeLocationFunctions.js.map +1 -0
  366. package/dist/dist/server-safe/EstimateFunctions.d.ts +15 -0
  367. package/dist/dist/server-safe/EstimateFunctions.d.ts.map +1 -0
  368. package/dist/dist/server-safe/EstimateFunctions.js +34 -0
  369. package/dist/dist/server-safe/EstimateFunctions.js.map +1 -0
  370. package/dist/dist/server-safe/InvoiceFunctions.d.ts +21 -0
  371. package/dist/dist/server-safe/InvoiceFunctions.d.ts.map +1 -0
  372. package/dist/dist/server-safe/InvoiceFunctions.js +124 -0
  373. package/dist/dist/server-safe/InvoiceFunctions.js.map +1 -0
  374. package/dist/dist/server-safe/JobFunctions.d.ts +15 -0
  375. package/dist/dist/server-safe/JobFunctions.d.ts.map +1 -0
  376. package/dist/dist/server-safe/JobFunctions.js +56 -0
  377. package/dist/dist/server-safe/JobFunctions.js.map +1 -0
  378. package/dist/dist/server-safe/PaymentFunctions.d.ts +16 -0
  379. package/dist/dist/server-safe/PaymentFunctions.d.ts.map +1 -0
  380. package/dist/dist/server-safe/PaymentFunctions.js +80 -0
  381. package/dist/dist/server-safe/PaymentFunctions.js.map +1 -0
  382. package/dist/dist/server-safe/PermissionFunctions.d.ts +7 -0
  383. package/dist/dist/server-safe/PermissionFunctions.d.ts.map +1 -0
  384. package/dist/dist/server-safe/PermissionFunctions.js +39 -0
  385. package/dist/dist/server-safe/PermissionFunctions.js.map +1 -0
  386. package/dist/dist/server-safe/TagFunctions.d.ts +18 -0
  387. package/dist/dist/server-safe/TagFunctions.d.ts.map +1 -0
  388. package/dist/dist/server-safe/TagFunctions.js +52 -0
  389. package/dist/dist/server-safe/TagFunctions.js.map +1 -0
  390. package/dist/dist/server-safe/autoJobType.d.ts +16 -0
  391. package/dist/dist/server-safe/autoJobType.d.ts.map +1 -0
  392. package/dist/dist/server-safe/autoJobType.js +39 -0
  393. package/dist/dist/server-safe/autoJobType.js.map +1 -0
  394. package/dist/dist/server-safe/autoJobType.test.d.ts +2 -0
  395. package/dist/dist/server-safe/autoJobType.test.d.ts.map +1 -0
  396. package/dist/dist/server-safe/autoJobType.test.js +83 -0
  397. package/dist/dist/server-safe/autoJobType.test.js.map +1 -0
  398. package/dist/dist/server-safe/automations.d.ts +197 -0
  399. package/dist/dist/server-safe/automations.d.ts.map +1 -0
  400. package/dist/dist/server-safe/automations.js +757 -0
  401. package/dist/dist/server-safe/automations.js.map +1 -0
  402. package/dist/dist/server-safe/automations.test.d.ts +2 -0
  403. package/dist/dist/server-safe/automations.test.d.ts.map +1 -0
  404. package/dist/dist/server-safe/automations.test.js +441 -0
  405. package/dist/dist/server-safe/automations.test.js.map +1 -0
  406. package/dist/dist/server-safe/constants.d.ts +156 -0
  407. package/dist/dist/server-safe/constants.d.ts.map +1 -0
  408. package/dist/dist/server-safe/constants.js +186 -0
  409. package/dist/dist/server-safe/constants.js.map +1 -0
  410. package/dist/dist/server-safe/contracts.d.ts +3 -0
  411. package/dist/dist/server-safe/contracts.d.ts.map +1 -0
  412. package/dist/dist/server-safe/contracts.js +15 -0
  413. package/dist/dist/server-safe/contracts.js.map +1 -0
  414. package/dist/dist/server-safe/database.types.d.ts +9510 -0
  415. package/dist/dist/server-safe/database.types.d.ts.map +1 -0
  416. package/dist/dist/server-safe/database.types.js +40 -0
  417. package/dist/dist/server-safe/database.types.js.map +1 -0
  418. package/dist/dist/server-safe/entitlements.d.ts +99 -0
  419. package/dist/dist/server-safe/entitlements.d.ts.map +1 -0
  420. package/dist/dist/server-safe/entitlements.js +487 -0
  421. package/dist/dist/server-safe/entitlements.js.map +1 -0
  422. package/dist/dist/server-safe/facebookAdsLeads.d.ts +225 -0
  423. package/dist/dist/server-safe/facebookAdsLeads.d.ts.map +1 -0
  424. package/dist/dist/server-safe/facebookAdsLeads.js +376 -0
  425. package/dist/dist/server-safe/facebookAdsLeads.js.map +1 -0
  426. package/dist/dist/server-safe/facebookAdsLeads.test.d.ts +2 -0
  427. package/dist/dist/server-safe/facebookAdsLeads.test.d.ts.map +1 -0
  428. package/dist/dist/server-safe/facebookAdsLeads.test.js +189 -0
  429. package/dist/dist/server-safe/facebookAdsLeads.test.js.map +1 -0
  430. package/dist/dist/server-safe/facebookAdsLeadsSql.test.d.ts +2 -0
  431. package/dist/dist/server-safe/facebookAdsLeadsSql.test.d.ts.map +1 -0
  432. package/dist/dist/server-safe/facebookAdsLeadsSql.test.js +32 -0
  433. package/dist/dist/server-safe/facebookAdsLeadsSql.test.js.map +1 -0
  434. package/dist/dist/server-safe/facebookMessenger.d.ts +20 -0
  435. package/dist/dist/server-safe/facebookMessenger.d.ts.map +1 -0
  436. package/dist/dist/server-safe/facebookMessenger.js +63 -0
  437. package/dist/dist/server-safe/facebookMessenger.js.map +1 -0
  438. package/dist/dist/server-safe/facebookMessenger.test.d.ts +2 -0
  439. package/dist/dist/server-safe/facebookMessenger.test.d.ts.map +1 -0
  440. package/dist/dist/server-safe/facebookMessenger.test.js +51 -0
  441. package/dist/dist/server-safe/facebookMessenger.test.js.map +1 -0
  442. package/dist/dist/server-safe/helpers.d.ts +78 -0
  443. package/dist/dist/server-safe/helpers.d.ts.map +1 -0
  444. package/dist/dist/server-safe/helpers.js +569 -0
  445. package/dist/dist/server-safe/helpers.js.map +1 -0
  446. package/dist/dist/server-safe/index.d.ts +30 -0
  447. package/dist/dist/server-safe/index.d.ts.map +1 -0
  448. package/dist/dist/server-safe/index.js +46 -0
  449. package/dist/dist/server-safe/index.js.map +1 -0
  450. package/dist/dist/server-safe/permissions.d.ts +29 -0
  451. package/dist/dist/server-safe/permissions.d.ts.map +1 -0
  452. package/dist/dist/server-safe/permissions.js +180 -0
  453. package/dist/dist/server-safe/permissions.js.map +1 -0
  454. package/dist/dist/server-safe/proposals.d.ts +6 -0
  455. package/dist/dist/server-safe/proposals.d.ts.map +1 -0
  456. package/dist/dist/server-safe/proposals.js +58 -0
  457. package/dist/dist/server-safe/proposals.js.map +1 -0
  458. package/dist/dist/server-safe/rentalAvailability.d.ts +56 -0
  459. package/dist/dist/server-safe/rentalAvailability.d.ts.map +1 -0
  460. package/dist/dist/server-safe/rentalAvailability.js +124 -0
  461. package/dist/dist/server-safe/rentalAvailability.js.map +1 -0
  462. package/dist/dist/server-safe/rentalAvailability.test.d.ts +2 -0
  463. package/dist/dist/server-safe/rentalAvailability.test.d.ts.map +1 -0
  464. package/dist/dist/server-safe/rentalAvailability.test.js +290 -0
  465. package/dist/dist/server-safe/rentalAvailability.test.js.map +1 -0
  466. package/dist/dist/server-safe/rentals.d.ts +95 -0
  467. package/dist/dist/server-safe/rentals.d.ts.map +1 -0
  468. package/dist/dist/server-safe/rentals.js +432 -0
  469. package/dist/dist/server-safe/rentals.js.map +1 -0
  470. package/dist/dist/server-safe/rentals.test.d.ts +2 -0
  471. package/dist/dist/server-safe/rentals.test.d.ts.map +1 -0
  472. package/dist/dist/server-safe/rentals.test.js +1034 -0
  473. package/dist/dist/server-safe/rentals.test.js.map +1 -0
  474. package/dist/dist/server-safe/routeOptimization.d.ts +155 -0
  475. package/dist/dist/server-safe/routeOptimization.d.ts.map +1 -0
  476. package/dist/dist/server-safe/routeOptimization.js +120 -0
  477. package/dist/dist/server-safe/routeOptimization.js.map +1 -0
  478. package/dist/dist/server-safe/routeOptimization.test.d.ts +2 -0
  479. package/dist/dist/server-safe/routeOptimization.test.d.ts.map +1 -0
  480. package/dist/dist/server-safe/routeOptimization.test.js +234 -0
  481. package/dist/dist/server-safe/routeOptimization.test.js.map +1 -0
  482. package/dist/dist/server-safe/routePlanFingerprint.d.ts +13 -0
  483. package/dist/dist/server-safe/routePlanFingerprint.d.ts.map +1 -0
  484. package/dist/dist/server-safe/routePlanFingerprint.js +49 -0
  485. package/dist/dist/server-safe/routePlanFingerprint.js.map +1 -0
  486. package/dist/dist/server-safe/routePlanFingerprint.test.d.ts +2 -0
  487. package/dist/dist/server-safe/routePlanFingerprint.test.d.ts.map +1 -0
  488. package/dist/dist/server-safe/routePlanFingerprint.test.js +68 -0
  489. package/dist/dist/server-safe/routePlanFingerprint.test.js.map +1 -0
  490. package/dist/dist/server-safe/scheduleListOrder.d.ts +18 -0
  491. package/dist/dist/server-safe/scheduleListOrder.d.ts.map +1 -0
  492. package/dist/dist/server-safe/scheduleListOrder.js +92 -0
  493. package/dist/dist/server-safe/scheduleListOrder.js.map +1 -0
  494. package/dist/dist/server-safe/scheduleListOrder.test.d.ts +2 -0
  495. package/dist/dist/server-safe/scheduleListOrder.test.d.ts.map +1 -0
  496. package/dist/dist/server-safe/scheduleListOrder.test.js +260 -0
  497. package/dist/dist/server-safe/scheduleListOrder.test.js.map +1 -0
  498. package/dist/dist/server-safe/serviceMinutes.d.ts +13 -0
  499. package/dist/dist/server-safe/serviceMinutes.d.ts.map +1 -0
  500. package/dist/dist/server-safe/serviceMinutes.js +38 -0
  501. package/dist/dist/server-safe/serviceMinutes.js.map +1 -0
  502. package/dist/dist/server-safe/serviceMinutes.test.d.ts +2 -0
  503. package/dist/dist/server-safe/serviceMinutes.test.d.ts.map +1 -0
  504. package/dist/dist/server-safe/serviceMinutes.test.js +86 -0
  505. package/dist/dist/server-safe/serviceMinutes.test.js.map +1 -0
  506. package/dist/dist/server-safe/settingsSearch.d.ts +24 -0
  507. package/dist/dist/server-safe/settingsSearch.d.ts.map +1 -0
  508. package/dist/dist/server-safe/settingsSearch.js +8 -0
  509. package/dist/dist/server-safe/settingsSearch.js.map +1 -0
  510. package/dist/dist/server-safe/supportChat.d.ts +59 -0
  511. package/dist/dist/server-safe/supportChat.d.ts.map +1 -0
  512. package/dist/dist/server-safe/supportChat.js +56 -0
  513. package/dist/dist/server-safe/supportChat.js.map +1 -0
  514. package/dist/dist/server-safe/templates.d.ts +15 -0
  515. package/dist/dist/server-safe/templates.d.ts.map +1 -0
  516. package/dist/dist/server-safe/templates.js +91 -0
  517. package/dist/dist/server-safe/templates.js.map +1 -0
  518. package/dist/dist/server-safe/templates.test.d.ts +2 -0
  519. package/dist/dist/server-safe/templates.test.d.ts.map +1 -0
  520. package/dist/dist/server-safe/templates.test.js +62 -0
  521. package/dist/dist/server-safe/templates.test.js.map +1 -0
  522. package/dist/dist/server-safe/trash.d.ts +50 -0
  523. package/dist/dist/server-safe/trash.d.ts.map +1 -0
  524. package/dist/dist/server-safe/trash.js +100 -0
  525. package/dist/dist/server-safe/trash.js.map +1 -0
  526. package/dist/dist/utils/LogHandler.d.ts +3 -0
  527. package/dist/dist/utils/LogHandler.d.ts.map +1 -0
  528. package/dist/dist/utils/LogHandler.js +16 -0
  529. package/dist/dist/utils/LogHandler.js.map +1 -0
  530. package/dist/dist/utils/convertSupabaseImagesToAssets.d.ts +28 -0
  531. package/dist/dist/utils/convertSupabaseImagesToAssets.d.ts.map +1 -0
  532. package/dist/dist/utils/convertSupabaseImagesToAssets.js +14 -0
  533. package/dist/dist/utils/convertSupabaseImagesToAssets.js.map +1 -0
  534. package/dist/index.d.ts +45 -0
  535. package/dist/index.d.ts.map +1 -0
  536. package/dist/index.js +61 -0
  537. package/dist/index.js.map +1 -0
  538. package/dist/interfaces/index.d.ts +1238 -0
  539. package/dist/interfaces/index.d.ts.map +1 -0
  540. package/dist/interfaces/index.js +3 -0
  541. package/dist/interfaces/index.js.map +1 -0
  542. package/dist/providers/AddressProvider.d.ts +9 -0
  543. package/dist/providers/AddressProvider.d.ts.map +1 -0
  544. package/dist/providers/AddressProvider.js +207 -0
  545. package/dist/providers/AddressProvider.js.map +1 -0
  546. package/dist/providers/AutomationsProvider.d.ts +11 -0
  547. package/dist/providers/AutomationsProvider.d.ts.map +1 -0
  548. package/dist/providers/AutomationsProvider.js +170 -0
  549. package/dist/providers/AutomationsProvider.js.map +1 -0
  550. package/dist/providers/BlockedNumbersProvider.d.ts +7 -0
  551. package/dist/providers/BlockedNumbersProvider.d.ts.map +1 -0
  552. package/dist/providers/BlockedNumbersProvider.js +71 -0
  553. package/dist/providers/BlockedNumbersProvider.js.map +1 -0
  554. package/dist/providers/CategoryProvider.d.ts +8 -0
  555. package/dist/providers/CategoryProvider.d.ts.map +1 -0
  556. package/dist/providers/CategoryProvider.js +95 -0
  557. package/dist/providers/CategoryProvider.js.map +1 -0
  558. package/dist/providers/ClientProvider.d.ts +39 -0
  559. package/dist/providers/ClientProvider.d.ts.map +1 -0
  560. package/dist/providers/ClientProvider.js +253 -0
  561. package/dist/providers/ClientProvider.js.map +1 -0
  562. package/dist/providers/CompanyProvider.d.ts +7 -0
  563. package/dist/providers/CompanyProvider.d.ts.map +1 -0
  564. package/dist/providers/CompanyProvider.js +114 -0
  565. package/dist/providers/CompanyProvider.js.map +1 -0
  566. package/dist/providers/DashboardProvider.d.ts +78 -0
  567. package/dist/providers/DashboardProvider.d.ts.map +1 -0
  568. package/dist/providers/DashboardProvider.js +568 -0
  569. package/dist/providers/DashboardProvider.js.map +1 -0
  570. package/dist/providers/DiscountProvider.d.ts +8 -0
  571. package/dist/providers/DiscountProvider.d.ts.map +1 -0
  572. package/dist/providers/DiscountProvider.js +92 -0
  573. package/dist/providers/DiscountProvider.js.map +1 -0
  574. package/dist/providers/EmployeesProvider.d.ts +6 -0
  575. package/dist/providers/EmployeesProvider.d.ts.map +1 -0
  576. package/dist/providers/EmployeesProvider.js +185 -0
  577. package/dist/providers/EmployeesProvider.js.map +1 -0
  578. package/dist/providers/EstimatesProvider.d.ts +16 -0
  579. package/dist/providers/EstimatesProvider.d.ts.map +1 -0
  580. package/dist/providers/EstimatesProvider.js +251 -0
  581. package/dist/providers/EstimatesProvider.js.map +1 -0
  582. package/dist/providers/EventsProvider.d.ts +4 -0
  583. package/dist/providers/EventsProvider.d.ts.map +1 -0
  584. package/dist/providers/EventsProvider.js +24 -0
  585. package/dist/providers/EventsProvider.js.map +1 -0
  586. package/dist/providers/FilesProvider.d.ts +17 -0
  587. package/dist/providers/FilesProvider.d.ts.map +1 -0
  588. package/dist/providers/FilesProvider.js +201 -0
  589. package/dist/providers/FilesProvider.js.map +1 -0
  590. package/dist/providers/InvoicesProvider.d.ts +13 -0
  591. package/dist/providers/InvoicesProvider.d.ts.map +1 -0
  592. package/dist/providers/InvoicesProvider.js +207 -0
  593. package/dist/providers/InvoicesProvider.js.map +1 -0
  594. package/dist/providers/ItemProvider.d.ts +9 -0
  595. package/dist/providers/ItemProvider.d.ts.map +1 -0
  596. package/dist/providers/ItemProvider.js +201 -0
  597. package/dist/providers/ItemProvider.js.map +1 -0
  598. package/dist/providers/JobSourceProvider.d.ts +8 -0
  599. package/dist/providers/JobSourceProvider.d.ts.map +1 -0
  600. package/dist/providers/JobSourceProvider.js +75 -0
  601. package/dist/providers/JobSourceProvider.js.map +1 -0
  602. package/dist/providers/JobTypeProvider.d.ts +8 -0
  603. package/dist/providers/JobTypeProvider.d.ts.map +1 -0
  604. package/dist/providers/JobTypeProvider.js +75 -0
  605. package/dist/providers/JobTypeProvider.js.map +1 -0
  606. package/dist/providers/JobsProvider.d.ts +20 -0
  607. package/dist/providers/JobsProvider.d.ts.map +1 -0
  608. package/dist/providers/JobsProvider.js +863 -0
  609. package/dist/providers/JobsProvider.js.map +1 -0
  610. package/dist/providers/MessagesProvider.d.ts +14 -0
  611. package/dist/providers/MessagesProvider.d.ts.map +1 -0
  612. package/dist/providers/MessagesProvider.js +415 -0
  613. package/dist/providers/MessagesProvider.js.map +1 -0
  614. package/dist/providers/PaymentProvider.d.ts +9 -0
  615. package/dist/providers/PaymentProvider.d.ts.map +1 -0
  616. package/dist/providers/PaymentProvider.js +48 -0
  617. package/dist/providers/PaymentProvider.js.map +1 -0
  618. package/dist/providers/PermissionRolesProvider.d.ts +5 -0
  619. package/dist/providers/PermissionRolesProvider.d.ts.map +1 -0
  620. package/dist/providers/PermissionRolesProvider.js +30 -0
  621. package/dist/providers/PermissionRolesProvider.js.map +1 -0
  622. package/dist/providers/PhoneNumberProvider.d.ts +12 -0
  623. package/dist/providers/PhoneNumberProvider.d.ts.map +1 -0
  624. package/dist/providers/PhoneNumberProvider.js +184 -0
  625. package/dist/providers/PhoneNumberProvider.js.map +1 -0
  626. package/dist/providers/ProfileProvider.d.ts +6 -0
  627. package/dist/providers/ProfileProvider.d.ts.map +1 -0
  628. package/dist/providers/ProfileProvider.js +71 -0
  629. package/dist/providers/ProfileProvider.js.map +1 -0
  630. package/dist/providers/RentalsProvider.d.ts +28 -0
  631. package/dist/providers/RentalsProvider.d.ts.map +1 -0
  632. package/dist/providers/RentalsProvider.js +438 -0
  633. package/dist/providers/RentalsProvider.js.map +1 -0
  634. package/dist/providers/SettingsBookingProvider.d.ts +9 -0
  635. package/dist/providers/SettingsBookingProvider.d.ts.map +1 -0
  636. package/dist/providers/SettingsBookingProvider.js +163 -0
  637. package/dist/providers/SettingsBookingProvider.js.map +1 -0
  638. package/dist/providers/SettingsProvider.d.ts +8 -0
  639. package/dist/providers/SettingsProvider.d.ts.map +1 -0
  640. package/dist/providers/SettingsProvider.js +86 -0
  641. package/dist/providers/SettingsProvider.js.map +1 -0
  642. package/dist/providers/TagsProvider.d.ts +12 -0
  643. package/dist/providers/TagsProvider.d.ts.map +1 -0
  644. package/dist/providers/TagsProvider.js +210 -0
  645. package/dist/providers/TagsProvider.js.map +1 -0
  646. package/dist/providers/TimeCardProvider.d.ts +8 -0
  647. package/dist/providers/TimeCardProvider.d.ts.map +1 -0
  648. package/dist/providers/TimeCardProvider.js +93 -0
  649. package/dist/providers/TimeCardProvider.js.map +1 -0
  650. package/dist/providers/TrashProvider.d.ts +13 -0
  651. package/dist/providers/TrashProvider.d.ts.map +1 -0
  652. package/dist/providers/TrashProvider.js +98 -0
  653. package/dist/providers/TrashProvider.js.map +1 -0
  654. package/dist/providers/dashboardAnalytics.d.ts +9 -0
  655. package/dist/providers/dashboardAnalytics.d.ts.map +1 -0
  656. package/dist/providers/dashboardAnalytics.js +41 -0
  657. package/dist/providers/dashboardAnalytics.js.map +1 -0
  658. package/dist/providers/dashboardAnalytics.test.d.ts +2 -0
  659. package/dist/providers/dashboardAnalytics.test.d.ts.map +1 -0
  660. package/dist/providers/dashboardAnalytics.test.js +111 -0
  661. package/dist/providers/dashboardAnalytics.test.js.map +1 -0
  662. package/dist/server-safe/BlockedNumberFunctions.d.ts +11 -0
  663. package/dist/server-safe/BlockedNumberFunctions.d.ts.map +1 -0
  664. package/dist/server-safe/BlockedNumberFunctions.js +16 -0
  665. package/dist/server-safe/BlockedNumberFunctions.js.map +1 -0
  666. package/dist/server-safe/EmployeeFunctions.d.ts +14 -0
  667. package/dist/server-safe/EmployeeFunctions.d.ts.map +1 -0
  668. package/dist/server-safe/EmployeeFunctions.js +70 -0
  669. package/dist/server-safe/EmployeeFunctions.js.map +1 -0
  670. package/dist/server-safe/EmployeeLocationFunctions.d.ts +7 -0
  671. package/dist/server-safe/EmployeeLocationFunctions.d.ts.map +1 -0
  672. package/dist/server-safe/EmployeeLocationFunctions.js +47 -0
  673. package/dist/server-safe/EmployeeLocationFunctions.js.map +1 -0
  674. package/dist/server-safe/EstimateFunctions.d.ts +15 -0
  675. package/dist/server-safe/EstimateFunctions.d.ts.map +1 -0
  676. package/dist/server-safe/EstimateFunctions.js +34 -0
  677. package/dist/server-safe/EstimateFunctions.js.map +1 -0
  678. package/dist/server-safe/InvoiceFunctions.d.ts +21 -0
  679. package/dist/server-safe/InvoiceFunctions.d.ts.map +1 -0
  680. package/dist/server-safe/InvoiceFunctions.js +124 -0
  681. package/dist/server-safe/InvoiceFunctions.js.map +1 -0
  682. package/dist/server-safe/JobFunctions.d.ts +15 -0
  683. package/dist/server-safe/JobFunctions.d.ts.map +1 -0
  684. package/dist/server-safe/JobFunctions.js +56 -0
  685. package/dist/server-safe/JobFunctions.js.map +1 -0
  686. package/dist/server-safe/PaymentFunctions.d.ts +16 -0
  687. package/dist/server-safe/PaymentFunctions.d.ts.map +1 -0
  688. package/dist/server-safe/PaymentFunctions.js +80 -0
  689. package/dist/server-safe/PaymentFunctions.js.map +1 -0
  690. package/dist/server-safe/PermissionFunctions.d.ts +7 -0
  691. package/dist/server-safe/PermissionFunctions.d.ts.map +1 -0
  692. package/dist/server-safe/PermissionFunctions.js +39 -0
  693. package/dist/server-safe/PermissionFunctions.js.map +1 -0
  694. package/dist/server-safe/TagFunctions.d.ts +18 -0
  695. package/dist/server-safe/TagFunctions.d.ts.map +1 -0
  696. package/dist/server-safe/TagFunctions.js +52 -0
  697. package/dist/server-safe/TagFunctions.js.map +1 -0
  698. package/dist/server-safe/autoJobType.d.ts +16 -0
  699. package/dist/server-safe/autoJobType.d.ts.map +1 -0
  700. package/dist/server-safe/autoJobType.js +39 -0
  701. package/dist/server-safe/autoJobType.js.map +1 -0
  702. package/dist/server-safe/autoJobType.test.d.ts +2 -0
  703. package/dist/server-safe/autoJobType.test.d.ts.map +1 -0
  704. package/dist/server-safe/autoJobType.test.js +83 -0
  705. package/dist/server-safe/autoJobType.test.js.map +1 -0
  706. package/dist/server-safe/automations.d.ts +197 -0
  707. package/dist/server-safe/automations.d.ts.map +1 -0
  708. package/dist/server-safe/automations.js +757 -0
  709. package/dist/server-safe/automations.js.map +1 -0
  710. package/dist/server-safe/automations.test.d.ts +2 -0
  711. package/dist/server-safe/automations.test.d.ts.map +1 -0
  712. package/dist/server-safe/automations.test.js +441 -0
  713. package/dist/server-safe/automations.test.js.map +1 -0
  714. package/dist/server-safe/constants.d.ts +156 -0
  715. package/dist/server-safe/constants.d.ts.map +1 -0
  716. package/dist/server-safe/constants.js +186 -0
  717. package/dist/server-safe/constants.js.map +1 -0
  718. package/dist/server-safe/contracts.d.ts +3 -0
  719. package/dist/server-safe/contracts.d.ts.map +1 -0
  720. package/dist/server-safe/contracts.js +15 -0
  721. package/dist/server-safe/contracts.js.map +1 -0
  722. package/dist/server-safe/database.types.d.ts +9510 -0
  723. package/dist/server-safe/database.types.d.ts.map +1 -0
  724. package/dist/server-safe/database.types.js +40 -0
  725. package/dist/server-safe/database.types.js.map +1 -0
  726. package/dist/server-safe/entitlements.d.ts +99 -0
  727. package/dist/server-safe/entitlements.d.ts.map +1 -0
  728. package/dist/server-safe/entitlements.js +487 -0
  729. package/dist/server-safe/entitlements.js.map +1 -0
  730. package/dist/server-safe/helpers.d.ts +78 -0
  731. package/dist/server-safe/helpers.d.ts.map +1 -0
  732. package/dist/server-safe/helpers.js +569 -0
  733. package/dist/server-safe/helpers.js.map +1 -0
  734. package/dist/server-safe/index.d.ts +30 -0
  735. package/dist/server-safe/index.d.ts.map +1 -0
  736. package/dist/server-safe/index.js +46 -0
  737. package/dist/server-safe/index.js.map +1 -0
  738. package/dist/server-safe/permissions.d.ts +29 -0
  739. package/dist/server-safe/permissions.d.ts.map +1 -0
  740. package/dist/server-safe/permissions.js +180 -0
  741. package/dist/server-safe/permissions.js.map +1 -0
  742. package/dist/server-safe/proposals.d.ts +6 -0
  743. package/dist/server-safe/proposals.d.ts.map +1 -0
  744. package/dist/server-safe/proposals.js +58 -0
  745. package/dist/server-safe/proposals.js.map +1 -0
  746. package/dist/server-safe/rentalAvailability.d.ts +56 -0
  747. package/dist/server-safe/rentalAvailability.d.ts.map +1 -0
  748. package/dist/server-safe/rentalAvailability.js +124 -0
  749. package/dist/server-safe/rentalAvailability.js.map +1 -0
  750. package/dist/server-safe/rentalAvailability.test.d.ts +2 -0
  751. package/dist/server-safe/rentalAvailability.test.d.ts.map +1 -0
  752. package/dist/server-safe/rentalAvailability.test.js +290 -0
  753. package/dist/server-safe/rentalAvailability.test.js.map +1 -0
  754. package/dist/server-safe/rentals.d.ts +95 -0
  755. package/dist/server-safe/rentals.d.ts.map +1 -0
  756. package/dist/server-safe/rentals.js +432 -0
  757. package/dist/server-safe/rentals.js.map +1 -0
  758. package/dist/server-safe/rentals.test.d.ts +2 -0
  759. package/dist/server-safe/rentals.test.d.ts.map +1 -0
  760. package/dist/server-safe/rentals.test.js +1034 -0
  761. package/dist/server-safe/rentals.test.js.map +1 -0
  762. package/dist/server-safe/routeOptimization.d.ts +155 -0
  763. package/dist/server-safe/routeOptimization.d.ts.map +1 -0
  764. package/dist/server-safe/routeOptimization.js +120 -0
  765. package/dist/server-safe/routeOptimization.js.map +1 -0
  766. package/dist/server-safe/routeOptimization.test.d.ts +2 -0
  767. package/dist/server-safe/routeOptimization.test.d.ts.map +1 -0
  768. package/dist/server-safe/routeOptimization.test.js +234 -0
  769. package/dist/server-safe/routeOptimization.test.js.map +1 -0
  770. package/dist/server-safe/routePlanFingerprint.d.ts +13 -0
  771. package/dist/server-safe/routePlanFingerprint.d.ts.map +1 -0
  772. package/dist/server-safe/routePlanFingerprint.js +49 -0
  773. package/dist/server-safe/routePlanFingerprint.js.map +1 -0
  774. package/dist/server-safe/routePlanFingerprint.test.d.ts +2 -0
  775. package/dist/server-safe/routePlanFingerprint.test.d.ts.map +1 -0
  776. package/dist/server-safe/routePlanFingerprint.test.js +68 -0
  777. package/dist/server-safe/routePlanFingerprint.test.js.map +1 -0
  778. package/dist/server-safe/scheduleListOrder.d.ts +18 -0
  779. package/dist/server-safe/scheduleListOrder.d.ts.map +1 -0
  780. package/dist/server-safe/scheduleListOrder.js +92 -0
  781. package/dist/server-safe/scheduleListOrder.js.map +1 -0
  782. package/dist/server-safe/scheduleListOrder.test.d.ts +2 -0
  783. package/dist/server-safe/scheduleListOrder.test.d.ts.map +1 -0
  784. package/dist/server-safe/scheduleListOrder.test.js +260 -0
  785. package/dist/server-safe/scheduleListOrder.test.js.map +1 -0
  786. package/dist/server-safe/serviceMinutes.d.ts +13 -0
  787. package/dist/server-safe/serviceMinutes.d.ts.map +1 -0
  788. package/dist/server-safe/serviceMinutes.js +38 -0
  789. package/dist/server-safe/serviceMinutes.js.map +1 -0
  790. package/dist/server-safe/serviceMinutes.test.d.ts +2 -0
  791. package/dist/server-safe/serviceMinutes.test.d.ts.map +1 -0
  792. package/dist/server-safe/serviceMinutes.test.js +86 -0
  793. package/dist/server-safe/serviceMinutes.test.js.map +1 -0
  794. package/dist/server-safe/settingsSearch.d.ts +24 -0
  795. package/dist/server-safe/settingsSearch.d.ts.map +1 -0
  796. package/dist/server-safe/settingsSearch.js +8 -0
  797. package/dist/server-safe/settingsSearch.js.map +1 -0
  798. package/dist/server-safe/supportChat.d.ts +59 -0
  799. package/dist/server-safe/supportChat.d.ts.map +1 -0
  800. package/dist/server-safe/supportChat.js +56 -0
  801. package/dist/server-safe/supportChat.js.map +1 -0
  802. package/dist/server-safe/templates.d.ts +15 -0
  803. package/dist/server-safe/templates.d.ts.map +1 -0
  804. package/dist/server-safe/templates.js +91 -0
  805. package/dist/server-safe/templates.js.map +1 -0
  806. package/dist/server-safe/templates.test.d.ts +2 -0
  807. package/dist/server-safe/templates.test.d.ts.map +1 -0
  808. package/dist/server-safe/templates.test.js +62 -0
  809. package/dist/server-safe/templates.test.js.map +1 -0
  810. package/dist/server-safe/trash.d.ts +50 -0
  811. package/dist/server-safe/trash.d.ts.map +1 -0
  812. package/dist/server-safe/trash.js +100 -0
  813. package/dist/server-safe/trash.js.map +1 -0
  814. package/dist/utils/LogHandler.d.ts +3 -0
  815. package/dist/utils/LogHandler.d.ts.map +1 -0
  816. package/dist/utils/LogHandler.js +16 -0
  817. package/dist/utils/LogHandler.js.map +1 -0
  818. package/dist/utils/convertSupabaseImagesToAssets.d.ts +28 -0
  819. package/dist/utils/convertSupabaseImagesToAssets.d.ts.map +1 -0
  820. package/dist/utils/convertSupabaseImagesToAssets.js +14 -0
  821. package/dist/utils/convertSupabaseImagesToAssets.js.map +1 -0
  822. package/package.json +37 -0
@@ -0,0 +1,3698 @@
1
+ -- Autopilot Fleet V1 shared schema.
2
+ --
3
+ -- Fleet organizations are one-to-one extensions of existing Autopilot
4
+ -- companies. public."user" remains the canonical employee/auth roster; a
5
+ -- fleet_member only adds Fleet access and a Fleet-specific role.
6
+ --
7
+ -- Every Fleet row carries company_id. Related Fleet records use composite
8
+ -- foreign keys so a valid identifier from another company can never be linked
9
+ -- accidentally, even by service-role code. The companion fleet_rls migration
10
+ -- enables client access and role-based policies separately.
11
+ -- Historical actor/author references intentionally use restrictive foreign
12
+ -- keys: offboard employees through public.user employment flags instead of
13
+ -- deleting identity rows that Fleet audit or safety records still reference.
14
+
15
+ -- Backing key for tenant-consistent employee foreign keys. public."user".id
16
+ -- is already globally unique, so this adds no new data assumption.
17
+ create unique index if not exists "user_id_company_id_key"
18
+ on "public"."user" ("id", "company_id");
19
+
20
+ -- ============================================================
21
+ -- Organization and membership
22
+ -- ============================================================
23
+
24
+ create table "public"."fleet_organization" (
25
+ "company_id" uuid not null,
26
+ "created_at" timestamptz default now() not null,
27
+ "updated_at" timestamptz default now() not null,
28
+ "created_by" uuid default auth.uid(),
29
+ "display_name" text,
30
+ "settings" jsonb default '{}'::jsonb not null,
31
+ constraint "fleet_organization_pkey" primary key ("company_id"),
32
+ constraint "fleet_organization_company_id_fkey"
33
+ foreign key ("company_id") references "public"."company" ("id")
34
+ on delete cascade,
35
+ constraint "fleet_organization_created_by_fkey"
36
+ foreign key ("created_by", "company_id")
37
+ references "public"."user" ("id", "company_id")
38
+ on delete set null ("created_by"),
39
+ constraint "fleet_organization_display_name_check"
40
+ check ("display_name" is null or nullif(btrim("display_name"), '') is not null),
41
+ constraint "fleet_organization_settings_check"
42
+ check (jsonb_typeof("settings") = 'object')
43
+ );
44
+
45
+ alter table "public"."fleet_organization" owner to "postgres";
46
+
47
+ create index "fleet_organization_created_by_idx"
48
+ on "public"."fleet_organization" ("created_by")
49
+ where "created_by" is not null;
50
+
51
+ comment on table "public"."fleet_organization" is
52
+ 'One-to-one Fleet configuration extension of an existing Autopilot company.';
53
+ comment on column "public"."fleet_organization"."settings" is
54
+ 'Tenant-level Fleet settings only; employee identity remains in public.user.';
55
+
56
+ create table "public"."fleet_member" (
57
+ "id" uuid default gen_random_uuid() not null,
58
+ "company_id" uuid not null,
59
+ "user_id" uuid not null,
60
+ "role" text not null,
61
+ "is_active" boolean default true not null,
62
+ "joined_at" timestamptz default now() not null,
63
+ "deactivated_at" timestamptz,
64
+ "created_at" timestamptz default now() not null,
65
+ "updated_at" timestamptz default now() not null,
66
+ "created_by" uuid default auth.uid(),
67
+ constraint "fleet_member_pkey" primary key ("id"),
68
+ constraint "fleet_member_id_company_id_key" unique ("id", "company_id"),
69
+ constraint "fleet_member_company_user_key" unique ("company_id", "user_id"),
70
+ constraint "fleet_member_company_id_fkey"
71
+ foreign key ("company_id")
72
+ references "public"."fleet_organization" ("company_id")
73
+ on delete cascade,
74
+ constraint "fleet_member_user_id_fkey"
75
+ foreign key ("user_id", "company_id")
76
+ references "public"."user" ("id", "company_id")
77
+ on delete cascade,
78
+ constraint "fleet_member_created_by_fkey"
79
+ foreign key ("created_by", "company_id")
80
+ references "public"."user" ("id", "company_id")
81
+ on delete set null ("created_by"),
82
+ constraint "fleet_member_role_check"
83
+ check ("role" in ('owner', 'manager', 'operator', 'technician', 'viewer')),
84
+ constraint "fleet_member_active_shape_check"
85
+ check (
86
+ ("is_active" and "deactivated_at" is null)
87
+ or (not "is_active" and "deactivated_at" is not null)
88
+ )
89
+ );
90
+
91
+ alter table "public"."fleet_member" owner to "postgres";
92
+
93
+ create index "fleet_member_user_id_idx" on "public"."fleet_member" ("user_id");
94
+ create index "fleet_member_created_by_idx" on "public"."fleet_member" ("created_by")
95
+ where "created_by" is not null;
96
+ create index "fleet_member_company_active_role_idx"
97
+ on "public"."fleet_member" ("company_id", "role")
98
+ where "is_active";
99
+
100
+ comment on table "public"."fleet_member" is
101
+ 'Fleet-specific access overlay referencing the canonical public.user employee row.';
102
+ comment on column "public"."fleet_member"."role" is
103
+ 'Lowercase storage value for Owner, Manager, Operator, Technician, or Viewer.';
104
+
105
+ -- ============================================================
106
+ -- Vehicles
107
+ -- ============================================================
108
+
109
+ create table "public"."fleet_vehicle" (
110
+ "id" uuid default gen_random_uuid() not null,
111
+ "company_id" uuid not null,
112
+ "unit_number" text not null,
113
+ "name" text not null,
114
+ "vehicle_type" text not null,
115
+ "status" text default 'ready' not null,
116
+ "vin" text,
117
+ "license_plate" text,
118
+ "license_region" text,
119
+ "make" text,
120
+ "model" text,
121
+ "model_year" smallint,
122
+ "current_odometer" numeric(12, 1),
123
+ "current_engine_hours" numeric(12, 1),
124
+ "fuel_capacity" numeric(10, 2),
125
+ "out_of_service_at" timestamptz,
126
+ "out_of_service_reason" text,
127
+ "archived_at" timestamptz,
128
+ "metadata" jsonb default '{}'::jsonb not null,
129
+ "created_at" timestamptz default now() not null,
130
+ "updated_at" timestamptz default now() not null,
131
+ "created_by" uuid default auth.uid(),
132
+ constraint "fleet_vehicle_pkey" primary key ("id"),
133
+ constraint "fleet_vehicle_id_company_id_key" unique ("id", "company_id"),
134
+ constraint "fleet_vehicle_company_unit_key" unique ("company_id", "unit_number"),
135
+ constraint "fleet_vehicle_company_id_fkey"
136
+ foreign key ("company_id")
137
+ references "public"."fleet_organization" ("company_id")
138
+ on delete cascade,
139
+ constraint "fleet_vehicle_created_by_fkey"
140
+ foreign key ("created_by", "company_id")
141
+ references "public"."user" ("id", "company_id")
142
+ on delete set null ("created_by"),
143
+ constraint "fleet_vehicle_unit_number_check"
144
+ check (nullif(btrim("unit_number"), '') is not null),
145
+ constraint "fleet_vehicle_name_check"
146
+ check (nullif(btrim("name"), '') is not null),
147
+ constraint "fleet_vehicle_type_check"
148
+ check ("vehicle_type" in (
149
+ 'truck', 'dump_truck', 'trailer', 'dump_trailer',
150
+ 'van', 'car', 'equipment', 'other'
151
+ )),
152
+ constraint "fleet_vehicle_status_check"
153
+ check ("status" in ('ready', 'active', 'out_of_service', 'inactive', 'retired')),
154
+ constraint "fleet_vehicle_model_year_check"
155
+ check ("model_year" is null or "model_year" between 1900 and 2200),
156
+ constraint "fleet_vehicle_odometer_check"
157
+ check ("current_odometer" is null or "current_odometer" >= 0),
158
+ constraint "fleet_vehicle_engine_hours_check"
159
+ check ("current_engine_hours" is null or "current_engine_hours" >= 0),
160
+ constraint "fleet_vehicle_fuel_capacity_check"
161
+ check ("fuel_capacity" is null or "fuel_capacity" > 0),
162
+ constraint "fleet_vehicle_out_of_service_shape_check"
163
+ check (
164
+ ("status" = 'out_of_service' and "out_of_service_at" is not null
165
+ and nullif(btrim("out_of_service_reason"), '') is not null)
166
+ or ("status" <> 'out_of_service' and "out_of_service_at" is null
167
+ and "out_of_service_reason" is null)
168
+ ),
169
+ constraint "fleet_vehicle_metadata_check"
170
+ check (jsonb_typeof("metadata") = 'object')
171
+ );
172
+
173
+ alter table "public"."fleet_vehicle" owner to "postgres";
174
+
175
+ create unique index "fleet_vehicle_company_vin_key"
176
+ on "public"."fleet_vehicle" ("company_id", upper("vin"))
177
+ where "vin" is not null;
178
+ create index "fleet_vehicle_company_status_idx"
179
+ on "public"."fleet_vehicle" ("company_id", "status")
180
+ where "archived_at" is null;
181
+ create index "fleet_vehicle_created_by_idx" on "public"."fleet_vehicle" ("created_by")
182
+ where "created_by" is not null;
183
+
184
+ comment on table "public"."fleet_vehicle" is
185
+ 'Fleet-managed trucks, trailers, road vehicles, and operational equipment.';
186
+ comment on column "public"."fleet_vehicle"."status" is
187
+ 'Operational availability; unsafe inspection failures set out_of_service and never auto-clear it.';
188
+
189
+ -- ============================================================
190
+ -- Versioned inspection forms
191
+ -- ============================================================
192
+
193
+ create table "public"."fleet_form_template" (
194
+ "id" uuid default gen_random_uuid() not null,
195
+ "company_id" uuid not null,
196
+ "slug" text not null,
197
+ "name" text not null,
198
+ "description" text,
199
+ "form_kind" text not null,
200
+ "vehicle_type" text,
201
+ "is_active" boolean default true not null,
202
+ "created_at" timestamptz default now() not null,
203
+ "updated_at" timestamptz default now() not null,
204
+ "created_by" uuid default auth.uid(),
205
+ constraint "fleet_form_template_pkey" primary key ("id"),
206
+ constraint "fleet_form_template_id_company_id_key" unique ("id", "company_id"),
207
+ constraint "fleet_form_template_company_slug_key" unique ("company_id", "slug"),
208
+ constraint "fleet_form_template_company_id_fkey"
209
+ foreign key ("company_id")
210
+ references "public"."fleet_organization" ("company_id")
211
+ on delete cascade,
212
+ constraint "fleet_form_template_created_by_fkey"
213
+ foreign key ("created_by", "company_id")
214
+ references "public"."user" ("id", "company_id")
215
+ on delete set null ("created_by"),
216
+ constraint "fleet_form_template_slug_check"
217
+ check ("slug" ~ '^[a-z0-9]+(?:[a-z0-9_-]*[a-z0-9])?$'),
218
+ constraint "fleet_form_template_name_check"
219
+ check (nullif(btrim("name"), '') is not null),
220
+ constraint "fleet_form_template_kind_check"
221
+ check ("form_kind" in (
222
+ 'start_of_day', 'end_of_day', 'weekly_vehicle', 'pre_trip',
223
+ 'post_trip', 'maintenance', 'custom'
224
+ )),
225
+ constraint "fleet_form_template_vehicle_type_check"
226
+ check ("vehicle_type" is null or nullif(btrim("vehicle_type"), '') is not null)
227
+ );
228
+
229
+ alter table "public"."fleet_form_template" owner to "postgres";
230
+
231
+ create index "fleet_form_template_company_active_kind_idx"
232
+ on "public"."fleet_form_template" ("company_id", "form_kind")
233
+ where "is_active";
234
+ create index "fleet_form_template_created_by_idx"
235
+ on "public"."fleet_form_template" ("created_by")
236
+ where "created_by" is not null;
237
+
238
+ comment on table "public"."fleet_form_template" is
239
+ 'Stable form identity; editable content lives in immutable published versions.';
240
+
241
+ create table "public"."fleet_form_template_version" (
242
+ "id" uuid default gen_random_uuid() not null,
243
+ "company_id" uuid not null,
244
+ "template_id" uuid not null,
245
+ "version_number" integer not null,
246
+ "status" text default 'draft' not null,
247
+ "change_summary" text,
248
+ "definition_snapshot" jsonb default '{}'::jsonb not null,
249
+ "published_at" timestamptz,
250
+ "published_by" uuid,
251
+ "created_at" timestamptz default now() not null,
252
+ "updated_at" timestamptz default now() not null,
253
+ "created_by" uuid default auth.uid(),
254
+ constraint "fleet_form_template_version_pkey" primary key ("id"),
255
+ constraint "fleet_form_template_version_id_company_id_key"
256
+ unique ("id", "company_id"),
257
+ constraint "fleet_form_template_version_company_template_version_key"
258
+ unique ("company_id", "template_id", "version_number"),
259
+ constraint "fleet_form_template_version_company_id_fkey"
260
+ foreign key ("company_id")
261
+ references "public"."fleet_organization" ("company_id")
262
+ on delete cascade,
263
+ constraint "fleet_form_template_version_template_id_fkey"
264
+ foreign key ("template_id", "company_id")
265
+ references "public"."fleet_form_template" ("id", "company_id")
266
+ on delete cascade,
267
+ constraint "fleet_form_template_version_published_by_fkey"
268
+ foreign key ("published_by", "company_id")
269
+ references "public"."user" ("id", "company_id"),
270
+ constraint "fleet_form_template_version_created_by_fkey"
271
+ foreign key ("created_by", "company_id")
272
+ references "public"."user" ("id", "company_id"),
273
+ constraint "fleet_form_template_version_number_check"
274
+ check ("version_number" > 0),
275
+ constraint "fleet_form_template_version_status_check"
276
+ check ("status" in ('draft', 'published', 'retired')),
277
+ constraint "fleet_form_template_version_snapshot_check"
278
+ check (jsonb_typeof("definition_snapshot") = 'object'),
279
+ constraint "fleet_form_template_version_publish_shape_check"
280
+ check (
281
+ ("status" = 'draft' and "published_at" is null and "published_by" is null
282
+ and "definition_snapshot" = '{}'::jsonb)
283
+ or ("status" in ('published', 'retired') and "published_at" is not null
284
+ and jsonb_typeof("definition_snapshot" -> 'sections') = 'array')
285
+ )
286
+ );
287
+
288
+ alter table "public"."fleet_form_template_version" owner to "postgres";
289
+
290
+ create index "fleet_form_template_version_template_status_idx"
291
+ on "public"."fleet_form_template_version" ("template_id", "status", "version_number" desc);
292
+ create index "fleet_form_template_version_published_by_idx"
293
+ on "public"."fleet_form_template_version" ("published_by")
294
+ where "published_by" is not null;
295
+ create index "fleet_form_template_version_created_by_idx"
296
+ on "public"."fleet_form_template_version" ("created_by")
297
+ where "created_by" is not null;
298
+
299
+ comment on table "public"."fleet_form_template_version" is
300
+ 'Draft/published form revision. Published definition_snapshot and child rows are immutable.';
301
+
302
+ create table "public"."fleet_form_section" (
303
+ "id" uuid default gen_random_uuid() not null,
304
+ "company_id" uuid not null,
305
+ "template_version_id" uuid not null,
306
+ "title" text not null,
307
+ "description" text,
308
+ "display_order" integer not null,
309
+ "created_at" timestamptz default now() not null,
310
+ "updated_at" timestamptz default now() not null,
311
+ constraint "fleet_form_section_pkey" primary key ("id"),
312
+ constraint "fleet_form_section_id_company_id_key" unique ("id", "company_id"),
313
+ constraint "fleet_form_section_id_company_version_key"
314
+ unique ("id", "company_id", "template_version_id"),
315
+ constraint "fleet_form_section_company_version_order_key"
316
+ unique ("company_id", "template_version_id", "display_order"),
317
+ constraint "fleet_form_section_company_id_fkey"
318
+ foreign key ("company_id")
319
+ references "public"."fleet_organization" ("company_id")
320
+ on delete cascade,
321
+ constraint "fleet_form_section_template_version_id_fkey"
322
+ foreign key ("template_version_id", "company_id")
323
+ references "public"."fleet_form_template_version" ("id", "company_id")
324
+ on delete cascade,
325
+ constraint "fleet_form_section_title_check"
326
+ check (nullif(btrim("title"), '') is not null),
327
+ constraint "fleet_form_section_order_check" check ("display_order" >= 0)
328
+ );
329
+
330
+ alter table "public"."fleet_form_section" owner to "postgres";
331
+
332
+ create index "fleet_form_section_version_idx"
333
+ on "public"."fleet_form_section" ("template_version_id", "display_order");
334
+
335
+ comment on table "public"."fleet_form_section" is
336
+ 'Ordered section within a draft or immutable published form version.';
337
+
338
+ create table "public"."fleet_form_question" (
339
+ "id" uuid default gen_random_uuid() not null,
340
+ "company_id" uuid not null,
341
+ "template_version_id" uuid not null,
342
+ "section_id" uuid not null,
343
+ "question_key" text not null,
344
+ "prompt" text not null,
345
+ "help_text" text,
346
+ "response_type" text not null,
347
+ "display_order" integer not null,
348
+ "is_required" boolean default true not null,
349
+ "options" jsonb default '[]'::jsonb not null,
350
+ "pass_answer" jsonb,
351
+ "failure_answer" jsonb,
352
+ "pass_label" text,
353
+ "failure_label" text,
354
+ "creates_issue_on_failure" boolean default false not null,
355
+ "failure_severity" text default 'major' not null,
356
+ "failure_title" text,
357
+ "requires_comment_on_failure" boolean default true not null,
358
+ "requires_photo_on_failure" boolean default true not null,
359
+ "marks_vehicle_unsafe_on_failure" boolean default false not null,
360
+ "allows_vehicle_out_of_service_override" boolean default false not null,
361
+ "configuration" jsonb default '{}'::jsonb not null,
362
+ "created_at" timestamptz default now() not null,
363
+ "updated_at" timestamptz default now() not null,
364
+ constraint "fleet_form_question_pkey" primary key ("id"),
365
+ constraint "fleet_form_question_id_company_id_key" unique ("id", "company_id"),
366
+ constraint "fleet_form_question_company_version_key_key"
367
+ unique ("company_id", "template_version_id", "question_key"),
368
+ constraint "fleet_form_question_company_section_order_key"
369
+ unique ("company_id", "section_id", "display_order"),
370
+ constraint "fleet_form_question_company_id_fkey"
371
+ foreign key ("company_id")
372
+ references "public"."fleet_organization" ("company_id")
373
+ on delete cascade,
374
+ constraint "fleet_form_question_template_version_id_fkey"
375
+ foreign key ("template_version_id", "company_id")
376
+ references "public"."fleet_form_template_version" ("id", "company_id")
377
+ on delete cascade,
378
+ constraint "fleet_form_question_section_id_fkey"
379
+ foreign key ("section_id", "company_id", "template_version_id")
380
+ references "public"."fleet_form_section" ("id", "company_id", "template_version_id")
381
+ on delete cascade,
382
+ constraint "fleet_form_question_key_check"
383
+ check ("question_key" ~ '^[a-z0-9]+(?:[a-z0-9_.-]*[a-z0-9])?$'),
384
+ constraint "fleet_form_question_prompt_check"
385
+ check (nullif(btrim("prompt"), '') is not null),
386
+ constraint "fleet_form_question_response_type_check"
387
+ check ("response_type" in (
388
+ 'pass_fail', 'yes_no', 'single_choice', 'multi_choice',
389
+ 'select_or_text', 'single_select', 'multi_select', 'text',
390
+ 'number', 'currency', 'date', 'time', 'datetime', 'photo', 'signature'
391
+ )),
392
+ constraint "fleet_form_question_order_check" check ("display_order" >= 0),
393
+ constraint "fleet_form_question_options_check"
394
+ check (jsonb_typeof("options") = 'array'),
395
+ constraint "fleet_form_question_configuration_check"
396
+ check (jsonb_typeof("configuration") = 'object'),
397
+ constraint "fleet_form_question_pass_failure_check"
398
+ check (
399
+ "pass_answer" is null or "failure_answer" is null
400
+ or "pass_answer" <> "failure_answer"
401
+ ),
402
+ constraint "fleet_form_question_pass_fail_shape_check"
403
+ check (
404
+ "response_type" not in ('pass_fail', 'yes_no')
405
+ or ("pass_answer" is not null and "failure_answer" is not null)
406
+ ),
407
+ constraint "fleet_form_question_select_options_check"
408
+ check (
409
+ "response_type" not in (
410
+ 'single_choice', 'multi_choice', 'select_or_text',
411
+ 'single_select', 'multi_select'
412
+ )
413
+ or jsonb_array_length("options") > 0
414
+ ),
415
+ constraint "fleet_form_question_failure_severity_check"
416
+ check ("failure_severity" in ('minor', 'major', 'critical')),
417
+ constraint "fleet_form_question_failure_title_check"
418
+ check (
419
+ "failure_title" is null or nullif(btrim("failure_title"), '') is not null
420
+ ),
421
+ constraint "fleet_form_question_failure_behavior_check"
422
+ check (
423
+ (not "creates_issue_on_failure"
424
+ and not "marks_vehicle_unsafe_on_failure"
425
+ and not "allows_vehicle_out_of_service_override")
426
+ or "failure_answer" is not null
427
+ )
428
+ );
429
+
430
+ alter table "public"."fleet_form_question" owner to "postgres";
431
+
432
+ create index "fleet_form_question_version_section_order_idx"
433
+ on "public"."fleet_form_question" (
434
+ "template_version_id", "section_id", "display_order"
435
+ );
436
+
437
+ comment on table "public"."fleet_form_question" is
438
+ 'Typed form question with explicit pass/failure semantics and conditional evidence rules.';
439
+ comment on column "public"."fleet_form_question"."marks_vehicle_unsafe_on_failure" is
440
+ 'When true, a failed answer takes the vehicle out of service at submission.';
441
+
442
+ -- Assignments pin a published version so scheduled work cannot silently
443
+ -- change when a new template revision is released.
444
+ create table "public"."fleet_form_assignment" (
445
+ "id" uuid default gen_random_uuid() not null,
446
+ "company_id" uuid not null,
447
+ "template_version_id" uuid not null,
448
+ "vehicle_id" uuid not null,
449
+ "assignee_user_id" uuid,
450
+ "name" text,
451
+ "starts_on" date not null,
452
+ "ends_on" date,
453
+ "recurrence_rule" text,
454
+ "due_local_time" time without time zone,
455
+ "time_zone" text not null,
456
+ "is_active" boolean default true not null,
457
+ "created_at" timestamptz default now() not null,
458
+ "updated_at" timestamptz default now() not null,
459
+ "created_by" uuid default auth.uid(),
460
+ constraint "fleet_form_assignment_pkey" primary key ("id"),
461
+ constraint "fleet_form_assignment_id_company_id_key" unique ("id", "company_id"),
462
+ constraint "fleet_form_assignment_company_id_fkey"
463
+ foreign key ("company_id")
464
+ references "public"."fleet_organization" ("company_id")
465
+ on delete cascade,
466
+ constraint "fleet_form_assignment_template_version_id_fkey"
467
+ foreign key ("template_version_id", "company_id")
468
+ references "public"."fleet_form_template_version" ("id", "company_id"),
469
+ constraint "fleet_form_assignment_vehicle_id_fkey"
470
+ foreign key ("vehicle_id", "company_id")
471
+ references "public"."fleet_vehicle" ("id", "company_id"),
472
+ constraint "fleet_form_assignment_assignee_user_id_fkey"
473
+ foreign key ("assignee_user_id", "company_id")
474
+ references "public"."user" ("id", "company_id")
475
+ on delete set null ("assignee_user_id"),
476
+ constraint "fleet_form_assignment_created_by_fkey"
477
+ foreign key ("created_by", "company_id")
478
+ references "public"."user" ("id", "company_id")
479
+ on delete set null ("created_by"),
480
+ constraint "fleet_form_assignment_name_check"
481
+ check ("name" is null or nullif(btrim("name"), '') is not null),
482
+ constraint "fleet_form_assignment_date_range_check"
483
+ check ("ends_on" is null or "ends_on" >= "starts_on"),
484
+ constraint "fleet_form_assignment_timezone_check"
485
+ check (nullif(btrim("time_zone"), '') is not null),
486
+ constraint "fleet_form_assignment_recurrence_check"
487
+ check ("recurrence_rule" is null or nullif(btrim("recurrence_rule"), '') is not null)
488
+ );
489
+
490
+ alter table "public"."fleet_form_assignment" owner to "postgres";
491
+
492
+ create index "fleet_form_assignment_company_active_start_idx"
493
+ on "public"."fleet_form_assignment" ("company_id", "starts_on", "ends_on")
494
+ where "is_active";
495
+ create index "fleet_form_assignment_company_id_idx"
496
+ on "public"."fleet_form_assignment" ("company_id");
497
+ create index "fleet_form_assignment_template_version_idx"
498
+ on "public"."fleet_form_assignment" ("template_version_id");
499
+ create index "fleet_form_assignment_vehicle_idx"
500
+ on "public"."fleet_form_assignment" ("vehicle_id");
501
+ create index "fleet_form_assignment_assignee_idx"
502
+ on "public"."fleet_form_assignment" ("assignee_user_id")
503
+ where "assignee_user_id" is not null;
504
+ create index "fleet_form_assignment_created_by_idx"
505
+ on "public"."fleet_form_assignment" ("created_by")
506
+ where "created_by" is not null;
507
+
508
+ comment on table "public"."fleet_form_assignment" is
509
+ 'Recurring or one-time vehicle form assignment pinned to a published template version.';
510
+
511
+ -- ============================================================
512
+ -- Inspections, answers, issues, and repair work
513
+ -- ============================================================
514
+
515
+ create table "public"."fleet_inspection" (
516
+ "id" uuid default gen_random_uuid() not null,
517
+ "company_id" uuid not null,
518
+ "vehicle_id" uuid not null,
519
+ "assignment_id" uuid,
520
+ "template_version_id" uuid not null,
521
+ "inspector_user_id" uuid not null,
522
+ "inspection_date" date not null,
523
+ "status" text default 'draft' not null,
524
+ "result" text,
525
+ "is_vehicle_safe" boolean default true not null,
526
+ "template_snapshot" jsonb not null,
527
+ "odometer" numeric(12, 1),
528
+ "engine_hours" numeric(12, 1),
529
+ "fuel_percent" smallint,
530
+ "started_at" timestamptz default now() not null,
531
+ "submitted_at" timestamptz,
532
+ "voided_at" timestamptz,
533
+ "void_reason" text,
534
+ "notes" text,
535
+ "created_at" timestamptz default now() not null,
536
+ "updated_at" timestamptz default now() not null,
537
+ "created_by" uuid default auth.uid(),
538
+ constraint "fleet_inspection_pkey" primary key ("id"),
539
+ constraint "fleet_inspection_id_company_id_key" unique ("id", "company_id"),
540
+ constraint "fleet_inspection_company_id_fkey"
541
+ foreign key ("company_id")
542
+ references "public"."fleet_organization" ("company_id")
543
+ on delete cascade,
544
+ constraint "fleet_inspection_vehicle_id_fkey"
545
+ foreign key ("vehicle_id", "company_id")
546
+ references "public"."fleet_vehicle" ("id", "company_id"),
547
+ constraint "fleet_inspection_assignment_id_fkey"
548
+ foreign key ("assignment_id", "company_id")
549
+ references "public"."fleet_form_assignment" ("id", "company_id"),
550
+ constraint "fleet_inspection_template_version_id_fkey"
551
+ foreign key ("template_version_id", "company_id")
552
+ references "public"."fleet_form_template_version" ("id", "company_id"),
553
+ constraint "fleet_inspection_inspector_user_id_fkey"
554
+ foreign key ("inspector_user_id", "company_id")
555
+ references "public"."user" ("id", "company_id"),
556
+ constraint "fleet_inspection_created_by_fkey"
557
+ foreign key ("created_by", "company_id")
558
+ references "public"."user" ("id", "company_id"),
559
+ constraint "fleet_inspection_status_check"
560
+ check ("status" in ('draft', 'submitted', 'voided')),
561
+ constraint "fleet_inspection_result_check"
562
+ check ("result" is null or "result" in ('passed', 'failed')),
563
+ constraint "fleet_inspection_snapshot_check"
564
+ check (
565
+ jsonb_typeof("template_snapshot") = 'object'
566
+ and jsonb_typeof("template_snapshot" -> 'sections') = 'array'
567
+ ),
568
+ constraint "fleet_inspection_odometer_check"
569
+ check ("odometer" is null or "odometer" >= 0),
570
+ constraint "fleet_inspection_engine_hours_check"
571
+ check ("engine_hours" is null or "engine_hours" >= 0),
572
+ constraint "fleet_inspection_fuel_percent_check"
573
+ check ("fuel_percent" is null or "fuel_percent" between 0 and 100),
574
+ constraint "fleet_inspection_state_shape_check"
575
+ check (
576
+ ("status" = 'draft' and "result" is null and "submitted_at" is null
577
+ and "voided_at" is null and "void_reason" is null)
578
+ or ("status" = 'submitted' and "result" is not null
579
+ and "submitted_at" is not null and "voided_at" is null
580
+ and "void_reason" is null)
581
+ or ("status" = 'voided' and "result" is null
582
+ and "submitted_at" is null and "voided_at" is not null
583
+ and nullif(btrim("void_reason"), '') is not null)
584
+ )
585
+ );
586
+
587
+ alter table "public"."fleet_inspection" owner to "postgres";
588
+
589
+ create index "fleet_inspection_company_status_date_idx"
590
+ on "public"."fleet_inspection" ("company_id", "status", "inspection_date" desc);
591
+ create index "fleet_inspection_vehicle_date_idx"
592
+ on "public"."fleet_inspection" ("vehicle_id", "inspection_date" desc);
593
+ create index "fleet_inspection_assignment_idx"
594
+ on "public"."fleet_inspection" ("assignment_id")
595
+ where "assignment_id" is not null;
596
+ create index "fleet_inspection_template_version_idx"
597
+ on "public"."fleet_inspection" ("template_version_id");
598
+ create index "fleet_inspection_inspector_date_idx"
599
+ on "public"."fleet_inspection" ("inspector_user_id", "inspection_date" desc);
600
+ create index "fleet_inspection_created_by_idx"
601
+ on "public"."fleet_inspection" ("created_by")
602
+ where "created_by" is not null;
603
+
604
+ comment on table "public"."fleet_inspection" is
605
+ 'Vehicle inspection bound to an immutable copy of its published form version.';
606
+ comment on column "public"."fleet_inspection"."template_snapshot" is
607
+ 'Server-generated immutable form definition used for historical rendering and audit.';
608
+
609
+ create table "public"."fleet_inspection_answer" (
610
+ "id" uuid default gen_random_uuid() not null,
611
+ "company_id" uuid not null,
612
+ "inspection_id" uuid not null,
613
+ "question_id" uuid not null,
614
+ "answer_value" jsonb not null,
615
+ "question_snapshot" jsonb not null,
616
+ "is_failure" boolean default false not null,
617
+ "marks_vehicle_unsafe" boolean default false not null,
618
+ "vehicle_out_of_service_override" boolean default false not null,
619
+ "comment" text,
620
+ "answered_at" timestamptz default now() not null,
621
+ "answered_by" uuid default auth.uid() not null,
622
+ "created_at" timestamptz default now() not null,
623
+ "updated_at" timestamptz default now() not null,
624
+ constraint "fleet_inspection_answer_pkey" primary key ("id"),
625
+ constraint "fleet_inspection_answer_id_company_id_key" unique ("id", "company_id"),
626
+ constraint "fleet_inspection_answer_company_inspection_question_key"
627
+ unique ("company_id", "inspection_id", "question_id"),
628
+ constraint "fleet_inspection_answer_company_id_fkey"
629
+ foreign key ("company_id")
630
+ references "public"."fleet_organization" ("company_id")
631
+ on delete cascade,
632
+ constraint "fleet_inspection_answer_inspection_id_fkey"
633
+ foreign key ("inspection_id", "company_id")
634
+ references "public"."fleet_inspection" ("id", "company_id")
635
+ on delete cascade,
636
+ constraint "fleet_inspection_answer_question_id_fkey"
637
+ foreign key ("question_id", "company_id")
638
+ references "public"."fleet_form_question" ("id", "company_id"),
639
+ constraint "fleet_inspection_answer_answered_by_fkey"
640
+ foreign key ("answered_by", "company_id")
641
+ references "public"."user" ("id", "company_id"),
642
+ constraint "fleet_inspection_answer_value_check"
643
+ check ("answer_value" <> 'null'::jsonb),
644
+ constraint "fleet_inspection_answer_snapshot_check"
645
+ check (jsonb_typeof("question_snapshot") = 'object'),
646
+ constraint "fleet_inspection_answer_unsafe_failure_check"
647
+ check (
648
+ (not "marks_vehicle_unsafe" or "is_failure")
649
+ and (not "vehicle_out_of_service_override" or "marks_vehicle_unsafe")
650
+ ),
651
+ constraint "fleet_inspection_answer_failure_comment_check"
652
+ check (
653
+ not (
654
+ "is_failure"
655
+ and coalesce(("question_snapshot" ->> 'requires_comment_on_failure')::boolean, false)
656
+ )
657
+ or nullif(btrim("comment"), '') is not null
658
+ )
659
+ );
660
+
661
+ alter table "public"."fleet_inspection_answer" owner to "postgres";
662
+
663
+ create index "fleet_inspection_answer_inspection_idx"
664
+ on "public"."fleet_inspection_answer" ("inspection_id");
665
+ create index "fleet_inspection_answer_question_idx"
666
+ on "public"."fleet_inspection_answer" ("question_id");
667
+ create index "fleet_inspection_answer_answered_by_idx"
668
+ on "public"."fleet_inspection_answer" ("answered_by");
669
+ create index "fleet_inspection_answer_failed_idx"
670
+ on "public"."fleet_inspection_answer" ("company_id", "inspection_id")
671
+ where "is_failure";
672
+
673
+ comment on table "public"."fleet_inspection_answer" is
674
+ 'Answer plus server-derived failure flags and an immutable question-level snapshot.';
675
+
676
+ create table "public"."fleet_issue" (
677
+ "id" uuid default gen_random_uuid() not null,
678
+ "company_id" uuid not null,
679
+ "vehicle_id" uuid not null,
680
+ "source_inspection_answer_id" uuid,
681
+ "title" text not null,
682
+ "description" text,
683
+ "severity" text default 'major' not null,
684
+ "status" text default 'open' not null,
685
+ "is_vehicle_unsafe" boolean default false not null,
686
+ "assigned_to_user_id" uuid,
687
+ "reported_by" uuid default auth.uid() not null,
688
+ "due_at" timestamptz,
689
+ "resolved_at" timestamptz,
690
+ "resolved_by" uuid,
691
+ "resolution_note" text,
692
+ "created_at" timestamptz default now() not null,
693
+ "updated_at" timestamptz default now() not null,
694
+ constraint "fleet_issue_pkey" primary key ("id"),
695
+ constraint "fleet_issue_id_company_id_key" unique ("id", "company_id"),
696
+ constraint "fleet_issue_company_id_fkey"
697
+ foreign key ("company_id")
698
+ references "public"."fleet_organization" ("company_id")
699
+ on delete cascade,
700
+ constraint "fleet_issue_vehicle_id_fkey"
701
+ foreign key ("vehicle_id", "company_id")
702
+ references "public"."fleet_vehicle" ("id", "company_id"),
703
+ constraint "fleet_issue_source_answer_id_fkey"
704
+ foreign key ("source_inspection_answer_id", "company_id")
705
+ references "public"."fleet_inspection_answer" ("id", "company_id"),
706
+ constraint "fleet_issue_assigned_to_user_id_fkey"
707
+ foreign key ("assigned_to_user_id", "company_id")
708
+ references "public"."user" ("id", "company_id")
709
+ on delete set null ("assigned_to_user_id"),
710
+ constraint "fleet_issue_reported_by_fkey"
711
+ foreign key ("reported_by", "company_id")
712
+ references "public"."user" ("id", "company_id"),
713
+ constraint "fleet_issue_resolved_by_fkey"
714
+ foreign key ("resolved_by", "company_id")
715
+ references "public"."user" ("id", "company_id"),
716
+ constraint "fleet_issue_title_check"
717
+ check (nullif(btrim("title"), '') is not null),
718
+ constraint "fleet_issue_severity_check"
719
+ check ("severity" in ('minor', 'major', 'critical', 'low', 'medium', 'high')),
720
+ constraint "fleet_issue_status_check"
721
+ check ("status" in (
722
+ 'open', 'acknowledged', 'in_repair', 'in_progress', 'resolved', 'closed'
723
+ )),
724
+ constraint "fleet_issue_unsafe_severity_check"
725
+ check (not "is_vehicle_unsafe" or "severity" in ('major', 'high', 'critical')),
726
+ constraint "fleet_issue_resolution_shape_check"
727
+ check (
728
+ ("status" in ('resolved', 'closed') and "resolved_at" is not null
729
+ and "resolved_by" is not null
730
+ and nullif(btrim("resolution_note"), '') is not null)
731
+ or ("status" not in ('resolved', 'closed') and "resolved_at" is null
732
+ and "resolved_by" is null and "resolution_note" is null)
733
+ )
734
+ );
735
+
736
+ alter table "public"."fleet_issue" owner to "postgres";
737
+
738
+ create unique index "fleet_issue_source_answer_key"
739
+ on "public"."fleet_issue" ("source_inspection_answer_id")
740
+ where "source_inspection_answer_id" is not null;
741
+ create index "fleet_issue_company_open_severity_idx"
742
+ on "public"."fleet_issue" ("company_id", "severity", "created_at" desc)
743
+ where "status" in ('open', 'acknowledged', 'in_repair', 'in_progress');
744
+ create index "fleet_issue_company_id_idx"
745
+ on "public"."fleet_issue" ("company_id");
746
+ create index "fleet_issue_vehicle_status_idx"
747
+ on "public"."fleet_issue" ("vehicle_id", "status");
748
+ create index "fleet_issue_assigned_to_idx"
749
+ on "public"."fleet_issue" ("assigned_to_user_id", "status")
750
+ where "assigned_to_user_id" is not null;
751
+ create index "fleet_issue_reported_by_idx" on "public"."fleet_issue" ("reported_by");
752
+ create index "fleet_issue_resolved_by_idx" on "public"."fleet_issue" ("resolved_by")
753
+ where "resolved_by" is not null;
754
+
755
+ comment on table "public"."fleet_issue" is
756
+ 'Corrective-action item, optionally created automatically from a failed inspection answer.';
757
+
758
+ create table "public"."fleet_issue_status_history" (
759
+ "id" uuid default gen_random_uuid() not null,
760
+ "company_id" uuid not null,
761
+ "issue_id" uuid not null,
762
+ "from_status" text,
763
+ "to_status" text not null,
764
+ "note" text,
765
+ "changed_at" timestamptz default now() not null,
766
+ "changed_by" uuid,
767
+ constraint "fleet_issue_status_history_pkey" primary key ("id"),
768
+ constraint "fleet_issue_status_history_id_company_id_key" unique ("id", "company_id"),
769
+ constraint "fleet_issue_status_history_company_id_fkey"
770
+ foreign key ("company_id")
771
+ references "public"."fleet_organization" ("company_id")
772
+ on delete cascade,
773
+ constraint "fleet_issue_status_history_issue_id_fkey"
774
+ foreign key ("issue_id", "company_id")
775
+ references "public"."fleet_issue" ("id", "company_id")
776
+ on delete cascade,
777
+ constraint "fleet_issue_status_history_changed_by_fkey"
778
+ foreign key ("changed_by", "company_id")
779
+ references "public"."user" ("id", "company_id"),
780
+ constraint "fleet_issue_status_history_from_status_check"
781
+ check ("from_status" is null or "from_status" in (
782
+ 'open', 'acknowledged', 'in_repair', 'in_progress', 'resolved', 'closed'
783
+ )),
784
+ constraint "fleet_issue_status_history_to_status_check"
785
+ check ("to_status" in (
786
+ 'open', 'acknowledged', 'in_repair', 'in_progress', 'resolved', 'closed'
787
+ )),
788
+ constraint "fleet_issue_status_history_change_check"
789
+ check ("from_status" is null or "from_status" <> "to_status")
790
+ );
791
+
792
+ alter table "public"."fleet_issue_status_history" owner to "postgres";
793
+
794
+ create index "fleet_issue_status_history_issue_changed_idx"
795
+ on "public"."fleet_issue_status_history" ("issue_id", "changed_at" desc);
796
+ create index "fleet_issue_status_history_company_changed_idx"
797
+ on "public"."fleet_issue_status_history" ("company_id", "changed_at" desc);
798
+ create index "fleet_issue_status_history_changed_by_idx"
799
+ on "public"."fleet_issue_status_history" ("changed_by")
800
+ where "changed_by" is not null;
801
+
802
+ comment on table "public"."fleet_issue_status_history" is
803
+ 'Append-only issue status transition history populated by a database trigger.';
804
+
805
+ create table "public"."fleet_issue_comment" (
806
+ "id" uuid default gen_random_uuid() not null,
807
+ "company_id" uuid not null,
808
+ "issue_id" uuid not null,
809
+ "body" text not null,
810
+ "author_user_id" uuid default auth.uid() not null,
811
+ "created_at" timestamptz default now() not null,
812
+ constraint "fleet_issue_comment_pkey" primary key ("id"),
813
+ constraint "fleet_issue_comment_id_company_id_key" unique ("id", "company_id"),
814
+ constraint "fleet_issue_comment_company_id_fkey"
815
+ foreign key ("company_id")
816
+ references "public"."fleet_organization" ("company_id")
817
+ on delete cascade,
818
+ constraint "fleet_issue_comment_issue_id_fkey"
819
+ foreign key ("issue_id", "company_id")
820
+ references "public"."fleet_issue" ("id", "company_id")
821
+ on delete cascade,
822
+ constraint "fleet_issue_comment_author_user_id_fkey"
823
+ foreign key ("author_user_id", "company_id")
824
+ references "public"."user" ("id", "company_id"),
825
+ constraint "fleet_issue_comment_body_check"
826
+ check (nullif(btrim("body"), '') is not null)
827
+ );
828
+
829
+ alter table "public"."fleet_issue_comment" owner to "postgres";
830
+
831
+ create index "fleet_issue_comment_issue_created_idx"
832
+ on "public"."fleet_issue_comment" ("issue_id", "created_at");
833
+ create index "fleet_issue_comment_company_id_idx"
834
+ on "public"."fleet_issue_comment" ("company_id");
835
+ create index "fleet_issue_comment_author_idx"
836
+ on "public"."fleet_issue_comment" ("author_user_id");
837
+
838
+ comment on table "public"."fleet_issue_comment" is
839
+ 'Append-only discussion and repair-context notes for a Fleet issue.';
840
+
841
+ create table "public"."fleet_repair_order" (
842
+ "id" uuid default gen_random_uuid() not null,
843
+ "company_id" uuid not null,
844
+ "vehicle_id" uuid not null,
845
+ "issue_id" uuid,
846
+ "order_number" text not null,
847
+ "title" text not null,
848
+ "description" text,
849
+ "status" text default 'draft' not null,
850
+ "priority" text default 'normal' not null,
851
+ "vendor_name" text,
852
+ "assigned_to_user_id" uuid,
853
+ "scheduled_for" date,
854
+ "opened_at" timestamptz,
855
+ "completed_at" timestamptz,
856
+ "completion_note" text,
857
+ "created_at" timestamptz default now() not null,
858
+ "updated_at" timestamptz default now() not null,
859
+ "created_by" uuid default auth.uid(),
860
+ constraint "fleet_repair_order_pkey" primary key ("id"),
861
+ constraint "fleet_repair_order_id_company_id_key" unique ("id", "company_id"),
862
+ constraint "fleet_repair_order_company_number_key" unique ("company_id", "order_number"),
863
+ constraint "fleet_repair_order_company_id_fkey"
864
+ foreign key ("company_id")
865
+ references "public"."fleet_organization" ("company_id")
866
+ on delete cascade,
867
+ constraint "fleet_repair_order_vehicle_id_fkey"
868
+ foreign key ("vehicle_id", "company_id")
869
+ references "public"."fleet_vehicle" ("id", "company_id"),
870
+ constraint "fleet_repair_order_issue_id_fkey"
871
+ foreign key ("issue_id", "company_id")
872
+ references "public"."fleet_issue" ("id", "company_id"),
873
+ constraint "fleet_repair_order_assigned_to_user_id_fkey"
874
+ foreign key ("assigned_to_user_id", "company_id")
875
+ references "public"."user" ("id", "company_id"),
876
+ constraint "fleet_repair_order_created_by_fkey"
877
+ foreign key ("created_by", "company_id")
878
+ references "public"."user" ("id", "company_id"),
879
+ constraint "fleet_repair_order_number_check"
880
+ check (nullif(btrim("order_number"), '') is not null),
881
+ constraint "fleet_repair_order_title_check"
882
+ check (nullif(btrim("title"), '') is not null),
883
+ constraint "fleet_repair_order_status_check"
884
+ check ("status" in (
885
+ 'draft', 'open', 'in_progress', 'awaiting_parts', 'completed', 'cancelled'
886
+ )),
887
+ constraint "fleet_repair_order_priority_check"
888
+ check ("priority" in ('low', 'normal', 'high', 'urgent')),
889
+ constraint "fleet_repair_order_state_shape_check"
890
+ check (
891
+ ("status" = 'draft' and "opened_at" is null and "completed_at" is null)
892
+ or ("status" in ('open', 'in_progress', 'awaiting_parts')
893
+ and "opened_at" is not null and "completed_at" is null)
894
+ or ("status" in ('completed', 'cancelled') and "opened_at" is not null
895
+ and "completed_at" is not null
896
+ and nullif(btrim("completion_note"), '') is not null)
897
+ )
898
+ );
899
+
900
+ alter table "public"."fleet_repair_order" owner to "postgres";
901
+
902
+ create index "fleet_repair_order_company_status_idx"
903
+ on "public"."fleet_repair_order" ("company_id", "status", "scheduled_for");
904
+ create index "fleet_repair_order_vehicle_status_idx"
905
+ on "public"."fleet_repair_order" ("vehicle_id", "status");
906
+ create index "fleet_repair_order_issue_idx"
907
+ on "public"."fleet_repair_order" ("issue_id") where "issue_id" is not null;
908
+ create index "fleet_repair_order_assignee_idx"
909
+ on "public"."fleet_repair_order" ("assigned_to_user_id")
910
+ where "assigned_to_user_id" is not null;
911
+ create index "fleet_repair_order_created_by_idx"
912
+ on "public"."fleet_repair_order" ("created_by")
913
+ where "created_by" is not null;
914
+
915
+ comment on table "public"."fleet_repair_order" is
916
+ 'Repair workflow for a vehicle, optionally linked to the issue that initiated it.';
917
+
918
+ create table "public"."fleet_repair_order_line_item" (
919
+ "id" uuid default gen_random_uuid() not null,
920
+ "company_id" uuid not null,
921
+ "repair_order_id" uuid not null,
922
+ "line_type" text not null,
923
+ "description" text not null,
924
+ "quantity" numeric(12, 2) default 1 not null,
925
+ "unit_cost" numeric(12, 2) default 0 not null,
926
+ "total_cost" numeric(14, 2) generated always as ("quantity" * "unit_cost") stored,
927
+ "part_number" text,
928
+ "display_order" integer default 0 not null,
929
+ "created_at" timestamptz default now() not null,
930
+ "updated_at" timestamptz default now() not null,
931
+ constraint "fleet_repair_order_line_item_pkey" primary key ("id"),
932
+ constraint "fleet_repair_order_line_item_id_company_id_key" unique ("id", "company_id"),
933
+ constraint "fleet_repair_order_line_item_company_id_fkey"
934
+ foreign key ("company_id")
935
+ references "public"."fleet_organization" ("company_id")
936
+ on delete cascade,
937
+ constraint "fleet_repair_order_line_item_order_id_fkey"
938
+ foreign key ("repair_order_id", "company_id")
939
+ references "public"."fleet_repair_order" ("id", "company_id")
940
+ on delete cascade,
941
+ constraint "fleet_repair_order_line_item_type_check"
942
+ check ("line_type" in ('labor', 'part', 'service', 'fee', 'other')),
943
+ constraint "fleet_repair_order_line_item_description_check"
944
+ check (nullif(btrim("description"), '') is not null),
945
+ constraint "fleet_repair_order_line_item_quantity_check" check ("quantity" > 0),
946
+ constraint "fleet_repair_order_line_item_unit_cost_check" check ("unit_cost" >= 0),
947
+ constraint "fleet_repair_order_line_item_order_check" check ("display_order" >= 0)
948
+ );
949
+
950
+ alter table "public"."fleet_repair_order_line_item" owner to "postgres";
951
+
952
+ create index "fleet_repair_order_line_item_order_idx"
953
+ on "public"."fleet_repair_order_line_item" ("repair_order_id", "display_order");
954
+ create index "fleet_repair_order_line_item_company_id_idx"
955
+ on "public"."fleet_repair_order_line_item" ("company_id");
956
+
957
+ comment on table "public"."fleet_repair_order_line_item" is
958
+ 'Costed labor, part, service, fee, or other line on a repair order.';
959
+
960
+ -- ============================================================
961
+ -- Preventive maintenance and service history
962
+ -- ============================================================
963
+
964
+ create table "public"."fleet_pm_schedule" (
965
+ "id" uuid default gen_random_uuid() not null,
966
+ "company_id" uuid not null,
967
+ "vehicle_id" uuid not null,
968
+ "name" text not null,
969
+ "description" text,
970
+ "is_active" boolean default true not null,
971
+ "interval_days" integer,
972
+ "interval_odometer" numeric(12, 1),
973
+ "interval_engine_hours" numeric(12, 1),
974
+ "last_service_date" date,
975
+ "last_service_odometer" numeric(12, 1),
976
+ "last_service_engine_hours" numeric(12, 1),
977
+ "next_due_date" date,
978
+ "next_due_odometer" numeric(12, 1),
979
+ "next_due_engine_hours" numeric(12, 1),
980
+ "warning_lead_days" integer default 7 not null,
981
+ "warning_lead_odometer" numeric(12, 1) default 250 not null,
982
+ "warning_lead_engine_hours" numeric(12, 1) default 10 not null,
983
+ "created_at" timestamptz default now() not null,
984
+ "updated_at" timestamptz default now() not null,
985
+ "created_by" uuid default auth.uid(),
986
+ constraint "fleet_pm_schedule_pkey" primary key ("id"),
987
+ constraint "fleet_pm_schedule_id_company_id_key" unique ("id", "company_id"),
988
+ constraint "fleet_pm_schedule_company_vehicle_name_key"
989
+ unique ("company_id", "vehicle_id", "name"),
990
+ constraint "fleet_pm_schedule_company_id_fkey"
991
+ foreign key ("company_id")
992
+ references "public"."fleet_organization" ("company_id")
993
+ on delete cascade,
994
+ constraint "fleet_pm_schedule_vehicle_id_fkey"
995
+ foreign key ("vehicle_id", "company_id")
996
+ references "public"."fleet_vehicle" ("id", "company_id"),
997
+ constraint "fleet_pm_schedule_created_by_fkey"
998
+ foreign key ("created_by", "company_id")
999
+ references "public"."user" ("id", "company_id"),
1000
+ constraint "fleet_pm_schedule_name_check"
1001
+ check (nullif(btrim("name"), '') is not null),
1002
+ constraint "fleet_pm_schedule_interval_check"
1003
+ check (
1004
+ "interval_days" is not null
1005
+ or "interval_odometer" is not null
1006
+ or "interval_engine_hours" is not null
1007
+ ),
1008
+ constraint "fleet_pm_schedule_interval_days_check"
1009
+ check ("interval_days" is null or "interval_days" > 0),
1010
+ constraint "fleet_pm_schedule_interval_odometer_check"
1011
+ check ("interval_odometer" is null or "interval_odometer" > 0),
1012
+ constraint "fleet_pm_schedule_interval_engine_hours_check"
1013
+ check ("interval_engine_hours" is null or "interval_engine_hours" > 0),
1014
+ constraint "fleet_pm_schedule_last_odometer_check"
1015
+ check ("last_service_odometer" is null or "last_service_odometer" >= 0),
1016
+ constraint "fleet_pm_schedule_last_engine_hours_check"
1017
+ check ("last_service_engine_hours" is null or "last_service_engine_hours" >= 0),
1018
+ constraint "fleet_pm_schedule_due_odometer_check"
1019
+ check ("next_due_odometer" is null or "next_due_odometer" >= 0),
1020
+ constraint "fleet_pm_schedule_due_engine_hours_check"
1021
+ check ("next_due_engine_hours" is null or "next_due_engine_hours" >= 0),
1022
+ constraint "fleet_pm_schedule_warning_leads_check"
1023
+ check (
1024
+ "warning_lead_days" >= 0
1025
+ and "warning_lead_odometer" >= 0
1026
+ and "warning_lead_engine_hours" >= 0
1027
+ ),
1028
+ constraint "fleet_pm_schedule_due_shape_check"
1029
+ check (
1030
+ ("interval_days" is null and "next_due_date" is null)
1031
+ or ("interval_days" is not null and "next_due_date" is not null)
1032
+ )
1033
+ );
1034
+
1035
+ alter table "public"."fleet_pm_schedule" owner to "postgres";
1036
+
1037
+ create index "fleet_pm_schedule_company_active_date_idx"
1038
+ on "public"."fleet_pm_schedule" ("company_id", "next_due_date")
1039
+ where "is_active";
1040
+ create index "fleet_pm_schedule_vehicle_active_idx"
1041
+ on "public"."fleet_pm_schedule" ("vehicle_id") where "is_active";
1042
+ create index "fleet_pm_schedule_created_by_idx"
1043
+ on "public"."fleet_pm_schedule" ("created_by") where "created_by" is not null;
1044
+
1045
+ comment on table "public"."fleet_pm_schedule" is
1046
+ 'Preventive-maintenance cadence with date, odometer, and/or engine-hour due thresholds.';
1047
+ comment on constraint "fleet_pm_schedule_due_shape_check"
1048
+ on "public"."fleet_pm_schedule" is
1049
+ 'A trigger deterministically computes the date-based due threshold when configured.';
1050
+
1051
+ create table "public"."fleet_service_record" (
1052
+ "id" uuid default gen_random_uuid() not null,
1053
+ "company_id" uuid not null,
1054
+ "vehicle_id" uuid not null,
1055
+ "pm_schedule_id" uuid,
1056
+ "repair_order_id" uuid,
1057
+ "service_type" text not null,
1058
+ "description" text not null,
1059
+ "performed_on" date not null,
1060
+ "odometer" numeric(12, 1),
1061
+ "engine_hours" numeric(12, 1),
1062
+ "vendor_name" text,
1063
+ "cost" numeric(12, 2),
1064
+ "notes" text,
1065
+ "performed_by_user_id" uuid,
1066
+ "created_at" timestamptz default now() not null,
1067
+ "updated_at" timestamptz default now() not null,
1068
+ "created_by" uuid default auth.uid(),
1069
+ constraint "fleet_service_record_pkey" primary key ("id"),
1070
+ constraint "fleet_service_record_id_company_id_key" unique ("id", "company_id"),
1071
+ constraint "fleet_service_record_company_id_fkey"
1072
+ foreign key ("company_id")
1073
+ references "public"."fleet_organization" ("company_id")
1074
+ on delete cascade,
1075
+ constraint "fleet_service_record_vehicle_id_fkey"
1076
+ foreign key ("vehicle_id", "company_id")
1077
+ references "public"."fleet_vehicle" ("id", "company_id"),
1078
+ constraint "fleet_service_record_pm_schedule_id_fkey"
1079
+ foreign key ("pm_schedule_id", "company_id")
1080
+ references "public"."fleet_pm_schedule" ("id", "company_id"),
1081
+ constraint "fleet_service_record_repair_order_id_fkey"
1082
+ foreign key ("repair_order_id", "company_id")
1083
+ references "public"."fleet_repair_order" ("id", "company_id"),
1084
+ constraint "fleet_service_record_performed_by_user_id_fkey"
1085
+ foreign key ("performed_by_user_id", "company_id")
1086
+ references "public"."user" ("id", "company_id"),
1087
+ constraint "fleet_service_record_created_by_fkey"
1088
+ foreign key ("created_by", "company_id")
1089
+ references "public"."user" ("id", "company_id"),
1090
+ constraint "fleet_service_record_type_check"
1091
+ check (nullif(btrim("service_type"), '') is not null),
1092
+ constraint "fleet_service_record_description_check"
1093
+ check (nullif(btrim("description"), '') is not null),
1094
+ constraint "fleet_service_record_odometer_check"
1095
+ check ("odometer" is null or "odometer" >= 0),
1096
+ constraint "fleet_service_record_engine_hours_check"
1097
+ check ("engine_hours" is null or "engine_hours" >= 0),
1098
+ constraint "fleet_service_record_cost_check"
1099
+ check ("cost" is null or "cost" >= 0)
1100
+ );
1101
+
1102
+ alter table "public"."fleet_service_record" owner to "postgres";
1103
+
1104
+ create index "fleet_service_record_vehicle_date_idx"
1105
+ on "public"."fleet_service_record" ("vehicle_id", "performed_on" desc);
1106
+ create index "fleet_service_record_company_date_idx"
1107
+ on "public"."fleet_service_record" ("company_id", "performed_on" desc);
1108
+ create index "fleet_service_record_pm_schedule_idx"
1109
+ on "public"."fleet_service_record" ("pm_schedule_id")
1110
+ where "pm_schedule_id" is not null;
1111
+ create index "fleet_service_record_repair_order_idx"
1112
+ on "public"."fleet_service_record" ("repair_order_id")
1113
+ where "repair_order_id" is not null;
1114
+ create index "fleet_service_record_performed_by_idx"
1115
+ on "public"."fleet_service_record" ("performed_by_user_id")
1116
+ where "performed_by_user_id" is not null;
1117
+ create index "fleet_service_record_created_by_idx"
1118
+ on "public"."fleet_service_record" ("created_by")
1119
+ where "created_by" is not null;
1120
+
1121
+ comment on table "public"."fleet_service_record" is
1122
+ 'Completed maintenance or repair evidence that advances a linked PM schedule.';
1123
+
1124
+ -- ============================================================
1125
+ -- Notifications, media, and audit
1126
+ -- ============================================================
1127
+
1128
+ create table "public"."fleet_notification_preference" (
1129
+ "id" uuid default gen_random_uuid() not null,
1130
+ "company_id" uuid not null,
1131
+ "user_id" uuid not null,
1132
+ "event_type" text not null,
1133
+ "in_app_enabled" boolean default true not null,
1134
+ "email_enabled" boolean default false not null,
1135
+ "push_enabled" boolean default true not null,
1136
+ "minimum_severity" text default 'minor' not null,
1137
+ "created_at" timestamptz default now() not null,
1138
+ "updated_at" timestamptz default now() not null,
1139
+ constraint "fleet_notification_preference_pkey" primary key ("id"),
1140
+ constraint "fleet_notification_preference_id_company_id_key" unique ("id", "company_id"),
1141
+ constraint "fleet_notification_preference_company_user_event_key"
1142
+ unique ("company_id", "user_id", "event_type"),
1143
+ constraint "fleet_notification_preference_company_id_fkey"
1144
+ foreign key ("company_id")
1145
+ references "public"."fleet_organization" ("company_id")
1146
+ on delete cascade,
1147
+ constraint "fleet_notification_preference_user_id_fkey"
1148
+ foreign key ("user_id", "company_id")
1149
+ references "public"."user" ("id", "company_id")
1150
+ on delete cascade,
1151
+ constraint "fleet_notification_preference_event_type_check"
1152
+ check ("event_type" in (
1153
+ 'inspection_failed', 'vehicle_unsafe', 'issue_assigned',
1154
+ 'issue_status_changed', 'repair_order_assigned', 'pm_due', 'pm_overdue'
1155
+ )),
1156
+ constraint "fleet_notification_preference_minimum_severity_check"
1157
+ check ("minimum_severity" in (
1158
+ 'minor', 'major', 'critical', 'low', 'medium', 'high'
1159
+ ))
1160
+ );
1161
+
1162
+ alter table "public"."fleet_notification_preference" owner to "postgres";
1163
+
1164
+ create index "fleet_notification_preference_user_idx"
1165
+ on "public"."fleet_notification_preference" ("user_id");
1166
+
1167
+ comment on table "public"."fleet_notification_preference" is
1168
+ 'Per-employee Fleet channel and severity preferences; employee identity remains canonical in public.user.';
1169
+
1170
+ create table "public"."fleet_media" (
1171
+ "id" uuid default gen_random_uuid() not null,
1172
+ "company_id" uuid not null,
1173
+ "inspection_id" uuid,
1174
+ "answer_id" uuid,
1175
+ "issue_id" uuid,
1176
+ "issue_comment_id" uuid,
1177
+ "repair_order_id" uuid,
1178
+ "service_record_id" uuid,
1179
+ "media_type" text not null,
1180
+ "storage_bucket" text not null,
1181
+ "storage_object_path" text not null,
1182
+ "file_name" text,
1183
+ "mime_type" text,
1184
+ "byte_size" bigint,
1185
+ "checksum_sha256" text,
1186
+ "captured_at" timestamptz,
1187
+ "created_at" timestamptz default now() not null,
1188
+ "uploaded_by" uuid default auth.uid() not null,
1189
+ constraint "fleet_media_pkey" primary key ("id"),
1190
+ constraint "fleet_media_id_company_id_key" unique ("id", "company_id"),
1191
+ constraint "fleet_media_storage_object_key" unique ("storage_bucket", "storage_object_path"),
1192
+ constraint "fleet_media_company_id_fkey"
1193
+ foreign key ("company_id")
1194
+ references "public"."fleet_organization" ("company_id")
1195
+ on delete cascade,
1196
+ constraint "fleet_media_inspection_id_fkey"
1197
+ foreign key ("inspection_id", "company_id")
1198
+ references "public"."fleet_inspection" ("id", "company_id")
1199
+ on delete cascade,
1200
+ constraint "fleet_media_answer_id_fkey"
1201
+ foreign key ("answer_id", "company_id")
1202
+ references "public"."fleet_inspection_answer" ("id", "company_id")
1203
+ on delete cascade,
1204
+ constraint "fleet_media_issue_id_fkey"
1205
+ foreign key ("issue_id", "company_id")
1206
+ references "public"."fleet_issue" ("id", "company_id")
1207
+ on delete cascade,
1208
+ constraint "fleet_media_issue_comment_id_fkey"
1209
+ foreign key ("issue_comment_id", "company_id")
1210
+ references "public"."fleet_issue_comment" ("id", "company_id")
1211
+ on delete cascade,
1212
+ constraint "fleet_media_repair_order_id_fkey"
1213
+ foreign key ("repair_order_id", "company_id")
1214
+ references "public"."fleet_repair_order" ("id", "company_id")
1215
+ on delete cascade,
1216
+ constraint "fleet_media_service_record_id_fkey"
1217
+ foreign key ("service_record_id", "company_id")
1218
+ references "public"."fleet_service_record" ("id", "company_id")
1219
+ on delete cascade,
1220
+ constraint "fleet_media_uploaded_by_fkey"
1221
+ foreign key ("uploaded_by", "company_id")
1222
+ references "public"."user" ("id", "company_id"),
1223
+ constraint "fleet_media_parent_check"
1224
+ check (num_nonnulls(
1225
+ "inspection_id", "answer_id", "issue_id", "issue_comment_id",
1226
+ "repair_order_id", "service_record_id"
1227
+ ) = 1),
1228
+ constraint "fleet_media_type_check"
1229
+ check ("media_type" in ('photo', 'video', 'document', 'signature', 'other')),
1230
+ constraint "fleet_media_bucket_check"
1231
+ check ("storage_bucket" = 'fleet-media'),
1232
+ constraint "fleet_media_object_path_check"
1233
+ check (
1234
+ nullif(btrim("storage_object_path"), '') is not null
1235
+ and "storage_object_path" like "company_id"::text || '/%'
1236
+ and "storage_object_path" !~ '(^|/)\.\.(/|$)'
1237
+ ),
1238
+ constraint "fleet_media_byte_size_check"
1239
+ check ("byte_size" is null or "byte_size" > 0),
1240
+ constraint "fleet_media_checksum_check"
1241
+ check ("checksum_sha256" is null or "checksum_sha256" ~ '^[0-9a-f]{64}$')
1242
+ );
1243
+
1244
+ alter table "public"."fleet_media" owner to "postgres";
1245
+
1246
+ create index "fleet_media_company_created_idx"
1247
+ on "public"."fleet_media" ("company_id", "created_at" desc);
1248
+ create index "fleet_media_inspection_idx" on "public"."fleet_media" ("inspection_id")
1249
+ where "inspection_id" is not null;
1250
+ create index "fleet_media_answer_idx" on "public"."fleet_media" ("answer_id")
1251
+ where "answer_id" is not null;
1252
+ create index "fleet_media_issue_idx" on "public"."fleet_media" ("issue_id")
1253
+ where "issue_id" is not null;
1254
+ create index "fleet_media_issue_comment_idx" on "public"."fleet_media" ("issue_comment_id")
1255
+ where "issue_comment_id" is not null;
1256
+ create index "fleet_media_repair_order_idx" on "public"."fleet_media" ("repair_order_id")
1257
+ where "repair_order_id" is not null;
1258
+ create index "fleet_media_service_record_idx" on "public"."fleet_media" ("service_record_id")
1259
+ where "service_record_id" is not null;
1260
+ create index "fleet_media_uploaded_by_idx" on "public"."fleet_media" ("uploaded_by");
1261
+
1262
+ comment on table "public"."fleet_media" is
1263
+ 'Private-storage metadata for evidence attached to exactly one Fleet record.';
1264
+ comment on column "public"."fleet_media"."storage_object_path" is
1265
+ 'Private key shaped as company UUID/uploader UUID/unique object path.';
1266
+
1267
+ -- Supabase Storage remains the blob source of truth. The companion RLS
1268
+ -- migration grants tenant-scoped object reads/inserts; authenticated clients
1269
+ -- cannot overwrite or delete evidence objects after upload.
1270
+ insert into "storage"."buckets" ("id", "name", "public")
1271
+ values ('fleet-media', 'fleet-media', false)
1272
+ on conflict ("id") do update
1273
+ set "public" = false;
1274
+
1275
+ create table "public"."fleet_audit_event" (
1276
+ "id" uuid default gen_random_uuid() not null,
1277
+ "company_id" uuid not null,
1278
+ "entity_table" text not null,
1279
+ "entity_id" uuid not null,
1280
+ "action" text not null,
1281
+ "actor_user_id" uuid,
1282
+ "occurred_at" timestamptz default now() not null,
1283
+ "before_data" jsonb,
1284
+ "after_data" jsonb,
1285
+ "metadata" jsonb default '{}'::jsonb not null,
1286
+ constraint "fleet_audit_event_pkey" primary key ("id"),
1287
+ constraint "fleet_audit_event_id_company_id_key" unique ("id", "company_id"),
1288
+ constraint "fleet_audit_event_company_id_fkey"
1289
+ foreign key ("company_id")
1290
+ references "public"."fleet_organization" ("company_id")
1291
+ on delete cascade,
1292
+ constraint "fleet_audit_event_actor_user_id_fkey"
1293
+ foreign key ("actor_user_id", "company_id")
1294
+ references "public"."user" ("id", "company_id"),
1295
+ constraint "fleet_audit_event_entity_table_check"
1296
+ check ("entity_table" ~ '^fleet_[a-z0-9_]+$'),
1297
+ constraint "fleet_audit_event_action_check"
1298
+ check ("action" in ('insert', 'update', 'delete')),
1299
+ constraint "fleet_audit_event_shape_check"
1300
+ check (
1301
+ ("action" = 'insert' and "before_data" is null and "after_data" is not null)
1302
+ or ("action" = 'update' and "before_data" is not null and "after_data" is not null)
1303
+ or ("action" = 'delete' and "before_data" is not null and "after_data" is null)
1304
+ ),
1305
+ constraint "fleet_audit_event_metadata_check"
1306
+ check (jsonb_typeof("metadata") = 'object')
1307
+ );
1308
+
1309
+ alter table "public"."fleet_audit_event" owner to "postgres";
1310
+
1311
+ create index "fleet_audit_event_company_occurred_idx"
1312
+ on "public"."fleet_audit_event" ("company_id", "occurred_at" desc);
1313
+ create index "fleet_audit_event_entity_idx"
1314
+ on "public"."fleet_audit_event" ("entity_table", "entity_id", "occurred_at" desc);
1315
+ create index "fleet_audit_event_actor_idx"
1316
+ on "public"."fleet_audit_event" ("actor_user_id", "occurred_at" desc)
1317
+ where "actor_user_id" is not null;
1318
+
1319
+ comment on table "public"."fleet_audit_event" is
1320
+ 'Append-only database audit log for material Fleet configuration and workflow changes.';
1321
+
1322
+ -- ============================================================
1323
+ -- Shared integrity and lifecycle functions
1324
+ -- ============================================================
1325
+
1326
+ create or replace function "public"."fleet_set_updated_at"()
1327
+ returns trigger
1328
+ language plpgsql
1329
+ set search_path = ''
1330
+ as $$
1331
+ begin
1332
+ new."updated_at" = now();
1333
+ return new;
1334
+ end;
1335
+ $$;
1336
+
1337
+ alter function "public"."fleet_set_updated_at"() owner to "postgres";
1338
+
1339
+ create or replace function "public"."fleet_guard_vehicle_status"()
1340
+ returns trigger
1341
+ language plpgsql
1342
+ security definer
1343
+ set search_path = ''
1344
+ as $$
1345
+ begin
1346
+ if new."id" is distinct from old."id"
1347
+ or new."company_id" is distinct from old."company_id" then
1348
+ raise exception using
1349
+ errcode = '23514',
1350
+ message = 'Fleet vehicle identity and company are immutable';
1351
+ end if;
1352
+
1353
+ if old."status" = 'out_of_service'
1354
+ and new."status" <> 'out_of_service'
1355
+ and exists (
1356
+ select 1
1357
+ from "public"."fleet_issue" as "issue"
1358
+ where "issue"."company_id" = old."company_id"
1359
+ and "issue"."vehicle_id" = old."id"
1360
+ and "issue"."is_vehicle_unsafe"
1361
+ and "issue"."status" in (
1362
+ 'open', 'acknowledged', 'in_repair', 'in_progress'
1363
+ )
1364
+ ) then
1365
+ raise exception using
1366
+ errcode = '23514',
1367
+ message = 'Resolve all unsafe Fleet issues before returning a vehicle to service';
1368
+ end if;
1369
+
1370
+ return new;
1371
+ end;
1372
+ $$;
1373
+
1374
+ alter function "public"."fleet_guard_vehicle_status"() owner to "postgres";
1375
+
1376
+ create trigger "fleet_guard_vehicle_status_trigger"
1377
+ before update on "public"."fleet_vehicle"
1378
+ for each row execute function "public"."fleet_guard_vehicle_status"();
1379
+
1380
+ create or replace function "public"."fleet_prepare_member"()
1381
+ returns trigger
1382
+ language plpgsql
1383
+ security definer
1384
+ set search_path = ''
1385
+ as $$
1386
+ begin
1387
+ if tg_op = 'DELETE' then
1388
+ -- Company deletion owns the cascade. Every other removal must preserve
1389
+ -- an active Fleet owner, including cascades initiated by user deletion.
1390
+ if not exists (
1391
+ select 1
1392
+ from "public"."fleet_organization" as "organization"
1393
+ where "organization"."company_id" = old."company_id"
1394
+ ) then
1395
+ return old;
1396
+ end if;
1397
+
1398
+ if old."role" = 'owner' and old."is_active" then
1399
+ -- Serialize owner-count changes per tenant so concurrent demotions
1400
+ -- or deletes cannot both observe the other owner and leave none.
1401
+ perform 1
1402
+ from "public"."fleet_organization" as "organization"
1403
+ where "organization"."company_id" = old."company_id"
1404
+ for update;
1405
+
1406
+ if not exists (
1407
+ select 1
1408
+ from "public"."fleet_member" as "other_owner"
1409
+ where "other_owner"."company_id" = old."company_id"
1410
+ and "other_owner"."id" <> old."id"
1411
+ and "other_owner"."role" = 'owner'
1412
+ and "other_owner"."is_active"
1413
+ ) then
1414
+ raise exception using
1415
+ errcode = '23514',
1416
+ message = 'A Fleet organization must retain at least one active owner';
1417
+ end if;
1418
+ end if;
1419
+
1420
+ return old;
1421
+ end if;
1422
+
1423
+ if tg_op = 'UPDATE' and (
1424
+ new."company_id" is distinct from old."company_id"
1425
+ or new."user_id" is distinct from old."user_id"
1426
+ ) then
1427
+ raise exception using
1428
+ errcode = '23514',
1429
+ message = 'Fleet membership company and employee are immutable';
1430
+ end if;
1431
+
1432
+ if new."is_active" and not exists (
1433
+ select 1
1434
+ from "public"."user" as "employee"
1435
+ where "employee"."id" = new."user_id"
1436
+ and "employee"."company_id" = new."company_id"
1437
+ and "employee"."is_current_employee"
1438
+ and not "employee"."is_hidden"
1439
+ ) then
1440
+ raise exception using
1441
+ errcode = '23514',
1442
+ message = 'Active Fleet membership requires a current, visible employee';
1443
+ end if;
1444
+
1445
+ if tg_op = 'UPDATE'
1446
+ and old."role" = 'owner'
1447
+ and old."is_active"
1448
+ and (new."role" <> 'owner' or not new."is_active") then
1449
+ -- See the DELETE branch: this row lock closes the concurrent
1450
+ -- demotion/delete write-skew window for the tenant.
1451
+ perform 1
1452
+ from "public"."fleet_organization" as "organization"
1453
+ where "organization"."company_id" = old."company_id"
1454
+ for update;
1455
+
1456
+ if not exists (
1457
+ select 1
1458
+ from "public"."fleet_member" as "other_owner"
1459
+ where "other_owner"."company_id" = old."company_id"
1460
+ and "other_owner"."id" <> old."id"
1461
+ and "other_owner"."role" = 'owner'
1462
+ and "other_owner"."is_active"
1463
+ ) then
1464
+ raise exception using
1465
+ errcode = '23514',
1466
+ message = 'A Fleet organization must retain at least one active owner';
1467
+ end if;
1468
+ end if;
1469
+
1470
+ if new."is_active" then
1471
+ new."deactivated_at" = null;
1472
+ elsif new."deactivated_at" is null then
1473
+ new."deactivated_at" = now();
1474
+ end if;
1475
+
1476
+ return new;
1477
+ end;
1478
+ $$;
1479
+
1480
+ alter function "public"."fleet_prepare_member"() owner to "postgres";
1481
+
1482
+ create trigger "fleet_prepare_member_trigger"
1483
+ before insert or update or delete on "public"."fleet_member"
1484
+ for each row execute function "public"."fleet_prepare_member"();
1485
+
1486
+ create or replace function "public"."fleet_build_form_version_snapshot"(
1487
+ "p_template_version_id" uuid
1488
+ )
1489
+ returns jsonb
1490
+ language sql
1491
+ stable
1492
+ set search_path = ''
1493
+ as $$
1494
+ select jsonb_build_object(
1495
+ 'template_id', "template"."id",
1496
+ 'template_slug', "template"."slug",
1497
+ 'template_name', "template"."name",
1498
+ 'form_kind', "template"."form_kind",
1499
+ 'vehicle_type', "template"."vehicle_type",
1500
+ 'template_version_id', "version"."id",
1501
+ 'version_number', "version"."version_number",
1502
+ 'sections', coalesce((
1503
+ select jsonb_agg(
1504
+ jsonb_build_object(
1505
+ 'id', "section"."id",
1506
+ 'title', "section"."title",
1507
+ 'description', "section"."description",
1508
+ 'display_order', "section"."display_order",
1509
+ 'questions', coalesce((
1510
+ select jsonb_agg(
1511
+ jsonb_build_object(
1512
+ 'id', "question"."id",
1513
+ 'question_key', "question"."question_key",
1514
+ 'prompt', "question"."prompt",
1515
+ 'help_text', "question"."help_text",
1516
+ 'response_type', "question"."response_type",
1517
+ 'display_order', "question"."display_order",
1518
+ 'is_required', "question"."is_required",
1519
+ 'options', "question"."options",
1520
+ 'pass_answer', "question"."pass_answer",
1521
+ 'failure_answer', "question"."failure_answer",
1522
+ 'pass_label', "question"."pass_label",
1523
+ 'failure_label', "question"."failure_label",
1524
+ 'creates_issue_on_failure',
1525
+ "question"."creates_issue_on_failure",
1526
+ 'failure_severity', "question"."failure_severity",
1527
+ 'failure_title', "question"."failure_title",
1528
+ 'requires_comment_on_failure',
1529
+ "question"."requires_comment_on_failure",
1530
+ 'requires_photo_on_failure',
1531
+ "question"."requires_photo_on_failure",
1532
+ 'marks_vehicle_unsafe_on_failure',
1533
+ "question"."marks_vehicle_unsafe_on_failure",
1534
+ 'allows_vehicle_out_of_service_override',
1535
+ "question"."allows_vehicle_out_of_service_override",
1536
+ 'configuration', "question"."configuration"
1537
+ )
1538
+ order by "question"."display_order", "question"."id"
1539
+ )
1540
+ from "public"."fleet_form_question" as "question"
1541
+ where "question"."company_id" = "section"."company_id"
1542
+ and "question"."template_version_id" = "section"."template_version_id"
1543
+ and "question"."section_id" = "section"."id"
1544
+ ), '[]'::jsonb)
1545
+ )
1546
+ order by "section"."display_order", "section"."id"
1547
+ )
1548
+ from "public"."fleet_form_section" as "section"
1549
+ where "section"."company_id" = "version"."company_id"
1550
+ and "section"."template_version_id" = "version"."id"
1551
+ ), '[]'::jsonb)
1552
+ )
1553
+ from "public"."fleet_form_template_version" as "version"
1554
+ join "public"."fleet_form_template" as "template"
1555
+ on "template"."id" = "version"."template_id"
1556
+ and "template"."company_id" = "version"."company_id"
1557
+ where "version"."id" = "p_template_version_id";
1558
+ $$;
1559
+
1560
+ alter function "public"."fleet_build_form_version_snapshot"(uuid)
1561
+ owner to "postgres";
1562
+
1563
+ create or replace function "public"."fleet_prepare_form_version"()
1564
+ returns trigger
1565
+ language plpgsql
1566
+ security definer
1567
+ set search_path = ''
1568
+ as $$
1569
+ declare
1570
+ "v_snapshot" jsonb;
1571
+ begin
1572
+ if tg_op = 'DELETE' then
1573
+ if not exists (
1574
+ select 1
1575
+ from "public"."fleet_organization" as "organization"
1576
+ where "organization"."company_id" = old."company_id"
1577
+ ) then
1578
+ return old;
1579
+ end if;
1580
+ if old."status" <> 'draft' then
1581
+ raise exception using
1582
+ errcode = '23514',
1583
+ message = 'Published or retired Fleet form versions cannot be deleted';
1584
+ end if;
1585
+ return old;
1586
+ end if;
1587
+
1588
+ if tg_op = 'INSERT' then
1589
+ if new."status" <> 'draft' then
1590
+ raise exception using
1591
+ errcode = '23514',
1592
+ message = 'Fleet form versions must be created as drafts';
1593
+ end if;
1594
+ new."definition_snapshot" = '{}'::jsonb;
1595
+ new."published_at" = null;
1596
+ new."published_by" = null;
1597
+ return new;
1598
+ end if;
1599
+
1600
+ if new."company_id" is distinct from old."company_id"
1601
+ or new."template_id" is distinct from old."template_id"
1602
+ or new."version_number" is distinct from old."version_number"
1603
+ or new."created_by" is distinct from old."created_by" then
1604
+ raise exception using
1605
+ errcode = '23514',
1606
+ message = 'Fleet form version identity is immutable';
1607
+ end if;
1608
+
1609
+ if old."status" in ('published', 'retired') then
1610
+ if old."status" = 'published' and new."status" = 'retired' then
1611
+ if (to_jsonb(new) - array['status', 'updated_at'])
1612
+ is distinct from
1613
+ (to_jsonb(old) - array['status', 'updated_at']) then
1614
+ raise exception using
1615
+ errcode = '23514',
1616
+ message = 'Retiring a Fleet form version may only change its status';
1617
+ end if;
1618
+
1619
+ if exists (
1620
+ select 1
1621
+ from "public"."fleet_form_assignment" as "assignment"
1622
+ where "assignment"."company_id" = old."company_id"
1623
+ and "assignment"."template_version_id" = old."id"
1624
+ and "assignment"."is_active"
1625
+ ) then
1626
+ raise exception using
1627
+ errcode = '23514',
1628
+ message = 'Deactivate assignments before retiring their Fleet form version';
1629
+ end if;
1630
+
1631
+ return new;
1632
+ end if;
1633
+
1634
+ if new is distinct from old then
1635
+ raise exception using
1636
+ errcode = '23514',
1637
+ message = 'Published and retired Fleet form versions are immutable';
1638
+ end if;
1639
+ return new;
1640
+ end if;
1641
+
1642
+ if new."status" = 'draft' then
1643
+ new."definition_snapshot" = '{}'::jsonb;
1644
+ new."published_at" = null;
1645
+ new."published_by" = null;
1646
+ return new;
1647
+ end if;
1648
+
1649
+ if new."status" <> 'published' then
1650
+ raise exception using
1651
+ errcode = '23514',
1652
+ message = 'A draft Fleet form version may only transition to published';
1653
+ end if;
1654
+
1655
+ if not exists (
1656
+ select 1
1657
+ from "public"."fleet_form_section" as "section"
1658
+ where "section"."company_id" = new."company_id"
1659
+ and "section"."template_version_id" = new."id"
1660
+ ) or not exists (
1661
+ select 1
1662
+ from "public"."fleet_form_question" as "question"
1663
+ where "question"."company_id" = new."company_id"
1664
+ and "question"."template_version_id" = new."id"
1665
+ ) then
1666
+ raise exception using
1667
+ errcode = '23514',
1668
+ message = 'A Fleet form version needs at least one section and question before publishing';
1669
+ end if;
1670
+
1671
+ "v_snapshot" = "public"."fleet_build_form_version_snapshot"(new."id");
1672
+ if "v_snapshot" is null then
1673
+ raise exception using
1674
+ errcode = '23514',
1675
+ message = 'Could not build Fleet form version snapshot';
1676
+ end if;
1677
+
1678
+ new."definition_snapshot" = "v_snapshot";
1679
+ new."published_at" = coalesce(new."published_at", now());
1680
+ new."published_by" = case
1681
+ when auth.uid() is not null then auth.uid()
1682
+ else coalesce(new."published_by", new."created_by")
1683
+ end;
1684
+ return new;
1685
+ end;
1686
+ $$;
1687
+
1688
+ alter function "public"."fleet_prepare_form_version"() owner to "postgres";
1689
+
1690
+ create trigger "fleet_prepare_form_version_trigger"
1691
+ before insert or update or delete on "public"."fleet_form_template_version"
1692
+ for each row execute function "public"."fleet_prepare_form_version"();
1693
+
1694
+ create or replace function "public"."fleet_guard_form_version_child"()
1695
+ returns trigger
1696
+ language plpgsql
1697
+ security definer
1698
+ set search_path = ''
1699
+ as $$
1700
+ declare
1701
+ "v_version_id" uuid;
1702
+ "v_company_id" uuid;
1703
+ "v_status" text;
1704
+ begin
1705
+ if tg_op = 'DELETE' then
1706
+ "v_version_id" = old."template_version_id";
1707
+ "v_company_id" = old."company_id";
1708
+ else
1709
+ "v_version_id" = new."template_version_id";
1710
+ "v_company_id" = new."company_id";
1711
+ end if;
1712
+
1713
+ if tg_op = 'DELETE' and not exists (
1714
+ select 1
1715
+ from "public"."fleet_organization" as "organization"
1716
+ where "organization"."company_id" = "v_company_id"
1717
+ ) then
1718
+ return old;
1719
+ end if;
1720
+
1721
+ if tg_op = 'UPDATE' and (
1722
+ new."template_version_id" is distinct from old."template_version_id"
1723
+ or new."company_id" is distinct from old."company_id"
1724
+ ) then
1725
+ raise exception using
1726
+ errcode = '23514',
1727
+ message = 'Fleet form content cannot move between versions or companies';
1728
+ end if;
1729
+
1730
+ select "version"."status"
1731
+ into "v_status"
1732
+ from "public"."fleet_form_template_version" as "version"
1733
+ where "version"."id" = "v_version_id"
1734
+ and "version"."company_id" = "v_company_id"
1735
+ for update;
1736
+
1737
+ if "v_status" is distinct from 'draft' then
1738
+ raise exception using
1739
+ errcode = '23514',
1740
+ message = 'Only draft Fleet form versions may change sections or questions';
1741
+ end if;
1742
+
1743
+ if tg_op = 'DELETE' then
1744
+ return old;
1745
+ end if;
1746
+ return new;
1747
+ end;
1748
+ $$;
1749
+
1750
+ alter function "public"."fleet_guard_form_version_child"() owner to "postgres";
1751
+
1752
+ create trigger "fleet_guard_form_section_trigger"
1753
+ before insert or update or delete on "public"."fleet_form_section"
1754
+ for each row execute function "public"."fleet_guard_form_version_child"();
1755
+
1756
+ create trigger "fleet_guard_form_question_trigger"
1757
+ before insert or update or delete on "public"."fleet_form_question"
1758
+ for each row execute function "public"."fleet_guard_form_version_child"();
1759
+
1760
+ create or replace function "public"."fleet_validate_form_assignment"()
1761
+ returns trigger
1762
+ language plpgsql
1763
+ security definer
1764
+ set search_path = ''
1765
+ as $$
1766
+ declare
1767
+ "v_version_status" text;
1768
+ "v_template_vehicle_type" text;
1769
+ "v_vehicle_type" text;
1770
+ "v_vehicle_status" text;
1771
+ "v_vehicle_archived_at" timestamptz;
1772
+ begin
1773
+ -- Lock both rows so an assignment cannot race form retirement or vehicle
1774
+ -- archival/status changes and commit in an invalid active state.
1775
+ select
1776
+ "version"."status",
1777
+ "version"."definition_snapshot" ->> 'vehicle_type',
1778
+ "vehicle"."vehicle_type",
1779
+ "vehicle"."status",
1780
+ "vehicle"."archived_at"
1781
+ into
1782
+ "v_version_status",
1783
+ "v_template_vehicle_type",
1784
+ "v_vehicle_type",
1785
+ "v_vehicle_status",
1786
+ "v_vehicle_archived_at"
1787
+ from "public"."fleet_form_template_version" as "version"
1788
+ join "public"."fleet_vehicle" as "vehicle"
1789
+ on "vehicle"."id" = new."vehicle_id"
1790
+ and "vehicle"."company_id" = new."company_id"
1791
+ where "version"."id" = new."template_version_id"
1792
+ and "version"."company_id" = new."company_id"
1793
+ for update of "version", "vehicle";
1794
+
1795
+ if new."is_active" and "v_version_status" is distinct from 'published' then
1796
+ raise exception using
1797
+ errcode = '23514',
1798
+ message = 'Active Fleet assignments require a published form version';
1799
+ end if;
1800
+
1801
+ if new."is_active" and (
1802
+ "v_vehicle_status" not in ('ready', 'active', 'out_of_service')
1803
+ or "v_vehicle_archived_at" is not null
1804
+ ) then
1805
+ raise exception using
1806
+ errcode = '23514',
1807
+ message = 'Active Fleet assignments require a non-archived operational vehicle';
1808
+ end if;
1809
+
1810
+ if new."is_active"
1811
+ and "v_template_vehicle_type" is not null
1812
+ and "v_template_vehicle_type" <> "v_vehicle_type" then
1813
+ raise exception using
1814
+ errcode = '23514',
1815
+ message = 'Fleet assignment vehicle type does not match its form template';
1816
+ end if;
1817
+
1818
+ if new."is_active" and new."assignee_user_id" is not null and not exists (
1819
+ select 1
1820
+ from "public"."user" as "employee"
1821
+ where "employee"."id" = new."assignee_user_id"
1822
+ and "employee"."company_id" = new."company_id"
1823
+ and "employee"."is_current_employee"
1824
+ and not "employee"."is_hidden"
1825
+ ) then
1826
+ raise exception using
1827
+ errcode = '23514',
1828
+ message = 'Fleet assignments require a current, visible employee';
1829
+ end if;
1830
+
1831
+ return new;
1832
+ end;
1833
+ $$;
1834
+
1835
+ alter function "public"."fleet_validate_form_assignment"() owner to "postgres";
1836
+
1837
+ create trigger "fleet_validate_form_assignment_trigger"
1838
+ before insert or update on "public"."fleet_form_assignment"
1839
+ for each row execute function "public"."fleet_validate_form_assignment"();
1840
+
1841
+ create or replace function "public"."fleet_prepare_inspection_answer"()
1842
+ returns trigger
1843
+ language plpgsql
1844
+ security definer
1845
+ set search_path = ''
1846
+ as $$
1847
+ declare
1848
+ "v_inspection_status" text;
1849
+ "v_template_version_id" uuid;
1850
+ "v_question" "public"."fleet_form_question"%rowtype;
1851
+ begin
1852
+ if tg_op = 'DELETE' then
1853
+ if not exists (
1854
+ select 1
1855
+ from "public"."fleet_organization" as "organization"
1856
+ where "organization"."company_id" = old."company_id"
1857
+ ) then
1858
+ return old;
1859
+ end if;
1860
+ select "inspection"."status"
1861
+ into "v_inspection_status"
1862
+ from "public"."fleet_inspection" as "inspection"
1863
+ where "inspection"."id" = old."inspection_id"
1864
+ and "inspection"."company_id" = old."company_id"
1865
+ for update;
1866
+
1867
+ if "v_inspection_status" is distinct from 'draft' then
1868
+ raise exception using
1869
+ errcode = '23514',
1870
+ message = 'Answers on submitted or voided Fleet inspections are immutable';
1871
+ end if;
1872
+ return old;
1873
+ end if;
1874
+
1875
+ if tg_op = 'UPDATE' and (
1876
+ new."company_id" is distinct from old."company_id"
1877
+ or new."inspection_id" is distinct from old."inspection_id"
1878
+ or new."question_id" is distinct from old."question_id"
1879
+ or new."answered_by" is distinct from old."answered_by"
1880
+ ) then
1881
+ raise exception using
1882
+ errcode = '23514',
1883
+ message = 'Fleet answer identity and author are immutable';
1884
+ end if;
1885
+
1886
+ select "inspection"."status", "inspection"."template_version_id"
1887
+ into "v_inspection_status", "v_template_version_id"
1888
+ from "public"."fleet_inspection" as "inspection"
1889
+ where "inspection"."id" = new."inspection_id"
1890
+ and "inspection"."company_id" = new."company_id"
1891
+ for update;
1892
+
1893
+ if "v_inspection_status" is distinct from 'draft' then
1894
+ raise exception using
1895
+ errcode = '23514',
1896
+ message = 'Only draft Fleet inspections may receive or change answers';
1897
+ end if;
1898
+
1899
+ select "question".*
1900
+ into "v_question"
1901
+ from "public"."fleet_form_question" as "question"
1902
+ where "question"."id" = new."question_id"
1903
+ and "question"."company_id" = new."company_id"
1904
+ and "question"."template_version_id" = "v_template_version_id";
1905
+
1906
+ if not found then
1907
+ raise exception using
1908
+ errcode = '23514',
1909
+ message = 'Fleet answer question does not belong to the inspection form version';
1910
+ end if;
1911
+
1912
+ -- Validate the JSON shape at write time. Submission must never treat an
1913
+ -- arbitrary object/array as a passing answer merely because it differs
1914
+ -- from failure_answer.
1915
+ if "v_question"."response_type" in ('pass_fail', 'yes_no') then
1916
+ if new."answer_value" is distinct from "v_question"."pass_answer"
1917
+ and new."answer_value" is distinct from "v_question"."failure_answer" then
1918
+ raise exception using
1919
+ errcode = '23514',
1920
+ message = 'Fleet pass/fail and yes/no answers must match an allowed answer';
1921
+ end if;
1922
+ elsif "v_question"."response_type" in ('number', 'currency') then
1923
+ if jsonb_typeof(new."answer_value") <> 'number' then
1924
+ raise exception using
1925
+ errcode = '23514',
1926
+ message = 'Fleet numeric answers must be JSON numbers';
1927
+ end if;
1928
+ if "v_question"."response_type" = 'currency'
1929
+ and (new."answer_value" #>> '{}')::numeric < 0 then
1930
+ raise exception using
1931
+ errcode = '23514',
1932
+ message = 'Fleet currency answers cannot be negative';
1933
+ end if;
1934
+ elsif "v_question"."response_type" in (
1935
+ 'text', 'date', 'time', 'datetime', 'signature'
1936
+ ) then
1937
+ if jsonb_typeof(new."answer_value") <> 'string'
1938
+ or nullif(btrim(new."answer_value" #>> '{}'), '') is null then
1939
+ raise exception using
1940
+ errcode = '23514',
1941
+ message = 'Fleet text, date/time, and signature answers must be non-empty strings';
1942
+ end if;
1943
+ elsif "v_question"."response_type" in ('single_choice', 'single_select') then
1944
+ if jsonb_typeof(new."answer_value") <> 'string'
1945
+ or not "v_question"."options" @> jsonb_build_array(new."answer_value") then
1946
+ raise exception using
1947
+ errcode = '23514',
1948
+ message = 'Fleet single-choice answer must be one of the configured options';
1949
+ end if;
1950
+ elsif "v_question"."response_type" = 'select_or_text' then
1951
+ if jsonb_typeof(new."answer_value") <> 'string'
1952
+ or nullif(btrim(new."answer_value" #>> '{}'), '') is null
1953
+ or (
1954
+ not coalesce(
1955
+ ("v_question"."configuration" ->> 'allows_custom_option')::boolean,
1956
+ false
1957
+ )
1958
+ and not "v_question"."options" @> jsonb_build_array(new."answer_value")
1959
+ ) then
1960
+ raise exception using
1961
+ errcode = '23514',
1962
+ message = 'Fleet select-or-text answer is not an allowed option';
1963
+ end if;
1964
+ elsif "v_question"."response_type" in ('multi_choice', 'multi_select') then
1965
+ if jsonb_typeof(new."answer_value") <> 'array' then
1966
+ raise exception using
1967
+ errcode = '23514',
1968
+ message = 'Fleet multi-choice answers must be a non-empty JSON array';
1969
+ end if;
1970
+ if jsonb_array_length(new."answer_value") = 0 then
1971
+ raise exception using
1972
+ errcode = '23514',
1973
+ message = 'Fleet multi-choice answers must be a non-empty JSON array';
1974
+ end if;
1975
+ if exists (
1976
+ select 1
1977
+ from jsonb_array_elements(new."answer_value") as "choice"("value")
1978
+ where jsonb_typeof("choice"."value") <> 'string'
1979
+ or not "v_question"."options" @> jsonb_build_array("choice"."value")
1980
+ ) then
1981
+ raise exception using
1982
+ errcode = '23514',
1983
+ message = 'Fleet multi-choice answer contains an invalid option';
1984
+ end if;
1985
+ end if;
1986
+
1987
+ new."question_snapshot" = jsonb_build_object(
1988
+ 'id', "v_question"."id",
1989
+ 'question_key', "v_question"."question_key",
1990
+ 'prompt', "v_question"."prompt",
1991
+ 'help_text', "v_question"."help_text",
1992
+ 'response_type', "v_question"."response_type",
1993
+ 'is_required', "v_question"."is_required",
1994
+ 'options', "v_question"."options",
1995
+ 'pass_answer', "v_question"."pass_answer",
1996
+ 'failure_answer', "v_question"."failure_answer",
1997
+ 'pass_label', "v_question"."pass_label",
1998
+ 'failure_label', "v_question"."failure_label",
1999
+ 'creates_issue_on_failure', "v_question"."creates_issue_on_failure",
2000
+ 'failure_severity', "v_question"."failure_severity",
2001
+ 'failure_title', "v_question"."failure_title",
2002
+ 'requires_comment_on_failure', "v_question"."requires_comment_on_failure",
2003
+ 'requires_photo_on_failure', "v_question"."requires_photo_on_failure",
2004
+ 'marks_vehicle_unsafe_on_failure', "v_question"."marks_vehicle_unsafe_on_failure",
2005
+ 'allows_vehicle_out_of_service_override',
2006
+ "v_question"."allows_vehicle_out_of_service_override",
2007
+ 'configuration', "v_question"."configuration"
2008
+ );
2009
+ new."is_failure" = (
2010
+ "v_question"."failure_answer" is not null
2011
+ and new."answer_value" = "v_question"."failure_answer"
2012
+ );
2013
+ new."vehicle_out_of_service_override" = (
2014
+ new."is_failure"
2015
+ and new."vehicle_out_of_service_override"
2016
+ and "v_question"."allows_vehicle_out_of_service_override"
2017
+ );
2018
+ new."marks_vehicle_unsafe" = (
2019
+ new."is_failure"
2020
+ and (
2021
+ "v_question"."marks_vehicle_unsafe_on_failure"
2022
+ or new."vehicle_out_of_service_override"
2023
+ )
2024
+ );
2025
+ new."answered_at" = now();
2026
+
2027
+ if new."is_failure"
2028
+ and "v_question"."requires_comment_on_failure"
2029
+ and nullif(btrim(new."comment"), '') is null then
2030
+ raise exception using
2031
+ errcode = '23514',
2032
+ message = 'This failed Fleet answer requires a comment';
2033
+ end if;
2034
+
2035
+ return new;
2036
+ end;
2037
+ $$;
2038
+
2039
+ alter function "public"."fleet_prepare_inspection_answer"() owner to "postgres";
2040
+
2041
+ create trigger "fleet_prepare_inspection_answer_trigger"
2042
+ before insert or update or delete on "public"."fleet_inspection_answer"
2043
+ for each row execute function "public"."fleet_prepare_inspection_answer"();
2044
+
2045
+ create or replace function "public"."fleet_question_condition_met"(
2046
+ "p_company_id" uuid,
2047
+ "p_inspection_id" uuid,
2048
+ "p_condition" jsonb
2049
+ )
2050
+ returns boolean
2051
+ language plpgsql
2052
+ stable
2053
+ security definer
2054
+ set search_path = ''
2055
+ as $$
2056
+ declare
2057
+ "v_answer" jsonb;
2058
+ "v_operator" text;
2059
+ begin
2060
+ if "p_condition" is null or jsonb_typeof("p_condition") <> 'object' then
2061
+ return false;
2062
+ end if;
2063
+
2064
+ "v_operator" = "p_condition" ->> 'operator';
2065
+ select "answer"."answer_value"
2066
+ into "v_answer"
2067
+ from "public"."fleet_inspection_answer" as "answer"
2068
+ join "public"."fleet_form_question" as "question"
2069
+ on "question"."id" = "answer"."question_id"
2070
+ and "question"."company_id" = "answer"."company_id"
2071
+ where "answer"."company_id" = "p_company_id"
2072
+ and "answer"."inspection_id" = "p_inspection_id"
2073
+ and "question"."question_key" = "p_condition" ->> 'question_key';
2074
+
2075
+ if not found then
2076
+ return false;
2077
+ end if;
2078
+
2079
+ if "v_operator" = 'equals' then
2080
+ return "v_answer" = "p_condition" -> 'value';
2081
+ end if;
2082
+ if "v_operator" = 'not_equals' then
2083
+ return "v_answer" <> "p_condition" -> 'value';
2084
+ end if;
2085
+ return false;
2086
+ end;
2087
+ $$;
2088
+
2089
+ alter function "public"."fleet_question_condition_met"(uuid, uuid, jsonb)
2090
+ owner to "postgres";
2091
+
2092
+ create or replace function "public"."fleet_prepare_inspection"()
2093
+ returns trigger
2094
+ language plpgsql
2095
+ security definer
2096
+ set search_path = ''
2097
+ as $$
2098
+ declare
2099
+ "v_version_status" text;
2100
+ "v_snapshot" jsonb;
2101
+ "v_has_failure" boolean;
2102
+ "v_has_unsafe_failure" boolean;
2103
+ begin
2104
+ if tg_op = 'DELETE' then
2105
+ if not exists (
2106
+ select 1
2107
+ from "public"."fleet_organization" as "organization"
2108
+ where "organization"."company_id" = old."company_id"
2109
+ ) then
2110
+ return old;
2111
+ end if;
2112
+ if old."status" <> 'draft' then
2113
+ raise exception using
2114
+ errcode = '23514',
2115
+ message = 'Submitted or voided Fleet inspections cannot be deleted';
2116
+ end if;
2117
+ return old;
2118
+ end if;
2119
+
2120
+ if tg_op = 'INSERT' then
2121
+ if new."status" <> 'draft' then
2122
+ raise exception using
2123
+ errcode = '23514',
2124
+ message = 'Fleet inspections must be created as drafts';
2125
+ end if;
2126
+
2127
+ select "version"."status", "version"."definition_snapshot"
2128
+ into "v_version_status", "v_snapshot"
2129
+ from "public"."fleet_form_template_version" as "version"
2130
+ where "version"."id" = new."template_version_id"
2131
+ and "version"."company_id" = new."company_id";
2132
+
2133
+ if "v_version_status" is distinct from 'published' then
2134
+ raise exception using
2135
+ errcode = '23514',
2136
+ message = 'Fleet inspections require a published form version';
2137
+ end if;
2138
+
2139
+ if not exists (
2140
+ select 1
2141
+ from "public"."fleet_vehicle" as "vehicle"
2142
+ where "vehicle"."id" = new."vehicle_id"
2143
+ and "vehicle"."company_id" = new."company_id"
2144
+ and "vehicle"."status" in ('ready', 'active', 'out_of_service')
2145
+ and "vehicle"."archived_at" is null
2146
+ ) then
2147
+ raise exception using
2148
+ errcode = '23514',
2149
+ message = 'Fleet inspections require a ready/active or out-of-service vehicle';
2150
+ end if;
2151
+
2152
+ if not exists (
2153
+ select 1
2154
+ from "public"."user" as "employee"
2155
+ where "employee"."id" = new."inspector_user_id"
2156
+ and "employee"."company_id" = new."company_id"
2157
+ and "employee"."is_current_employee"
2158
+ and not "employee"."is_hidden"
2159
+ ) then
2160
+ raise exception using
2161
+ errcode = '23514',
2162
+ message = 'Fleet inspections require a current, visible employee';
2163
+ end if;
2164
+
2165
+ if new."assignment_id" is not null and not exists (
2166
+ select 1
2167
+ from "public"."fleet_form_assignment" as "assignment"
2168
+ where "assignment"."id" = new."assignment_id"
2169
+ and "assignment"."company_id" = new."company_id"
2170
+ and "assignment"."template_version_id" = new."template_version_id"
2171
+ and "assignment"."vehicle_id" = new."vehicle_id"
2172
+ and "assignment"."is_active"
2173
+ and "assignment"."starts_on" <= new."inspection_date"
2174
+ and ("assignment"."ends_on" is null
2175
+ or "assignment"."ends_on" >= new."inspection_date")
2176
+ and ("assignment"."assignee_user_id" is null
2177
+ or "assignment"."assignee_user_id" = new."inspector_user_id")
2178
+ ) then
2179
+ raise exception using
2180
+ errcode = '23514',
2181
+ message = 'Fleet inspection does not match its active assignment';
2182
+ end if;
2183
+
2184
+ new."template_snapshot" = "v_snapshot";
2185
+ new."result" = null;
2186
+ new."is_vehicle_safe" = true;
2187
+ new."submitted_at" = null;
2188
+ new."voided_at" = null;
2189
+ new."void_reason" = null;
2190
+ return new;
2191
+ end if;
2192
+
2193
+ if old."status" <> 'draft' then
2194
+ raise exception using
2195
+ errcode = '23514',
2196
+ message = 'Submitted and voided Fleet inspections are immutable';
2197
+ end if;
2198
+
2199
+ if new."company_id" is distinct from old."company_id"
2200
+ or new."vehicle_id" is distinct from old."vehicle_id"
2201
+ or new."assignment_id" is distinct from old."assignment_id"
2202
+ or new."template_version_id" is distinct from old."template_version_id"
2203
+ or new."inspector_user_id" is distinct from old."inspector_user_id"
2204
+ or new."created_by" is distinct from old."created_by"
2205
+ or new."inspection_date" is distinct from old."inspection_date"
2206
+ or new."template_snapshot" is distinct from old."template_snapshot"
2207
+ or new."started_at" is distinct from old."started_at" then
2208
+ raise exception using
2209
+ errcode = '23514',
2210
+ message = 'Fleet inspection identity and form snapshot are immutable';
2211
+ end if;
2212
+
2213
+ if new."status" = 'draft' then
2214
+ new."result" = null;
2215
+ new."is_vehicle_safe" = true;
2216
+ new."submitted_at" = null;
2217
+ new."voided_at" = null;
2218
+ new."void_reason" = null;
2219
+ return new;
2220
+ end if;
2221
+
2222
+ if new."status" = 'voided' then
2223
+ if nullif(btrim(new."void_reason"), '') is null then
2224
+ raise exception using
2225
+ errcode = '23514',
2226
+ message = 'Voiding a Fleet inspection requires a reason';
2227
+ end if;
2228
+ new."result" = null;
2229
+ new."is_vehicle_safe" = true;
2230
+ new."submitted_at" = null;
2231
+ new."voided_at" = coalesce(new."voided_at", now());
2232
+ return new;
2233
+ end if;
2234
+
2235
+ if new."status" <> 'submitted' then
2236
+ raise exception using
2237
+ errcode = '23514',
2238
+ message = 'Unsupported Fleet inspection status transition';
2239
+ end if;
2240
+
2241
+ if exists (
2242
+ select 1
2243
+ from "public"."fleet_form_question" as "question"
2244
+ where "question"."company_id" = new."company_id"
2245
+ and "question"."template_version_id" = new."template_version_id"
2246
+ and (
2247
+ not ("question"."configuration" ? 'visible_when')
2248
+ or "public"."fleet_question_condition_met"(
2249
+ new."company_id",
2250
+ new."id",
2251
+ "question"."configuration" -> 'visible_when'
2252
+ )
2253
+ )
2254
+ and (
2255
+ "question"."is_required"
2256
+ or (
2257
+ "question"."configuration" ? 'required_when'
2258
+ and "public"."fleet_question_condition_met"(
2259
+ new."company_id",
2260
+ new."id",
2261
+ "question"."configuration" -> 'required_when'
2262
+ )
2263
+ )
2264
+ )
2265
+ and not exists (
2266
+ select 1
2267
+ from "public"."fleet_inspection_answer" as "answer"
2268
+ where "answer"."company_id" = new."company_id"
2269
+ and "answer"."inspection_id" = new."id"
2270
+ and "answer"."question_id" = "question"."id"
2271
+ )
2272
+ ) then
2273
+ raise exception using
2274
+ errcode = '23514',
2275
+ message = 'All required Fleet inspection questions must be answered';
2276
+ end if;
2277
+
2278
+ if exists (
2279
+ select 1
2280
+ from "public"."fleet_inspection_answer" as "answer"
2281
+ join "public"."fleet_form_question" as "question"
2282
+ on "question"."id" = "answer"."question_id"
2283
+ and "question"."company_id" = "answer"."company_id"
2284
+ where "answer"."company_id" = new."company_id"
2285
+ and "answer"."inspection_id" = new."id"
2286
+ and "question"."configuration" ? 'minimum_value'
2287
+ and jsonb_typeof("answer"."answer_value") = 'number'
2288
+ and ("answer"."answer_value" #>> '{}')::numeric
2289
+ < ("question"."configuration" ->> 'minimum_value')::numeric
2290
+ ) then
2291
+ raise exception using
2292
+ errcode = '23514',
2293
+ message = 'A Fleet inspection numeric answer is below its configured minimum';
2294
+ end if;
2295
+
2296
+ if exists (
2297
+ select 1
2298
+ from "public"."fleet_inspection_answer" as "answer"
2299
+ join "public"."fleet_form_question" as "question"
2300
+ on "question"."id" = "answer"."question_id"
2301
+ and "question"."company_id" = "answer"."company_id"
2302
+ cross join lateral (
2303
+ select ("prior_answer"."answer_value" #>> '{}')::numeric as "prior_value"
2304
+ from "public"."fleet_inspection" as "prior_inspection"
2305
+ join "public"."fleet_inspection_answer" as "prior_answer"
2306
+ on "prior_answer"."inspection_id" = "prior_inspection"."id"
2307
+ and "prior_answer"."company_id" = "prior_inspection"."company_id"
2308
+ where "prior_inspection"."company_id" = new."company_id"
2309
+ and "prior_inspection"."vehicle_id" = new."vehicle_id"
2310
+ and "prior_inspection"."id" <> new."id"
2311
+ and "prior_inspection"."status" = 'submitted'
2312
+ and "prior_inspection"."inspection_date" <= new."inspection_date"
2313
+ and "prior_inspection"."template_snapshot" ->> 'form_kind'
2314
+ = ("question"."configuration"
2315
+ -> 'minimum_from_prior_inspection' ->> 'form_kind')
2316
+ and "prior_answer"."question_snapshot" ->> 'question_key'
2317
+ = ("question"."configuration"
2318
+ -> 'minimum_from_prior_inspection' ->> 'question_key')
2319
+ and jsonb_typeof("prior_answer"."answer_value") = 'number'
2320
+ order by "prior_inspection"."submitted_at" desc
2321
+ limit 1
2322
+ ) as "prior"
2323
+ where "answer"."company_id" = new."company_id"
2324
+ and "answer"."inspection_id" = new."id"
2325
+ and "question"."configuration" ? 'minimum_from_prior_inspection'
2326
+ and jsonb_typeof("answer"."answer_value") = 'number'
2327
+ and ("answer"."answer_value" #>> '{}')::numeric < "prior"."prior_value"
2328
+ ) then
2329
+ raise exception using
2330
+ errcode = '23514',
2331
+ message = 'A Fleet inspection reading is below the latest configured prior reading';
2332
+ end if;
2333
+
2334
+ if exists (
2335
+ select 1
2336
+ from "public"."fleet_inspection_answer" as "answer"
2337
+ where "answer"."company_id" = new."company_id"
2338
+ and "answer"."inspection_id" = new."id"
2339
+ and "answer"."is_failure"
2340
+ and coalesce(
2341
+ ("answer"."question_snapshot" ->> 'requires_photo_on_failure')::boolean,
2342
+ false
2343
+ )
2344
+ and not exists (
2345
+ select 1
2346
+ from "public"."fleet_media" as "media"
2347
+ where "media"."company_id" = new."company_id"
2348
+ and "media"."answer_id" = "answer"."id"
2349
+ and "media"."media_type" in ('photo', 'video')
2350
+ )
2351
+ ) then
2352
+ raise exception using
2353
+ errcode = '23514',
2354
+ message = 'Every failed Fleet answer requiring evidence needs a photo or video';
2355
+ end if;
2356
+
2357
+ if exists (
2358
+ select 1
2359
+ from "public"."fleet_form_question" as "question"
2360
+ join "public"."fleet_inspection_answer" as "answer"
2361
+ on "answer"."question_id" = "question"."id"
2362
+ and "answer"."company_id" = "question"."company_id"
2363
+ and "answer"."inspection_id" = new."id"
2364
+ where "question"."company_id" = new."company_id"
2365
+ and "question"."template_version_id" = new."template_version_id"
2366
+ and "question"."response_type" = 'photo'
2367
+ and (
2368
+ not ("question"."configuration" ? 'visible_when')
2369
+ or "public"."fleet_question_condition_met"(
2370
+ new."company_id",
2371
+ new."id",
2372
+ "question"."configuration" -> 'visible_when'
2373
+ )
2374
+ )
2375
+ and (
2376
+ "question"."is_required"
2377
+ or (
2378
+ "question"."configuration" ? 'required_when'
2379
+ and "public"."fleet_question_condition_met"(
2380
+ new."company_id",
2381
+ new."id",
2382
+ "question"."configuration" -> 'required_when'
2383
+ )
2384
+ )
2385
+ )
2386
+ and not exists (
2387
+ select 1
2388
+ from "public"."fleet_media" as "media"
2389
+ where "media"."company_id" = new."company_id"
2390
+ and "media"."answer_id" = "answer"."id"
2391
+ and "media"."media_type" in ('photo', 'video')
2392
+ )
2393
+ ) then
2394
+ raise exception using
2395
+ errcode = '23514',
2396
+ message = 'Required Fleet photo questions need uploaded photo or video evidence';
2397
+ end if;
2398
+
2399
+ select
2400
+ coalesce(bool_or("answer"."is_failure"), false),
2401
+ coalesce(bool_or("answer"."marks_vehicle_unsafe"), false)
2402
+ into "v_has_failure", "v_has_unsafe_failure"
2403
+ from "public"."fleet_inspection_answer" as "answer"
2404
+ where "answer"."company_id" = new."company_id"
2405
+ and "answer"."inspection_id" = new."id";
2406
+
2407
+ new."result" = case when "v_has_failure" then 'failed' else 'passed' end;
2408
+ new."is_vehicle_safe" = not "v_has_unsafe_failure";
2409
+ new."submitted_at" = coalesce(new."submitted_at", now());
2410
+ new."voided_at" = null;
2411
+ new."void_reason" = null;
2412
+
2413
+ update "public"."fleet_vehicle" as "vehicle"
2414
+ set "current_odometer" = case
2415
+ when new."odometer" is null then "vehicle"."current_odometer"
2416
+ else greatest(coalesce("vehicle"."current_odometer", 0), new."odometer")
2417
+ end,
2418
+ "current_engine_hours" = case
2419
+ when new."engine_hours" is null then "vehicle"."current_engine_hours"
2420
+ else greatest(coalesce("vehicle"."current_engine_hours", 0), new."engine_hours")
2421
+ end,
2422
+ "status" = case
2423
+ when "v_has_unsafe_failure" then 'out_of_service'
2424
+ else "vehicle"."status"
2425
+ end,
2426
+ "out_of_service_at" = case
2427
+ when "v_has_unsafe_failure" then coalesce("vehicle"."out_of_service_at", now())
2428
+ else "vehicle"."out_of_service_at"
2429
+ end,
2430
+ "out_of_service_reason" = case
2431
+ when "v_has_unsafe_failure" then
2432
+ 'Unsafe Fleet inspection ' || new."id"::text
2433
+ else "vehicle"."out_of_service_reason"
2434
+ end
2435
+ where "vehicle"."id" = new."vehicle_id"
2436
+ and "vehicle"."company_id" = new."company_id";
2437
+
2438
+ insert into "public"."fleet_issue" (
2439
+ "company_id", "vehicle_id", "source_inspection_answer_id",
2440
+ "title", "description", "severity", "status", "is_vehicle_unsafe",
2441
+ "reported_by"
2442
+ )
2443
+ select
2444
+ new."company_id",
2445
+ new."vehicle_id",
2446
+ "answer"."id",
2447
+ coalesce(
2448
+ nullif("answer"."question_snapshot" ->> 'failure_title', ''),
2449
+ "answer"."question_snapshot" ->> 'prompt',
2450
+ 'Failed inspection item'
2451
+ ),
2452
+ case
2453
+ when nullif(btrim("answer"."comment"), '') is null then
2454
+ 'Created automatically from Fleet inspection ' || new."id"::text
2455
+ else
2456
+ 'Created automatically from Fleet inspection ' || new."id"::text
2457
+ || '. Crew comment: ' || btrim("answer"."comment")
2458
+ end,
2459
+ coalesce(
2460
+ nullif("answer"."question_snapshot" ->> 'failure_severity', ''),
2461
+ case when "answer"."marks_vehicle_unsafe" then 'critical' else 'major' end
2462
+ ),
2463
+ 'open',
2464
+ "answer"."marks_vehicle_unsafe",
2465
+ "answer"."answered_by"
2466
+ from "public"."fleet_inspection_answer" as "answer"
2467
+ where "answer"."company_id" = new."company_id"
2468
+ and "answer"."inspection_id" = new."id"
2469
+ and "answer"."is_failure"
2470
+ and coalesce(
2471
+ ("answer"."question_snapshot" ->> 'creates_issue_on_failure')::boolean,
2472
+ false
2473
+ )
2474
+ on conflict ("source_inspection_answer_id")
2475
+ where "source_inspection_answer_id" is not null
2476
+ do nothing;
2477
+
2478
+ return new;
2479
+ end;
2480
+ $$;
2481
+
2482
+ alter function "public"."fleet_prepare_inspection"() owner to "postgres";
2483
+
2484
+ create trigger "fleet_prepare_inspection_trigger"
2485
+ before insert or update or delete on "public"."fleet_inspection"
2486
+ for each row execute function "public"."fleet_prepare_inspection"();
2487
+
2488
+ create or replace function "public"."fleet_guard_inspection_media"()
2489
+ returns trigger
2490
+ language plpgsql
2491
+ security definer
2492
+ set search_path = ''
2493
+ as $$
2494
+ declare
2495
+ "v_status" text;
2496
+ "v_inspection_id" uuid;
2497
+ "v_issue_id" uuid;
2498
+ "v_repair_order_id" uuid;
2499
+ "v_service_record_id" uuid;
2500
+ "v_company_id" uuid;
2501
+ begin
2502
+ if tg_op = 'DELETE' then
2503
+ "v_company_id" = old."company_id";
2504
+ if old."inspection_id" is not null then
2505
+ "v_inspection_id" = old."inspection_id";
2506
+ elsif old."answer_id" is not null then
2507
+ select "answer"."inspection_id"
2508
+ into "v_inspection_id"
2509
+ from "public"."fleet_inspection_answer" as "answer"
2510
+ where "answer"."id" = old."answer_id"
2511
+ and "answer"."company_id" = old."company_id";
2512
+ end if;
2513
+ if old."issue_id" is not null then
2514
+ "v_issue_id" = old."issue_id";
2515
+ elsif old."issue_comment_id" is not null then
2516
+ select "comment"."issue_id"
2517
+ into "v_issue_id"
2518
+ from "public"."fleet_issue_comment" as "comment"
2519
+ where "comment"."id" = old."issue_comment_id"
2520
+ and "comment"."company_id" = old."company_id";
2521
+ end if;
2522
+ "v_repair_order_id" = old."repair_order_id";
2523
+ "v_service_record_id" = old."service_record_id";
2524
+ else
2525
+ "v_company_id" = new."company_id";
2526
+ if new."inspection_id" is not null then
2527
+ "v_inspection_id" = new."inspection_id";
2528
+ elsif new."answer_id" is not null then
2529
+ select "answer"."inspection_id"
2530
+ into "v_inspection_id"
2531
+ from "public"."fleet_inspection_answer" as "answer"
2532
+ where "answer"."id" = new."answer_id"
2533
+ and "answer"."company_id" = new."company_id";
2534
+ end if;
2535
+ if new."issue_id" is not null then
2536
+ "v_issue_id" = new."issue_id";
2537
+ elsif new."issue_comment_id" is not null then
2538
+ select "comment"."issue_id"
2539
+ into "v_issue_id"
2540
+ from "public"."fleet_issue_comment" as "comment"
2541
+ where "comment"."id" = new."issue_comment_id"
2542
+ and "comment"."company_id" = new."company_id";
2543
+ end if;
2544
+ "v_repair_order_id" = new."repair_order_id";
2545
+ "v_service_record_id" = new."service_record_id";
2546
+ end if;
2547
+
2548
+ if tg_op = 'DELETE' and not exists (
2549
+ select 1
2550
+ from "public"."fleet_organization" as "organization"
2551
+ where "organization"."company_id" = "v_company_id"
2552
+ ) then
2553
+ return old;
2554
+ end if;
2555
+
2556
+ if "v_inspection_id" is not null then
2557
+ select "inspection"."status"
2558
+ into "v_status"
2559
+ from "public"."fleet_inspection" as "inspection"
2560
+ where "inspection"."id" = "v_inspection_id"
2561
+ and "inspection"."company_id" = "v_company_id"
2562
+ for update;
2563
+
2564
+ if "v_status" is distinct from 'draft' then
2565
+ raise exception using
2566
+ errcode = '23514',
2567
+ message = 'Evidence on a submitted or voided Fleet inspection is immutable';
2568
+ end if;
2569
+ end if;
2570
+
2571
+ if "v_issue_id" is not null then
2572
+ select "issue"."status"
2573
+ into "v_status"
2574
+ from "public"."fleet_issue" as "issue"
2575
+ where "issue"."id" = "v_issue_id"
2576
+ and "issue"."company_id" = "v_company_id"
2577
+ for update;
2578
+
2579
+ if "v_status" in ('resolved', 'closed') then
2580
+ raise exception using
2581
+ errcode = '23514',
2582
+ message = 'Evidence on a resolved or closed Fleet issue is immutable';
2583
+ end if;
2584
+ end if;
2585
+
2586
+ if "v_repair_order_id" is not null then
2587
+ select "repair_order"."status"
2588
+ into "v_status"
2589
+ from "public"."fleet_repair_order" as "repair_order"
2590
+ where "repair_order"."id" = "v_repair_order_id"
2591
+ and "repair_order"."company_id" = "v_company_id"
2592
+ for update;
2593
+
2594
+ if "v_status" in ('completed', 'cancelled') then
2595
+ raise exception using
2596
+ errcode = '23514',
2597
+ message = 'Evidence on a completed or cancelled Fleet repair is immutable';
2598
+ end if;
2599
+ end if;
2600
+
2601
+ if "v_service_record_id" is not null and tg_op <> 'INSERT' then
2602
+ raise exception using
2603
+ errcode = '23514',
2604
+ message = 'Fleet service-record evidence is immutable after upload';
2605
+ end if;
2606
+
2607
+ if tg_op = 'UPDATE' and (
2608
+ new."company_id" is distinct from old."company_id"
2609
+ or new."inspection_id" is distinct from old."inspection_id"
2610
+ or new."answer_id" is distinct from old."answer_id"
2611
+ or new."issue_id" is distinct from old."issue_id"
2612
+ or new."issue_comment_id" is distinct from old."issue_comment_id"
2613
+ or new."repair_order_id" is distinct from old."repair_order_id"
2614
+ or new."service_record_id" is distinct from old."service_record_id"
2615
+ or new."storage_bucket" is distinct from old."storage_bucket"
2616
+ or new."storage_object_path" is distinct from old."storage_object_path"
2617
+ or new."uploaded_by" is distinct from old."uploaded_by"
2618
+ ) then
2619
+ raise exception using
2620
+ errcode = '23514',
2621
+ message = 'Fleet media parent, company, and uploader are immutable';
2622
+ end if;
2623
+
2624
+ if tg_op = 'DELETE' then
2625
+ return old;
2626
+ end if;
2627
+ return new;
2628
+ end;
2629
+ $$;
2630
+
2631
+ alter function "public"."fleet_guard_inspection_media"() owner to "postgres";
2632
+
2633
+ create trigger "fleet_guard_inspection_media_trigger"
2634
+ before insert or update or delete on "public"."fleet_media"
2635
+ for each row execute function "public"."fleet_guard_inspection_media"();
2636
+
2637
+ create or replace function "public"."fleet_prepare_issue"()
2638
+ returns trigger
2639
+ language plpgsql
2640
+ security definer
2641
+ set search_path = ''
2642
+ as $$
2643
+ begin
2644
+ if tg_op = 'INSERT' then
2645
+ if new."status" <> 'open' then
2646
+ raise exception using
2647
+ errcode = '23514',
2648
+ message = 'Fleet issues must be created with open status';
2649
+ end if;
2650
+ if new."source_inspection_answer_id" is not null and not exists (
2651
+ select 1
2652
+ from "public"."fleet_inspection_answer" as "answer"
2653
+ join "public"."fleet_inspection" as "inspection"
2654
+ on "inspection"."id" = "answer"."inspection_id"
2655
+ and "inspection"."company_id" = "answer"."company_id"
2656
+ where "answer"."id" = new."source_inspection_answer_id"
2657
+ and "answer"."company_id" = new."company_id"
2658
+ and "inspection"."vehicle_id" = new."vehicle_id"
2659
+ ) then
2660
+ raise exception using
2661
+ errcode = '23514',
2662
+ message = 'Fleet issue source answer belongs to a different vehicle';
2663
+ end if;
2664
+ if new."assigned_to_user_id" is not null and not exists (
2665
+ select 1
2666
+ from "public"."user" as "employee"
2667
+ where "employee"."id" = new."assigned_to_user_id"
2668
+ and "employee"."company_id" = new."company_id"
2669
+ and "employee"."is_current_employee"
2670
+ and not "employee"."is_hidden"
2671
+ ) then
2672
+ raise exception using
2673
+ errcode = '23514',
2674
+ message = 'Fleet issues can only be assigned to a current, visible employee';
2675
+ end if;
2676
+ new."resolved_at" = null;
2677
+ new."resolved_by" = null;
2678
+ new."resolution_note" = null;
2679
+ return new;
2680
+ end if;
2681
+
2682
+ if new."company_id" is distinct from old."company_id"
2683
+ or new."vehicle_id" is distinct from old."vehicle_id"
2684
+ or new."source_inspection_answer_id" is distinct from old."source_inspection_answer_id"
2685
+ or new."reported_by" is distinct from old."reported_by" then
2686
+ raise exception using
2687
+ errcode = '23514',
2688
+ message = 'Fleet issue tenant, vehicle, source, and reporter are immutable';
2689
+ end if;
2690
+
2691
+ if old."is_vehicle_unsafe" and not new."is_vehicle_unsafe" then
2692
+ raise exception using
2693
+ errcode = '23514',
2694
+ message = 'An unsafe Fleet issue must remain marked unsafe for its history';
2695
+ end if;
2696
+
2697
+ if new."assigned_to_user_id" is not null and not exists (
2698
+ select 1
2699
+ from "public"."user" as "employee"
2700
+ where "employee"."id" = new."assigned_to_user_id"
2701
+ and "employee"."company_id" = new."company_id"
2702
+ and "employee"."is_current_employee"
2703
+ and not "employee"."is_hidden"
2704
+ ) then
2705
+ raise exception using
2706
+ errcode = '23514',
2707
+ message = 'Fleet issues can only be assigned to a current, visible employee';
2708
+ end if;
2709
+
2710
+ if new."status" in ('resolved', 'closed') then
2711
+ new."resolved_at" = coalesce(new."resolved_at", now());
2712
+ new."resolved_by" = coalesce(new."resolved_by", auth.uid());
2713
+ if nullif(btrim(new."resolution_note"), '') is null then
2714
+ raise exception using
2715
+ errcode = '23514',
2716
+ message = 'Resolving or closing a Fleet issue requires a resolution note';
2717
+ end if;
2718
+ else
2719
+ new."resolved_at" = null;
2720
+ new."resolved_by" = null;
2721
+ new."resolution_note" = null;
2722
+ end if;
2723
+
2724
+ return new;
2725
+ end;
2726
+ $$;
2727
+
2728
+ alter function "public"."fleet_prepare_issue"() owner to "postgres";
2729
+
2730
+ create trigger "fleet_prepare_issue_trigger"
2731
+ before insert or update on "public"."fleet_issue"
2732
+ for each row execute function "public"."fleet_prepare_issue"();
2733
+
2734
+ create or replace function "public"."fleet_record_issue_status"()
2735
+ returns trigger
2736
+ language plpgsql
2737
+ security definer
2738
+ set search_path = ''
2739
+ as $$
2740
+ declare
2741
+ "v_actor_user_id" uuid;
2742
+ begin
2743
+ select "employee"."id"
2744
+ into "v_actor_user_id"
2745
+ from "public"."user" as "employee"
2746
+ where "employee"."id" = auth.uid()
2747
+ and "employee"."company_id" = new."company_id";
2748
+
2749
+ if "v_actor_user_id" is null
2750
+ and new."status" in ('resolved', 'closed') then
2751
+ -- A service-role resolution may carry an explicit resolver. Do not
2752
+ -- attribute unrelated backend transitions to the original reporter.
2753
+ "v_actor_user_id" = new."resolved_by";
2754
+ end if;
2755
+
2756
+ if tg_op = 'INSERT' or new."status" is distinct from old."status" then
2757
+ insert into "public"."fleet_issue_status_history" (
2758
+ "company_id", "issue_id", "from_status", "to_status", "note", "changed_by"
2759
+ ) values (
2760
+ new."company_id",
2761
+ new."id",
2762
+ case when tg_op = 'INSERT' then null else old."status" end,
2763
+ new."status",
2764
+ case when new."status" in ('resolved', 'closed')
2765
+ then new."resolution_note" else null end,
2766
+ "v_actor_user_id"
2767
+ );
2768
+ end if;
2769
+
2770
+ if new."is_vehicle_unsafe"
2771
+ and new."status" in ('open', 'acknowledged', 'in_repair', 'in_progress') then
2772
+ update "public"."fleet_vehicle" as "vehicle"
2773
+ set "status" = 'out_of_service',
2774
+ "out_of_service_at" = coalesce("vehicle"."out_of_service_at", now()),
2775
+ "out_of_service_reason" = 'Unsafe Fleet issue ' || new."id"::text
2776
+ where "vehicle"."id" = new."vehicle_id"
2777
+ and "vehicle"."company_id" = new."company_id";
2778
+ end if;
2779
+
2780
+ return null;
2781
+ end;
2782
+ $$;
2783
+
2784
+ alter function "public"."fleet_record_issue_status"() owner to "postgres";
2785
+
2786
+ create trigger "fleet_record_issue_status_trigger"
2787
+ after insert or update on "public"."fleet_issue"
2788
+ for each row execute function "public"."fleet_record_issue_status"();
2789
+
2790
+ create or replace function "public"."fleet_reject_mutation"()
2791
+ returns trigger
2792
+ language plpgsql
2793
+ set search_path = ''
2794
+ as $$
2795
+ begin
2796
+ if tg_op = 'DELETE' and not exists (
2797
+ select 1
2798
+ from "public"."fleet_organization" as "organization"
2799
+ where "organization"."company_id" = (to_jsonb(old) ->> 'company_id')::uuid
2800
+ ) then
2801
+ return old;
2802
+ end if;
2803
+
2804
+ raise exception using
2805
+ errcode = '23514',
2806
+ message = format('%s rows are append-only', tg_table_name);
2807
+ end;
2808
+ $$;
2809
+
2810
+ alter function "public"."fleet_reject_mutation"() owner to "postgres";
2811
+
2812
+ create trigger "fleet_issue_status_history_append_only_trigger"
2813
+ before update or delete on "public"."fleet_issue_status_history"
2814
+ for each row execute function "public"."fleet_reject_mutation"();
2815
+
2816
+ create trigger "fleet_issue_comment_append_only_trigger"
2817
+ before update or delete on "public"."fleet_issue_comment"
2818
+ for each row execute function "public"."fleet_reject_mutation"();
2819
+
2820
+ create trigger "fleet_audit_event_append_only_trigger"
2821
+ before update or delete on "public"."fleet_audit_event"
2822
+ for each row execute function "public"."fleet_reject_mutation"();
2823
+
2824
+ create or replace function "public"."fleet_prepare_repair_order"()
2825
+ returns trigger
2826
+ language plpgsql
2827
+ security definer
2828
+ set search_path = ''
2829
+ as $$
2830
+ begin
2831
+ if tg_op = 'DELETE' then
2832
+ if not exists (
2833
+ select 1
2834
+ from "public"."fleet_organization" as "organization"
2835
+ where "organization"."company_id" = old."company_id"
2836
+ ) then
2837
+ return old;
2838
+ end if;
2839
+
2840
+ if old."status" in ('completed', 'cancelled') then
2841
+ raise exception using
2842
+ errcode = '23514',
2843
+ message = 'Completed and cancelled Fleet repair orders cannot be deleted';
2844
+ end if;
2845
+ return old;
2846
+ end if;
2847
+
2848
+ if tg_op = 'UPDATE' then
2849
+ if new."company_id" is distinct from old."company_id"
2850
+ or new."vehicle_id" is distinct from old."vehicle_id"
2851
+ or new."issue_id" is distinct from old."issue_id"
2852
+ or new."order_number" is distinct from old."order_number"
2853
+ or new."created_by" is distinct from old."created_by" then
2854
+ raise exception using
2855
+ errcode = '23514',
2856
+ message = 'Fleet repair order tenant, source, number, and creator are immutable';
2857
+ end if;
2858
+
2859
+ if old."status" in ('completed', 'cancelled') and new is distinct from old then
2860
+ raise exception using
2861
+ errcode = '23514',
2862
+ message = 'Completed and cancelled Fleet repair orders are immutable';
2863
+ end if;
2864
+ end if;
2865
+
2866
+ if new."issue_id" is not null and not exists (
2867
+ select 1
2868
+ from "public"."fleet_issue" as "issue"
2869
+ where "issue"."id" = new."issue_id"
2870
+ and "issue"."company_id" = new."company_id"
2871
+ and "issue"."vehicle_id" = new."vehicle_id"
2872
+ ) then
2873
+ raise exception using
2874
+ errcode = '23514',
2875
+ message = 'Fleet repair order issue belongs to a different vehicle';
2876
+ end if;
2877
+
2878
+ if new."assigned_to_user_id" is not null and not exists (
2879
+ select 1
2880
+ from "public"."user" as "employee"
2881
+ where "employee"."id" = new."assigned_to_user_id"
2882
+ and "employee"."company_id" = new."company_id"
2883
+ and "employee"."is_current_employee"
2884
+ and not "employee"."is_hidden"
2885
+ ) then
2886
+ raise exception using
2887
+ errcode = '23514',
2888
+ message = 'Fleet repair orders can only be assigned to a current, visible employee';
2889
+ end if;
2890
+
2891
+ if new."status" = 'draft' then
2892
+ new."opened_at" = null;
2893
+ new."completed_at" = null;
2894
+ new."completion_note" = null;
2895
+ elsif new."status" in ('open', 'in_progress', 'awaiting_parts') then
2896
+ new."opened_at" = coalesce(new."opened_at", now());
2897
+ new."completed_at" = null;
2898
+ new."completion_note" = null;
2899
+ else
2900
+ new."opened_at" = coalesce(new."opened_at", now());
2901
+ new."completed_at" = coalesce(new."completed_at", now());
2902
+ if nullif(btrim(new."completion_note"), '') is null then
2903
+ raise exception using
2904
+ errcode = '23514',
2905
+ message = 'Completing or cancelling a Fleet repair order requires a note';
2906
+ end if;
2907
+ end if;
2908
+
2909
+ return new;
2910
+ end;
2911
+ $$;
2912
+
2913
+ alter function "public"."fleet_prepare_repair_order"() owner to "postgres";
2914
+
2915
+ create trigger "fleet_prepare_repair_order_trigger"
2916
+ before insert or update or delete on "public"."fleet_repair_order"
2917
+ for each row execute function "public"."fleet_prepare_repair_order"();
2918
+
2919
+ create or replace function "public"."fleet_guard_repair_line_item"()
2920
+ returns trigger
2921
+ language plpgsql
2922
+ security definer
2923
+ set search_path = ''
2924
+ as $$
2925
+ declare
2926
+ "v_company_id" uuid;
2927
+ "v_repair_order_id" uuid;
2928
+ "v_status" text;
2929
+ begin
2930
+ if tg_op = 'DELETE' then
2931
+ "v_company_id" = old."company_id";
2932
+ "v_repair_order_id" = old."repair_order_id";
2933
+ else
2934
+ "v_company_id" = new."company_id";
2935
+ "v_repair_order_id" = new."repair_order_id";
2936
+ end if;
2937
+
2938
+ if tg_op = 'UPDATE' and (
2939
+ new."company_id" is distinct from old."company_id"
2940
+ or new."repair_order_id" is distinct from old."repair_order_id"
2941
+ ) then
2942
+ raise exception using
2943
+ errcode = '23514',
2944
+ message = 'Fleet repair line items cannot move between orders or companies';
2945
+ end if;
2946
+
2947
+ if tg_op = 'DELETE' and not exists (
2948
+ select 1
2949
+ from "public"."fleet_organization" as "organization"
2950
+ where "organization"."company_id" = "v_company_id"
2951
+ ) then
2952
+ return old;
2953
+ end if;
2954
+
2955
+ select "repair_order"."status"
2956
+ into "v_status"
2957
+ from "public"."fleet_repair_order" as "repair_order"
2958
+ where "repair_order"."id" = "v_repair_order_id"
2959
+ and "repair_order"."company_id" = "v_company_id";
2960
+
2961
+ if "v_status" in ('completed', 'cancelled') then
2962
+ raise exception using
2963
+ errcode = '23514',
2964
+ message = 'Line items on completed or cancelled Fleet repair orders are immutable';
2965
+ end if;
2966
+
2967
+ if tg_op = 'DELETE' then
2968
+ return old;
2969
+ end if;
2970
+ return new;
2971
+ end;
2972
+ $$;
2973
+
2974
+ alter function "public"."fleet_guard_repair_line_item"() owner to "postgres";
2975
+
2976
+ create trigger "fleet_guard_repair_line_item_trigger"
2977
+ before insert or update or delete on "public"."fleet_repair_order_line_item"
2978
+ for each row execute function "public"."fleet_guard_repair_line_item"();
2979
+
2980
+ create or replace function "public"."fleet_prepare_pm_schedule"()
2981
+ returns trigger
2982
+ language plpgsql
2983
+ security definer
2984
+ set search_path = ''
2985
+ as $$
2986
+ declare
2987
+ "v_vehicle_odometer" numeric;
2988
+ "v_vehicle_engine_hours" numeric;
2989
+ begin
2990
+ if tg_op = 'UPDATE' and (
2991
+ new."company_id" is distinct from old."company_id"
2992
+ or new."vehicle_id" is distinct from old."vehicle_id"
2993
+ or new."created_by" is distinct from old."created_by"
2994
+ ) then
2995
+ raise exception using
2996
+ errcode = '23514',
2997
+ message = 'Fleet PM schedules cannot move between vehicles or companies';
2998
+ end if;
2999
+
3000
+ select "vehicle"."current_odometer", "vehicle"."current_engine_hours"
3001
+ into "v_vehicle_odometer", "v_vehicle_engine_hours"
3002
+ from "public"."fleet_vehicle" as "vehicle"
3003
+ where "vehicle"."id" = new."vehicle_id"
3004
+ and "vehicle"."company_id" = new."company_id";
3005
+
3006
+ if tg_op = 'UPDATE'
3007
+ and new."interval_days" is not distinct from old."interval_days"
3008
+ and new."last_service_date" is not distinct from old."last_service_date" then
3009
+ new."next_due_date" = old."next_due_date";
3010
+ elsif new."interval_days" is null then
3011
+ new."next_due_date" = null;
3012
+ else
3013
+ new."next_due_date" = coalesce(new."last_service_date", current_date)
3014
+ + new."interval_days";
3015
+ end if;
3016
+
3017
+ if tg_op = 'UPDATE'
3018
+ and new."interval_odometer" is not distinct from old."interval_odometer"
3019
+ and new."last_service_odometer" is not distinct from old."last_service_odometer" then
3020
+ new."next_due_odometer" = old."next_due_odometer";
3021
+ elsif new."interval_odometer" is null then
3022
+ new."next_due_odometer" = null;
3023
+ else
3024
+ new."next_due_odometer" = coalesce(
3025
+ new."last_service_odometer", "v_vehicle_odometer", 0
3026
+ ) + new."interval_odometer";
3027
+ end if;
3028
+
3029
+ if tg_op = 'UPDATE'
3030
+ and new."interval_engine_hours" is not distinct from old."interval_engine_hours"
3031
+ and new."last_service_engine_hours"
3032
+ is not distinct from old."last_service_engine_hours" then
3033
+ new."next_due_engine_hours" = old."next_due_engine_hours";
3034
+ elsif new."interval_engine_hours" is null then
3035
+ new."next_due_engine_hours" = null;
3036
+ else
3037
+ new."next_due_engine_hours" = coalesce(
3038
+ new."last_service_engine_hours", "v_vehicle_engine_hours", 0
3039
+ ) + new."interval_engine_hours";
3040
+ end if;
3041
+
3042
+ return new;
3043
+ end;
3044
+ $$;
3045
+
3046
+ alter function "public"."fleet_prepare_pm_schedule"() owner to "postgres";
3047
+
3048
+ create trigger "fleet_prepare_pm_schedule_trigger"
3049
+ before insert or update on "public"."fleet_pm_schedule"
3050
+ for each row execute function "public"."fleet_prepare_pm_schedule"();
3051
+
3052
+ create or replace function "public"."fleet_prepare_service_record"()
3053
+ returns trigger
3054
+ language plpgsql
3055
+ security definer
3056
+ set search_path = ''
3057
+ as $$
3058
+ declare
3059
+ "v_schedule_vehicle_id" uuid;
3060
+ "v_last_service_date" date;
3061
+ "v_last_service_odometer" numeric;
3062
+ "v_last_service_engine_hours" numeric;
3063
+ begin
3064
+ if tg_op = 'DELETE' then
3065
+ if not exists (
3066
+ select 1
3067
+ from "public"."fleet_organization" as "organization"
3068
+ where "organization"."company_id" = old."company_id"
3069
+ ) then
3070
+ return old;
3071
+ end if;
3072
+
3073
+ raise exception using
3074
+ errcode = '23514',
3075
+ message = 'Fleet service records are immutable and cannot be deleted';
3076
+ end if;
3077
+
3078
+ if tg_op = 'UPDATE' then
3079
+ if (to_jsonb(new) - array['notes', 'updated_at'])
3080
+ is distinct from
3081
+ (to_jsonb(old) - array['notes', 'updated_at']) then
3082
+ raise exception using
3083
+ errcode = '23514',
3084
+ message = 'Only notes may be corrected on a Fleet service record';
3085
+ end if;
3086
+ return new;
3087
+ end if;
3088
+
3089
+ if new."pm_schedule_id" is not null then
3090
+ -- Serialize records for the same schedule. The AFTER trigger advances
3091
+ -- this locked row before commit, so a later concurrent insert sees the
3092
+ -- new baseline and cannot move it backward.
3093
+ select
3094
+ "schedule"."vehicle_id",
3095
+ "schedule"."last_service_date",
3096
+ "schedule"."last_service_odometer",
3097
+ "schedule"."last_service_engine_hours"
3098
+ into
3099
+ "v_schedule_vehicle_id",
3100
+ "v_last_service_date",
3101
+ "v_last_service_odometer",
3102
+ "v_last_service_engine_hours"
3103
+ from "public"."fleet_pm_schedule" as "schedule"
3104
+ where "schedule"."id" = new."pm_schedule_id"
3105
+ and "schedule"."company_id" = new."company_id"
3106
+ for update;
3107
+
3108
+ if not found or "v_schedule_vehicle_id" <> new."vehicle_id" then
3109
+ raise exception using
3110
+ errcode = '23514',
3111
+ message = 'Fleet service record PM schedule belongs to a different vehicle';
3112
+ end if;
3113
+
3114
+ if ("v_last_service_date" is not null
3115
+ and new."performed_on" < "v_last_service_date")
3116
+ or ("v_last_service_odometer" is not null
3117
+ and new."odometer" is not null
3118
+ and new."odometer" < "v_last_service_odometer")
3119
+ or ("v_last_service_engine_hours" is not null
3120
+ and new."engine_hours" is not null
3121
+ and new."engine_hours" < "v_last_service_engine_hours") then
3122
+ raise exception using
3123
+ errcode = '23514',
3124
+ message = 'Fleet service record cannot move a PM schedule backward';
3125
+ end if;
3126
+ end if;
3127
+
3128
+ if new."repair_order_id" is not null and not exists (
3129
+ select 1
3130
+ from "public"."fleet_repair_order" as "repair_order"
3131
+ where "repair_order"."id" = new."repair_order_id"
3132
+ and "repair_order"."company_id" = new."company_id"
3133
+ and "repair_order"."vehicle_id" = new."vehicle_id"
3134
+ ) then
3135
+ raise exception using
3136
+ errcode = '23514',
3137
+ message = 'Fleet service record repair order belongs to a different vehicle';
3138
+ end if;
3139
+
3140
+ return new;
3141
+ end;
3142
+ $$;
3143
+
3144
+ alter function "public"."fleet_prepare_service_record"() owner to "postgres";
3145
+
3146
+ create trigger "fleet_prepare_service_record_trigger"
3147
+ before insert or update or delete on "public"."fleet_service_record"
3148
+ for each row execute function "public"."fleet_prepare_service_record"();
3149
+
3150
+ create or replace function "public"."fleet_apply_service_record"()
3151
+ returns trigger
3152
+ language plpgsql
3153
+ security definer
3154
+ set search_path = ''
3155
+ as $$
3156
+ begin
3157
+ update "public"."fleet_vehicle" as "vehicle"
3158
+ set "current_odometer" = case
3159
+ when new."odometer" is null then "vehicle"."current_odometer"
3160
+ else greatest(coalesce("vehicle"."current_odometer", 0), new."odometer")
3161
+ end,
3162
+ "current_engine_hours" = case
3163
+ when new."engine_hours" is null then "vehicle"."current_engine_hours"
3164
+ else greatest(coalesce("vehicle"."current_engine_hours", 0), new."engine_hours")
3165
+ end
3166
+ where "vehicle"."id" = new."vehicle_id"
3167
+ and "vehicle"."company_id" = new."company_id";
3168
+
3169
+ if new."pm_schedule_id" is not null then
3170
+ update "public"."fleet_pm_schedule" as "schedule"
3171
+ set "last_service_date" = new."performed_on",
3172
+ "last_service_odometer" = coalesce(
3173
+ new."odometer", "schedule"."last_service_odometer"
3174
+ ),
3175
+ "last_service_engine_hours" = coalesce(
3176
+ new."engine_hours", "schedule"."last_service_engine_hours"
3177
+ )
3178
+ where "schedule"."id" = new."pm_schedule_id"
3179
+ and "schedule"."company_id" = new."company_id";
3180
+ end if;
3181
+
3182
+ return null;
3183
+ end;
3184
+ $$;
3185
+
3186
+ alter function "public"."fleet_apply_service_record"() owner to "postgres";
3187
+
3188
+ create trigger "fleet_apply_service_record_trigger"
3189
+ after insert on "public"."fleet_service_record"
3190
+ for each row execute function "public"."fleet_apply_service_record"();
3191
+
3192
+ create or replace function "public"."fleet_write_audit_event"()
3193
+ returns trigger
3194
+ language plpgsql
3195
+ security definer
3196
+ set search_path = ''
3197
+ as $$
3198
+ declare
3199
+ "v_before" jsonb;
3200
+ "v_after" jsonb;
3201
+ "v_company_id" uuid;
3202
+ "v_entity_id" uuid;
3203
+ "v_actor_user_id" uuid;
3204
+ begin
3205
+ if tg_op = 'INSERT' then
3206
+ "v_before" = null;
3207
+ "v_after" = to_jsonb(new);
3208
+ elsif tg_op = 'UPDATE' then
3209
+ "v_before" = to_jsonb(old);
3210
+ "v_after" = to_jsonb(new);
3211
+ else
3212
+ "v_before" = to_jsonb(old);
3213
+ "v_after" = null;
3214
+ end if;
3215
+
3216
+ "v_company_id" = coalesce(
3217
+ nullif("v_after" ->> 'company_id', '')::uuid,
3218
+ nullif("v_before" ->> 'company_id', '')::uuid
3219
+ );
3220
+ "v_entity_id" = coalesce(
3221
+ nullif("v_after" ->> 'id', '')::uuid,
3222
+ nullif("v_before" ->> 'id', '')::uuid,
3223
+ "v_company_id"
3224
+ );
3225
+
3226
+ -- During a company-wide cascade the organization may already be gone;
3227
+ -- do not make the audit FK prevent the owning company from being deleted.
3228
+ if "v_company_id" is null
3229
+ or "v_entity_id" is null
3230
+ or not exists (
3231
+ select 1
3232
+ from "public"."fleet_organization" as "organization"
3233
+ where "organization"."company_id" = "v_company_id"
3234
+ ) then
3235
+ if tg_op = 'DELETE' then
3236
+ return old;
3237
+ end if;
3238
+ return new;
3239
+ end if;
3240
+
3241
+ select "employee"."id"
3242
+ into "v_actor_user_id"
3243
+ from "public"."user" as "employee"
3244
+ where "employee"."id" = auth.uid()
3245
+ and "employee"."company_id" = "v_company_id";
3246
+
3247
+ insert into "public"."fleet_audit_event" (
3248
+ "company_id", "entity_table", "entity_id", "action",
3249
+ "actor_user_id", "before_data", "after_data", "metadata"
3250
+ ) values (
3251
+ "v_company_id",
3252
+ tg_table_name,
3253
+ "v_entity_id",
3254
+ lower(tg_op),
3255
+ "v_actor_user_id",
3256
+ "v_before",
3257
+ "v_after",
3258
+ jsonb_build_object('transaction_id', txid_current())
3259
+ );
3260
+
3261
+ if tg_op = 'DELETE' then
3262
+ return old;
3263
+ end if;
3264
+ return new;
3265
+ end;
3266
+ $$;
3267
+
3268
+ alter function "public"."fleet_write_audit_event"() owner to "postgres";
3269
+
3270
+ -- Mutable rows use one feature-scoped timestamp trigger.
3271
+ create trigger "fleet_set_organization_updated_at_trigger"
3272
+ before update on "public"."fleet_organization"
3273
+ for each row execute function "public"."fleet_set_updated_at"();
3274
+ create trigger "fleet_set_member_updated_at_trigger"
3275
+ before update on "public"."fleet_member"
3276
+ for each row execute function "public"."fleet_set_updated_at"();
3277
+ create trigger "fleet_set_vehicle_updated_at_trigger"
3278
+ before update on "public"."fleet_vehicle"
3279
+ for each row execute function "public"."fleet_set_updated_at"();
3280
+ create trigger "fleet_set_form_template_updated_at_trigger"
3281
+ before update on "public"."fleet_form_template"
3282
+ for each row execute function "public"."fleet_set_updated_at"();
3283
+ create trigger "fleet_set_form_version_updated_at_trigger"
3284
+ before update on "public"."fleet_form_template_version"
3285
+ for each row execute function "public"."fleet_set_updated_at"();
3286
+ create trigger "fleet_set_form_section_updated_at_trigger"
3287
+ before update on "public"."fleet_form_section"
3288
+ for each row execute function "public"."fleet_set_updated_at"();
3289
+ create trigger "fleet_set_form_question_updated_at_trigger"
3290
+ before update on "public"."fleet_form_question"
3291
+ for each row execute function "public"."fleet_set_updated_at"();
3292
+ create trigger "fleet_set_form_assignment_updated_at_trigger"
3293
+ before update on "public"."fleet_form_assignment"
3294
+ for each row execute function "public"."fleet_set_updated_at"();
3295
+ create trigger "fleet_set_inspection_updated_at_trigger"
3296
+ before update on "public"."fleet_inspection"
3297
+ for each row execute function "public"."fleet_set_updated_at"();
3298
+ create trigger "fleet_set_inspection_answer_updated_at_trigger"
3299
+ before update on "public"."fleet_inspection_answer"
3300
+ for each row execute function "public"."fleet_set_updated_at"();
3301
+ create trigger "fleet_set_issue_updated_at_trigger"
3302
+ before update on "public"."fleet_issue"
3303
+ for each row execute function "public"."fleet_set_updated_at"();
3304
+ create trigger "fleet_set_repair_order_updated_at_trigger"
3305
+ before update on "public"."fleet_repair_order"
3306
+ for each row execute function "public"."fleet_set_updated_at"();
3307
+ create trigger "fleet_set_repair_line_item_updated_at_trigger"
3308
+ before update on "public"."fleet_repair_order_line_item"
3309
+ for each row execute function "public"."fleet_set_updated_at"();
3310
+ create trigger "fleet_set_pm_schedule_updated_at_trigger"
3311
+ before update on "public"."fleet_pm_schedule"
3312
+ for each row execute function "public"."fleet_set_updated_at"();
3313
+ create trigger "fleet_set_service_record_updated_at_trigger"
3314
+ before update on "public"."fleet_service_record"
3315
+ for each row execute function "public"."fleet_set_updated_at"();
3316
+ create trigger "fleet_set_notification_preference_updated_at_trigger"
3317
+ before update on "public"."fleet_notification_preference"
3318
+ for each row execute function "public"."fleet_set_updated_at"();
3319
+
3320
+ -- Audit material configuration/workflow tables. Status history, comments,
3321
+ -- and the audit table itself are already append-only evidence and are not
3322
+ -- recursively audited.
3323
+ create trigger "fleet_audit_organization_trigger"
3324
+ after insert or update or delete on "public"."fleet_organization"
3325
+ for each row execute function "public"."fleet_write_audit_event"();
3326
+ create trigger "fleet_audit_member_trigger"
3327
+ after insert or update or delete on "public"."fleet_member"
3328
+ for each row execute function "public"."fleet_write_audit_event"();
3329
+ create trigger "fleet_audit_vehicle_trigger"
3330
+ after insert or update or delete on "public"."fleet_vehicle"
3331
+ for each row execute function "public"."fleet_write_audit_event"();
3332
+ create trigger "fleet_audit_form_template_trigger"
3333
+ after insert or update or delete on "public"."fleet_form_template"
3334
+ for each row execute function "public"."fleet_write_audit_event"();
3335
+ create trigger "fleet_audit_form_version_trigger"
3336
+ after insert or update or delete on "public"."fleet_form_template_version"
3337
+ for each row execute function "public"."fleet_write_audit_event"();
3338
+ create trigger "fleet_audit_form_section_trigger"
3339
+ after insert or update or delete on "public"."fleet_form_section"
3340
+ for each row execute function "public"."fleet_write_audit_event"();
3341
+ create trigger "fleet_audit_form_question_trigger"
3342
+ after insert or update or delete on "public"."fleet_form_question"
3343
+ for each row execute function "public"."fleet_write_audit_event"();
3344
+ create trigger "fleet_audit_form_assignment_trigger"
3345
+ after insert or update or delete on "public"."fleet_form_assignment"
3346
+ for each row execute function "public"."fleet_write_audit_event"();
3347
+ create trigger "fleet_audit_inspection_trigger"
3348
+ after insert or update or delete on "public"."fleet_inspection"
3349
+ for each row execute function "public"."fleet_write_audit_event"();
3350
+ create trigger "fleet_audit_inspection_answer_trigger"
3351
+ after insert or update or delete on "public"."fleet_inspection_answer"
3352
+ for each row execute function "public"."fleet_write_audit_event"();
3353
+ create trigger "fleet_audit_issue_trigger"
3354
+ after insert or update or delete on "public"."fleet_issue"
3355
+ for each row execute function "public"."fleet_write_audit_event"();
3356
+ create trigger "fleet_audit_repair_order_trigger"
3357
+ after insert or update or delete on "public"."fleet_repair_order"
3358
+ for each row execute function "public"."fleet_write_audit_event"();
3359
+ create trigger "fleet_audit_repair_line_item_trigger"
3360
+ after insert or update or delete on "public"."fleet_repair_order_line_item"
3361
+ for each row execute function "public"."fleet_write_audit_event"();
3362
+ create trigger "fleet_audit_pm_schedule_trigger"
3363
+ after insert or update or delete on "public"."fleet_pm_schedule"
3364
+ for each row execute function "public"."fleet_write_audit_event"();
3365
+ create trigger "fleet_audit_service_record_trigger"
3366
+ after insert or update or delete on "public"."fleet_service_record"
3367
+ for each row execute function "public"."fleet_write_audit_event"();
3368
+ create trigger "fleet_audit_notification_preference_trigger"
3369
+ after insert or update or delete on "public"."fleet_notification_preference"
3370
+ for each row execute function "public"."fleet_write_audit_event"();
3371
+ create trigger "fleet_audit_media_trigger"
3372
+ after insert or update or delete on "public"."fleet_media"
3373
+ for each row execute function "public"."fleet_write_audit_event"();
3374
+
3375
+ -- ============================================================
3376
+ -- Exact, idempotent Fleet V1 default templates
3377
+ -- ============================================================
3378
+
3379
+ create or replace function "public"."fleet_seed_default_form_templates"(
3380
+ "p_company_id" uuid,
3381
+ "p_created_by" uuid default auth.uid()
3382
+ )
3383
+ returns void
3384
+ language plpgsql
3385
+ security definer
3386
+ set search_path = ''
3387
+ as $$
3388
+ declare
3389
+ "v_templates" jsonb = $templates$
3390
+ [
3391
+ {
3392
+ "slug": "trailer-daily",
3393
+ "name": "Trailer Daily",
3394
+ "description": "Four required daily readiness checks for dump trailers.",
3395
+ "form_kind": "start_of_day",
3396
+ "vehicle_type": "dump_trailer",
3397
+ "sections": [
3398
+ {
3399
+ "title": "Trailer readiness", "order": 1,
3400
+ "questions": [
3401
+ {"key":"new_tarp_needed","prompt":"Do we need a new tarp?","type":"yes_no","order":1,"pass":"no","failure":"yes","creates_issue":true,"severity":"major","failure_title":"Trailer tarp needs replacement"},
3402
+ {"key":"tires_low_pressure","prompt":"Are any tires low on air pressure?","type":"yes_no","order":2,"pass":"no","failure":"yes","creates_issue":true,"severity":"major","failure_title":"Trailer tire pressure is low"},
3403
+ {"key":"flat_tires","prompt":"Are there any flat tires?","type":"yes_no","order":3,"pass":"no","failure":"yes","creates_issue":true,"severity":"critical","failure_title":"Trailer has a flat tire","unsafe":true},
3404
+ {"key":"dump_trailer_operates","prompt":"Does the dump trailer work up and down?","type":"yes_no","order":4,"pass":"yes","failure":"no","creates_issue":true,"severity":"critical","failure_title":"Dump trailer lift does not operate","unsafe":true}
3405
+ ]
3406
+ }
3407
+ ]
3408
+ },
3409
+ {
3410
+ "slug": "start-day",
3411
+ "name": "Start Day",
3412
+ "description": "Required field checks before a vehicle begins work.",
3413
+ "form_kind": "start_of_day",
3414
+ "sections": [
3415
+ {
3416
+ "title": "Vehicle and readings", "order": 1,
3417
+ "questions": [
3418
+ {"key":"current_odometer","prompt":"Enter the current odometer reading","type":"number","order":1,"configuration":{"minimum_value":0}},
3419
+ {"key":"front_work_shirt_photo","prompt":"Upload a front “work shirt” vehicle photo","type":"photo","order":2},
3420
+ {"key":"rear_work_shirt_photo","prompt":"Upload a rear “work shirt” vehicle photo","type":"photo","order":3}
3421
+ ]
3422
+ },
3423
+ {
3424
+ "title": "Safety check", "order": 2,
3425
+ "questions": [
3426
+ {"key":"brakes_safe","prompt":"Are the brakes safe?","type":"pass_fail","order":1,"description":"A failed check requires a note. Add a photo when it helps document the defect.","pass":"pass","failure":"fail","creates_issue":true,"severity":"critical","failure_title":"Are the brakes safe failed safety check","unsafe":true,"requires_comment":true},
3427
+ {"key":"steering_safe","prompt":"Is the steering safe?","type":"pass_fail","order":2,"description":"A failed check requires a note. Add a photo when it helps document the defect.","pass":"pass","failure":"fail","creates_issue":true,"severity":"critical","failure_title":"Is the steering safe failed safety check","unsafe":true,"requires_comment":true},
3428
+ {"key":"tires_safe","prompt":"Are the tires safe?","type":"pass_fail","order":3,"description":"A failed check requires a note. Add a photo when it helps document the defect.","pass":"pass","failure":"fail","creates_issue":true,"severity":"critical","failure_title":"Are the tires safe failed safety check","unsafe":true,"requires_comment":true},
3429
+ {"key":"lights_safe","prompt":"Are the lights safe?","type":"pass_fail","order":4,"description":"A failed check requires a note. Add a photo when it helps document the defect.","pass":"pass","failure":"fail","creates_issue":true,"severity":"major","failure_title":"Are the lights safe failed safety check","requires_comment":true},
3430
+ {"key":"mirrors_safe","prompt":"Are the mirrors safe?","type":"pass_fail","order":5,"description":"A failed check requires a note. Add a photo when it helps document the defect.","pass":"pass","failure":"fail","creates_issue":true,"severity":"major","failure_title":"Are the mirrors safe failed safety check","requires_comment":true},
3431
+ {"key":"seat_belts_safe","prompt":"Are the seat belts safe?","type":"pass_fail","order":6,"description":"A failed check requires a note. Add a photo when it helps document the defect.","pass":"pass","failure":"fail","creates_issue":true,"severity":"major","failure_title":"Are the seat belts safe failed safety check","requires_comment":true},
3432
+ {"key":"safety_kit_present","prompt":"Are the first aid kit, fire extinguisher, and cones present?","type":"pass_fail","order":7,"pass":"pass","failure":"fail","creates_issue":true,"severity":"major","failure_title":"Safety kit is incomplete","requires_comment":true},
3433
+ {"key":"daily_tools_loaded","prompt":"Are the daily tools and equipment loaded?","type":"pass_fail","order":8,"pass":"pass","failure":"fail","creates_issue":true,"severity":"minor","failure_title":"Daily tools or equipment are missing","requires_comment":true}
3434
+ ]
3435
+ },
3436
+ {
3437
+ "title": "Finish", "order": 3,
3438
+ "questions": [
3439
+ {"key":"start_notes","prompt":"Add any start-of-day notes","type":"text","order":1,"required":false},
3440
+ {"key":"start_attestation","prompt":"Sign to attest this inspection is accurate","type":"signature","order":2}
3441
+ ]
3442
+ }
3443
+ ]
3444
+ },
3445
+ {
3446
+ "slug": "end-day",
3447
+ "name": "End Day",
3448
+ "description": "Cash, load, condition, and secure-parking closeout.",
3449
+ "form_kind": "end_of_day",
3450
+ "sections": [
3451
+ {
3452
+ "title": "Daily totals", "order": 1,
3453
+ "questions": [
3454
+ {"key":"cash_collected","prompt":"Cash collected","description":"Enter 0 when no cash was collected.","type":"currency","order":1,"configuration":{"minimum_value":0}},
3455
+ {"key":"crew_tips","prompt":"Crew tips","description":"Enter 0 when no tips were collected.","type":"currency","order":2,"configuration":{"minimum_value":0}}
3456
+ ]
3457
+ },
3458
+ {
3459
+ "title": "Load and condition", "order": 2,
3460
+ "questions": [
3461
+ {"key":"load_remaining","prompt":"Is there any load remaining in the truck or trailer?","type":"yes_no","order":1,"pass":"no","failure":"yes","creates_issue":true,"severity":"minor","failure_title":"Load remains in vehicle","requires_comment":true,"requires_photo":true},
3462
+ {"key":"next_dump_location","prompt":"Where should the vehicle dump next?","type":"select_or_text","order":2,"required":false,"options":["Transfer station","Landfill","Donation center","None"],"configuration":{"allows_custom_option":true,"visible_when":{"question_key":"load_remaining","operator":"equals","value":"yes"},"required_when":{"question_key":"load_remaining","operator":"equals","value":"yes"}}},
3463
+ {"key":"new_damage_or_problems","prompt":"Is there any new damage or are there new problems?","type":"yes_no","order":3,"pass":"no","failure":"yes","creates_issue":true,"severity":"major","failure_title":"New vehicle damage or problem reported","requires_comment":true,"requires_photo":true,"allows_unsafe_override":true}
3464
+ ]
3465
+ },
3466
+ {
3467
+ "title": "Close out", "order": 3,
3468
+ "questions": [
3469
+ {"key":"end_odometer","prompt":"Enter the end odometer reading","type":"number","order":1,"configuration":{"minimum_value":0,"minimum_from_prior_inspection":{"form_kind":"start_of_day","question_key":"current_odometer"}}},
3470
+ {"key":"secured_and_parked","prompt":"Is the cab and bed secured and the vehicle parked in its assigned location?","type":"pass_fail","order":2,"pass":"pass","failure":"fail","creates_issue":true,"severity":"minor","failure_title":"Vehicle was not secured or parked correctly","requires_comment":true},
3471
+ {"key":"end_attestation","prompt":"Sign to attest this end-of-day report is accurate","type":"signature","order":3}
3472
+ ]
3473
+ }
3474
+ ]
3475
+ },
3476
+ {
3477
+ "slug": "weekly-vehicle",
3478
+ "name": "Weekly Vehicle",
3479
+ "description": "A deeper weekly safety, condition, inventory, and documentation check.",
3480
+ "form_kind": "weekly_vehicle",
3481
+ "sections": [
3482
+ {
3483
+ "title": "Readings and fluids", "order": 1,
3484
+ "questions": [
3485
+ {"key":"weekly_odometer","prompt":"Enter the current odometer reading","type":"number","order":1,"configuration":{"minimum_value":0}},
3486
+ {"key":"engine_oil","prompt":"Is the engine oil at a safe level and condition?","type":"pass_fail","order":2,"pass":"pass","failure":"fail","creates_issue":true,"severity":"major","failure_title":"Is the engine oil at a safe level and condition failed weekly check","requires_comment":true},
3487
+ {"key":"coolant","prompt":"Is the coolant at a safe level and condition?","type":"pass_fail","order":3,"pass":"pass","failure":"fail","creates_issue":true,"severity":"major","failure_title":"Is the coolant at a safe level and condition failed weekly check","requires_comment":true},
3488
+ {"key":"washer_fluid","prompt":"Is the washer fluid filled?","type":"pass_fail","order":4,"pass":"pass","failure":"fail","creates_issue":true,"severity":"minor","failure_title":"Is the washer fluid filled failed weekly check","requires_comment":true}
3489
+ ]
3490
+ },
3491
+ {
3492
+ "title": "Safety and condition", "order": 2,
3493
+ "questions": [
3494
+ {"key":"tire_condition_pressure","prompt":"Are tire condition and pressure safe?","type":"pass_fail","order":1,"pass":"pass","failure":"fail","creates_issue":true,"severity":"critical","failure_title":"Are tire condition and pressure safe failed weekly check","unsafe":true,"requires_comment":true},
3495
+ {"key":"weekly_lights","prompt":"Are all lights working?","type":"pass_fail","order":2,"pass":"pass","failure":"fail","creates_issue":true,"severity":"major","failure_title":"Are all lights working failed weekly check","requires_comment":true},
3496
+ {"key":"weekly_brakes","prompt":"Are the brakes safe?","type":"pass_fail","order":3,"pass":"pass","failure":"fail","creates_issue":true,"severity":"critical","failure_title":"Are the brakes safe failed weekly check","unsafe":true,"requires_comment":true},
3497
+ {"key":"weekly_steering","prompt":"Is the steering safe?","type":"pass_fail","order":4,"pass":"pass","failure":"fail","creates_issue":true,"severity":"critical","failure_title":"Is the steering safe failed weekly check","unsafe":true,"requires_comment":true},
3498
+ {"key":"body_cab_condition","prompt":"Are the body and cab in acceptable condition?","type":"pass_fail","order":5,"pass":"pass","failure":"fail","creates_issue":true,"severity":"major","failure_title":"Are the body and cab in acceptable condition failed weekly check","requires_comment":true},
3499
+ {"key":"safety_equipment_inventory","prompt":"Is all safety equipment present?","type":"pass_fail","order":6,"pass":"pass","failure":"fail","creates_issue":true,"severity":"major","failure_title":"Is all safety equipment present failed weekly check","requires_comment":true},
3500
+ {"key":"tools_equipment_inventory","prompt":"Are all assigned tools and equipment present?","type":"pass_fail","order":7,"pass":"pass","failure":"fail","creates_issue":true,"severity":"minor","failure_title":"Are all assigned tools and equipment present failed weekly check","requires_comment":true},
3501
+ {"key":"registration_insurance","prompt":"Are registration and insurance documents present?","type":"pass_fail","order":8,"pass":"pass","failure":"fail","creates_issue":true,"severity":"major","failure_title":"Are registration and insurance documents present failed weekly check","requires_comment":true}
3502
+ ]
3503
+ },
3504
+ {
3505
+ "title": "Evidence and attestation", "order": 3,
3506
+ "questions": [
3507
+ {"key":"weekly_photo_set","prompt":"Upload the weekly vehicle photo set","type":"photo","order":1},
3508
+ {"key":"weekly_notes","prompt":"Add weekly inspection notes","type":"text","order":2,"required":false},
3509
+ {"key":"weekly_attestation","prompt":"Sign to attest this weekly inspection is accurate","type":"signature","order":3}
3510
+ ]
3511
+ }
3512
+ ]
3513
+ }
3514
+ ]
3515
+ $templates$::jsonb;
3516
+ "v_template" jsonb;
3517
+ "v_section" jsonb;
3518
+ "v_question" jsonb;
3519
+ "v_template_id" uuid;
3520
+ "v_version_id" uuid;
3521
+ "v_version_status" text;
3522
+ "v_section_id" uuid;
3523
+ begin
3524
+ if not exists (
3525
+ select 1
3526
+ from "public"."fleet_organization" as "organization"
3527
+ where "organization"."company_id" = "p_company_id"
3528
+ ) then
3529
+ raise exception using
3530
+ errcode = '23503',
3531
+ message = 'Cannot seed Fleet templates without a Fleet organization';
3532
+ end if;
3533
+
3534
+ for "v_template" in
3535
+ select "template_row"."value"
3536
+ from jsonb_array_elements("v_templates") as "template_row"("value")
3537
+ loop
3538
+ insert into "public"."fleet_form_template" (
3539
+ "company_id", "slug", "name", "description", "form_kind",
3540
+ "vehicle_type", "is_active", "created_by"
3541
+ ) values (
3542
+ "p_company_id",
3543
+ "v_template" ->> 'slug',
3544
+ "v_template" ->> 'name',
3545
+ "v_template" ->> 'description',
3546
+ "v_template" ->> 'form_kind',
3547
+ "v_template" ->> 'vehicle_type',
3548
+ true,
3549
+ "p_created_by"
3550
+ )
3551
+ on conflict ("company_id", "slug") do nothing;
3552
+
3553
+ select "template"."id"
3554
+ into "v_template_id"
3555
+ from "public"."fleet_form_template" as "template"
3556
+ where "template"."company_id" = "p_company_id"
3557
+ and "template"."slug" = "v_template" ->> 'slug';
3558
+
3559
+ insert into "public"."fleet_form_template_version" (
3560
+ "company_id", "template_id", "version_number", "status",
3561
+ "change_summary", "created_by"
3562
+ ) values (
3563
+ "p_company_id", "v_template_id", 1, 'draft',
3564
+ 'Autopilot Fleet V1 default template', "p_created_by"
3565
+ )
3566
+ on conflict ("company_id", "template_id", "version_number") do nothing;
3567
+
3568
+ select "version"."id", "version"."status"
3569
+ into "v_version_id", "v_version_status"
3570
+ from "public"."fleet_form_template_version" as "version"
3571
+ where "version"."company_id" = "p_company_id"
3572
+ and "version"."template_id" = "v_template_id"
3573
+ and "version"."version_number" = 1;
3574
+
3575
+ -- Re-running the seed never mutates an already-published version.
3576
+ if "v_version_status" <> 'draft' then
3577
+ continue;
3578
+ end if;
3579
+
3580
+ for "v_section" in
3581
+ select "section_row"."value"
3582
+ from jsonb_array_elements("v_template" -> 'sections')
3583
+ as "section_row"("value")
3584
+ loop
3585
+ insert into "public"."fleet_form_section" (
3586
+ "company_id", "template_version_id", "title", "description",
3587
+ "display_order"
3588
+ ) values (
3589
+ "p_company_id",
3590
+ "v_version_id",
3591
+ "v_section" ->> 'title',
3592
+ "v_section" ->> 'description',
3593
+ ("v_section" ->> 'order')::integer
3594
+ )
3595
+ on conflict ("company_id", "template_version_id", "display_order")
3596
+ do update set
3597
+ "title" = excluded."title",
3598
+ "description" = excluded."description";
3599
+
3600
+ select "section"."id"
3601
+ into "v_section_id"
3602
+ from "public"."fleet_form_section" as "section"
3603
+ where "section"."company_id" = "p_company_id"
3604
+ and "section"."template_version_id" = "v_version_id"
3605
+ and "section"."display_order" = ("v_section" ->> 'order')::integer;
3606
+
3607
+ for "v_question" in
3608
+ select "question_row"."value"
3609
+ from jsonb_array_elements("v_section" -> 'questions')
3610
+ as "question_row"("value")
3611
+ loop
3612
+ insert into "public"."fleet_form_question" (
3613
+ "company_id", "template_version_id", "section_id",
3614
+ "question_key", "prompt", "help_text", "response_type",
3615
+ "display_order", "is_required", "options", "pass_answer",
3616
+ "failure_answer", "creates_issue_on_failure",
3617
+ "failure_severity", "failure_title",
3618
+ "requires_comment_on_failure", "requires_photo_on_failure",
3619
+ "marks_vehicle_unsafe_on_failure",
3620
+ "allows_vehicle_out_of_service_override", "configuration"
3621
+ ) values (
3622
+ "p_company_id",
3623
+ "v_version_id",
3624
+ "v_section_id",
3625
+ "v_question" ->> 'key',
3626
+ "v_question" ->> 'prompt',
3627
+ "v_question" ->> 'description',
3628
+ "v_question" ->> 'type',
3629
+ ("v_question" ->> 'order')::integer,
3630
+ coalesce(("v_question" ->> 'required')::boolean, true),
3631
+ coalesce("v_question" -> 'options', '[]'::jsonb),
3632
+ "v_question" -> 'pass',
3633
+ "v_question" -> 'failure',
3634
+ coalesce(("v_question" ->> 'creates_issue')::boolean, false),
3635
+ coalesce("v_question" ->> 'severity', 'major'),
3636
+ "v_question" ->> 'failure_title',
3637
+ coalesce(("v_question" ->> 'requires_comment')::boolean, false),
3638
+ coalesce(("v_question" ->> 'requires_photo')::boolean, false),
3639
+ coalesce(("v_question" ->> 'unsafe')::boolean, false),
3640
+ coalesce(("v_question" ->> 'allows_unsafe_override')::boolean, false),
3641
+ coalesce("v_question" -> 'configuration', '{}'::jsonb)
3642
+ )
3643
+ on conflict ("company_id", "template_version_id", "question_key")
3644
+ do update set
3645
+ "section_id" = excluded."section_id",
3646
+ "prompt" = excluded."prompt",
3647
+ "help_text" = excluded."help_text",
3648
+ "response_type" = excluded."response_type",
3649
+ "display_order" = excluded."display_order",
3650
+ "is_required" = excluded."is_required",
3651
+ "options" = excluded."options",
3652
+ "pass_answer" = excluded."pass_answer",
3653
+ "failure_answer" = excluded."failure_answer",
3654
+ "creates_issue_on_failure" = excluded."creates_issue_on_failure",
3655
+ "failure_severity" = excluded."failure_severity",
3656
+ "failure_title" = excluded."failure_title",
3657
+ "requires_comment_on_failure" = excluded."requires_comment_on_failure",
3658
+ "requires_photo_on_failure" = excluded."requires_photo_on_failure",
3659
+ "marks_vehicle_unsafe_on_failure" = excluded."marks_vehicle_unsafe_on_failure",
3660
+ "allows_vehicle_out_of_service_override" =
3661
+ excluded."allows_vehicle_out_of_service_override",
3662
+ "configuration" = excluded."configuration";
3663
+ end loop;
3664
+ end loop;
3665
+
3666
+ update "public"."fleet_form_template_version"
3667
+ set "status" = 'published',
3668
+ "published_by" = "p_created_by"
3669
+ where "id" = "v_version_id"
3670
+ and "company_id" = "p_company_id"
3671
+ and "status" = 'draft';
3672
+ end loop;
3673
+ end;
3674
+ $$;
3675
+
3676
+ alter function "public"."fleet_seed_default_form_templates"(uuid, uuid)
3677
+ owner to "postgres";
3678
+
3679
+ create or replace function "public"."fleet_seed_default_form_templates_trigger"()
3680
+ returns trigger
3681
+ language plpgsql
3682
+ security definer
3683
+ set search_path = ''
3684
+ as $$
3685
+ begin
3686
+ perform "public"."fleet_seed_default_form_templates"(
3687
+ new."company_id", new."created_by"
3688
+ );
3689
+ return null;
3690
+ end;
3691
+ $$;
3692
+
3693
+ alter function "public"."fleet_seed_default_form_templates_trigger"()
3694
+ owner to "postgres";
3695
+
3696
+ create trigger "fleet_seed_default_form_templates_trigger"
3697
+ after insert on "public"."fleet_organization"
3698
+ for each row execute function "public"."fleet_seed_default_form_templates_trigger"();