-- ============================================================================
-- MIGRACIÓN: Validación de Período Fiscal Cerrado
-- Fecha: 2025-12-12
-- Descripción: Agrega trigger para prevenir asientos en períodos cerrados
-- Impacto: Todas las verticales que usan el módulo financiero
-- Rollback: DROP TRIGGER y DROP FUNCTION incluidos al final
-- ============================================================================

-- ============================================================================
-- 1. FUNCIÓN DE VALIDACIÓN
-- ============================================================================

CREATE OR REPLACE FUNCTION financial.validate_period_not_closed()
RETURNS TRIGGER AS $$
DECLARE
  v_period_status TEXT;
  v_period_name TEXT;
BEGIN
  -- Solo validar si hay un fiscal_period_id
  IF NEW.fiscal_period_id IS NULL THEN
    RETURN NEW;
  END IF;

  -- Obtener el estado del período
  SELECT fp.status, fp.name INTO v_period_status, v_period_name
  FROM financial.fiscal_periods fp
  WHERE fp.id = NEW.fiscal_period_id;

  -- Validar que el período no esté cerrado
  IF v_period_status = 'closed' THEN
    RAISE EXCEPTION 'ERR_PERIOD_CLOSED: No se pueden crear o modificar asientos en el período cerrado: %', v_period_name
      USING ERRCODE = 'P0001';
  END IF;

  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

COMMENT ON FUNCTION financial.validate_period_not_closed() IS
'Valida que no se creen asientos contables en períodos fiscales cerrados.
Lanza excepción ERR_PERIOD_CLOSED si el período está cerrado.';

-- ============================================================================
-- 2. TRIGGER EN JOURNAL_ENTRIES
-- ============================================================================

-- Eliminar trigger si existe (idempotente)
DROP TRIGGER IF EXISTS trg_validate_period_before_entry ON financial.journal_entries;

-- Crear trigger BEFORE INSERT OR UPDATE
CREATE TRIGGER trg_validate_period_before_entry
  BEFORE INSERT OR UPDATE ON financial.journal_entries
  FOR EACH ROW
  EXECUTE FUNCTION financial.validate_period_not_closed();

COMMENT ON TRIGGER trg_validate_period_before_entry ON financial.journal_entries IS
'Previene la creación o modificación de asientos en períodos fiscales cerrados';

-- ============================================================================
-- 3. FUNCIÓN PARA CERRAR PERÍODO
-- ============================================================================

CREATE OR REPLACE FUNCTION financial.close_fiscal_period(
  p_period_id UUID,
  p_user_id UUID
)
RETURNS financial.fiscal_periods AS $$
DECLARE
  v_period financial.fiscal_periods;
  v_unposted_count INTEGER;
BEGIN
  -- Obtener período
  SELECT * INTO v_period
  FROM financial.fiscal_periods
  WHERE id = p_period_id
  FOR UPDATE;

  IF NOT FOUND THEN
    RAISE EXCEPTION 'Período fiscal no encontrado' USING ERRCODE = 'P0002';
  END IF;

  IF v_period.status = 'closed' THEN
    RAISE EXCEPTION 'El período ya está cerrado' USING ERRCODE = 'P0003';
  END IF;

  -- Verificar que no haya asientos sin postear
  SELECT COUNT(*) INTO v_unposted_count
  FROM financial.journal_entries je
  WHERE je.fiscal_period_id = p_period_id
    AND je.status = 'draft';

  IF v_unposted_count > 0 THEN
    RAISE EXCEPTION 'Existen % asientos sin postear en este período. Postéelos antes de cerrar.',
      v_unposted_count USING ERRCODE = 'P0004';
  END IF;

  -- Cerrar el período
  UPDATE financial.fiscal_periods
  SET status = 'closed',
      closed_at = NOW(),
      closed_by = p_user_id,
      updated_at = NOW()
  WHERE id = p_period_id
  RETURNING * INTO v_period;

  RETURN v_period;
END;
$$ LANGUAGE plpgsql;

COMMENT ON FUNCTION financial.close_fiscal_period(UUID, UUID) IS
'Cierra un período fiscal. Valida que todos los asientos estén posteados.';

-- ============================================================================
-- 4. FUNCIÓN PARA REABRIR PERÍODO (Solo admins)
-- ============================================================================

CREATE OR REPLACE FUNCTION financial.reopen_fiscal_period(
  p_period_id UUID,
  p_user_id UUID,
  p_reason TEXT DEFAULT NULL
)
RETURNS financial.fiscal_periods AS $$
DECLARE
  v_period financial.fiscal_periods;
BEGIN
  -- Obtener período
  SELECT * INTO v_period
  FROM financial.fiscal_periods
  WHERE id = p_period_id
  FOR UPDATE;

  IF NOT FOUND THEN
    RAISE EXCEPTION 'Período fiscal no encontrado' USING ERRCODE = 'P0002';
  END IF;

  IF v_period.status = 'open' THEN
    RAISE EXCEPTION 'El período ya está abierto' USING ERRCODE = 'P0005';
  END IF;

  -- Reabrir el período
  UPDATE financial.fiscal_periods
  SET status = 'open',
      closed_at = NULL,
      closed_by = NULL,
      updated_at = NOW()
  WHERE id = p_period_id
  RETURNING * INTO v_period;

  -- Registrar en log de auditoría
  INSERT INTO system.logs (
    tenant_id, level, module, message, context, user_id
  )
  SELECT
    v_period.tenant_id,
    'warning',
    'financial',
    'Período fiscal reabierto',
    jsonb_build_object(
      'period_id', p_period_id,
      'period_name', v_period.name,
      'reason', p_reason,
      'reopened_by', p_user_id
    ),
    p_user_id;

  RETURN v_period;
END;
$$ LANGUAGE plpgsql;

COMMENT ON FUNCTION financial.reopen_fiscal_period(UUID, UUID, TEXT) IS
'Reabre un período fiscal cerrado. Registra en auditoría. Solo para administradores.';

-- ============================================================================
-- 5. ÍNDICE PARA PERFORMANCE
-- ============================================================================

CREATE INDEX IF NOT EXISTS idx_journal_entries_fiscal_period
  ON financial.journal_entries(fiscal_period_id)
  WHERE fiscal_period_id IS NOT NULL;

-- ============================================================================
-- ROLLBACK SCRIPT (ejecutar si es necesario revertir)
-- ============================================================================
/*
DROP TRIGGER IF EXISTS trg_validate_period_before_entry ON financial.journal_entries;
DROP FUNCTION IF EXISTS financial.validate_period_not_closed();
DROP FUNCTION IF EXISTS financial.close_fiscal_period(UUID, UUID);
DROP FUNCTION IF EXISTS financial.reopen_fiscal_period(UUID, UUID, TEXT);
DROP INDEX IF EXISTS financial.idx_journal_entries_fiscal_period;
*/

-- ============================================================================
-- VERIFICACIÓN
-- ============================================================================

DO $$
BEGIN
  -- Verificar que el trigger existe
  IF NOT EXISTS (
    SELECT 1 FROM pg_trigger
    WHERE tgname = 'trg_validate_period_before_entry'
  ) THEN
    RAISE EXCEPTION 'Error: Trigger no fue creado correctamente';
  END IF;

  RAISE NOTICE 'Migración completada exitosamente: Validación de período fiscal';
END $$;
