feat(MMD-014): Add GPS tracking schema
- Create gps_tracking schema with 5 tables - Tables: gps_devices, gps_positions, geofences, geofence_events, route_segments - Add PostGIS-compatible distance functions (haversine_distance) - Add RLS policies for multi-tenant isolation - Add views: vw_last_positions, vw_device_summary - Add triggers for automatic last_position updates Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
863ab28b09
commit
d72145f0b7
530
init/14-gps-tracking-schema.sql
Normal file
530
init/14-gps-tracking-schema.sql
Normal file
@ -0,0 +1,530 @@
|
||||
-- ===========================================
|
||||
-- MECANICAS DIESEL - Schema gps_tracking
|
||||
-- ===========================================
|
||||
-- Integracion GPS, posiciones, geocercas, rutas
|
||||
-- Modulo: MMD-014 GPS Integration
|
||||
|
||||
-- -------------------------------------------
|
||||
-- SCHEMA CREATION
|
||||
-- -------------------------------------------
|
||||
CREATE SCHEMA IF NOT EXISTS gps_tracking;
|
||||
COMMENT ON SCHEMA gps_tracking IS 'Tracking GPS, posiciones en tiempo real, geocercas y calculo de rutas';
|
||||
|
||||
GRANT USAGE ON SCHEMA gps_tracking TO mecanicas_user;
|
||||
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA gps_tracking TO mecanicas_user;
|
||||
ALTER DEFAULT PRIVILEGES IN SCHEMA gps_tracking GRANT ALL ON TABLES TO mecanicas_user;
|
||||
|
||||
SET search_path TO gps_tracking, public;
|
||||
|
||||
-- -------------------------------------------
|
||||
-- GPS_DEVICES - Dispositivos GPS vinculados a unidades
|
||||
-- -------------------------------------------
|
||||
CREATE TABLE gps_tracking.gps_devices (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID NOT NULL,
|
||||
|
||||
-- Vinculacion a unidad de flota
|
||||
unit_id UUID NOT NULL, -- FK a vehicle_management.vehicles
|
||||
unit_type VARCHAR(20) DEFAULT 'vehicle'
|
||||
CHECK (unit_type IN ('vehicle', 'trailer', 'equipment', 'technician')),
|
||||
|
||||
-- Identificacion externa (plataforma GPS)
|
||||
external_device_id VARCHAR(100) NOT NULL,
|
||||
platform VARCHAR(30) NOT NULL DEFAULT 'traccar'
|
||||
CHECK (platform IN ('traccar', 'wialon', 'samsara', 'geotab', 'manual')),
|
||||
|
||||
-- Identificadores del dispositivo
|
||||
imei VARCHAR(20),
|
||||
serial_number VARCHAR(50),
|
||||
phone_number VARCHAR(20),
|
||||
model VARCHAR(50),
|
||||
manufacturer VARCHAR(50),
|
||||
|
||||
-- Estado
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
last_position_at TIMESTAMP WITH TIME ZONE,
|
||||
last_position_lat DECIMAL(10, 7),
|
||||
last_position_lng DECIMAL(10, 7),
|
||||
|
||||
-- Configuracion
|
||||
position_interval_seconds INTEGER DEFAULT 30 CHECK (position_interval_seconds >= 5),
|
||||
metadata JSONB DEFAULT '{}',
|
||||
|
||||
-- Auditoria
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
created_by UUID,
|
||||
|
||||
CONSTRAINT uq_device_external UNIQUE (tenant_id, platform, external_device_id),
|
||||
CONSTRAINT uq_device_unit UNIQUE (tenant_id, unit_id)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_gps_devices_tenant ON gps_tracking.gps_devices(tenant_id);
|
||||
CREATE INDEX idx_gps_devices_unit ON gps_tracking.gps_devices(unit_id);
|
||||
CREATE INDEX idx_gps_devices_external ON gps_tracking.gps_devices(external_device_id);
|
||||
CREATE INDEX idx_gps_devices_platform ON gps_tracking.gps_devices(platform);
|
||||
CREATE INDEX idx_gps_devices_active ON gps_tracking.gps_devices(is_active) WHERE is_active = TRUE;
|
||||
|
||||
CREATE TRIGGER trg_gps_devices_updated_at
|
||||
BEFORE UPDATE ON gps_tracking.gps_devices
|
||||
FOR EACH ROW EXECUTE FUNCTION trigger_set_updated_at();
|
||||
|
||||
SELECT create_tenant_rls_policies('gps_tracking', 'gps_devices');
|
||||
|
||||
COMMENT ON TABLE gps_tracking.gps_devices IS 'Dispositivos GPS vinculados a unidades de la flota';
|
||||
COMMENT ON COLUMN gps_tracking.gps_devices.external_device_id IS 'ID del dispositivo en la plataforma GPS externa (Traccar, etc.)';
|
||||
COMMENT ON COLUMN gps_tracking.gps_devices.platform IS 'Plataforma GPS: traccar (recomendado), wialon, samsara, geotab, manual';
|
||||
|
||||
-- -------------------------------------------
|
||||
-- GPS_POSITIONS - Posiciones registradas (time-series)
|
||||
-- -------------------------------------------
|
||||
CREATE TABLE gps_tracking.gps_positions (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID NOT NULL,
|
||||
|
||||
device_id UUID NOT NULL REFERENCES gps_tracking.gps_devices(id) ON DELETE CASCADE,
|
||||
unit_id UUID NOT NULL, -- Denormalizado para queries rapidos
|
||||
|
||||
-- Posicion
|
||||
latitude DECIMAL(10, 7) NOT NULL CHECK (latitude >= -90 AND latitude <= 90),
|
||||
longitude DECIMAL(10, 7) NOT NULL CHECK (longitude >= -180 AND longitude <= 180),
|
||||
altitude DECIMAL(8, 2), -- metros sobre nivel del mar
|
||||
|
||||
-- Movimiento
|
||||
speed DECIMAL(6, 2) CHECK (speed >= 0), -- km/h
|
||||
course DECIMAL(5, 2) CHECK (course >= 0 AND course < 360), -- grados (0 = norte)
|
||||
|
||||
-- Precision
|
||||
accuracy DECIMAL(6, 2) CHECK (accuracy >= 0), -- metros
|
||||
hdop DECIMAL(4, 2), -- Horizontal Dilution of Precision
|
||||
|
||||
-- Atributos adicionales (del dispositivo)
|
||||
attributes JSONB DEFAULT '{}',
|
||||
-- attributes puede contener: ignition, fuel, odometer, engineHours, batteryLevel, etc.
|
||||
|
||||
-- Timestamps
|
||||
device_time TIMESTAMP WITH TIME ZONE NOT NULL, -- Hora del dispositivo
|
||||
server_time TIMESTAMP WITH TIME ZONE DEFAULT NOW(), -- Hora de recepcion
|
||||
fix_time TIMESTAMP WITH TIME ZONE, -- Hora del fix GPS
|
||||
|
||||
-- Validacion
|
||||
is_valid BOOLEAN DEFAULT TRUE, -- Si paso filtros anti-ruido
|
||||
|
||||
-- Indices para busquedas
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Particionar por fecha si hay muchos registros
|
||||
-- CREATE TABLE gps_tracking.gps_positions_YYYY_MM PARTITION OF gps_tracking.gps_positions
|
||||
-- FOR VALUES FROM ('2026-01-01') TO ('2026-02-01');
|
||||
|
||||
CREATE INDEX idx_gps_positions_tenant ON gps_tracking.gps_positions(tenant_id);
|
||||
CREATE INDEX idx_gps_positions_device ON gps_tracking.gps_positions(device_id);
|
||||
CREATE INDEX idx_gps_positions_unit ON gps_tracking.gps_positions(unit_id);
|
||||
CREATE INDEX idx_gps_positions_device_time ON gps_tracking.gps_positions(device_id, device_time DESC);
|
||||
CREATE INDEX idx_gps_positions_time ON gps_tracking.gps_positions(device_time DESC);
|
||||
CREATE INDEX idx_gps_positions_valid ON gps_tracking.gps_positions(is_valid) WHERE is_valid = TRUE;
|
||||
|
||||
-- Indice espacial para queries geograficos
|
||||
CREATE INDEX idx_gps_positions_location ON gps_tracking.gps_positions
|
||||
USING GIST (ST_SetSRID(ST_MakePoint(longitude, latitude), 4326))
|
||||
WHERE longitude IS NOT NULL AND latitude IS NOT NULL;
|
||||
|
||||
SELECT create_tenant_rls_policies('gps_tracking', 'gps_positions');
|
||||
|
||||
COMMENT ON TABLE gps_tracking.gps_positions IS 'Posiciones GPS registradas - datos time-series';
|
||||
COMMENT ON COLUMN gps_tracking.gps_positions.attributes IS 'Atributos adicionales: ignition, fuel, odometer, engineHours, etc.';
|
||||
COMMENT ON COLUMN gps_tracking.gps_positions.is_valid IS 'Si la posicion paso los filtros anti-ruido';
|
||||
|
||||
-- -------------------------------------------
|
||||
-- GEOFENCES - Geocercas (areas de interes)
|
||||
-- -------------------------------------------
|
||||
CREATE TABLE gps_tracking.geofences (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID NOT NULL,
|
||||
|
||||
-- Identificacion
|
||||
name VARCHAR(100) NOT NULL,
|
||||
code VARCHAR(30),
|
||||
description TEXT,
|
||||
|
||||
-- Tipo de geocerca
|
||||
geofence_type VARCHAR(20) NOT NULL DEFAULT 'circle'
|
||||
CHECK (geofence_type IN ('circle', 'polygon')),
|
||||
|
||||
-- Para tipo CIRCLE
|
||||
center_lat DECIMAL(10, 7),
|
||||
center_lng DECIMAL(10, 7),
|
||||
radius_meters DECIMAL(10, 2) CHECK (radius_meters > 0),
|
||||
|
||||
-- Para tipo POLYGON (formato GeoJSON)
|
||||
polygon_geojson JSONB,
|
||||
-- Ejemplo: {"type": "Polygon", "coordinates": [[[lng1,lat1],[lng2,lat2],...]]}
|
||||
|
||||
-- Geometria PostGIS (calculada)
|
||||
geometry GEOMETRY(Geometry, 4326),
|
||||
|
||||
-- Categoria operativa
|
||||
category VARCHAR(30) NOT NULL DEFAULT 'custom'
|
||||
CHECK (category IN ('base', 'coverage', 'restricted', 'high_risk', 'client', 'custom')),
|
||||
|
||||
-- Configuracion de eventos
|
||||
trigger_on_enter BOOLEAN DEFAULT TRUE,
|
||||
trigger_on_exit BOOLEAN DEFAULT TRUE,
|
||||
dwell_time_seconds INTEGER DEFAULT 0, -- Tiempo minimo para disparar evento
|
||||
|
||||
-- Estado
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
color VARCHAR(7) DEFAULT '#3388ff', -- Color en mapa (hex)
|
||||
metadata JSONB DEFAULT '{}',
|
||||
|
||||
-- Auditoria
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
created_by UUID
|
||||
);
|
||||
|
||||
CREATE INDEX idx_geofences_tenant ON gps_tracking.geofences(tenant_id);
|
||||
CREATE INDEX idx_geofences_category ON gps_tracking.geofences(category);
|
||||
CREATE INDEX idx_geofences_active ON gps_tracking.geofences(is_active) WHERE is_active = TRUE;
|
||||
|
||||
-- Indice espacial
|
||||
CREATE INDEX idx_geofences_geometry ON gps_tracking.geofences USING GIST (geometry);
|
||||
|
||||
CREATE TRIGGER trg_geofences_updated_at
|
||||
BEFORE UPDATE ON gps_tracking.geofences
|
||||
FOR EACH ROW EXECUTE FUNCTION trigger_set_updated_at();
|
||||
|
||||
SELECT create_tenant_rls_policies('gps_tracking', 'geofences');
|
||||
|
||||
COMMENT ON TABLE gps_tracking.geofences IS 'Geocercas para detectar entrada/salida de areas';
|
||||
COMMENT ON COLUMN gps_tracking.geofences.category IS 'Categoria: base (taller), coverage (zona atendida), restricted, high_risk, client, custom';
|
||||
COMMENT ON COLUMN gps_tracking.geofences.dwell_time_seconds IS 'Segundos minimos dentro/fuera para disparar evento';
|
||||
|
||||
-- Trigger para calcular geometria automaticamente
|
||||
CREATE OR REPLACE FUNCTION gps_tracking.calculate_geofence_geometry()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
IF NEW.geofence_type = 'circle' AND NEW.center_lat IS NOT NULL AND NEW.center_lng IS NOT NULL THEN
|
||||
-- Crear circulo
|
||||
NEW.geometry := ST_Buffer(
|
||||
ST_SetSRID(ST_MakePoint(NEW.center_lng, NEW.center_lat), 4326)::geography,
|
||||
NEW.radius_meters
|
||||
)::geometry;
|
||||
ELSIF NEW.geofence_type = 'polygon' AND NEW.polygon_geojson IS NOT NULL THEN
|
||||
-- Crear poligono desde GeoJSON
|
||||
NEW.geometry := ST_SetSRID(ST_GeomFromGeoJSON(NEW.polygon_geojson::text), 4326);
|
||||
END IF;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE TRIGGER trg_geofences_geometry
|
||||
BEFORE INSERT OR UPDATE ON gps_tracking.geofences
|
||||
FOR EACH ROW EXECUTE FUNCTION gps_tracking.calculate_geofence_geometry();
|
||||
|
||||
-- -------------------------------------------
|
||||
-- GEOFENCE_EVENTS - Eventos de entrada/salida
|
||||
-- -------------------------------------------
|
||||
CREATE TABLE gps_tracking.geofence_events (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID NOT NULL,
|
||||
|
||||
geofence_id UUID NOT NULL REFERENCES gps_tracking.geofences(id) ON DELETE CASCADE,
|
||||
device_id UUID NOT NULL REFERENCES gps_tracking.gps_devices(id) ON DELETE CASCADE,
|
||||
unit_id UUID NOT NULL,
|
||||
|
||||
-- Tipo de evento
|
||||
event_type VARCHAR(10) NOT NULL CHECK (event_type IN ('enter', 'exit', 'dwell')),
|
||||
|
||||
-- Posicion que disparo el evento
|
||||
position_id UUID REFERENCES gps_tracking.gps_positions(id),
|
||||
latitude DECIMAL(10, 7) NOT NULL,
|
||||
longitude DECIMAL(10, 7) NOT NULL,
|
||||
|
||||
-- Timestamps
|
||||
event_time TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
processed_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
|
||||
-- Vinculacion a operaciones
|
||||
related_incident_id UUID, -- FK a rescue_order si aplica
|
||||
|
||||
-- Metadata
|
||||
metadata JSONB DEFAULT '{}'
|
||||
);
|
||||
|
||||
CREATE INDEX idx_geofence_events_tenant ON gps_tracking.geofence_events(tenant_id);
|
||||
CREATE INDEX idx_geofence_events_geofence ON gps_tracking.geofence_events(geofence_id);
|
||||
CREATE INDEX idx_geofence_events_device ON gps_tracking.geofence_events(device_id);
|
||||
CREATE INDEX idx_geofence_events_unit ON gps_tracking.geofence_events(unit_id);
|
||||
CREATE INDEX idx_geofence_events_time ON gps_tracking.geofence_events(event_time DESC);
|
||||
CREATE INDEX idx_geofence_events_incident ON gps_tracking.geofence_events(related_incident_id)
|
||||
WHERE related_incident_id IS NOT NULL;
|
||||
|
||||
SELECT create_tenant_rls_policies('gps_tracking', 'geofence_events');
|
||||
|
||||
COMMENT ON TABLE gps_tracking.geofence_events IS 'Eventos de entrada/salida de geocercas';
|
||||
|
||||
-- -------------------------------------------
|
||||
-- ROUTE_SEGMENTS - Segmentos de ruta para km facturables
|
||||
-- -------------------------------------------
|
||||
CREATE TABLE gps_tracking.route_segments (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID NOT NULL,
|
||||
|
||||
-- Vinculacion a incidente/servicio
|
||||
incident_id UUID, -- FK a rescue_order
|
||||
unit_id UUID NOT NULL,
|
||||
device_id UUID REFERENCES gps_tracking.gps_devices(id),
|
||||
|
||||
-- Posiciones de inicio/fin
|
||||
start_position_id UUID REFERENCES gps_tracking.gps_positions(id),
|
||||
end_position_id UUID REFERENCES gps_tracking.gps_positions(id),
|
||||
|
||||
-- Coordenadas (denormalizadas)
|
||||
start_lat DECIMAL(10, 7) NOT NULL,
|
||||
start_lng DECIMAL(10, 7) NOT NULL,
|
||||
end_lat DECIMAL(10, 7) NOT NULL,
|
||||
end_lng DECIMAL(10, 7) NOT NULL,
|
||||
|
||||
-- Distancias
|
||||
distance_km DECIMAL(10, 3) NOT NULL CHECK (distance_km >= 0),
|
||||
raw_distance_km DECIMAL(10, 3), -- Antes de filtros
|
||||
|
||||
-- Tiempos
|
||||
start_time TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
end_time TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
duration_minutes DECIMAL(8, 2) CHECK (duration_minutes >= 0),
|
||||
|
||||
-- Tipo de segmento
|
||||
segment_type VARCHAR(30) NOT NULL DEFAULT 'other'
|
||||
CHECK (segment_type IN ('to_incident', 'at_incident', 'return', 'between_incidents', 'other')),
|
||||
|
||||
-- Validacion
|
||||
is_valid BOOLEAN DEFAULT TRUE,
|
||||
validation_notes TEXT,
|
||||
|
||||
-- Polyline codificada (para visualizacion)
|
||||
encoded_polyline TEXT,
|
||||
|
||||
-- Metadata
|
||||
metadata JSONB DEFAULT '{}',
|
||||
|
||||
-- Auditoria
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
calculated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
|
||||
CONSTRAINT chk_segment_times CHECK (end_time >= start_time)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_route_segments_tenant ON gps_tracking.route_segments(tenant_id);
|
||||
CREATE INDEX idx_route_segments_incident ON gps_tracking.route_segments(incident_id);
|
||||
CREATE INDEX idx_route_segments_unit ON gps_tracking.route_segments(unit_id);
|
||||
CREATE INDEX idx_route_segments_type ON gps_tracking.route_segments(segment_type);
|
||||
CREATE INDEX idx_route_segments_time ON gps_tracking.route_segments(start_time DESC);
|
||||
CREATE INDEX idx_route_segments_valid ON gps_tracking.route_segments(is_valid) WHERE is_valid = TRUE;
|
||||
|
||||
SELECT create_tenant_rls_policies('gps_tracking', 'route_segments');
|
||||
|
||||
COMMENT ON TABLE gps_tracking.route_segments IS 'Segmentos de ruta calculados para facturacion de km';
|
||||
COMMENT ON COLUMN gps_tracking.route_segments.segment_type IS 'Tipo: to_incident (ida), at_incident (en sitio), return (regreso), between_incidents, other';
|
||||
COMMENT ON COLUMN gps_tracking.route_segments.is_valid IS 'Si el segmento paso validacion anti-ruido';
|
||||
COMMENT ON COLUMN gps_tracking.route_segments.encoded_polyline IS 'Polyline codificada (Google format) para visualizar en mapa';
|
||||
|
||||
-- -------------------------------------------
|
||||
-- FUNCTIONS: Calculo de distancias
|
||||
-- -------------------------------------------
|
||||
|
||||
-- Funcion para calcular distancia entre dos puntos (Haversine)
|
||||
CREATE OR REPLACE FUNCTION gps_tracking.haversine_distance(
|
||||
lat1 DECIMAL, lng1 DECIMAL,
|
||||
lat2 DECIMAL, lng2 DECIMAL
|
||||
)
|
||||
RETURNS DECIMAL AS $$
|
||||
DECLARE
|
||||
R DECIMAL := 6371; -- Radio de la Tierra en km
|
||||
dlat DECIMAL;
|
||||
dlng DECIMAL;
|
||||
a DECIMAL;
|
||||
c DECIMAL;
|
||||
BEGIN
|
||||
dlat := RADIANS(lat2 - lat1);
|
||||
dlng := RADIANS(lng2 - lng1);
|
||||
|
||||
a := SIN(dlat/2) * SIN(dlat/2) +
|
||||
COS(RADIANS(lat1)) * COS(RADIANS(lat2)) *
|
||||
SIN(dlng/2) * SIN(dlng/2);
|
||||
|
||||
c := 2 * ATAN2(SQRT(a), SQRT(1-a));
|
||||
|
||||
RETURN R * c;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql IMMUTABLE;
|
||||
|
||||
COMMENT ON FUNCTION gps_tracking.haversine_distance IS 'Calcula distancia en km entre dos puntos usando formula Haversine';
|
||||
|
||||
-- Funcion para calcular distancia total de una ruta (array de posiciones)
|
||||
CREATE OR REPLACE FUNCTION gps_tracking.calculate_route_distance(
|
||||
p_device_id UUID,
|
||||
p_start_time TIMESTAMP WITH TIME ZONE,
|
||||
p_end_time TIMESTAMP WITH TIME ZONE
|
||||
)
|
||||
RETURNS TABLE (
|
||||
total_distance_km DECIMAL,
|
||||
position_count INTEGER,
|
||||
duration_minutes DECIMAL
|
||||
) AS $$
|
||||
DECLARE
|
||||
v_prev_lat DECIMAL;
|
||||
v_prev_lng DECIMAL;
|
||||
v_prev_time TIMESTAMP WITH TIME ZONE;
|
||||
v_distance DECIMAL := 0;
|
||||
v_count INTEGER := 0;
|
||||
v_rec RECORD;
|
||||
BEGIN
|
||||
FOR v_rec IN
|
||||
SELECT latitude, longitude, device_time
|
||||
FROM gps_tracking.gps_positions
|
||||
WHERE device_id = p_device_id
|
||||
AND device_time BETWEEN p_start_time AND p_end_time
|
||||
AND is_valid = TRUE
|
||||
ORDER BY device_time
|
||||
LOOP
|
||||
IF v_prev_lat IS NOT NULL THEN
|
||||
v_distance := v_distance + gps_tracking.haversine_distance(
|
||||
v_prev_lat, v_prev_lng,
|
||||
v_rec.latitude, v_rec.longitude
|
||||
);
|
||||
ELSE
|
||||
v_prev_time := v_rec.device_time;
|
||||
END IF;
|
||||
|
||||
v_prev_lat := v_rec.latitude;
|
||||
v_prev_lng := v_rec.longitude;
|
||||
v_count := v_count + 1;
|
||||
END LOOP;
|
||||
|
||||
RETURN QUERY SELECT
|
||||
ROUND(v_distance, 3)::DECIMAL,
|
||||
v_count,
|
||||
ROUND(EXTRACT(EPOCH FROM (v_rec.device_time - v_prev_time)) / 60, 2)::DECIMAL;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql STABLE;
|
||||
|
||||
COMMENT ON FUNCTION gps_tracking.calculate_route_distance IS 'Calcula distancia total de una ruta entre dos timestamps';
|
||||
|
||||
-- Funcion para verificar si un punto esta dentro de una geocerca
|
||||
CREATE OR REPLACE FUNCTION gps_tracking.is_point_in_geofence(
|
||||
p_lat DECIMAL,
|
||||
p_lng DECIMAL,
|
||||
p_geofence_id UUID
|
||||
)
|
||||
RETURNS BOOLEAN AS $$
|
||||
DECLARE
|
||||
v_geofence gps_tracking.geofences;
|
||||
BEGIN
|
||||
SELECT * INTO v_geofence
|
||||
FROM gps_tracking.geofences
|
||||
WHERE id = p_geofence_id AND is_active = TRUE;
|
||||
|
||||
IF v_geofence IS NULL THEN
|
||||
RETURN FALSE;
|
||||
END IF;
|
||||
|
||||
-- Usar PostGIS para verificacion precisa
|
||||
RETURN ST_Contains(
|
||||
v_geofence.geometry,
|
||||
ST_SetSRID(ST_MakePoint(p_lng, p_lat), 4326)
|
||||
);
|
||||
END;
|
||||
$$ LANGUAGE plpgsql STABLE;
|
||||
|
||||
COMMENT ON FUNCTION gps_tracking.is_point_in_geofence IS 'Verifica si un punto esta dentro de una geocerca';
|
||||
|
||||
-- -------------------------------------------
|
||||
-- VIEWS
|
||||
-- -------------------------------------------
|
||||
|
||||
-- Vista de ultima posicion por unidad
|
||||
CREATE OR REPLACE VIEW gps_tracking.vw_last_positions AS
|
||||
SELECT DISTINCT ON (d.unit_id)
|
||||
d.id as device_id,
|
||||
d.tenant_id,
|
||||
d.unit_id,
|
||||
d.external_device_id,
|
||||
d.platform,
|
||||
d.is_active,
|
||||
p.id as position_id,
|
||||
p.latitude,
|
||||
p.longitude,
|
||||
p.speed,
|
||||
p.course,
|
||||
p.attributes,
|
||||
p.device_time,
|
||||
p.is_valid,
|
||||
EXTRACT(EPOCH FROM (NOW() - p.device_time)) / 60 as minutes_ago
|
||||
FROM gps_tracking.gps_devices d
|
||||
LEFT JOIN gps_tracking.gps_positions p ON d.id = p.device_id
|
||||
WHERE d.is_active = TRUE
|
||||
AND d.tenant_id = get_current_tenant_id()
|
||||
ORDER BY d.unit_id, p.device_time DESC;
|
||||
|
||||
COMMENT ON VIEW gps_tracking.vw_last_positions IS 'Vista de ultima posicion conocida por unidad con filtro RLS';
|
||||
|
||||
-- Vista de resumen de dispositivos
|
||||
CREATE OR REPLACE VIEW gps_tracking.vw_device_summary AS
|
||||
SELECT
|
||||
d.id,
|
||||
d.tenant_id,
|
||||
d.unit_id,
|
||||
d.external_device_id,
|
||||
d.platform,
|
||||
d.model,
|
||||
d.is_active,
|
||||
d.last_position_at,
|
||||
d.last_position_lat,
|
||||
d.last_position_lng,
|
||||
CASE
|
||||
WHEN d.last_position_at IS NULL THEN 'never'
|
||||
WHEN NOW() - d.last_position_at < INTERVAL '5 minutes' THEN 'online'
|
||||
WHEN NOW() - d.last_position_at < INTERVAL '1 hour' THEN 'recent'
|
||||
ELSE 'offline'
|
||||
END as connection_status,
|
||||
(SELECT COUNT(*) FROM gps_tracking.gps_positions p
|
||||
WHERE p.device_id = d.id AND p.device_time > NOW() - INTERVAL '24 hours') as positions_24h
|
||||
FROM gps_tracking.gps_devices d
|
||||
WHERE d.tenant_id = get_current_tenant_id();
|
||||
|
||||
COMMENT ON VIEW gps_tracking.vw_device_summary IS 'Vista resumida de dispositivos GPS con estado de conexion';
|
||||
|
||||
-- -------------------------------------------
|
||||
-- TRIGGER: Actualizar ultima posicion en device
|
||||
-- -------------------------------------------
|
||||
CREATE OR REPLACE FUNCTION gps_tracking.update_device_last_position()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
UPDATE gps_tracking.gps_devices
|
||||
SET
|
||||
last_position_at = NEW.device_time,
|
||||
last_position_lat = NEW.latitude,
|
||||
last_position_lng = NEW.longitude
|
||||
WHERE id = NEW.device_id
|
||||
AND (last_position_at IS NULL OR last_position_at < NEW.device_time);
|
||||
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE TRIGGER trg_update_device_last_position
|
||||
AFTER INSERT ON gps_tracking.gps_positions
|
||||
FOR EACH ROW EXECUTE FUNCTION gps_tracking.update_device_last_position();
|
||||
|
||||
-- -------------------------------------------
|
||||
-- DATOS INICIALES: Geocercas base
|
||||
-- -------------------------------------------
|
||||
-- NOTA: Ejecutar despues de tener tenant_id valido
|
||||
-- INSERT INTO gps_tracking.geofences (tenant_id, name, code, geofence_type, center_lat, center_lng, radius_meters, category)
|
||||
-- VALUES
|
||||
-- ('{tenant_id}', 'Taller Principal', 'BASE-01', 'circle', 19.4326, -99.1332, 200, 'base');
|
||||
|
||||
COMMENT ON SCHEMA gps_tracking IS 'Schema completo para tracking GPS - MMD-014';
|
||||
Loading…
Reference in New Issue
Block a user