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.
- package/README.md +53 -0
- package/dist/Provider.d.ts +21 -0
- package/dist/Provider.d.ts.map +1 -0
- package/dist/Provider.js +61 -0
- package/dist/Provider.js.map +1 -0
- package/dist/dist/Provider.d.ts +21 -0
- package/dist/dist/Provider.d.ts.map +1 -0
- package/dist/dist/Provider.js +61 -0
- package/dist/dist/Provider.js.map +1 -0
- package/dist/dist/index.d.ts +45 -0
- package/dist/dist/index.d.ts.map +1 -0
- package/dist/dist/index.js +61 -0
- package/dist/dist/index.js.map +1 -0
- package/dist/dist/interfaces/index.d.ts +1238 -0
- package/dist/dist/interfaces/index.d.ts.map +1 -0
- package/dist/dist/interfaces/index.js +3 -0
- package/dist/dist/interfaces/index.js.map +1 -0
- package/dist/dist/migrations/20260608222757_remote_schema.sql +5666 -0
- package/dist/dist/migrations/20260611174826_add_job_item_recurring_id_to_job_item.sql +7 -0
- package/dist/dist/migrations/20260625224612_add_dashboard_guided_tour_dismissed_to_settings_user.sql +5 -0
- package/dist/dist/migrations/20260627031433_support_chat_tables.sql +115 -0
- package/dist/dist/migrations/20260627031434_support_chat_rls.sql +37 -0
- package/dist/dist/migrations/20260704170209_add_estimate_scheduling.sql +121 -0
- package/dist/dist/migrations/20260704170210_add_estimate_scheduling_rls.sql +42 -0
- package/dist/dist/migrations/20260704235034_add_estimate_reminder_settings.sql +11 -0
- package/dist/dist/migrations/20260707003825_blocked_number_table.sql +32 -0
- package/dist/dist/migrations/20260707003826_blocked_number_rls.sql +22 -0
- package/dist/dist/migrations/20260709191055_sms_opt_out_table.sql +38 -0
- package/dist/dist/migrations/20260709191056_sms_opt_out_rls.sql +21 -0
- package/dist/dist/migrations/20260709214507_job_time_entry_table.sql +70 -0
- package/dist/dist/migrations/20260709214508_job_time_entry_rls.sql +21 -0
- package/dist/dist/migrations/20260711185956_phone_call_table.sql +99 -0
- package/dist/dist/migrations/20260711185957_phone_call_rls.sql +25 -0
- package/dist/dist/migrations/20260712014705_ai_voice_assistant_settings.sql +71 -0
- package/dist/dist/migrations/20260712014706_ai_voice_assistant_settings_rls.sql +14 -0
- package/dist/dist/migrations/20260712014707_ai_prompt_templates.sql +28 -0
- package/dist/dist/migrations/20260712014708_ai_prompt_templates_rls.sql +5 -0
- package/dist/dist/migrations/20260712014709_ai_prompt_templates_seed.sql +77 -0
- package/dist/dist/migrations/20260715011550_reminder_dispatch_table.sql +47 -0
- package/dist/dist/migrations/20260715011551_reminder_dispatch_rls.sql +22 -0
- package/dist/dist/migrations/20260715192923_dedup_sequence_contact_add_unique_constraint.sql +27 -0
- package/dist/dist/migrations/20260716194420_add_employee_location_tracking.sql +146 -0
- package/dist/dist/migrations/20260716194421_add_employee_location_tracking_rls.sql +154 -0
- package/dist/dist/migrations/20260716194422_get_client_map_last_completed_jobs.sql +28 -0
- package/dist/dist/migrations/20260717144535_add_blast_email_invalid_skipped_count.sql +2 -0
- package/dist/dist/migrations/20260717162900_create_blast_email_chunk.sql +36 -0
- package/dist/dist/migrations/20260717162901_add_blast_email_chunk_rls.sql +5 -0
- package/dist/dist/migrations/20260717162902_add_blast_email_chunk_progress_columns.sql +3 -0
- package/dist/dist/migrations/20260717162903_add_blast_email_bounce_tracking_columns.sql +4 -0
- package/dist/dist/migrations/20260717162904_create_email_hard_bounced.sql +23 -0
- package/dist/dist/migrations/20260717162905_add_email_hard_bounced_rls.sql +5 -0
- package/dist/dist/migrations/20260717164837_add_sequence_contact_sent_at.sql +19 -0
- package/dist/dist/migrations/20260717170920_add_sequence_contact_claim_index.sql +12 -0
- package/dist/dist/migrations/20260719215139_create_outbound_event.sql +59 -0
- package/dist/dist/migrations/20260719215140_create_outbound_event_rls.sql +16 -0
- package/dist/dist/migrations/20260719215141_create_webhook_delivery.sql +99 -0
- package/dist/dist/migrations/20260719215142_create_webhook_delivery_rls.sql +11 -0
- package/dist/dist/migrations/20260719215143_outbound_event_triggers.sql +219 -0
- package/dist/dist/migrations/20260719215144_create_api_key.sql +34 -0
- package/dist/dist/migrations/20260719215146_create_api_key_rls.sql +12 -0
- package/dist/dist/migrations/20260719215147_webhook_subscription_add_secret_active.sql +28 -0
- package/dist/dist/migrations/20260719215148_api_aggregate_create_functions.sql +479 -0
- package/dist/dist/migrations/20260721174837_webhook_subscription_generic_endpoints.sql +41 -0
- package/dist/dist/migrations/20260721185631_webhook_outbox_retention.sql +87 -0
- package/dist/dist/migrations/20260721190304_create_qb_sync_task.sql +120 -0
- package/dist/dist/migrations/20260721190306_qb_sync_task_rls.sql +8 -0
- package/dist/dist/migrations/20260721190307_qb_mapping_orphan_fks.sql +29 -0
- package/dist/dist/migrations/20260721190308_qb_task_retention.sql +90 -0
- package/dist/dist/migrations/20260721234401_create_permission_role.sql +83 -0
- package/dist/dist/migrations/20260721234402_permission_role_rls.sql +22 -0
- package/dist/dist/migrations/20260721234403_user_role_add_permission_role_id.sql +12 -0
- package/dist/dist/migrations/20260721234404_seed_permission_roles.sql +100 -0
- package/dist/dist/migrations/20260721234405_backfill_user_role_permission_role_id.sql +20 -0
- package/dist/dist/migrations/20260722193800_permission_role_add_manage_employees.sql +56 -0
- package/dist/dist/migrations/20260723025606_staging_new_employee_company_read_rls.sql +10 -0
- package/dist/dist/migrations/20260723134328_create_google_lsa_integration.sql +1016 -0
- package/dist/dist/migrations/20260723134329_google_lsa_integration_rls.sql +205 -0
- package/dist/dist/migrations/20260723150640_google_lsa_hardening.sql +169 -0
- package/dist/dist/migrations/20260723150641_google_lsa_hardening_rls.sql +12 -0
- package/dist/dist/migrations/20260724230721_google_lsa_facebook_exclusivity_attach.sql +104 -0
- package/dist/dist/migrations/20260724230722_google_lsa_facebook_exclusivity_attach_rls.sql +20 -0
- package/dist/dist/migrations/20260725172630_add_message_pagination_and_search.sql +265 -0
- package/dist/dist/migrations/20260725172631_add_message_pagination_and_search_rls.sql +72 -0
- package/dist/dist/migrations/20260725172632_optimize_message_thread_pagination_search.sql +245 -0
- package/dist/dist/migrations/20260725172633_harden_message_search_and_photo_pagination.sql +256 -0
- package/dist/dist/migrations/20260725172634_fix_message_thread_line_filter.sql +250 -0
- package/dist/dist/migrations/20260725174948_google_lsa_attach_bing_exclusivity_guard.sql +23 -0
- package/dist/dist/migrations/20260725200430_client_portal_settings.sql +41 -0
- package/dist/dist/migrations/20260725200431_client_portal_settings_rls.sql +21 -0
- package/dist/dist/migrations/20260725200433_client_portal_auth_tables.sql +81 -0
- package/dist/dist/migrations/20260725200434_client_portal_auth_rls.sql +18 -0
- package/dist/dist/migrations/20260725200435_portal_claim_login_attempt.sql +36 -0
- package/dist/dist/migrations/20260725200436_portal_appointment_request.sql +39 -0
- package/dist/dist/migrations/20260725200437_portal_appointment_request_rls.sql +10 -0
- package/dist/dist/migrations/20260725210000_google_reserve_integration.sql +85 -0
- package/dist/dist/migrations/20260725210001_google_reserve_integration_rls.sql +10 -0
- package/dist/dist/migrations/20260725220000_add_analytics_query_indexes.sql +47 -0
- package/dist/dist/migrations/20260725230000_onboarding_preload_data.sql +1074 -0
- package/dist/dist/migrations/20260725230001_onboarding_preload_data_rls.sql +40 -0
- package/dist/dist/migrations/20260725230002_add_pricebook_query_indexes.sql +13 -0
- package/dist/dist/migrations/20260726000000_facebook_ads_integration.sql +1538 -0
- package/dist/dist/migrations/20260726000001_facebook_ads_integration_rls.sql +356 -0
- package/dist/dist/migrations/20260726000002_facebook_ads_attach_lsa_exclusivity_guard.sql +25 -0
- package/dist/dist/migrations/20260726000003_facebook_ads_attach_bing_exclusivity_guard.sql +22 -0
- package/dist/dist/migrations/20260726010000_create_bing_ads_integration.sql +1112 -0
- package/dist/dist/migrations/20260726010001_bing_ads_integration_rls.sql +227 -0
- package/dist/dist/migrations/20260726220000_add_document_theme_settings.sql +11 -0
- package/dist/dist/migrations/20260726220001_update_document_theme_comments.sql +7 -0
- package/dist/dist/migrations/20260727210319_evolve_tier_for_pricing.sql +68 -0
- package/dist/dist/migrations/20260727210320_create_billing_addon.sql +85 -0
- package/dist/dist/migrations/20260727210321_create_billing_addon_rls.sql +21 -0
- package/dist/dist/migrations/20260728213930_create_usage_metering.sql +189 -0
- package/dist/dist/migrations/20260728213931_create_usage_metering_rls.sql +32 -0
- package/dist/dist/migrations/20260729004305_marketing_charge_txn_functions.sql +242 -0
- package/dist/dist/migrations/20260729004306_marketing_charge_txn_functions_rls.sql +37 -0
- package/dist/dist/migrations/20260729005524_update_deduct_from_balance_errcode.sql +42 -0
- package/dist/dist/migrations/20260730193601_fix_deduct_from_balance_lost_update.sql +46 -0
- package/dist/dist/migrations/20260730193602_add_blast_sms_chunk_reaper_columns.sql +27 -0
- package/dist/dist/migrations/20260731011415_create_google_calendar_connection.sql +84 -0
- package/dist/dist/migrations/20260731011416_google_calendar_connection_rls.sql +23 -0
- package/dist/dist/migrations/20260731011417_create_google_calendar_sync.sql +157 -0
- package/dist/dist/migrations/20260731011418_google_calendar_sync_rls.sql +26 -0
- package/dist/dist/migrations/20260731011419_event_outbound_triggers.sql +28 -0
- package/dist/dist/migrations/20260731011420_add_google_calendar_addon_key.sql +24 -0
- package/dist/dist/migrations/20260731011421_gcal_task_retention.sql +109 -0
- package/dist/dist/migrations/20260731234245_create_estimate_proposal.sql +157 -0
- package/dist/dist/migrations/20260731234247_estimate_proposal_rls.sql +46 -0
- package/dist/dist/migrations/20260731234248_add_proposal_estimate_settings.sql +17 -0
- package/dist/dist/migrations/20260731234249_add_estimate_proposals_addon_key.sql +25 -0
- package/dist/dist/migrations/20260731234250_approve_estimate_proposal_function.sql +133 -0
- package/dist/dist/migrations/20260801010609_save_estimate_proposal_function.sql +154 -0
- package/dist/dist/migrations/20260801173554_add_hide_autopilot_branding_setting.sql +6 -0
- package/dist/dist/migrations/20260806230917_add_client_billing_address_columns.sql +9 -0
- package/dist/dist/migrations/20260806230944_api_create_client_billing_address.sql +93 -0
- package/dist/dist/migrations/20260807014702_create_parent_id_company_id_keys.sql +20 -0
- package/dist/dist/migrations/20260807014703_create_contract_template.sql +30 -0
- package/dist/dist/migrations/20260807014704_create_contract.sql +233 -0
- package/dist/dist/migrations/20260807014705_create_contract_signature.sql +39 -0
- package/dist/dist/migrations/20260807014706_create_item_contract_requirement.sql +38 -0
- package/dist/dist/migrations/20260807014707_create_settings_contract.sql +23 -0
- package/dist/dist/migrations/20260807014708_contract_rls.sql +74 -0
- package/dist/dist/migrations/20260807014709_sign_contract_function.sql +92 -0
- package/dist/dist/migrations/20260807014710_create_settings_rental.sql +34 -0
- package/dist/dist/migrations/20260807014711_create_saved_location.sql +56 -0
- package/dist/dist/migrations/20260807014712_create_rental_task_option.sql +52 -0
- package/dist/dist/migrations/20260807014713_create_asset.sql +90 -0
- package/dist/dist/migrations/20260807014714_add_job_schedule_asset_fkeys.sql +39 -0
- package/dist/dist/migrations/20260807014715_rental_rls.sql +48 -0
- package/dist/dist/migrations/20260807014716_add_job_schedule_any_time.sql +7 -0
- package/dist/dist/migrations/20260807014717_add_job_schedule_target_location.sql +48 -0
- package/dist/dist/migrations/20260807014718_job_schedule_completion_guard.sql +53 -0
- package/dist/dist/migrations/20260807014719_add_job_schedule_list_sort_key.sql +7 -0
- package/dist/dist/migrations/20260807014720_add_item_duration.sql +11 -0
- package/dist/dist/migrations/20260807014721_add_settings_booking_enforce_rental_availability.sql +9 -0
- package/dist/dist/migrations/20260807014722_add_job_unsigned_rental_agreement.sql +10 -0
- package/dist/dist/migrations/20260807014723_add_item_size.sql +12 -0
- package/dist/dist/migrations/20260809193533_add_booking_path_button_labels.sql +11 -0
- package/dist/dist/migrations/20260809231721_add_route_distance_cache.sql +37 -0
- package/dist/dist/migrations/20260809231722_route_distance_cache_rls.sql +15 -0
- package/dist/dist/migrations/20260809231723_apply_rental_route_plan_function.sql +275 -0
- package/dist/dist/migrations/20260810171550_add_settings_job_default_job_types.sql +9 -0
- package/dist/dist/migrations/20260810212430_backfill_rental_task_option_sort_order.sql +61 -0
- package/dist/dist/migrations/20260811224317_add_settings_user_dnd_hint_dismissed.sql +9 -0
- package/dist/dist/migrations/20260813174503_repair_user_role_email_drift.sql +59 -0
- package/dist/dist/migrations/20260813174504_sync_user_role_email_trigger.sql +30 -0
- package/dist/dist/migrations/20260813175210_add_event_list_sort_key.sql +5 -0
- package/dist/dist/migrations/20260814172222_add_client_auto_bill_on_completion.sql +6 -0
- package/dist/dist/migrations/20260816160252_create_automation_rule.sql +99 -0
- package/dist/dist/migrations/20260816160253_automation_rule_rls.sql +19 -0
- package/dist/dist/migrations/20260816160254_create_settings_automation.sql +36 -0
- package/dist/dist/migrations/20260816160255_settings_automation_rls.sql +18 -0
- package/dist/dist/migrations/20260816160256_create_automation_event.sql +71 -0
- package/dist/dist/migrations/20260816160257_automation_event_rls.sql +12 -0
- package/dist/dist/migrations/20260816160258_create_automation_run.sql +129 -0
- package/dist/dist/migrations/20260816160259_automation_run_rls.sql +19 -0
- package/dist/dist/migrations/20260816160300_add_client_created_via.sql +25 -0
- package/dist/dist/migrations/20260816160301_add_estimate_sent_at.sql +39 -0
- package/dist/dist/migrations/20260816160302_add_message_automation_ref.sql +10 -0
- package/dist/dist/migrations/20260816160303_automation_event_triggers.sql +236 -0
- package/dist/dist/migrations/20260816160304_automation_retention.sql +142 -0
- package/dist/dist/migrations/20260816202404_add_automation_center_addon_key.sql +26 -0
- package/dist/dist/migrations/20260816225501_widen_automation_rule_trigger_action_checks.sql +25 -0
- package/dist/dist/migrations/20260816225502_widen_automation_event_type_check.sql +18 -0
- package/dist/dist/migrations/20260816225503_add_invoice_id_to_automation_tables.sql +18 -0
- package/dist/dist/migrations/20260816225504_add_job_rescheduled_capture_trigger.sql +51 -0
- package/dist/dist/migrations/20260816225505_add_lead_converted_capture_trigger.sql +26 -0
- package/dist/dist/migrations/20260816225506_fix_booking_request_import_guard.sql +27 -0
- package/dist/dist/migrations/20260816225507_widen_outbound_event_for_automation.sql +38 -0
- package/dist/dist/migrations/20260817002626_add_user_last_login_platform_columns.sql +14 -0
- package/dist/dist/migrations/20260817221703_create_deletion.sql +34 -0
- package/dist/dist/migrations/20260817221707_deletion_rls.sql +21 -0
- package/dist/dist/migrations/20260817221708_add_soft_delete_columns.sql +82 -0
- package/dist/dist/migrations/20260817221709_trash_functions.sql +434 -0
- package/dist/dist/migrations/20260817221710_outbound_event_soft_delete.sql +55 -0
- package/dist/dist/migrations/20260817221711_automation_soft_delete_events.sql +77 -0
- package/dist/dist/migrations/20260817221712_fix_estimate_address_cascade.sql +14 -0
- package/dist/dist/migrations/20260817232630_trash_batch_function.sql +67 -0
- package/dist/dist/migrations/20260819170204_create_email_spam_complaint.sql +23 -0
- package/dist/dist/migrations/20260819170205_add_email_spam_complaint_rls.sql +5 -0
- package/dist/dist/migrations/20260819170206_email_unsubscribed_upsert_constraints.sql +14 -0
- package/dist/dist/migrations/20260819180151_get_suppressed_emails_function.sql +23 -0
- package/dist/dist/migrations/20260819191143_is_deleted_backfill_guard.sql +29 -0
- package/dist/dist/migrations/20260819191144_trash_functions_no_dual_write.sql +319 -0
- package/dist/dist/migrations/20260819191145_client_map_jobs_deleted_at.sql +33 -0
- package/dist/dist/migrations/20260819191147_booking_declined_deleted_at.sql +28 -0
- package/dist/dist/migrations/20260819191148_onboarding_preload_no_address_is_deleted.sql +1019 -0
- package/dist/dist/migrations/20260820222756_add_settings_job_hide_task_timer.sql +2 -0
- package/dist/dist/migrations/20260824234151_add_blast_chunk_recipients.sql +13 -0
- package/dist/dist/migrations/20260824234152_add_blast_pause_cancel_columns.sql +10 -0
- package/dist/dist/migrations/20260825014725_get_job_ids_in_range.sql +42 -0
- package/dist/dist/migrations/20260825194452_add_failed_job_status.sql +15 -0
- package/dist/dist/migrations/20260825194457_remove_take_to_rental_task_options.sql +24 -0
- package/dist/dist/migrations/20260825194458_remove_take_to_job_schedules.sql +12 -0
- package/dist/dist/migrations/20260825194459_rework_apply_rental_route_plan_no_take_to.sql +168 -0
- package/dist/dist/migrations/20260825194500_drop_job_schedule_target_location.sql +24 -0
- package/dist/dist/migrations/20260825194501_add_job_schedule_size.sql +17 -0
- package/dist/dist/migrations/20260825233001_add_job_schedule_id_company_id_key.sql +11 -0
- package/dist/dist/migrations/20260825233002_create_route_plan.sql +52 -0
- package/dist/dist/migrations/20260825233003_create_route_plan_stop.sql +159 -0
- package/dist/dist/migrations/20260825233004_route_plan_rls.sql +32 -0
- package/dist/dist/migrations/20260825233005_apply_rental_route_plan_v2.sql +351 -0
- package/dist/dist/migrations/20260826150934_facebook_ads_lead_ingestion.sql +1711 -0
- package/dist/dist/migrations/20260826151141_facebook_ads_lead_ingestion_rls_v2.sql +43 -0
- package/dist/dist/migrations/20260826234722_facebook_messenger_ingestion.sql +703 -0
- package/dist/dist/migrations/20260826234723_facebook_messenger_ingestion_rls.sql +80 -0
- package/dist/dist/migrations/20260826234724_widen_automations_messenger.sql +46 -0
- package/dist/dist/migrations/20260827100001_add_route_plan_minutes.sql +10 -0
- package/dist/dist/migrations/20260827100002_add_route_plan_stop_leg_minutes.sql +9 -0
- package/dist/dist/migrations/20260827100003_apply_rental_route_plan_v3.sql +341 -0
- package/dist/dist/migrations/20260827230346_apply_rental_route_plan_v4.sql +366 -0
- package/dist/dist/migrations/20260829090911_create_fleet_schema.sql +3698 -0
- package/dist/dist/migrations/20260829090912_fleet_rls.sql +1011 -0
- package/dist/dist/migrations/20260831102212_add_online_booking_address_display.sql +8 -0
- package/dist/dist/providers/AddressProvider.d.ts +9 -0
- package/dist/dist/providers/AddressProvider.d.ts.map +1 -0
- package/dist/dist/providers/AddressProvider.js +207 -0
- package/dist/dist/providers/AddressProvider.js.map +1 -0
- package/dist/dist/providers/AutomationsProvider.d.ts +11 -0
- package/dist/dist/providers/AutomationsProvider.d.ts.map +1 -0
- package/dist/dist/providers/AutomationsProvider.js +170 -0
- package/dist/dist/providers/AutomationsProvider.js.map +1 -0
- package/dist/dist/providers/BlockedNumbersProvider.d.ts +13 -0
- package/dist/dist/providers/BlockedNumbersProvider.d.ts.map +1 -0
- package/dist/dist/providers/BlockedNumbersProvider.js +71 -0
- package/dist/dist/providers/BlockedNumbersProvider.js.map +1 -0
- package/dist/dist/providers/CategoryProvider.d.ts +8 -0
- package/dist/dist/providers/CategoryProvider.d.ts.map +1 -0
- package/dist/dist/providers/CategoryProvider.js +95 -0
- package/dist/dist/providers/CategoryProvider.js.map +1 -0
- package/dist/dist/providers/ClientProvider.d.ts +39 -0
- package/dist/dist/providers/ClientProvider.d.ts.map +1 -0
- package/dist/dist/providers/ClientProvider.js +253 -0
- package/dist/dist/providers/ClientProvider.js.map +1 -0
- package/dist/dist/providers/CompanyProvider.d.ts +7 -0
- package/dist/dist/providers/CompanyProvider.d.ts.map +1 -0
- package/dist/dist/providers/CompanyProvider.js +114 -0
- package/dist/dist/providers/CompanyProvider.js.map +1 -0
- package/dist/dist/providers/DashboardProvider.d.ts +78 -0
- package/dist/dist/providers/DashboardProvider.d.ts.map +1 -0
- package/dist/dist/providers/DashboardProvider.js +568 -0
- package/dist/dist/providers/DashboardProvider.js.map +1 -0
- package/dist/dist/providers/DiscountProvider.d.ts +8 -0
- package/dist/dist/providers/DiscountProvider.d.ts.map +1 -0
- package/dist/dist/providers/DiscountProvider.js +92 -0
- package/dist/dist/providers/DiscountProvider.js.map +1 -0
- package/dist/dist/providers/EmployeesProvider.d.ts +6 -0
- package/dist/dist/providers/EmployeesProvider.d.ts.map +1 -0
- package/dist/dist/providers/EmployeesProvider.js +185 -0
- package/dist/dist/providers/EmployeesProvider.js.map +1 -0
- package/dist/dist/providers/EstimatesProvider.d.ts +16 -0
- package/dist/dist/providers/EstimatesProvider.d.ts.map +1 -0
- package/dist/dist/providers/EstimatesProvider.js +251 -0
- package/dist/dist/providers/EstimatesProvider.js.map +1 -0
- package/dist/dist/providers/EventsProvider.d.ts +4 -0
- package/dist/dist/providers/EventsProvider.d.ts.map +1 -0
- package/dist/dist/providers/EventsProvider.js +24 -0
- package/dist/dist/providers/EventsProvider.js.map +1 -0
- package/dist/dist/providers/FilesProvider.d.ts +17 -0
- package/dist/dist/providers/FilesProvider.d.ts.map +1 -0
- package/dist/dist/providers/FilesProvider.js +201 -0
- package/dist/dist/providers/FilesProvider.js.map +1 -0
- package/dist/dist/providers/InvoicesProvider.d.ts +13 -0
- package/dist/dist/providers/InvoicesProvider.d.ts.map +1 -0
- package/dist/dist/providers/InvoicesProvider.js +207 -0
- package/dist/dist/providers/InvoicesProvider.js.map +1 -0
- package/dist/dist/providers/ItemProvider.d.ts +9 -0
- package/dist/dist/providers/ItemProvider.d.ts.map +1 -0
- package/dist/dist/providers/ItemProvider.js +201 -0
- package/dist/dist/providers/ItemProvider.js.map +1 -0
- package/dist/dist/providers/JobSourceProvider.d.ts +8 -0
- package/dist/dist/providers/JobSourceProvider.d.ts.map +1 -0
- package/dist/dist/providers/JobSourceProvider.js +75 -0
- package/dist/dist/providers/JobSourceProvider.js.map +1 -0
- package/dist/dist/providers/JobTypeProvider.d.ts +8 -0
- package/dist/dist/providers/JobTypeProvider.d.ts.map +1 -0
- package/dist/dist/providers/JobTypeProvider.js +75 -0
- package/dist/dist/providers/JobTypeProvider.js.map +1 -0
- package/dist/dist/providers/JobsProvider.d.ts +35 -0
- package/dist/dist/providers/JobsProvider.d.ts.map +1 -0
- package/dist/dist/providers/JobsProvider.js +863 -0
- package/dist/dist/providers/JobsProvider.js.map +1 -0
- package/dist/dist/providers/MessagesProvider.d.ts +33 -0
- package/dist/dist/providers/MessagesProvider.d.ts.map +1 -0
- package/dist/dist/providers/MessagesProvider.js +415 -0
- package/dist/dist/providers/MessagesProvider.js.map +1 -0
- package/dist/dist/providers/PaymentProvider.d.ts +9 -0
- package/dist/dist/providers/PaymentProvider.d.ts.map +1 -0
- package/dist/dist/providers/PaymentProvider.js +48 -0
- package/dist/dist/providers/PaymentProvider.js.map +1 -0
- package/dist/dist/providers/PermissionRolesProvider.d.ts +5 -0
- package/dist/dist/providers/PermissionRolesProvider.d.ts.map +1 -0
- package/dist/dist/providers/PermissionRolesProvider.js +30 -0
- package/dist/dist/providers/PermissionRolesProvider.js.map +1 -0
- package/dist/dist/providers/PhoneNumberProvider.d.ts +12 -0
- package/dist/dist/providers/PhoneNumberProvider.d.ts.map +1 -0
- package/dist/dist/providers/PhoneNumberProvider.js +184 -0
- package/dist/dist/providers/PhoneNumberProvider.js.map +1 -0
- package/dist/dist/providers/ProfileProvider.d.ts +6 -0
- package/dist/dist/providers/ProfileProvider.d.ts.map +1 -0
- package/dist/dist/providers/ProfileProvider.js +71 -0
- package/dist/dist/providers/ProfileProvider.js.map +1 -0
- package/dist/dist/providers/RentalsProvider.d.ts +28 -0
- package/dist/dist/providers/RentalsProvider.d.ts.map +1 -0
- package/dist/dist/providers/RentalsProvider.js +438 -0
- package/dist/dist/providers/RentalsProvider.js.map +1 -0
- package/dist/dist/providers/SettingsBookingProvider.d.ts +9 -0
- package/dist/dist/providers/SettingsBookingProvider.d.ts.map +1 -0
- package/dist/dist/providers/SettingsBookingProvider.js +163 -0
- package/dist/dist/providers/SettingsBookingProvider.js.map +1 -0
- package/dist/dist/providers/SettingsProvider.d.ts +8 -0
- package/dist/dist/providers/SettingsProvider.d.ts.map +1 -0
- package/dist/dist/providers/SettingsProvider.js +86 -0
- package/dist/dist/providers/SettingsProvider.js.map +1 -0
- package/dist/dist/providers/TagsProvider.d.ts +12 -0
- package/dist/dist/providers/TagsProvider.d.ts.map +1 -0
- package/dist/dist/providers/TagsProvider.js +210 -0
- package/dist/dist/providers/TagsProvider.js.map +1 -0
- package/dist/dist/providers/TimeCardProvider.d.ts +8 -0
- package/dist/dist/providers/TimeCardProvider.d.ts.map +1 -0
- package/dist/dist/providers/TimeCardProvider.js +93 -0
- package/dist/dist/providers/TimeCardProvider.js.map +1 -0
- package/dist/dist/providers/TrashProvider.d.ts +13 -0
- package/dist/dist/providers/TrashProvider.d.ts.map +1 -0
- package/dist/dist/providers/TrashProvider.js +98 -0
- package/dist/dist/providers/TrashProvider.js.map +1 -0
- package/dist/dist/providers/dashboardAnalytics.d.ts +9 -0
- package/dist/dist/providers/dashboardAnalytics.d.ts.map +1 -0
- package/dist/dist/providers/dashboardAnalytics.js +41 -0
- package/dist/dist/providers/dashboardAnalytics.js.map +1 -0
- package/dist/dist/providers/dashboardAnalytics.test.d.ts +2 -0
- package/dist/dist/providers/dashboardAnalytics.test.d.ts.map +1 -0
- package/dist/dist/providers/dashboardAnalytics.test.js +111 -0
- package/dist/dist/providers/dashboardAnalytics.test.js.map +1 -0
- package/dist/dist/server-safe/BlockedNumberFunctions.d.ts +11 -0
- package/dist/dist/server-safe/BlockedNumberFunctions.d.ts.map +1 -0
- package/dist/dist/server-safe/BlockedNumberFunctions.js +16 -0
- package/dist/dist/server-safe/BlockedNumberFunctions.js.map +1 -0
- package/dist/dist/server-safe/EmployeeFunctions.d.ts +14 -0
- package/dist/dist/server-safe/EmployeeFunctions.d.ts.map +1 -0
- package/dist/dist/server-safe/EmployeeFunctions.js +70 -0
- package/dist/dist/server-safe/EmployeeFunctions.js.map +1 -0
- package/dist/dist/server-safe/EmployeeLocationFunctions.d.ts +7 -0
- package/dist/dist/server-safe/EmployeeLocationFunctions.d.ts.map +1 -0
- package/dist/dist/server-safe/EmployeeLocationFunctions.js +47 -0
- package/dist/dist/server-safe/EmployeeLocationFunctions.js.map +1 -0
- package/dist/dist/server-safe/EstimateFunctions.d.ts +15 -0
- package/dist/dist/server-safe/EstimateFunctions.d.ts.map +1 -0
- package/dist/dist/server-safe/EstimateFunctions.js +34 -0
- package/dist/dist/server-safe/EstimateFunctions.js.map +1 -0
- package/dist/dist/server-safe/InvoiceFunctions.d.ts +21 -0
- package/dist/dist/server-safe/InvoiceFunctions.d.ts.map +1 -0
- package/dist/dist/server-safe/InvoiceFunctions.js +124 -0
- package/dist/dist/server-safe/InvoiceFunctions.js.map +1 -0
- package/dist/dist/server-safe/JobFunctions.d.ts +15 -0
- package/dist/dist/server-safe/JobFunctions.d.ts.map +1 -0
- package/dist/dist/server-safe/JobFunctions.js +56 -0
- package/dist/dist/server-safe/JobFunctions.js.map +1 -0
- package/dist/dist/server-safe/PaymentFunctions.d.ts +16 -0
- package/dist/dist/server-safe/PaymentFunctions.d.ts.map +1 -0
- package/dist/dist/server-safe/PaymentFunctions.js +80 -0
- package/dist/dist/server-safe/PaymentFunctions.js.map +1 -0
- package/dist/dist/server-safe/PermissionFunctions.d.ts +7 -0
- package/dist/dist/server-safe/PermissionFunctions.d.ts.map +1 -0
- package/dist/dist/server-safe/PermissionFunctions.js +39 -0
- package/dist/dist/server-safe/PermissionFunctions.js.map +1 -0
- package/dist/dist/server-safe/TagFunctions.d.ts +18 -0
- package/dist/dist/server-safe/TagFunctions.d.ts.map +1 -0
- package/dist/dist/server-safe/TagFunctions.js +52 -0
- package/dist/dist/server-safe/TagFunctions.js.map +1 -0
- package/dist/dist/server-safe/autoJobType.d.ts +16 -0
- package/dist/dist/server-safe/autoJobType.d.ts.map +1 -0
- package/dist/dist/server-safe/autoJobType.js +39 -0
- package/dist/dist/server-safe/autoJobType.js.map +1 -0
- package/dist/dist/server-safe/autoJobType.test.d.ts +2 -0
- package/dist/dist/server-safe/autoJobType.test.d.ts.map +1 -0
- package/dist/dist/server-safe/autoJobType.test.js +83 -0
- package/dist/dist/server-safe/autoJobType.test.js.map +1 -0
- package/dist/dist/server-safe/automations.d.ts +197 -0
- package/dist/dist/server-safe/automations.d.ts.map +1 -0
- package/dist/dist/server-safe/automations.js +757 -0
- package/dist/dist/server-safe/automations.js.map +1 -0
- package/dist/dist/server-safe/automations.test.d.ts +2 -0
- package/dist/dist/server-safe/automations.test.d.ts.map +1 -0
- package/dist/dist/server-safe/automations.test.js +441 -0
- package/dist/dist/server-safe/automations.test.js.map +1 -0
- package/dist/dist/server-safe/constants.d.ts +156 -0
- package/dist/dist/server-safe/constants.d.ts.map +1 -0
- package/dist/dist/server-safe/constants.js +186 -0
- package/dist/dist/server-safe/constants.js.map +1 -0
- package/dist/dist/server-safe/contracts.d.ts +3 -0
- package/dist/dist/server-safe/contracts.d.ts.map +1 -0
- package/dist/dist/server-safe/contracts.js +15 -0
- package/dist/dist/server-safe/contracts.js.map +1 -0
- package/dist/dist/server-safe/database.types.d.ts +9510 -0
- package/dist/dist/server-safe/database.types.d.ts.map +1 -0
- package/dist/dist/server-safe/database.types.js +40 -0
- package/dist/dist/server-safe/database.types.js.map +1 -0
- package/dist/dist/server-safe/entitlements.d.ts +99 -0
- package/dist/dist/server-safe/entitlements.d.ts.map +1 -0
- package/dist/dist/server-safe/entitlements.js +487 -0
- package/dist/dist/server-safe/entitlements.js.map +1 -0
- package/dist/dist/server-safe/facebookAdsLeads.d.ts +225 -0
- package/dist/dist/server-safe/facebookAdsLeads.d.ts.map +1 -0
- package/dist/dist/server-safe/facebookAdsLeads.js +376 -0
- package/dist/dist/server-safe/facebookAdsLeads.js.map +1 -0
- package/dist/dist/server-safe/facebookAdsLeads.test.d.ts +2 -0
- package/dist/dist/server-safe/facebookAdsLeads.test.d.ts.map +1 -0
- package/dist/dist/server-safe/facebookAdsLeads.test.js +189 -0
- package/dist/dist/server-safe/facebookAdsLeads.test.js.map +1 -0
- package/dist/dist/server-safe/facebookAdsLeadsSql.test.d.ts +2 -0
- package/dist/dist/server-safe/facebookAdsLeadsSql.test.d.ts.map +1 -0
- package/dist/dist/server-safe/facebookAdsLeadsSql.test.js +32 -0
- package/dist/dist/server-safe/facebookAdsLeadsSql.test.js.map +1 -0
- package/dist/dist/server-safe/facebookMessenger.d.ts +20 -0
- package/dist/dist/server-safe/facebookMessenger.d.ts.map +1 -0
- package/dist/dist/server-safe/facebookMessenger.js +63 -0
- package/dist/dist/server-safe/facebookMessenger.js.map +1 -0
- package/dist/dist/server-safe/facebookMessenger.test.d.ts +2 -0
- package/dist/dist/server-safe/facebookMessenger.test.d.ts.map +1 -0
- package/dist/dist/server-safe/facebookMessenger.test.js +51 -0
- package/dist/dist/server-safe/facebookMessenger.test.js.map +1 -0
- package/dist/dist/server-safe/helpers.d.ts +78 -0
- package/dist/dist/server-safe/helpers.d.ts.map +1 -0
- package/dist/dist/server-safe/helpers.js +569 -0
- package/dist/dist/server-safe/helpers.js.map +1 -0
- package/dist/dist/server-safe/index.d.ts +30 -0
- package/dist/dist/server-safe/index.d.ts.map +1 -0
- package/dist/dist/server-safe/index.js +46 -0
- package/dist/dist/server-safe/index.js.map +1 -0
- package/dist/dist/server-safe/permissions.d.ts +29 -0
- package/dist/dist/server-safe/permissions.d.ts.map +1 -0
- package/dist/dist/server-safe/permissions.js +180 -0
- package/dist/dist/server-safe/permissions.js.map +1 -0
- package/dist/dist/server-safe/proposals.d.ts +6 -0
- package/dist/dist/server-safe/proposals.d.ts.map +1 -0
- package/dist/dist/server-safe/proposals.js +58 -0
- package/dist/dist/server-safe/proposals.js.map +1 -0
- package/dist/dist/server-safe/rentalAvailability.d.ts +56 -0
- package/dist/dist/server-safe/rentalAvailability.d.ts.map +1 -0
- package/dist/dist/server-safe/rentalAvailability.js +124 -0
- package/dist/dist/server-safe/rentalAvailability.js.map +1 -0
- package/dist/dist/server-safe/rentalAvailability.test.d.ts +2 -0
- package/dist/dist/server-safe/rentalAvailability.test.d.ts.map +1 -0
- package/dist/dist/server-safe/rentalAvailability.test.js +290 -0
- package/dist/dist/server-safe/rentalAvailability.test.js.map +1 -0
- package/dist/dist/server-safe/rentals.d.ts +95 -0
- package/dist/dist/server-safe/rentals.d.ts.map +1 -0
- package/dist/dist/server-safe/rentals.js +432 -0
- package/dist/dist/server-safe/rentals.js.map +1 -0
- package/dist/dist/server-safe/rentals.test.d.ts +2 -0
- package/dist/dist/server-safe/rentals.test.d.ts.map +1 -0
- package/dist/dist/server-safe/rentals.test.js +1034 -0
- package/dist/dist/server-safe/rentals.test.js.map +1 -0
- package/dist/dist/server-safe/routeOptimization.d.ts +155 -0
- package/dist/dist/server-safe/routeOptimization.d.ts.map +1 -0
- package/dist/dist/server-safe/routeOptimization.js +120 -0
- package/dist/dist/server-safe/routeOptimization.js.map +1 -0
- package/dist/dist/server-safe/routeOptimization.test.d.ts +2 -0
- package/dist/dist/server-safe/routeOptimization.test.d.ts.map +1 -0
- package/dist/dist/server-safe/routeOptimization.test.js +234 -0
- package/dist/dist/server-safe/routeOptimization.test.js.map +1 -0
- package/dist/dist/server-safe/routePlanFingerprint.d.ts +13 -0
- package/dist/dist/server-safe/routePlanFingerprint.d.ts.map +1 -0
- package/dist/dist/server-safe/routePlanFingerprint.js +49 -0
- package/dist/dist/server-safe/routePlanFingerprint.js.map +1 -0
- package/dist/dist/server-safe/routePlanFingerprint.test.d.ts +2 -0
- package/dist/dist/server-safe/routePlanFingerprint.test.d.ts.map +1 -0
- package/dist/dist/server-safe/routePlanFingerprint.test.js +68 -0
- package/dist/dist/server-safe/routePlanFingerprint.test.js.map +1 -0
- package/dist/dist/server-safe/scheduleListOrder.d.ts +18 -0
- package/dist/dist/server-safe/scheduleListOrder.d.ts.map +1 -0
- package/dist/dist/server-safe/scheduleListOrder.js +92 -0
- package/dist/dist/server-safe/scheduleListOrder.js.map +1 -0
- package/dist/dist/server-safe/scheduleListOrder.test.d.ts +2 -0
- package/dist/dist/server-safe/scheduleListOrder.test.d.ts.map +1 -0
- package/dist/dist/server-safe/scheduleListOrder.test.js +260 -0
- package/dist/dist/server-safe/scheduleListOrder.test.js.map +1 -0
- package/dist/dist/server-safe/serviceMinutes.d.ts +13 -0
- package/dist/dist/server-safe/serviceMinutes.d.ts.map +1 -0
- package/dist/dist/server-safe/serviceMinutes.js +38 -0
- package/dist/dist/server-safe/serviceMinutes.js.map +1 -0
- package/dist/dist/server-safe/serviceMinutes.test.d.ts +2 -0
- package/dist/dist/server-safe/serviceMinutes.test.d.ts.map +1 -0
- package/dist/dist/server-safe/serviceMinutes.test.js +86 -0
- package/dist/dist/server-safe/serviceMinutes.test.js.map +1 -0
- package/dist/dist/server-safe/settingsSearch.d.ts +24 -0
- package/dist/dist/server-safe/settingsSearch.d.ts.map +1 -0
- package/dist/dist/server-safe/settingsSearch.js +8 -0
- package/dist/dist/server-safe/settingsSearch.js.map +1 -0
- package/dist/dist/server-safe/supportChat.d.ts +59 -0
- package/dist/dist/server-safe/supportChat.d.ts.map +1 -0
- package/dist/dist/server-safe/supportChat.js +56 -0
- package/dist/dist/server-safe/supportChat.js.map +1 -0
- package/dist/dist/server-safe/templates.d.ts +15 -0
- package/dist/dist/server-safe/templates.d.ts.map +1 -0
- package/dist/dist/server-safe/templates.js +91 -0
- package/dist/dist/server-safe/templates.js.map +1 -0
- package/dist/dist/server-safe/templates.test.d.ts +2 -0
- package/dist/dist/server-safe/templates.test.d.ts.map +1 -0
- package/dist/dist/server-safe/templates.test.js +62 -0
- package/dist/dist/server-safe/templates.test.js.map +1 -0
- package/dist/dist/server-safe/trash.d.ts +50 -0
- package/dist/dist/server-safe/trash.d.ts.map +1 -0
- package/dist/dist/server-safe/trash.js +100 -0
- package/dist/dist/server-safe/trash.js.map +1 -0
- package/dist/dist/utils/LogHandler.d.ts +3 -0
- package/dist/dist/utils/LogHandler.d.ts.map +1 -0
- package/dist/dist/utils/LogHandler.js +16 -0
- package/dist/dist/utils/LogHandler.js.map +1 -0
- package/dist/dist/utils/convertSupabaseImagesToAssets.d.ts +28 -0
- package/dist/dist/utils/convertSupabaseImagesToAssets.d.ts.map +1 -0
- package/dist/dist/utils/convertSupabaseImagesToAssets.js +14 -0
- package/dist/dist/utils/convertSupabaseImagesToAssets.js.map +1 -0
- package/dist/index.d.ts +45 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +61 -0
- package/dist/index.js.map +1 -0
- package/dist/interfaces/index.d.ts +1238 -0
- package/dist/interfaces/index.d.ts.map +1 -0
- package/dist/interfaces/index.js +3 -0
- package/dist/interfaces/index.js.map +1 -0
- package/dist/providers/AddressProvider.d.ts +9 -0
- package/dist/providers/AddressProvider.d.ts.map +1 -0
- package/dist/providers/AddressProvider.js +207 -0
- package/dist/providers/AddressProvider.js.map +1 -0
- package/dist/providers/AutomationsProvider.d.ts +11 -0
- package/dist/providers/AutomationsProvider.d.ts.map +1 -0
- package/dist/providers/AutomationsProvider.js +170 -0
- package/dist/providers/AutomationsProvider.js.map +1 -0
- package/dist/providers/BlockedNumbersProvider.d.ts +7 -0
- package/dist/providers/BlockedNumbersProvider.d.ts.map +1 -0
- package/dist/providers/BlockedNumbersProvider.js +71 -0
- package/dist/providers/BlockedNumbersProvider.js.map +1 -0
- package/dist/providers/CategoryProvider.d.ts +8 -0
- package/dist/providers/CategoryProvider.d.ts.map +1 -0
- package/dist/providers/CategoryProvider.js +95 -0
- package/dist/providers/CategoryProvider.js.map +1 -0
- package/dist/providers/ClientProvider.d.ts +39 -0
- package/dist/providers/ClientProvider.d.ts.map +1 -0
- package/dist/providers/ClientProvider.js +253 -0
- package/dist/providers/ClientProvider.js.map +1 -0
- package/dist/providers/CompanyProvider.d.ts +7 -0
- package/dist/providers/CompanyProvider.d.ts.map +1 -0
- package/dist/providers/CompanyProvider.js +114 -0
- package/dist/providers/CompanyProvider.js.map +1 -0
- package/dist/providers/DashboardProvider.d.ts +78 -0
- package/dist/providers/DashboardProvider.d.ts.map +1 -0
- package/dist/providers/DashboardProvider.js +568 -0
- package/dist/providers/DashboardProvider.js.map +1 -0
- package/dist/providers/DiscountProvider.d.ts +8 -0
- package/dist/providers/DiscountProvider.d.ts.map +1 -0
- package/dist/providers/DiscountProvider.js +92 -0
- package/dist/providers/DiscountProvider.js.map +1 -0
- package/dist/providers/EmployeesProvider.d.ts +6 -0
- package/dist/providers/EmployeesProvider.d.ts.map +1 -0
- package/dist/providers/EmployeesProvider.js +185 -0
- package/dist/providers/EmployeesProvider.js.map +1 -0
- package/dist/providers/EstimatesProvider.d.ts +16 -0
- package/dist/providers/EstimatesProvider.d.ts.map +1 -0
- package/dist/providers/EstimatesProvider.js +251 -0
- package/dist/providers/EstimatesProvider.js.map +1 -0
- package/dist/providers/EventsProvider.d.ts +4 -0
- package/dist/providers/EventsProvider.d.ts.map +1 -0
- package/dist/providers/EventsProvider.js +24 -0
- package/dist/providers/EventsProvider.js.map +1 -0
- package/dist/providers/FilesProvider.d.ts +17 -0
- package/dist/providers/FilesProvider.d.ts.map +1 -0
- package/dist/providers/FilesProvider.js +201 -0
- package/dist/providers/FilesProvider.js.map +1 -0
- package/dist/providers/InvoicesProvider.d.ts +13 -0
- package/dist/providers/InvoicesProvider.d.ts.map +1 -0
- package/dist/providers/InvoicesProvider.js +207 -0
- package/dist/providers/InvoicesProvider.js.map +1 -0
- package/dist/providers/ItemProvider.d.ts +9 -0
- package/dist/providers/ItemProvider.d.ts.map +1 -0
- package/dist/providers/ItemProvider.js +201 -0
- package/dist/providers/ItemProvider.js.map +1 -0
- package/dist/providers/JobSourceProvider.d.ts +8 -0
- package/dist/providers/JobSourceProvider.d.ts.map +1 -0
- package/dist/providers/JobSourceProvider.js +75 -0
- package/dist/providers/JobSourceProvider.js.map +1 -0
- package/dist/providers/JobTypeProvider.d.ts +8 -0
- package/dist/providers/JobTypeProvider.d.ts.map +1 -0
- package/dist/providers/JobTypeProvider.js +75 -0
- package/dist/providers/JobTypeProvider.js.map +1 -0
- package/dist/providers/JobsProvider.d.ts +20 -0
- package/dist/providers/JobsProvider.d.ts.map +1 -0
- package/dist/providers/JobsProvider.js +863 -0
- package/dist/providers/JobsProvider.js.map +1 -0
- package/dist/providers/MessagesProvider.d.ts +14 -0
- package/dist/providers/MessagesProvider.d.ts.map +1 -0
- package/dist/providers/MessagesProvider.js +415 -0
- package/dist/providers/MessagesProvider.js.map +1 -0
- package/dist/providers/PaymentProvider.d.ts +9 -0
- package/dist/providers/PaymentProvider.d.ts.map +1 -0
- package/dist/providers/PaymentProvider.js +48 -0
- package/dist/providers/PaymentProvider.js.map +1 -0
- package/dist/providers/PermissionRolesProvider.d.ts +5 -0
- package/dist/providers/PermissionRolesProvider.d.ts.map +1 -0
- package/dist/providers/PermissionRolesProvider.js +30 -0
- package/dist/providers/PermissionRolesProvider.js.map +1 -0
- package/dist/providers/PhoneNumberProvider.d.ts +12 -0
- package/dist/providers/PhoneNumberProvider.d.ts.map +1 -0
- package/dist/providers/PhoneNumberProvider.js +184 -0
- package/dist/providers/PhoneNumberProvider.js.map +1 -0
- package/dist/providers/ProfileProvider.d.ts +6 -0
- package/dist/providers/ProfileProvider.d.ts.map +1 -0
- package/dist/providers/ProfileProvider.js +71 -0
- package/dist/providers/ProfileProvider.js.map +1 -0
- package/dist/providers/RentalsProvider.d.ts +28 -0
- package/dist/providers/RentalsProvider.d.ts.map +1 -0
- package/dist/providers/RentalsProvider.js +438 -0
- package/dist/providers/RentalsProvider.js.map +1 -0
- package/dist/providers/SettingsBookingProvider.d.ts +9 -0
- package/dist/providers/SettingsBookingProvider.d.ts.map +1 -0
- package/dist/providers/SettingsBookingProvider.js +163 -0
- package/dist/providers/SettingsBookingProvider.js.map +1 -0
- package/dist/providers/SettingsProvider.d.ts +8 -0
- package/dist/providers/SettingsProvider.d.ts.map +1 -0
- package/dist/providers/SettingsProvider.js +86 -0
- package/dist/providers/SettingsProvider.js.map +1 -0
- package/dist/providers/TagsProvider.d.ts +12 -0
- package/dist/providers/TagsProvider.d.ts.map +1 -0
- package/dist/providers/TagsProvider.js +210 -0
- package/dist/providers/TagsProvider.js.map +1 -0
- package/dist/providers/TimeCardProvider.d.ts +8 -0
- package/dist/providers/TimeCardProvider.d.ts.map +1 -0
- package/dist/providers/TimeCardProvider.js +93 -0
- package/dist/providers/TimeCardProvider.js.map +1 -0
- package/dist/providers/TrashProvider.d.ts +13 -0
- package/dist/providers/TrashProvider.d.ts.map +1 -0
- package/dist/providers/TrashProvider.js +98 -0
- package/dist/providers/TrashProvider.js.map +1 -0
- package/dist/providers/dashboardAnalytics.d.ts +9 -0
- package/dist/providers/dashboardAnalytics.d.ts.map +1 -0
- package/dist/providers/dashboardAnalytics.js +41 -0
- package/dist/providers/dashboardAnalytics.js.map +1 -0
- package/dist/providers/dashboardAnalytics.test.d.ts +2 -0
- package/dist/providers/dashboardAnalytics.test.d.ts.map +1 -0
- package/dist/providers/dashboardAnalytics.test.js +111 -0
- package/dist/providers/dashboardAnalytics.test.js.map +1 -0
- package/dist/server-safe/BlockedNumberFunctions.d.ts +11 -0
- package/dist/server-safe/BlockedNumberFunctions.d.ts.map +1 -0
- package/dist/server-safe/BlockedNumberFunctions.js +16 -0
- package/dist/server-safe/BlockedNumberFunctions.js.map +1 -0
- package/dist/server-safe/EmployeeFunctions.d.ts +14 -0
- package/dist/server-safe/EmployeeFunctions.d.ts.map +1 -0
- package/dist/server-safe/EmployeeFunctions.js +70 -0
- package/dist/server-safe/EmployeeFunctions.js.map +1 -0
- package/dist/server-safe/EmployeeLocationFunctions.d.ts +7 -0
- package/dist/server-safe/EmployeeLocationFunctions.d.ts.map +1 -0
- package/dist/server-safe/EmployeeLocationFunctions.js +47 -0
- package/dist/server-safe/EmployeeLocationFunctions.js.map +1 -0
- package/dist/server-safe/EstimateFunctions.d.ts +15 -0
- package/dist/server-safe/EstimateFunctions.d.ts.map +1 -0
- package/dist/server-safe/EstimateFunctions.js +34 -0
- package/dist/server-safe/EstimateFunctions.js.map +1 -0
- package/dist/server-safe/InvoiceFunctions.d.ts +21 -0
- package/dist/server-safe/InvoiceFunctions.d.ts.map +1 -0
- package/dist/server-safe/InvoiceFunctions.js +124 -0
- package/dist/server-safe/InvoiceFunctions.js.map +1 -0
- package/dist/server-safe/JobFunctions.d.ts +15 -0
- package/dist/server-safe/JobFunctions.d.ts.map +1 -0
- package/dist/server-safe/JobFunctions.js +56 -0
- package/dist/server-safe/JobFunctions.js.map +1 -0
- package/dist/server-safe/PaymentFunctions.d.ts +16 -0
- package/dist/server-safe/PaymentFunctions.d.ts.map +1 -0
- package/dist/server-safe/PaymentFunctions.js +80 -0
- package/dist/server-safe/PaymentFunctions.js.map +1 -0
- package/dist/server-safe/PermissionFunctions.d.ts +7 -0
- package/dist/server-safe/PermissionFunctions.d.ts.map +1 -0
- package/dist/server-safe/PermissionFunctions.js +39 -0
- package/dist/server-safe/PermissionFunctions.js.map +1 -0
- package/dist/server-safe/TagFunctions.d.ts +18 -0
- package/dist/server-safe/TagFunctions.d.ts.map +1 -0
- package/dist/server-safe/TagFunctions.js +52 -0
- package/dist/server-safe/TagFunctions.js.map +1 -0
- package/dist/server-safe/autoJobType.d.ts +16 -0
- package/dist/server-safe/autoJobType.d.ts.map +1 -0
- package/dist/server-safe/autoJobType.js +39 -0
- package/dist/server-safe/autoJobType.js.map +1 -0
- package/dist/server-safe/autoJobType.test.d.ts +2 -0
- package/dist/server-safe/autoJobType.test.d.ts.map +1 -0
- package/dist/server-safe/autoJobType.test.js +83 -0
- package/dist/server-safe/autoJobType.test.js.map +1 -0
- package/dist/server-safe/automations.d.ts +197 -0
- package/dist/server-safe/automations.d.ts.map +1 -0
- package/dist/server-safe/automations.js +757 -0
- package/dist/server-safe/automations.js.map +1 -0
- package/dist/server-safe/automations.test.d.ts +2 -0
- package/dist/server-safe/automations.test.d.ts.map +1 -0
- package/dist/server-safe/automations.test.js +441 -0
- package/dist/server-safe/automations.test.js.map +1 -0
- package/dist/server-safe/constants.d.ts +156 -0
- package/dist/server-safe/constants.d.ts.map +1 -0
- package/dist/server-safe/constants.js +186 -0
- package/dist/server-safe/constants.js.map +1 -0
- package/dist/server-safe/contracts.d.ts +3 -0
- package/dist/server-safe/contracts.d.ts.map +1 -0
- package/dist/server-safe/contracts.js +15 -0
- package/dist/server-safe/contracts.js.map +1 -0
- package/dist/server-safe/database.types.d.ts +9510 -0
- package/dist/server-safe/database.types.d.ts.map +1 -0
- package/dist/server-safe/database.types.js +40 -0
- package/dist/server-safe/database.types.js.map +1 -0
- package/dist/server-safe/entitlements.d.ts +99 -0
- package/dist/server-safe/entitlements.d.ts.map +1 -0
- package/dist/server-safe/entitlements.js +487 -0
- package/dist/server-safe/entitlements.js.map +1 -0
- package/dist/server-safe/helpers.d.ts +78 -0
- package/dist/server-safe/helpers.d.ts.map +1 -0
- package/dist/server-safe/helpers.js +569 -0
- package/dist/server-safe/helpers.js.map +1 -0
- package/dist/server-safe/index.d.ts +30 -0
- package/dist/server-safe/index.d.ts.map +1 -0
- package/dist/server-safe/index.js +46 -0
- package/dist/server-safe/index.js.map +1 -0
- package/dist/server-safe/permissions.d.ts +29 -0
- package/dist/server-safe/permissions.d.ts.map +1 -0
- package/dist/server-safe/permissions.js +180 -0
- package/dist/server-safe/permissions.js.map +1 -0
- package/dist/server-safe/proposals.d.ts +6 -0
- package/dist/server-safe/proposals.d.ts.map +1 -0
- package/dist/server-safe/proposals.js +58 -0
- package/dist/server-safe/proposals.js.map +1 -0
- package/dist/server-safe/rentalAvailability.d.ts +56 -0
- package/dist/server-safe/rentalAvailability.d.ts.map +1 -0
- package/dist/server-safe/rentalAvailability.js +124 -0
- package/dist/server-safe/rentalAvailability.js.map +1 -0
- package/dist/server-safe/rentalAvailability.test.d.ts +2 -0
- package/dist/server-safe/rentalAvailability.test.d.ts.map +1 -0
- package/dist/server-safe/rentalAvailability.test.js +290 -0
- package/dist/server-safe/rentalAvailability.test.js.map +1 -0
- package/dist/server-safe/rentals.d.ts +95 -0
- package/dist/server-safe/rentals.d.ts.map +1 -0
- package/dist/server-safe/rentals.js +432 -0
- package/dist/server-safe/rentals.js.map +1 -0
- package/dist/server-safe/rentals.test.d.ts +2 -0
- package/dist/server-safe/rentals.test.d.ts.map +1 -0
- package/dist/server-safe/rentals.test.js +1034 -0
- package/dist/server-safe/rentals.test.js.map +1 -0
- package/dist/server-safe/routeOptimization.d.ts +155 -0
- package/dist/server-safe/routeOptimization.d.ts.map +1 -0
- package/dist/server-safe/routeOptimization.js +120 -0
- package/dist/server-safe/routeOptimization.js.map +1 -0
- package/dist/server-safe/routeOptimization.test.d.ts +2 -0
- package/dist/server-safe/routeOptimization.test.d.ts.map +1 -0
- package/dist/server-safe/routeOptimization.test.js +234 -0
- package/dist/server-safe/routeOptimization.test.js.map +1 -0
- package/dist/server-safe/routePlanFingerprint.d.ts +13 -0
- package/dist/server-safe/routePlanFingerprint.d.ts.map +1 -0
- package/dist/server-safe/routePlanFingerprint.js +49 -0
- package/dist/server-safe/routePlanFingerprint.js.map +1 -0
- package/dist/server-safe/routePlanFingerprint.test.d.ts +2 -0
- package/dist/server-safe/routePlanFingerprint.test.d.ts.map +1 -0
- package/dist/server-safe/routePlanFingerprint.test.js +68 -0
- package/dist/server-safe/routePlanFingerprint.test.js.map +1 -0
- package/dist/server-safe/scheduleListOrder.d.ts +18 -0
- package/dist/server-safe/scheduleListOrder.d.ts.map +1 -0
- package/dist/server-safe/scheduleListOrder.js +92 -0
- package/dist/server-safe/scheduleListOrder.js.map +1 -0
- package/dist/server-safe/scheduleListOrder.test.d.ts +2 -0
- package/dist/server-safe/scheduleListOrder.test.d.ts.map +1 -0
- package/dist/server-safe/scheduleListOrder.test.js +260 -0
- package/dist/server-safe/scheduleListOrder.test.js.map +1 -0
- package/dist/server-safe/serviceMinutes.d.ts +13 -0
- package/dist/server-safe/serviceMinutes.d.ts.map +1 -0
- package/dist/server-safe/serviceMinutes.js +38 -0
- package/dist/server-safe/serviceMinutes.js.map +1 -0
- package/dist/server-safe/serviceMinutes.test.d.ts +2 -0
- package/dist/server-safe/serviceMinutes.test.d.ts.map +1 -0
- package/dist/server-safe/serviceMinutes.test.js +86 -0
- package/dist/server-safe/serviceMinutes.test.js.map +1 -0
- package/dist/server-safe/settingsSearch.d.ts +24 -0
- package/dist/server-safe/settingsSearch.d.ts.map +1 -0
- package/dist/server-safe/settingsSearch.js +8 -0
- package/dist/server-safe/settingsSearch.js.map +1 -0
- package/dist/server-safe/supportChat.d.ts +59 -0
- package/dist/server-safe/supportChat.d.ts.map +1 -0
- package/dist/server-safe/supportChat.js +56 -0
- package/dist/server-safe/supportChat.js.map +1 -0
- package/dist/server-safe/templates.d.ts +15 -0
- package/dist/server-safe/templates.d.ts.map +1 -0
- package/dist/server-safe/templates.js +91 -0
- package/dist/server-safe/templates.js.map +1 -0
- package/dist/server-safe/templates.test.d.ts +2 -0
- package/dist/server-safe/templates.test.d.ts.map +1 -0
- package/dist/server-safe/templates.test.js +62 -0
- package/dist/server-safe/templates.test.js.map +1 -0
- package/dist/server-safe/trash.d.ts +50 -0
- package/dist/server-safe/trash.d.ts.map +1 -0
- package/dist/server-safe/trash.js +100 -0
- package/dist/server-safe/trash.js.map +1 -0
- package/dist/utils/LogHandler.d.ts +3 -0
- package/dist/utils/LogHandler.d.ts.map +1 -0
- package/dist/utils/LogHandler.js +16 -0
- package/dist/utils/LogHandler.js.map +1 -0
- package/dist/utils/convertSupabaseImagesToAssets.d.ts +28 -0
- package/dist/utils/convertSupabaseImagesToAssets.d.ts.map +1 -0
- package/dist/utils/convertSupabaseImagesToAssets.js +14 -0
- package/dist/utils/convertSupabaseImagesToAssets.js.map +1 -0
- 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"();
|