Agrego carpeta del source sql de la Base de Datos
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
-- ============================================================================
|
||||
-- internal.limpiar_dias_especiales_antiguos
|
||||
-- ============================================================================
|
||||
-- PROPÓSITO
|
||||
-- Higiene operativa: elimina filas de public.dias_especiales cuya
|
||||
-- `fecha` esté fuera de la ventana de retención. Un día especial es
|
||||
-- una excepción puntual al régimen regular (cerrado u horario
|
||||
-- diferente); una vez pasado, no tiene valor operativo ni se archiva.
|
||||
--
|
||||
-- DOMINIO
|
||||
-- Implementa la higiene de días especiales descrita en
|
||||
-- Documentation/PoliticaRetencion.md §7. La ventana es corta (1 mes
|
||||
-- por default) porque un día especial sólo importa mientras condiciona
|
||||
-- la generación de turnos; vencido el mes, ya no afecta nada vigente.
|
||||
--
|
||||
-- PARÁMETROS
|
||||
-- Ninguno. La ventana en meses se lee de `internal.app_config` con la
|
||||
-- clave `retencion.dias_especiales_meses` (default 1). El ajuste
|
||||
-- operativo se hace en la tabla, sin redeploy.
|
||||
--
|
||||
-- AUTORIZACIÓN
|
||||
-- No valida permiso propio. El schema `internal` no es alcanzable por
|
||||
-- roles cliente (ver hardening en `database/schema/00_schemas.sql`).
|
||||
-- Callers legítimos: pg_cron y fachadas `public.fc_*` SECURITY DEFINER
|
||||
-- que orquesten el pipeline mensual (US-R17).
|
||||
--
|
||||
-- ERRORES (RAISE EXCEPTION)
|
||||
-- Ninguno propio.
|
||||
--
|
||||
-- RETORNA
|
||||
-- JSONB con {status, fecha_corte, meses_conservados,
|
||||
-- dias_eliminados, mensaje}.
|
||||
--
|
||||
-- EFECTOS SECUNDARIOS
|
||||
-- DELETE de dias_especiales con fecha < (CURRENT_DATE - ventana). Las
|
||||
-- filas de horario_actividad_especial asociadas caen por CASCADE
|
||||
-- (horario_actividad_especial_dia_fkey ON DELETE CASCADE). No registra
|
||||
-- evento.
|
||||
-- ============================================================================
|
||||
|
||||
CREATE OR REPLACE FUNCTION internal.limpiar_dias_especiales_antiguos()
|
||||
RETURNS JSONB
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER
|
||||
SET search_path = public
|
||||
SET timezone = 'America/Argentina/Buenos_Aires'
|
||||
VOLATILE
|
||||
AS $$
|
||||
DECLARE
|
||||
v_meses INT;
|
||||
v_fecha_limite DATE;
|
||||
v_eliminados INT;
|
||||
BEGIN
|
||||
v_meses := internal.get_config_int('retencion.dias_especiales_meses', 1);
|
||||
v_fecha_limite := CURRENT_DATE - (v_meses * INTERVAL '1 month');
|
||||
|
||||
DELETE FROM dias_especiales
|
||||
WHERE fecha < v_fecha_limite;
|
||||
|
||||
GET DIAGNOSTICS v_eliminados = ROW_COUNT;
|
||||
|
||||
RETURN jsonb_build_object(
|
||||
'status', 'success',
|
||||
'fecha_corte', v_fecha_limite,
|
||||
'meses_conservados', v_meses,
|
||||
'dias_eliminados', v_eliminados,
|
||||
'mensaje', format(
|
||||
'Se eliminaron %s días especiales anteriores a %s (rangos horarios asociados eliminados por CASCADE).',
|
||||
v_eliminados, v_fecha_limite
|
||||
)
|
||||
);
|
||||
END;
|
||||
$$;
|
||||
|
||||
-- Defensa en profundidad: aunque el hardening del schema (00_schemas.sql)
|
||||
-- ya bloquee USAGE y EXECUTE por default, revocamos explícitamente acá
|
||||
-- por si en algún futuro alguien afloja las defensas a nivel schema.
|
||||
REVOKE ALL ON FUNCTION internal.limpiar_dias_especiales_antiguos()
|
||||
FROM PUBLIC, anon, authenticated;
|
||||
@@ -0,0 +1,144 @@
|
||||
-- ============================================================================
|
||||
-- internal.upsert_horario_especial
|
||||
-- ============================================================================
|
||||
-- PROPÓSITO
|
||||
-- Helper interno invocado por fc_insertar_horario_con_actividades cuando
|
||||
-- el payload corresponde a un día con horario especial (override puntual
|
||||
-- o cierre). Crea/actualiza el registro en dias_especiales y materializa
|
||||
-- o limpia los turnos en consecuencia.
|
||||
--
|
||||
-- Si rangos[] viene vacío o no viene → el día queda como 'cerrado'.
|
||||
-- Si trae rangos → el día queda como 'horario_diferente'.
|
||||
--
|
||||
-- PARÁMETROS
|
||||
-- p_datos JSONB con: fecha (obligatorio), motivo?, rangos[]?.
|
||||
--
|
||||
-- AUTORIZACIÓN
|
||||
-- No valida permiso propio (lo hizo la fachada antes de invocar).
|
||||
--
|
||||
-- ERRORES (RAISE EXCEPTION)
|
||||
-- - 'No se pueden alterar horarios en fechas pasadas.'
|
||||
--
|
||||
-- RETORNA
|
||||
-- DATE = la fecha del día especial.
|
||||
--
|
||||
-- EFECTOS SECUNDARIOS
|
||||
-- - UPSERT en dias_especiales (ON CONFLICT (fecha) DO UPDATE).
|
||||
-- - DELETE de horario_actividad_especial previo (reemplazo).
|
||||
-- - Si tipo='horario_diferente': INSERT de los rangos nuevos en
|
||||
-- horario_actividad_especial.
|
||||
-- - Si la fecha tenía turnos materializados:
|
||||
-- - tipo='cerrado': rescata TODAS las reservas activas como huérfanas
|
||||
-- y borra todos los turnos del día.
|
||||
-- - tipo='horario_diferente': rescate selectivo + delete selectivo +
|
||||
-- parcheo JIT (mismo patrón que upsert_horario_regular).
|
||||
-- ============================================================================
|
||||
|
||||
CREATE OR REPLACE FUNCTION internal.upsert_horario_especial(
|
||||
p_datos JSONB
|
||||
)
|
||||
RETURNS DATE
|
||||
LANGUAGE plpgsql
|
||||
SET search_path = public
|
||||
SET timezone = 'America/Argentina/Buenos_Aires'
|
||||
VOLATILE
|
||||
AS $function$
|
||||
DECLARE
|
||||
v_fecha DATE;
|
||||
v_dia_semana SMALLINT;
|
||||
v_tipo VARCHAR(25);
|
||||
v_motivo TEXT;
|
||||
v_dia_especial_id INT;
|
||||
v_turnos_generados BOOLEAN;
|
||||
v_bloque RECORD;
|
||||
v_hora_iter TIME;
|
||||
BEGIN
|
||||
v_fecha := (p_datos->>'fecha')::DATE;
|
||||
v_dia_semana := EXTRACT(ISODOW FROM v_fecha)::SMALLINT;
|
||||
|
||||
|
||||
IF v_fecha < (now() AT TIME ZONE 'America/Argentina/Buenos_Aires')::DATE THEN
|
||||
RAISE EXCEPTION 'No se pueden alterar horarios en fechas pasadas.';
|
||||
END IF;
|
||||
|
||||
v_motivo := p_datos->>'motivo';
|
||||
|
||||
IF (p_datos->'rangos') IS NULL OR jsonb_typeof(p_datos->'rangos') <> 'array' OR jsonb_array_length(p_datos->'rangos') = 0 THEN
|
||||
v_tipo := 'cerrado';
|
||||
ELSE
|
||||
v_tipo := 'horario_diferente';
|
||||
END IF;
|
||||
|
||||
-- 1. Upsert del día especial
|
||||
INSERT INTO dias_especiales (fecha, tipo, motivo)
|
||||
VALUES (v_fecha, v_tipo, v_motivo)
|
||||
ON CONFLICT (fecha) DO UPDATE
|
||||
SET tipo = EXCLUDED.tipo, motivo = EXCLUDED.motivo
|
||||
RETURNING id INTO v_dia_especial_id;
|
||||
|
||||
DELETE FROM horario_actividad_especial WHERE dia_especial_id = v_dia_especial_id;
|
||||
|
||||
IF v_tipo = 'horario_diferente' THEN
|
||||
INSERT INTO horario_actividad_especial (dia_especial_id, actividad_id, hora_inicio, hora_fin)
|
||||
SELECT DISTINCT v_dia_especial_id, (rango->>'actividad_id')::INT, (rango->>'hora_inicio')::TIME, (rango->>'hora_fin')::TIME
|
||||
FROM jsonb_array_elements(p_datos->'rangos') AS rango;
|
||||
END IF;
|
||||
|
||||
-- 2. Análisis del estado JIT
|
||||
SELECT EXISTS (SELECT 1 FROM turnos WHERE fecha = v_fecha) INTO v_turnos_generados;
|
||||
|
||||
IF v_turnos_generados THEN
|
||||
IF v_tipo = 'cerrado' THEN
|
||||
-- Rescate total
|
||||
INSERT INTO reservas_huerfanas (cliente_id, actividad_nombre, fecha_original, hora_inicio_original)
|
||||
SELECT r.cliente_id, a.nombre, t.fecha, t.hora_inicio
|
||||
FROM reservas r JOIN turnos t ON r.turno_id = t.id JOIN actividades a ON t.actividad_id = a.id
|
||||
WHERE t.fecha = v_fecha AND r.cancelada = false;
|
||||
|
||||
-- Borrado total
|
||||
DELETE FROM turnos WHERE fecha = v_fecha;
|
||||
ELSE
|
||||
-- Rescate parcial (Match Suave)
|
||||
INSERT INTO reservas_huerfanas (cliente_id, actividad_nombre, fecha_original, hora_inicio_original)
|
||||
SELECT r.cliente_id, a.nombre, t.fecha, t.hora_inicio
|
||||
FROM reservas r JOIN turnos t ON r.turno_id = t.id JOIN actividades a ON t.actividad_id = a.id
|
||||
WHERE t.fecha = v_fecha
|
||||
AND r.cancelada = false
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM jsonb_array_elements(p_datos->'rangos') AS rng
|
||||
WHERE (rng->>'actividad_id')::INT = t.actividad_id
|
||||
AND t.hora_inicio >= (rng->>'hora_inicio')::TIME
|
||||
AND t.hora_fin <= (rng->>'hora_fin')::TIME
|
||||
);
|
||||
|
||||
-- Borrado parcial
|
||||
DELETE FROM turnos t
|
||||
WHERE t.fecha = v_fecha
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM jsonb_array_elements(p_datos->'rangos') AS rng
|
||||
WHERE (rng->>'actividad_id')::INT = t.actividad_id
|
||||
AND t.hora_inicio >= (rng->>'hora_inicio')::TIME
|
||||
AND t.hora_fin <= (rng->>'hora_fin')::TIME
|
||||
);
|
||||
|
||||
-- Parcheo JIT
|
||||
FOR v_bloque IN (
|
||||
SELECT (r->>'actividad_id')::INT AS act_id, (r->>'hora_inicio')::TIME AS h_in, (r->>'hora_fin')::TIME AS h_out,
|
||||
a.duracion, a.capacidad_por_defecto
|
||||
FROM jsonb_array_elements(p_datos->'rangos') AS r
|
||||
JOIN actividades a ON a.id = (r->>'actividad_id')::INT
|
||||
) LOOP
|
||||
v_hora_iter := v_bloque.h_in;
|
||||
WHILE v_hora_iter + (v_bloque.duracion || ' minutes')::INTERVAL <= v_bloque.h_out LOOP
|
||||
INSERT INTO turnos (actividad_id, fecha, hora_inicio, hora_fin, capacidad_maxima, es_especial, dia_semana)
|
||||
VALUES (v_bloque.act_id, v_fecha, v_hora_iter, v_hora_iter + (v_bloque.duracion || ' minutes')::INTERVAL, v_bloque.capacidad_por_defecto, true, v_dia_semana)
|
||||
ON CONFLICT DO NOTHING;
|
||||
v_hora_iter := v_hora_iter + (v_bloque.duracion || ' minutes')::INTERVAL;
|
||||
END LOOP;
|
||||
END LOOP;
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
RETURN v_fecha;
|
||||
END;
|
||||
$function$;
|
||||
@@ -0,0 +1,228 @@
|
||||
-- ============================================================================
|
||||
-- internal.upsert_horario_regular
|
||||
-- ============================================================================
|
||||
-- PROPÓSITO
|
||||
-- Helper interno invocado por fc_insertar_horario_con_actividades cuando
|
||||
-- el payload corresponde a un horario regular (semanal). Reemplaza /
|
||||
-- crea la plantilla horaria del día de la semana para una vigencia
|
||||
-- declarada, cierra plantillas previas que se solapan y sincroniza los
|
||||
-- turnos JIT ya materializados.
|
||||
--
|
||||
-- PARÁMETROS
|
||||
-- p_datos JSONB con (todos los campos parseados acá; ver fachada para
|
||||
-- descripción completa): fecha, valido_desde?, alcance.{tipo,
|
||||
-- fecha?}, rangos[].
|
||||
--
|
||||
-- AUTORIZACIÓN
|
||||
-- No valida permiso propio (lo hizo la fachada antes de invocar).
|
||||
--
|
||||
-- ERRORES (RAISE EXCEPTION)
|
||||
-- - 'valido_desde no puede ser una fecha pasada.'
|
||||
-- - 'alcance.tipo inválido: X. Debe ser ''indefinido'', ''hasta_proximo''
|
||||
-- o ''hasta''.'
|
||||
-- - 'alcance.fecha es obligatorio cuando alcance.tipo = ''hasta''.'
|
||||
-- - 'alcance.fecha (X) no puede ser anterior a valido_desde (Y).'
|
||||
-- - 'Conflicto: existe una planificación con valido_desde = X dentro
|
||||
-- del intervalo [Y, Z]. Modificá el alcance o eliminá la planificación
|
||||
-- previamente.'
|
||||
--
|
||||
-- RETORNA
|
||||
-- DATE = valido_desde efectivamente aplicado.
|
||||
--
|
||||
-- EFECTOS SECUNDARIOS
|
||||
-- - Según el alcance: borra plantillas futuras del mismo día_semana, o
|
||||
-- deja vigencia abierta, o cierra contra la próxima futura.
|
||||
-- - DELETE de plantilla con valido_desde exactamente igual (reemplazo).
|
||||
-- - UPDATE de plantillas anteriores que se solapan: valido_hasta =
|
||||
-- valido_desde - 1.
|
||||
-- - INSERT de los rangos nuevos en horario_actividad.
|
||||
-- - Para las fechas afectadas con turnos ya materializados: rescate de
|
||||
-- huérfanas + delete selectivo + parcheo JIT (mismo patrón que usan
|
||||
-- fc_eliminar_dia_especial e internal.cerrar_actividad).
|
||||
-- ============================================================================
|
||||
|
||||
CREATE OR REPLACE FUNCTION internal.upsert_horario_regular(
|
||||
p_datos JSONB
|
||||
)
|
||||
RETURNS DATE
|
||||
LANGUAGE plpgsql
|
||||
SET search_path = public
|
||||
SET timezone = 'America/Argentina/Buenos_Aires'
|
||||
VOLATILE
|
||||
AS $function$
|
||||
DECLARE
|
||||
v_fecha DATE;
|
||||
v_dia_semana SMALLINT;
|
||||
v_valido_desde DATE;
|
||||
v_alcance_tipo TEXT;
|
||||
v_alcance_fecha DATE;
|
||||
v_valido_hasta_nuevo DATE;
|
||||
v_fecha_conflicto DATE;
|
||||
v_fechas_afectadas DATE[];
|
||||
v_f DATE;
|
||||
v_bloque RECORD;
|
||||
v_hora_iter TIME;
|
||||
BEGIN
|
||||
------------------------------------------------------------
|
||||
-- 0. Parseo y validación de entrada
|
||||
------------------------------------------------------------
|
||||
v_fecha := (p_datos->>'fecha')::DATE;
|
||||
v_dia_semana := EXTRACT(ISODOW FROM v_fecha)::SMALLINT;
|
||||
v_valido_desde := COALESCE((p_datos->>'valido_desde')::DATE, v_fecha);
|
||||
|
||||
IF v_valido_desde < (now() AT TIME ZONE 'America/Argentina/Buenos_Aires')::DATE THEN
|
||||
RAISE EXCEPTION 'valido_desde no puede ser una fecha pasada.';
|
||||
END IF;
|
||||
|
||||
v_alcance_tipo := COALESCE(p_datos->'alcance'->>'tipo', 'hasta_proximo');
|
||||
|
||||
IF v_alcance_tipo NOT IN ('indefinido', 'hasta_proximo', 'hasta') THEN
|
||||
RAISE EXCEPTION
|
||||
'alcance.tipo inválido: %. Debe ser ''indefinido'', ''hasta_proximo'' o ''hasta''.',
|
||||
v_alcance_tipo;
|
||||
END IF;
|
||||
|
||||
IF v_alcance_tipo = 'hasta' THEN
|
||||
v_alcance_fecha := (p_datos->'alcance'->>'fecha')::DATE;
|
||||
IF v_alcance_fecha IS NULL THEN
|
||||
RAISE EXCEPTION 'alcance.fecha es obligatorio cuando alcance.tipo = ''hasta''.';
|
||||
END IF;
|
||||
IF v_alcance_fecha < v_valido_desde THEN
|
||||
RAISE EXCEPTION
|
||||
'alcance.fecha (%) no puede ser anterior a valido_desde (%).',
|
||||
v_alcance_fecha, v_valido_desde;
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
------------------------------------------------------------
|
||||
-- 1. Resolver el valido_hasta del nuevo intervalo según el alcance
|
||||
------------------------------------------------------------
|
||||
IF v_alcance_tipo = 'indefinido' THEN
|
||||
-- Purga todas las plantillas futuras del mismo dia_semana
|
||||
DELETE FROM horario_actividad
|
||||
WHERE dia_semana = v_dia_semana
|
||||
AND valido_desde > v_valido_desde;
|
||||
v_valido_hasta_nuevo := NULL;
|
||||
|
||||
ELSIF v_alcance_tipo = 'hasta_proximo' THEN
|
||||
-- Cerrar antes del próximo futuro, o quedar abierto si no hay
|
||||
SELECT MIN(valido_desde) - 1
|
||||
INTO v_valido_hasta_nuevo
|
||||
FROM horario_actividad
|
||||
WHERE dia_semana = v_dia_semana
|
||||
AND valido_desde > v_valido_desde;
|
||||
|
||||
ELSE -- 'hasta'
|
||||
-- Validar que no haya futuros dentro del intervalo solicitado
|
||||
SELECT MIN(valido_desde)
|
||||
INTO v_fecha_conflicto
|
||||
FROM horario_actividad
|
||||
WHERE dia_semana = v_dia_semana
|
||||
AND valido_desde > v_valido_desde
|
||||
AND valido_desde <= v_alcance_fecha;
|
||||
|
||||
IF v_fecha_conflicto IS NOT NULL THEN
|
||||
RAISE EXCEPTION
|
||||
'Conflicto: existe una planificación con valido_desde = % dentro del intervalo [%, %]. Modificá el alcance o eliminá la planificación previamente.',
|
||||
v_fecha_conflicto, v_valido_desde, v_alcance_fecha;
|
||||
END IF;
|
||||
v_valido_hasta_nuevo := v_alcance_fecha;
|
||||
END IF;
|
||||
|
||||
------------------------------------------------------------
|
||||
-- 2. Limpieza estructural en horario_actividad
|
||||
------------------------------------------------------------
|
||||
-- 2.A Colisión exacta: si ya existía una plantilla con el mismo valido_desde,
|
||||
-- la borramos para reemplazarla con los rangos nuevos.
|
||||
DELETE FROM horario_actividad
|
||||
WHERE dia_semana = v_dia_semana
|
||||
AND valido_desde = v_valido_desde;
|
||||
|
||||
-- 2.B Cerrar la(s) plantilla(s) anterior(es) que se solapen con el nuevo desde.
|
||||
UPDATE horario_actividad
|
||||
SET valido_hasta = v_valido_desde - 1
|
||||
WHERE dia_semana = v_dia_semana
|
||||
AND valido_desde < v_valido_desde
|
||||
AND (valido_hasta IS NULL OR valido_hasta >= v_valido_desde);
|
||||
|
||||
-- 2.C Insertar los rangos nuevos con la vigencia ya resuelta.
|
||||
INSERT INTO horario_actividad
|
||||
(dia_semana, actividad_id, hora_inicio, hora_fin, valido_desde, valido_hasta)
|
||||
SELECT DISTINCT
|
||||
v_dia_semana,
|
||||
(rango->>'actividad_id')::INT,
|
||||
(rango->>'hora_inicio')::TIME,
|
||||
(rango->>'hora_fin')::TIME,
|
||||
v_valido_desde,
|
||||
v_valido_hasta_nuevo
|
||||
FROM jsonb_array_elements(p_datos->'rangos') AS rango;
|
||||
|
||||
------------------------------------------------------------
|
||||
-- 3. Match suave sobre turnos JIT ya materializados
|
||||
-- Acotado al intervalo efectivo del nuevo (fix del bug viejo:
|
||||
-- no tocar turnos que pertenezcan a futuros que se respetan).
|
||||
------------------------------------------------------------
|
||||
SELECT array_agg(DISTINCT fecha)
|
||||
INTO v_fechas_afectadas
|
||||
FROM turnos
|
||||
WHERE dia_semana = v_dia_semana
|
||||
AND fecha >= v_valido_desde
|
||||
AND fecha <= COALESCE(v_valido_hasta_nuevo, 'infinity'::date);
|
||||
|
||||
IF v_fechas_afectadas IS NOT NULL THEN
|
||||
-- 3.A Rescate de huérfanas
|
||||
INSERT INTO reservas_huerfanas
|
||||
(cliente_id, actividad_nombre, fecha_original, hora_inicio_original)
|
||||
SELECT r.cliente_id, a.nombre, t.fecha, t.hora_inicio
|
||||
FROM reservas r
|
||||
JOIN turnos t ON r.turno_id = t.id
|
||||
JOIN actividades a ON t.actividad_id = a.id
|
||||
WHERE t.fecha = ANY(v_fechas_afectadas)
|
||||
AND r.cancelada = false
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM jsonb_array_elements(p_datos->'rangos') AS rng
|
||||
WHERE (rng->>'actividad_id')::INT = t.actividad_id
|
||||
AND t.hora_inicio >= (rng->>'hora_inicio')::TIME
|
||||
AND t.hora_fin <= (rng->>'hora_fin')::TIME
|
||||
);
|
||||
|
||||
-- 3.B Borrado selectivo de turnos que ya no calzan con los rangos nuevos
|
||||
DELETE FROM turnos t
|
||||
WHERE t.fecha = ANY(v_fechas_afectadas)
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM jsonb_array_elements(p_datos->'rangos') AS rng
|
||||
WHERE (rng->>'actividad_id')::INT = t.actividad_id
|
||||
AND t.hora_inicio >= (rng->>'hora_inicio')::TIME
|
||||
AND t.hora_fin <= (rng->>'hora_fin')::TIME
|
||||
);
|
||||
|
||||
-- 3.C Parcheo JIT iterativo para esas fechas
|
||||
FOREACH v_f IN ARRAY v_fechas_afectadas LOOP
|
||||
FOR v_bloque IN (
|
||||
SELECT (r->>'actividad_id')::INT AS act_id,
|
||||
(r->>'hora_inicio')::TIME AS h_in,
|
||||
(r->>'hora_fin')::TIME AS h_out,
|
||||
a.duracion, a.capacidad_por_defecto
|
||||
FROM jsonb_array_elements(p_datos->'rangos') AS r
|
||||
JOIN actividades a ON a.id = (r->>'actividad_id')::INT
|
||||
) LOOP
|
||||
v_hora_iter := v_bloque.h_in;
|
||||
WHILE v_hora_iter + (v_bloque.duracion || ' minutes')::INTERVAL <= v_bloque.h_out LOOP
|
||||
INSERT INTO turnos
|
||||
(actividad_id, fecha, hora_inicio, hora_fin,
|
||||
capacidad_maxima, es_especial, dia_semana)
|
||||
VALUES (
|
||||
v_bloque.act_id, v_f, v_hora_iter,
|
||||
v_hora_iter + (v_bloque.duracion || ' minutes')::INTERVAL,
|
||||
v_bloque.capacidad_por_defecto, false, v_dia_semana
|
||||
)
|
||||
ON CONFLICT DO NOTHING;
|
||||
v_hora_iter := v_hora_iter + (v_bloque.duracion || ' minutes')::INTERVAL;
|
||||
END LOOP;
|
||||
END LOOP;
|
||||
END LOOP;
|
||||
END IF;
|
||||
|
||||
RETURN v_valido_desde;
|
||||
END;
|
||||
$function$;
|
||||
Reference in New Issue
Block a user