-- =============================================================
-- ARCHIVO: 53-financial-entries.sql
-- DESCRIPCION: Asientos contables y lineas de asiento
-- VERSION: 1.0.0
-- PROYECTO: ERP-Core V2
-- FECHA: 2026-01-20
-- DEPENDE DE: 50-financial-schema.sql, 51-financial-accounts.sql, 52-financial-journals.sql
-- =============================================================

-- =====================
-- TABLA: journal_entries
-- Asientos contables (cabecera)
-- =====================
CREATE TABLE IF NOT EXISTS financial.journal_entries (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),

    -- Multi-tenant
    tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
    company_id UUID,

    -- Diario
    journal_id UUID NOT NULL REFERENCES financial.journals(id) ON DELETE RESTRICT,

    -- Identificacion
    name VARCHAR(100) NOT NULL,  -- Numero o identificador del asiento
    ref VARCHAR(255),            -- Referencia externa (factura, pago, etc.)

    -- Fecha
    date DATE NOT NULL,

    -- Estado
    status financial.entry_status_enum DEFAULT 'draft',

    -- Notas
    notes TEXT,

    -- Periodo fiscal
    fiscal_period_id UUID REFERENCES financial.fiscal_periods(id) ON DELETE RESTRICT,

    -- Audit columns
    created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
    created_by UUID REFERENCES auth.users(id),
    updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
    updated_by UUID REFERENCES auth.users(id),

    -- Publicacion
    posted_at TIMESTAMPTZ,
    posted_by UUID REFERENCES auth.users(id),

    -- Cancelacion
    cancelled_at TIMESTAMPTZ,
    cancelled_by UUID REFERENCES auth.users(id)
);

-- Indices para journal_entries
CREATE INDEX IF NOT EXISTS idx_financial_journal_entries_tenant ON financial.journal_entries(tenant_id);
CREATE INDEX IF NOT EXISTS idx_financial_journal_entries_company ON financial.journal_entries(company_id);
CREATE INDEX IF NOT EXISTS idx_financial_journal_entries_journal ON financial.journal_entries(journal_id);
CREATE INDEX IF NOT EXISTS idx_financial_journal_entries_name ON financial.journal_entries(name);
CREATE INDEX IF NOT EXISTS idx_financial_journal_entries_ref ON financial.journal_entries(ref);
CREATE INDEX IF NOT EXISTS idx_financial_journal_entries_date ON financial.journal_entries(date);
CREATE INDEX IF NOT EXISTS idx_financial_journal_entries_status ON financial.journal_entries(status);
CREATE INDEX IF NOT EXISTS idx_financial_journal_entries_period ON financial.journal_entries(fiscal_period_id);
CREATE INDEX IF NOT EXISTS idx_financial_journal_entries_posted ON financial.journal_entries(tenant_id, status) WHERE status = 'posted';
CREATE INDEX IF NOT EXISTS idx_financial_journal_entries_draft ON financial.journal_entries(tenant_id, status) WHERE status = 'draft';
CREATE INDEX IF NOT EXISTS idx_financial_journal_entries_date_range ON financial.journal_entries(tenant_id, date, status);

-- =====================
-- TABLA: journal_entry_lines
-- Lineas de asiento contable (debe/haber)
-- =====================
CREATE TABLE IF NOT EXISTS financial.journal_entry_lines (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),

    -- Relacion con asiento (cascade delete)
    entry_id UUID NOT NULL REFERENCES financial.journal_entries(id) ON DELETE CASCADE,

    -- Multi-tenant (denormalizado para queries rapidas)
    tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,

    -- Cuenta contable
    account_id UUID NOT NULL REFERENCES financial.accounts(id) ON DELETE RESTRICT,

    -- Partner asociado (opcional, para cuentas por cobrar/pagar)
    partner_id UUID,  -- FK a partners si existe

    -- Montos (solo debe o solo haber, nunca ambos)
    debit DECIMAL(15, 2) DEFAULT 0 CHECK (debit >= 0),
    credit DECIMAL(15, 2) DEFAULT 0 CHECK (credit >= 0),

    -- Descripcion de la linea
    description TEXT,

    -- Referencia adicional
    ref VARCHAR(255),

    -- Metadata
    created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,

    -- Validacion: debe tener debit XOR credit (no ambos, no ninguno)
    CONSTRAINT chk_journal_entry_lines_debit_credit CHECK (
        (debit > 0 AND credit = 0) OR (debit = 0 AND credit > 0)
    )
);

