-- Migration: 0041_route_optimization.sql -- Route optimization schema: geocoding columns on clients, groomerRoutes + -- routeStops tables, and route settings on business_settings. -- Written idempotently so it is safe to re-run. -- ─── Enums ──────────────────────────────────────────────────────────────────── DO $$ BEGIN CREATE TYPE "route_status" AS ENUM ('draft', 'optimized', 'in_progress', 'completed'); EXCEPTION WHEN duplicate_object THEN NULL; END $$; -- ─── Clients: geocoding columns ─────────────────────────────────────────────── ALTER TABLE "clients" ADD COLUMN IF NOT EXISTS "latitude" double precision; ALTER TABLE "clients" ADD COLUMN IF NOT EXISTS "longitude" double precision; ALTER TABLE "clients" ADD COLUMN IF NOT EXISTS "geocoded_at" timestamp; -- ─── Business settings: route optimization config ───────────────────────────── ALTER TABLE "business_settings" ADD COLUMN IF NOT EXISTS "default_travel_buffer_mins" integer NOT NULL DEFAULT 15; ALTER TABLE "business_settings" ADD COLUMN IF NOT EXISTS "route_optimization_provider" text DEFAULT 'nominatim'; -- Encrypted at rest at the application layer (AES-256-GCM). ALTER TABLE "business_settings" ADD COLUMN IF NOT EXISTS "google_maps_api_key" text; -- ─── Groomer routes table ───────────────────────────────────────────────────── CREATE TABLE IF NOT EXISTS "groomer_routes" ( "id" uuid PRIMARY KEY DEFAULT gen_random_uuid(), "staff_id" uuid NOT NULL REFERENCES "staff"("id") ON DELETE CASCADE, "route_date" date NOT NULL, "status" "route_status" NOT NULL DEFAULT 'draft', "total_travel_mins" integer, "total_distance_km" numeric(8, 2), "optimized_at" timestamp, "created_at" timestamp NOT NULL DEFAULT now(), "updated_at" timestamp NOT NULL DEFAULT now(), CONSTRAINT "uq_groomer_routes_staff_date" UNIQUE ("staff_id", "route_date") ); CREATE INDEX IF NOT EXISTS "idx_groomer_routes_staff_id" ON "groomer_routes"("staff_id"); -- ─── Route stops table ──────────────────────────────────────────────────────── CREATE TABLE IF NOT EXISTS "route_stops" ( "id" uuid PRIMARY KEY DEFAULT gen_random_uuid(), "route_id" uuid NOT NULL REFERENCES "groomer_routes"("id") ON DELETE CASCADE, "appointment_id" uuid NOT NULL REFERENCES "appointments"("id") ON DELETE CASCADE, "stop_order" integer NOT NULL, "latitude" double precision NOT NULL, "longitude" double precision NOT NULL, "travel_mins_from_prev" integer, "travel_distance_km_from_prev" numeric(8, 2), "buffer_mins" integer NOT NULL DEFAULT 15, "created_at" timestamp NOT NULL DEFAULT now(), "updated_at" timestamp NOT NULL DEFAULT now(), CONSTRAINT "uq_route_stops_route_appointment" UNIQUE ("route_id", "appointment_id"), CONSTRAINT "uq_route_stops_route_order" UNIQUE ("route_id", "stop_order") ); CREATE INDEX IF NOT EXISTS "idx_route_stops_route_id" ON "route_stops"("route_id");