-- ============================================ -- MCH-016: Entregas a Domicilio -- Schema: delivery -- Version: 1.0.0 -- Created: 2026-01-18 -- ============================================ -- Create schema if not exists CREATE SCHEMA IF NOT EXISTS delivery; -- ============================================ -- Table: delivery_zones -- Delivery zone configuration (radius or polygon) -- ============================================ CREATE TABLE IF NOT EXISTS delivery.delivery_zones ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES core.tenants(id) ON DELETE CASCADE, name VARCHAR(100) NOT NULL, type VARCHAR(20) NOT NULL DEFAULT 'radius' CHECK (type IN ('radius', 'polygon')), coordinates JSONB, delivery_fee DECIMAL(10,2) NOT NULL DEFAULT 0, min_order DECIMAL(10,2) NOT NULL DEFAULT 0, estimated_time_minutes INTEGER NOT NULL DEFAULT 30, active BOOLEAN NOT NULL DEFAULT true, priority INTEGER NOT NULL DEFAULT 0, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); -- Indexes for delivery_zones CREATE INDEX IF NOT EXISTS idx_delivery_zones_tenant_active ON delivery.delivery_zones(tenant_id, active); CREATE INDEX IF NOT EXISTS idx_delivery_zones_priority ON delivery.delivery_zones(tenant_id, priority); -- Comments COMMENT ON TABLE delivery.delivery_zones IS 'Delivery zone configuration with coverage areas'; COMMENT ON COLUMN delivery.delivery_zones.type IS 'Zone type: radius or polygon'; COMMENT ON COLUMN delivery.delivery_zones.coordinates IS 'JSON: center+radiusKm for radius, polygon array for polygon, colonias list for colonia-based'; COMMENT ON COLUMN delivery.delivery_zones.priority IS 'Lower number = higher priority for overlapping zones'; -- ============================================ -- Table: deliveries -- Delivery tracking for orders -- ============================================ CREATE TABLE IF NOT EXISTS delivery.deliveries ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES core.tenants(id) ON DELETE CASCADE, order_id UUID NOT NULL REFERENCES orders.orders(id) ON DELETE CASCADE, zone_id UUID REFERENCES delivery.delivery_zones(id) ON DELETE SET NULL, -- Assignment assigned_to UUID, assigned_to_name VARCHAR(100), -- Status status VARCHAR(20) NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'assigned', 'picked_up', 'in_transit', 'arrived', 'delivered', 'failed', 'cancelled')), -- Delivery info delivery_address TEXT NOT NULL, delivery_coordinates JSONB, delivery_notes TEXT, customer_phone VARCHAR(20), delivery_fee DECIMAL(10,2) NOT NULL DEFAULT 0, estimated_minutes INTEGER, -- Timestamps for tracking assigned_at TIMESTAMPTZ, picked_up_at TIMESTAMPTZ, in_transit_at TIMESTAMPTZ, arrived_at TIMESTAMPTZ, delivered_at TIMESTAMPTZ, -- Proof of delivery proof_photo_url TEXT, signature_url TEXT, recipient_name VARCHAR(100), -- Failure handling failure_reason TEXT, retry_count INTEGER NOT NULL DEFAULT 0, -- Audit created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); -- Indexes for deliveries CREATE INDEX IF NOT EXISTS idx_deliveries_tenant_status ON delivery.deliveries(tenant_id, status); CREATE INDEX IF NOT EXISTS idx_deliveries_order ON delivery.deliveries(order_id); CREATE INDEX IF NOT EXISTS idx_deliveries_assigned ON delivery.deliveries(assigned_to, status); CREATE INDEX IF NOT EXISTS idx_deliveries_created ON delivery.deliveries(tenant_id, created_at DESC); -- Unique constraint: one delivery per order CREATE UNIQUE INDEX IF NOT EXISTS idx_deliveries_order_unique ON delivery.deliveries(order_id); -- Comments COMMENT ON TABLE delivery.deliveries IS 'Delivery tracking for orders with status history'; COMMENT ON COLUMN delivery.deliveries.status IS 'Delivery status: pending->assigned->picked_up->in_transit->arrived->delivered'; COMMENT ON COLUMN delivery.deliveries.delivery_coordinates IS 'JSON with lat/lng for delivery location'; COMMENT ON COLUMN delivery.deliveries.proof_photo_url IS 'Photo proof of delivery'; COMMENT ON COLUMN delivery.deliveries.signature_url IS 'Digital signature from recipient'; -- ============================================ -- Table: delivery_status_history -- Audit trail for delivery status changes -- ============================================ CREATE TABLE IF NOT EXISTS delivery.delivery_status_history ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), delivery_id UUID NOT NULL REFERENCES delivery.deliveries(id) ON DELETE CASCADE, status VARCHAR(20) NOT NULL, previous_status VARCHAR(20), changed_by UUID, changed_by_name VARCHAR(100), notes TEXT, location JSONB, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); -- Indexes for status history CREATE INDEX IF NOT EXISTS idx_delivery_status_history_delivery ON delivery.delivery_status_history(delivery_id, created_at DESC); -- Comments COMMENT ON TABLE delivery.delivery_status_history IS 'Audit trail for delivery status changes'; COMMENT ON COLUMN delivery.delivery_status_history.location IS 'GPS coordinates when status changed'; -- ============================================ -- Table: drivers -- Driver registry for delivery assignments -- ============================================ CREATE TABLE IF NOT EXISTS delivery.drivers ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES core.tenants(id) ON DELETE CASCADE, user_id UUID, name VARCHAR(100) NOT NULL, phone VARCHAR(20) NOT NULL, vehicle_type VARCHAR(50), vehicle_plate VARCHAR(20), photo_url TEXT, status VARCHAR(20) NOT NULL DEFAULT 'available' CHECK (status IN ('available', 'busy', 'offline', 'inactive')), current_location JSONB, last_seen_at TIMESTAMPTZ, active BOOLEAN NOT NULL DEFAULT true, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); -- Indexes for drivers CREATE INDEX IF NOT EXISTS idx_drivers_tenant_status ON delivery.drivers(tenant_id, status); CREATE INDEX IF NOT EXISTS idx_drivers_tenant_active ON delivery.drivers(tenant_id, active); -- Comments COMMENT ON TABLE delivery.drivers IS 'Driver registry for delivery operations'; COMMENT ON COLUMN delivery.drivers.vehicle_type IS 'Type of vehicle: moto, bici, auto, pie'; COMMENT ON COLUMN delivery.drivers.current_location IS 'Last known GPS location'; -- ============================================ -- Triggers for updated_at -- ============================================ -- delivery_zones CREATE OR REPLACE FUNCTION delivery.update_delivery_zones_updated_at() RETURNS TRIGGER AS $$ BEGIN NEW.updated_at = NOW(); RETURN NEW; END; $$ LANGUAGE plpgsql; DROP TRIGGER IF EXISTS trigger_delivery_zones_updated_at ON delivery.delivery_zones; CREATE TRIGGER trigger_delivery_zones_updated_at BEFORE UPDATE ON delivery.delivery_zones FOR EACH ROW EXECUTE FUNCTION delivery.update_delivery_zones_updated_at(); -- deliveries CREATE OR REPLACE FUNCTION delivery.update_deliveries_updated_at() RETURNS TRIGGER AS $$ BEGIN NEW.updated_at = NOW(); RETURN NEW; END; $$ LANGUAGE plpgsql; DROP TRIGGER IF EXISTS trigger_deliveries_updated_at ON delivery.deliveries; CREATE TRIGGER trigger_deliveries_updated_at BEFORE UPDATE ON delivery.deliveries FOR EACH ROW EXECUTE FUNCTION delivery.update_deliveries_updated_at(); -- drivers CREATE OR REPLACE FUNCTION delivery.update_drivers_updated_at() RETURNS TRIGGER AS $$ BEGIN NEW.updated_at = NOW(); RETURN NEW; END; $$ LANGUAGE plpgsql; DROP TRIGGER IF EXISTS trigger_drivers_updated_at ON delivery.drivers; CREATE TRIGGER trigger_drivers_updated_at BEFORE UPDATE ON delivery.drivers FOR EACH ROW EXECUTE FUNCTION delivery.update_drivers_updated_at(); -- ============================================ -- Trigger for automatic status history -- ============================================ CREATE OR REPLACE FUNCTION delivery.log_delivery_status_change() RETURNS TRIGGER AS $$ BEGIN IF OLD.status IS DISTINCT FROM NEW.status THEN INSERT INTO delivery.delivery_status_history ( delivery_id, status, previous_status, changed_by, notes ) VALUES ( NEW.id, NEW.status, OLD.status, NULL, -- Would need to get from context CASE WHEN NEW.status = 'failed' THEN NEW.failure_reason ELSE NULL END ); END IF; RETURN NEW; END; $$ LANGUAGE plpgsql; DROP TRIGGER IF EXISTS trigger_delivery_status_history ON delivery.deliveries; CREATE TRIGGER trigger_delivery_status_history AFTER UPDATE ON delivery.deliveries FOR EACH ROW EXECUTE FUNCTION delivery.log_delivery_status_change(); -- ============================================ -- Default zone templates for new tenants -- ============================================ -- This function can be called to set up default zones for a tenant CREATE OR REPLACE FUNCTION delivery.setup_default_zones(p_tenant_id UUID, p_center_lat DECIMAL, p_center_lng DECIMAL) RETURNS void AS $$ BEGIN -- Zone 1: Centro (0-2 km) - Free delivery INSERT INTO delivery.delivery_zones (tenant_id, name, type, coordinates, delivery_fee, min_order, estimated_time_minutes, priority) VALUES ( p_tenant_id, 'Centro (0-2 km)', 'radius', jsonb_build_object('center', jsonb_build_object('lat', p_center_lat, 'lng', p_center_lng), 'radiusKm', 2), 0, 0, 15, 1 ); -- Zone 2: Cercano (2-5 km) - $20 delivery INSERT INTO delivery.delivery_zones (tenant_id, name, type, coordinates, delivery_fee, min_order, estimated_time_minutes, priority) VALUES ( p_tenant_id, 'Cercano (2-5 km)', 'radius', jsonb_build_object('center', jsonb_build_object('lat', p_center_lat, 'lng', p_center_lng), 'radiusKm', 5), 20, 50, 25, 2 ); -- Zone 3: Lejano (5-10 km) - $35 delivery INSERT INTO delivery.delivery_zones (tenant_id, name, type, coordinates, delivery_fee, min_order, estimated_time_minutes, priority) VALUES ( p_tenant_id, 'Lejano (5-10 km)', 'radius', jsonb_build_object('center', jsonb_build_object('lat', p_center_lat, 'lng', p_center_lng), 'radiusKm', 10), 35, 100, 40, 3 ); END; $$ LANGUAGE plpgsql; -- ============================================ -- Grant permissions -- ============================================ GRANT USAGE ON SCHEMA delivery TO michangarrito_app; GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA delivery TO michangarrito_app; GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA delivery TO michangarrito_app; GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA delivery TO michangarrito_app; -- ============================================ -- Version tracking -- ============================================ COMMENT ON SCHEMA delivery IS 'MCH-016: Delivery management - zones, tracking, drivers. Version 1.0.0';