feat(MCH-016): Add delivery schema DDL
- Create delivery schema - Add delivery_zones table with radius/polygon support - Add deliveries table with status tracking and proof of delivery - Add delivery_status_history for audit trail - Add drivers table for delivery personnel - Create updated_at triggers for all tables - Add automatic status history logging trigger - Create setup_default_zones() function for new tenants Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
c730a9672d
commit
362e488ae7
318
schemas/delivery.sql
Normal file
318
schemas/delivery.sql
Normal file
@ -0,0 +1,318 @@
|
||||
-- ============================================
|
||||
-- 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';
|
||||
Loading…
Reference in New Issue
Block a user