-- Indices para journal_entry_lines
CREATE INDEX IF NOT EXISTS idx_financial_journal_entry_lines_entry ON financial.journal_entry_lines(entry_id);
CREATE INDEX IF NOT EXISTS idx_financial_journal_entry_lines_tenant ON financial.journal_entry_lines(tenant_id);
CREATE INDEX IF NOT EXISTS idx_financial_journal_entry_lines_account ON financial.journal_entry_lines(account_id);
CREATE INDEX IF NOT EXISTS idx_financial_journal_entry_lines_partner ON financial.journal_entry_lines(partner_id) WHERE partner_id IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_financial_journal_entry_lines_debit ON financial.journal_entry_lines(account_id, debit) WHERE debit > 0;
CREATE INDEX IF NOT EXISTS idx_financial_journal_entry_lines_credit ON financial.journal_entry_lines(account_id, credit) WHERE credit > 0;

-- =====================
-- FUNCION: Validar balance de asiento
-- Un asiento debe estar balanceado (sum debit = sum credit)
-- =====================
CREATE OR REPLACE FUNCTION financial.check_entry_balance()
RETURNS TRIGGER AS $$
DECLARE
    v_total_debit DECIMAL(15, 2);
    v_total_credit DECIMAL(15, 2);
    v_entry_status financial.entry_status_enum;
BEGIN
    -- Solo validar cuando el asiento se publica
    SELECT status INTO v_entry_status
    FROM financial.journal_entries
    WHERE id = COALESCE(NEW.entry_id, OLD.entry_id);

    -- Solo validar si el asiento esta siendo publicado
    IF v_entry_status = 'posted' THEN
        SELECT
            COALESCE(SUM(debit), 0),
            COALESCE(SUM(credit), 0)
        INTO v_total_debit, v_total_credit
        FROM financial.journal_entry_lines
        WHERE entry_id = COALESCE(NEW.entry_id, OLD.entry_id);

        IF v_total_debit != v_total_credit THEN
            RAISE EXCEPTION 'El asiento no esta balanceado. Debe: %, Haber: %',
                v_total_debit, v_total_credit;
        END IF;
    END IF;

    RETURN COALESCE(NEW, OLD);
END;
$$ LANGUAGE plpgsql;

-- Trigger para validar balance (se ejecuta despues de INSERT/UPDATE/DELETE en lineas)
-- Nota: El trigger se crea pero puede deshabilitarse en ambientes de migracion
DROP TRIGGER IF EXISTS trg_check_entry_balance ON financial.journal_entry_lines;
-- CREATE TRIGGER trg_check_entry_balance
--     AFTER INSERT OR UPDATE OR DELETE ON financial.journal_entry_lines
--     FOR EACH ROW
--     EXECUTE FUNCTION financial.check_entry_balance();

-- =====================
-- COMENTARIOS
-- =====================
COMMENT ON TABLE financial.journal_entries IS 'Cabecera de asientos contables';
COMMENT ON COLUMN financial.journal_entries.name IS 'Numero o identificador unico del asiento';
COMMENT ON COLUMN financial.journal_entries.ref IS 'Referencia externa (numero de factura, pago, etc.)';
COMMENT ON COLUMN financial.journal_entries.status IS 'Estado: draft (editable), posted (contabilizado), cancelled';
COMMENT ON COLUMN financial.journal_entries.fiscal_period_id IS 'Periodo fiscal al que pertenece el asiento';
COMMENT ON COLUMN financial.journal_entries.posted_at IS 'Fecha y hora de publicacion/contabilizacion';
COMMENT ON COLUMN financial.journal_entries.cancelled_at IS 'Fecha y hora de cancelacion';

COMMENT ON TABLE financial.journal_entry_lines IS 'Lineas de asiento contable (partidas de debe y haber)';
COMMENT ON COLUMN financial.journal_entry_lines.account_id IS 'Cuenta contable afectada';
COMMENT ON COLUMN financial.journal_entry_lines.partner_id IS 'Partner asociado (para cuentas por cobrar/pagar)';
COMMENT ON COLUMN financial.journal_entry_lines.debit IS 'Monto al debe (cargo)';
COMMENT ON COLUMN financial.journal_entry_lines.credit IS 'Monto al haber (abono)';
COMMENT ON COLUMN financial.journal_entry_lines.description IS 'Descripcion o concepto de la linea';

COMMENT ON FUNCTION financial.check_entry_balance() IS 'Valida que el asiento este balanceado (sum debit = sum credit)';
