Financial Schema (50-57): - 50-financial-schema.sql: Schema + 10 ENUMs - 51-financial-accounts.sql: account_types, accounts, account_mappings - 52-financial-journals.sql: fiscal_years, fiscal_periods, journals - 53-financial-entries.sql: journal_entries, journal_entry_lines - 54-financial-invoices.sql: invoices, invoice_lines - 55-financial-payments.sql: payments, payment_invoice_allocations - 56-financial-taxes.sql: taxes, tax_groups - 57-financial-bank-reconciliation.sql: bank_statements, bank_statement_lines, rules Purchases Matching (46): - 46-purchases-matching.sql: purchase_order_matching, purchase_matching_lines, matching_exceptions Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
176 lines
7.8 KiB
PL/PgSQL
176 lines
7.8 KiB
PL/PgSQL
-- =============================================================
|
|
-- 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)';
|