[TASK-2026-01-20-003] feat: Add financial DDL and matching tables
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>
This commit is contained in:
parent
1ac7d1e60b
commit
4b6240311d
140
ddl/46-purchases-matching.sql
Normal file
140
ddl/46-purchases-matching.sql
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
-- =============================================================
|
||||||
|
-- ARCHIVO: 46-purchases-matching.sql
|
||||||
|
-- DESCRIPCION: 3-Way Matching (PO-Receipt-Invoice)
|
||||||
|
-- VERSION: 1.0.0
|
||||||
|
-- PROYECTO: ERP-Core V2
|
||||||
|
-- FECHA: 2026-01-20
|
||||||
|
-- DEPENDE DE: 23-purchases.sql
|
||||||
|
-- =============================================================
|
||||||
|
|
||||||
|
-- =====================
|
||||||
|
-- TABLA: purchase_order_matching
|
||||||
|
-- Registro de matching por orden de compra
|
||||||
|
-- =====================
|
||||||
|
CREATE TABLE IF NOT EXISTS purchases.purchase_order_matching (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
|
||||||
|
purchase_order_id UUID NOT NULL REFERENCES purchases.purchase_orders(id) ON DELETE RESTRICT,
|
||||||
|
|
||||||
|
-- Estado del matching
|
||||||
|
status VARCHAR(20) NOT NULL DEFAULT 'pending', -- pending, partial_receipt, received, partial_invoice, matched, mismatch
|
||||||
|
|
||||||
|
-- Totales
|
||||||
|
total_ordered DECIMAL(15, 2) NOT NULL,
|
||||||
|
total_received DECIMAL(15, 2) DEFAULT 0,
|
||||||
|
total_invoiced DECIMAL(15, 2) DEFAULT 0,
|
||||||
|
|
||||||
|
-- Varianzas calculadas
|
||||||
|
receipt_variance DECIMAL(15, 2) GENERATED ALWAYS AS (total_ordered - total_received) STORED,
|
||||||
|
invoice_variance DECIMAL(15, 2) GENERATED ALWAYS AS (total_received - total_invoiced) STORED,
|
||||||
|
|
||||||
|
-- Referencias al ultimo documento
|
||||||
|
last_receipt_id UUID REFERENCES purchases.purchase_receipts(id),
|
||||||
|
last_invoice_id UUID,
|
||||||
|
|
||||||
|
-- Matching completado
|
||||||
|
matched_at TIMESTAMPTZ,
|
||||||
|
matched_by UUID REFERENCES auth.users(id),
|
||||||
|
|
||||||
|
-- Metadata
|
||||||
|
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
|
||||||
|
UNIQUE(tenant_id, purchase_order_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Indices para purchase_order_matching
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_purchase_order_matching_tenant ON purchases.purchase_order_matching(tenant_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_purchase_order_matching_po ON purchases.purchase_order_matching(purchase_order_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_purchase_order_matching_status ON purchases.purchase_order_matching(status);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_purchase_order_matching_receipt ON purchases.purchase_order_matching(last_receipt_id);
|
||||||
|
|
||||||
|
-- =====================
|
||||||
|
-- TABLA: purchase_matching_lines
|
||||||
|
-- Matching por linea de orden de compra
|
||||||
|
-- =====================
|
||||||
|
CREATE TABLE IF NOT EXISTS purchases.purchase_matching_lines (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
matching_id UUID NOT NULL REFERENCES purchases.purchase_order_matching(id) ON DELETE CASCADE,
|
||||||
|
order_item_id UUID NOT NULL REFERENCES purchases.purchase_order_items(id) ON DELETE RESTRICT,
|
||||||
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
|
||||||
|
|
||||||
|
-- Cantidades
|
||||||
|
qty_ordered DECIMAL(15, 4) NOT NULL,
|
||||||
|
qty_received DECIMAL(15, 4) DEFAULT 0,
|
||||||
|
qty_invoiced DECIMAL(15, 4) DEFAULT 0,
|
||||||
|
|
||||||
|
-- Precios
|
||||||
|
price_ordered DECIMAL(15, 2) NOT NULL,
|
||||||
|
price_invoiced DECIMAL(15, 2) DEFAULT 0,
|
||||||
|
|
||||||
|
-- Varianzas calculadas
|
||||||
|
qty_variance DECIMAL(15, 4) GENERATED ALWAYS AS (qty_ordered - qty_received) STORED,
|
||||||
|
invoice_qty_variance DECIMAL(15, 4) GENERATED ALWAYS AS (qty_received - qty_invoiced) STORED,
|
||||||
|
price_variance DECIMAL(15, 2) GENERATED ALWAYS AS (price_ordered - price_invoiced) STORED,
|
||||||
|
|
||||||
|
-- Estado
|
||||||
|
status VARCHAR(20) DEFAULT 'pending', -- pending, partial, matched, mismatch
|
||||||
|
|
||||||
|
-- Metadata
|
||||||
|
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Indices para purchase_matching_lines
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_purchase_matching_lines_matching ON purchases.purchase_matching_lines(matching_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_purchase_matching_lines_order_item ON purchases.purchase_matching_lines(order_item_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_purchase_matching_lines_tenant ON purchases.purchase_matching_lines(tenant_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_purchase_matching_lines_status ON purchases.purchase_matching_lines(status);
|
||||||
|
|
||||||
|
-- =====================
|
||||||
|
-- TABLA: matching_exceptions
|
||||||
|
-- Excepciones de matching
|
||||||
|
-- =====================
|
||||||
|
CREATE TABLE IF NOT EXISTS purchases.matching_exceptions (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
|
||||||
|
matching_id UUID REFERENCES purchases.purchase_order_matching(id) ON DELETE CASCADE,
|
||||||
|
matching_line_id UUID REFERENCES purchases.purchase_matching_lines(id) ON DELETE CASCADE,
|
||||||
|
|
||||||
|
-- Tipo de excepcion
|
||||||
|
exception_type VARCHAR(50) NOT NULL, -- over_receipt, short_receipt, over_invoice, short_invoice, price_variance
|
||||||
|
|
||||||
|
-- Valores
|
||||||
|
expected_value DECIMAL(15, 4),
|
||||||
|
actual_value DECIMAL(15, 4),
|
||||||
|
variance_value DECIMAL(15, 4),
|
||||||
|
variance_percent DECIMAL(5, 2),
|
||||||
|
|
||||||
|
-- Resolucion
|
||||||
|
status VARCHAR(20) DEFAULT 'pending', -- pending, approved, rejected
|
||||||
|
resolved_at TIMESTAMPTZ,
|
||||||
|
resolved_by UUID REFERENCES auth.users(id),
|
||||||
|
resolution_notes TEXT,
|
||||||
|
|
||||||
|
-- Metadata
|
||||||
|
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Indices para matching_exceptions
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_matching_exceptions_tenant ON purchases.matching_exceptions(tenant_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_matching_exceptions_matching ON purchases.matching_exceptions(matching_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_matching_exceptions_line ON purchases.matching_exceptions(matching_line_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_matching_exceptions_type ON purchases.matching_exceptions(exception_type);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_matching_exceptions_status ON purchases.matching_exceptions(status);
|
||||||
|
|
||||||
|
-- =====================
|
||||||
|
-- COMENTARIOS
|
||||||
|
-- =====================
|
||||||
|
COMMENT ON TABLE purchases.purchase_order_matching IS 'Registro de 3-way matching por orden de compra (PO-Receipt-Invoice)';
|
||||||
|
COMMENT ON COLUMN purchases.purchase_order_matching.status IS 'Estado: pending, partial_receipt, received, partial_invoice, matched, mismatch';
|
||||||
|
COMMENT ON COLUMN purchases.purchase_order_matching.receipt_variance IS 'Varianza = total_ordered - total_received (columna generada)';
|
||||||
|
COMMENT ON COLUMN purchases.purchase_order_matching.invoice_variance IS 'Varianza = total_received - total_invoiced (columna generada)';
|
||||||
|
|
||||||
|
COMMENT ON TABLE purchases.purchase_matching_lines IS 'Matching por linea de detalle de orden de compra';
|
||||||
|
COMMENT ON COLUMN purchases.purchase_matching_lines.qty_variance IS 'Varianza de cantidad = qty_ordered - qty_received (columna generada)';
|
||||||
|
COMMENT ON COLUMN purchases.purchase_matching_lines.invoice_qty_variance IS 'Varianza de factura = qty_received - qty_invoiced (columna generada)';
|
||||||
|
COMMENT ON COLUMN purchases.purchase_matching_lines.price_variance IS 'Varianza de precio = price_ordered - price_invoiced (columna generada)';
|
||||||
|
|
||||||
|
COMMENT ON TABLE purchases.matching_exceptions IS 'Excepciones detectadas durante el proceso de matching';
|
||||||
|
COMMENT ON COLUMN purchases.matching_exceptions.exception_type IS 'Tipo: over_receipt, short_receipt, over_invoice, short_invoice, price_variance';
|
||||||
|
COMMENT ON COLUMN purchases.matching_exceptions.status IS 'Estado: pending, approved, rejected';
|
||||||
154
ddl/50-financial-schema.sql
Normal file
154
ddl/50-financial-schema.sql
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
-- =============================================================
|
||||||
|
-- ARCHIVO: 50-financial-schema.sql
|
||||||
|
-- DESCRIPCION: Schema de contabilidad financiera y tipos enumerados
|
||||||
|
-- VERSION: 1.0.0
|
||||||
|
-- PROYECTO: ERP-Core V2
|
||||||
|
-- FECHA: 2026-01-20
|
||||||
|
-- DEPENDE DE: Ninguno (define el schema base)
|
||||||
|
-- =============================================================
|
||||||
|
|
||||||
|
-- =====================
|
||||||
|
-- SCHEMA: financial
|
||||||
|
-- Schema para modulo de contabilidad general
|
||||||
|
-- =====================
|
||||||
|
CREATE SCHEMA IF NOT EXISTS financial;
|
||||||
|
|
||||||
|
-- =====================
|
||||||
|
-- EXTENSIONES REQUERIDAS
|
||||||
|
-- =====================
|
||||||
|
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
||||||
|
|
||||||
|
-- =====================
|
||||||
|
-- TIPOS ENUMERADOS
|
||||||
|
-- =====================
|
||||||
|
|
||||||
|
-- Tipo de cuenta contable (activo, pasivo, capital, ingreso, gasto)
|
||||||
|
DO $$ BEGIN
|
||||||
|
CREATE TYPE financial.account_type_enum AS ENUM (
|
||||||
|
'asset', -- Activo
|
||||||
|
'liability', -- Pasivo
|
||||||
|
'equity', -- Capital/Patrimonio
|
||||||
|
'income', -- Ingreso
|
||||||
|
'expense' -- Gasto
|
||||||
|
);
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
-- Tipo de diario contable
|
||||||
|
DO $$ BEGIN
|
||||||
|
CREATE TYPE financial.journal_type_enum AS ENUM (
|
||||||
|
'sale', -- Ventas
|
||||||
|
'purchase', -- Compras
|
||||||
|
'cash', -- Caja
|
||||||
|
'bank', -- Banco
|
||||||
|
'general' -- General
|
||||||
|
);
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
-- Estado de asiento contable
|
||||||
|
DO $$ BEGIN
|
||||||
|
CREATE TYPE financial.entry_status_enum AS ENUM (
|
||||||
|
'draft', -- Borrador
|
||||||
|
'posted', -- Publicado/Contabilizado
|
||||||
|
'cancelled' -- Cancelado
|
||||||
|
);
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
-- Estado de periodo fiscal
|
||||||
|
DO $$ BEGIN
|
||||||
|
CREATE TYPE financial.period_status_enum AS ENUM (
|
||||||
|
'open', -- Abierto
|
||||||
|
'closed' -- Cerrado
|
||||||
|
);
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
-- Tipo de factura
|
||||||
|
DO $$ BEGIN
|
||||||
|
CREATE TYPE financial.invoice_type_enum AS ENUM (
|
||||||
|
'customer', -- Factura de cliente (venta)
|
||||||
|
'supplier' -- Factura de proveedor (compra)
|
||||||
|
);
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
-- Estado de factura
|
||||||
|
DO $$ BEGIN
|
||||||
|
CREATE TYPE financial.invoice_status_enum AS ENUM (
|
||||||
|
'draft', -- Borrador
|
||||||
|
'open', -- Abierta/Validada
|
||||||
|
'paid', -- Pagada
|
||||||
|
'cancelled' -- Cancelada
|
||||||
|
);
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
-- Tipo de pago
|
||||||
|
DO $$ BEGIN
|
||||||
|
CREATE TYPE financial.payment_type_enum AS ENUM (
|
||||||
|
'inbound', -- Entrada (cobro)
|
||||||
|
'outbound' -- Salida (pago)
|
||||||
|
);
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
-- Metodo de pago
|
||||||
|
DO $$ BEGIN
|
||||||
|
CREATE TYPE financial.payment_method_enum AS ENUM (
|
||||||
|
'cash', -- Efectivo
|
||||||
|
'bank_transfer', -- Transferencia bancaria
|
||||||
|
'check', -- Cheque
|
||||||
|
'card', -- Tarjeta
|
||||||
|
'other' -- Otro
|
||||||
|
);
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
-- Estado de pago
|
||||||
|
DO $$ BEGIN
|
||||||
|
CREATE TYPE financial.payment_status_enum AS ENUM (
|
||||||
|
'draft', -- Borrador
|
||||||
|
'posted', -- Contabilizado
|
||||||
|
'reconciled', -- Conciliado
|
||||||
|
'cancelled' -- Cancelado
|
||||||
|
);
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
-- Tipo de impuesto
|
||||||
|
DO $$ BEGIN
|
||||||
|
CREATE TYPE financial.tax_type_enum AS ENUM (
|
||||||
|
'sales', -- Solo ventas
|
||||||
|
'purchase', -- Solo compras
|
||||||
|
'all' -- Ambos (ventas y compras)
|
||||||
|
);
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
-- =====================
|
||||||
|
-- COMENTARIOS DEL SCHEMA
|
||||||
|
-- =====================
|
||||||
|
COMMENT ON SCHEMA financial IS 'Schema para el modulo de contabilidad general: plan de cuentas, diarios, asientos, facturas financieras y pagos';
|
||||||
|
|
||||||
|
COMMENT ON TYPE financial.account_type_enum IS 'Clasificacion de cuentas contables: activo, pasivo, capital, ingreso, gasto';
|
||||||
|
COMMENT ON TYPE financial.journal_type_enum IS 'Tipo de diario contable: ventas, compras, caja, banco, general';
|
||||||
|
COMMENT ON TYPE financial.entry_status_enum IS 'Estado del asiento contable: borrador, publicado, cancelado';
|
||||||
|
COMMENT ON TYPE financial.period_status_enum IS 'Estado del periodo fiscal: abierto, cerrado';
|
||||||
|
COMMENT ON TYPE financial.invoice_type_enum IS 'Tipo de factura financiera: cliente (venta), proveedor (compra)';
|
||||||
|
COMMENT ON TYPE financial.invoice_status_enum IS 'Estado de factura: borrador, abierta, pagada, cancelada';
|
||||||
|
COMMENT ON TYPE financial.payment_type_enum IS 'Direccion del pago: inbound (cobro), outbound (pago)';
|
||||||
|
COMMENT ON TYPE financial.payment_method_enum IS 'Metodo de pago: efectivo, transferencia, cheque, tarjeta, otro';
|
||||||
|
COMMENT ON TYPE financial.payment_status_enum IS 'Estado del pago: borrador, contabilizado, conciliado, cancelado';
|
||||||
|
COMMENT ON TYPE financial.tax_type_enum IS 'Aplicacion del impuesto: ventas, compras, ambos';
|
||||||
143
ddl/51-financial-accounts.sql
Normal file
143
ddl/51-financial-accounts.sql
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
-- =============================================================
|
||||||
|
-- ARCHIVO: 51-financial-accounts.sql
|
||||||
|
-- DESCRIPCION: Plan de cuentas, tipos de cuenta y mapeos
|
||||||
|
-- VERSION: 1.0.0
|
||||||
|
-- PROYECTO: ERP-Core V2
|
||||||
|
-- FECHA: 2026-01-20
|
||||||
|
-- DEPENDE DE: 50-financial-schema.sql
|
||||||
|
-- =============================================================
|
||||||
|
|
||||||
|
-- =====================
|
||||||
|
-- TABLA: account_types
|
||||||
|
-- Catalogo de tipos de cuenta contable
|
||||||
|
-- =====================
|
||||||
|
CREATE TABLE IF NOT EXISTS financial.account_types (
|
||||||
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||||
|
|
||||||
|
-- Identificacion
|
||||||
|
code VARCHAR(20) NOT NULL UNIQUE,
|
||||||
|
name VARCHAR(100) NOT NULL,
|
||||||
|
|
||||||
|
-- Clasificacion
|
||||||
|
account_type financial.account_type_enum NOT NULL,
|
||||||
|
|
||||||
|
-- Descripcion
|
||||||
|
description TEXT,
|
||||||
|
|
||||||
|
-- Metadata
|
||||||
|
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Indices para account_types
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_account_types_code ON financial.account_types(code);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_account_types_type ON financial.account_types(account_type);
|
||||||
|
|
||||||
|
-- =====================
|
||||||
|
-- TABLA: accounts
|
||||||
|
-- Plan de cuentas contables
|
||||||
|
-- =====================
|
||||||
|
CREATE TABLE IF NOT EXISTS financial.accounts (
|
||||||
|
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, -- FK a company si existe multi-company
|
||||||
|
|
||||||
|
-- Identificacion
|
||||||
|
code VARCHAR(50) NOT NULL,
|
||||||
|
name VARCHAR(255) NOT NULL,
|
||||||
|
|
||||||
|
-- Clasificacion
|
||||||
|
account_type_id UUID NOT NULL REFERENCES financial.account_types(id) ON DELETE RESTRICT,
|
||||||
|
|
||||||
|
-- Jerarquia (cuenta padre para estructura arborea)
|
||||||
|
parent_id UUID REFERENCES financial.accounts(id) ON DELETE SET NULL,
|
||||||
|
|
||||||
|
-- Moneda preferida
|
||||||
|
currency_id UUID, -- FK a catalogo de monedas si existe
|
||||||
|
|
||||||
|
-- Configuracion
|
||||||
|
is_reconcilable BOOLEAN DEFAULT FALSE, -- Permite conciliacion bancaria
|
||||||
|
is_deprecated BOOLEAN DEFAULT FALSE, -- Cuenta obsoleta (no usar en nuevos movimientos)
|
||||||
|
|
||||||
|
-- Notas
|
||||||
|
notes TEXT,
|
||||||
|
|
||||||
|
-- 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),
|
||||||
|
deleted_at TIMESTAMPTZ,
|
||||||
|
|
||||||
|
-- Constraint de unicidad por tenant
|
||||||
|
UNIQUE(tenant_id, code)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Indices para accounts
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_accounts_tenant ON financial.accounts(tenant_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_accounts_company ON financial.accounts(company_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_accounts_code ON financial.accounts(code);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_accounts_type ON financial.accounts(account_type_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_accounts_parent ON financial.accounts(parent_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_accounts_active ON financial.accounts(tenant_id) WHERE deleted_at IS NULL AND is_deprecated = FALSE;
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_accounts_reconcilable ON financial.accounts(tenant_id, is_reconcilable) WHERE is_reconcilable = TRUE;
|
||||||
|
|
||||||
|
-- =====================
|
||||||
|
-- TABLA: account_mappings
|
||||||
|
-- Mapeos de cuentas para automatizaciones
|
||||||
|
-- Ej: cuenta_ingreso_default, cuenta_iva_16, cuenta_banco_principal
|
||||||
|
-- =====================
|
||||||
|
CREATE TABLE IF NOT EXISTS financial.account_mappings (
|
||||||
|
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,
|
||||||
|
|
||||||
|
-- Identificacion del mapeo
|
||||||
|
mapping_type VARCHAR(50) NOT NULL, -- Ej: default_income, default_expense, vat_16, bank_main
|
||||||
|
|
||||||
|
-- Cuenta mapeada
|
||||||
|
account_id UUID NOT NULL REFERENCES financial.accounts(id) ON DELETE CASCADE,
|
||||||
|
|
||||||
|
-- Descripcion
|
||||||
|
description TEXT,
|
||||||
|
|
||||||
|
-- Estado
|
||||||
|
is_active BOOLEAN DEFAULT TRUE,
|
||||||
|
|
||||||
|
-- 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),
|
||||||
|
|
||||||
|
-- Un solo mapeo activo por tipo por tenant/company
|
||||||
|
UNIQUE(tenant_id, company_id, mapping_type)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Indices para account_mappings
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_account_mappings_tenant ON financial.account_mappings(tenant_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_account_mappings_company ON financial.account_mappings(company_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_account_mappings_type ON financial.account_mappings(mapping_type);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_account_mappings_account ON financial.account_mappings(account_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_account_mappings_active ON financial.account_mappings(tenant_id, is_active) WHERE is_active = TRUE;
|
||||||
|
|
||||||
|
-- =====================
|
||||||
|
-- COMENTARIOS
|
||||||
|
-- =====================
|
||||||
|
COMMENT ON TABLE financial.account_types IS 'Catalogo de tipos de cuenta contable (activo, pasivo, capital, ingreso, gasto)';
|
||||||
|
COMMENT ON COLUMN financial.account_types.code IS 'Codigo unico del tipo (ej: ASSET_CURRENT, LIABILITY_LONG)';
|
||||||
|
COMMENT ON COLUMN financial.account_types.account_type IS 'Clasificacion principal: asset, liability, equity, income, expense';
|
||||||
|
|
||||||
|
COMMENT ON TABLE financial.accounts IS 'Plan de cuentas contables con estructura jerarquica';
|
||||||
|
COMMENT ON COLUMN financial.accounts.code IS 'Codigo de cuenta (ej: 1100, 1100.01)';
|
||||||
|
COMMENT ON COLUMN financial.accounts.parent_id IS 'Referencia a cuenta padre para estructura de arbol';
|
||||||
|
COMMENT ON COLUMN financial.accounts.is_reconcilable IS 'TRUE si la cuenta permite conciliacion bancaria';
|
||||||
|
COMMENT ON COLUMN financial.accounts.is_deprecated IS 'TRUE si la cuenta esta obsoleta (no usar en nuevos movimientos)';
|
||||||
|
|
||||||
|
COMMENT ON TABLE financial.account_mappings IS 'Mapeos de cuentas para automatizaciones contables';
|
||||||
|
COMMENT ON COLUMN financial.account_mappings.mapping_type IS 'Tipo de mapeo (ej: default_income, vat_16, bank_main)';
|
||||||
|
COMMENT ON COLUMN financial.account_mappings.account_id IS 'Cuenta contable asociada al mapeo';
|
||||||
162
ddl/52-financial-journals.sql
Normal file
162
ddl/52-financial-journals.sql
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
-- =============================================================
|
||||||
|
-- ARCHIVO: 52-financial-journals.sql
|
||||||
|
-- DESCRIPCION: Diarios contables y periodos fiscales
|
||||||
|
-- VERSION: 1.0.0
|
||||||
|
-- PROYECTO: ERP-Core V2
|
||||||
|
-- FECHA: 2026-01-20
|
||||||
|
-- DEPENDE DE: 50-financial-schema.sql, 51-financial-accounts.sql
|
||||||
|
-- =============================================================
|
||||||
|
|
||||||
|
-- =====================
|
||||||
|
-- TABLA: fiscal_years
|
||||||
|
-- Anos fiscales
|
||||||
|
-- =====================
|
||||||
|
CREATE TABLE IF NOT EXISTS financial.fiscal_years (
|
||||||
|
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,
|
||||||
|
|
||||||
|
-- Identificacion
|
||||||
|
name VARCHAR(100) NOT NULL, -- Ej: "Ejercicio 2026"
|
||||||
|
code VARCHAR(20) NOT NULL, -- Ej: "FY2026"
|
||||||
|
|
||||||
|
-- Periodo
|
||||||
|
date_from DATE NOT NULL,
|
||||||
|
date_to DATE NOT NULL,
|
||||||
|
|
||||||
|
-- Estado
|
||||||
|
status financial.period_status_enum DEFAULT 'open',
|
||||||
|
|
||||||
|
-- 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),
|
||||||
|
|
||||||
|
-- Validaciones
|
||||||
|
CONSTRAINT chk_fiscal_years_dates CHECK (date_to > date_from),
|
||||||
|
UNIQUE(tenant_id, code)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Indices para fiscal_years
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_fiscal_years_tenant ON financial.fiscal_years(tenant_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_fiscal_years_code ON financial.fiscal_years(code);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_fiscal_years_status ON financial.fiscal_years(status);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_fiscal_years_dates ON financial.fiscal_years(date_from, date_to);
|
||||||
|
|
||||||
|
-- =====================
|
||||||
|
-- TABLA: fiscal_periods
|
||||||
|
-- Periodos fiscales (meses o trimestres dentro de un ano fiscal)
|
||||||
|
-- =====================
|
||||||
|
CREATE TABLE IF NOT EXISTS financial.fiscal_periods (
|
||||||
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||||
|
|
||||||
|
-- Multi-tenant
|
||||||
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
|
||||||
|
|
||||||
|
-- Relacion con ano fiscal
|
||||||
|
fiscal_year_id UUID NOT NULL REFERENCES financial.fiscal_years(id) ON DELETE CASCADE,
|
||||||
|
|
||||||
|
-- Identificacion
|
||||||
|
code VARCHAR(20) NOT NULL, -- Ej: "2026-01", "Q1-2026"
|
||||||
|
name VARCHAR(100) NOT NULL, -- Ej: "Enero 2026", "Primer Trimestre 2026"
|
||||||
|
|
||||||
|
-- Periodo
|
||||||
|
date_from DATE NOT NULL,
|
||||||
|
date_to DATE NOT NULL,
|
||||||
|
|
||||||
|
-- Estado
|
||||||
|
status financial.period_status_enum DEFAULT 'open',
|
||||||
|
|
||||||
|
-- Cierre
|
||||||
|
closed_at TIMESTAMPTZ,
|
||||||
|
closed_by UUID REFERENCES auth.users(id),
|
||||||
|
|
||||||
|
-- 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),
|
||||||
|
|
||||||
|
-- Validaciones
|
||||||
|
CONSTRAINT chk_fiscal_periods_dates CHECK (date_to >= date_from),
|
||||||
|
UNIQUE(tenant_id, fiscal_year_id, code)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Indices para fiscal_periods
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_fiscal_periods_tenant ON financial.fiscal_periods(tenant_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_fiscal_periods_year ON financial.fiscal_periods(fiscal_year_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_fiscal_periods_code ON financial.fiscal_periods(code);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_fiscal_periods_status ON financial.fiscal_periods(status);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_fiscal_periods_dates ON financial.fiscal_periods(date_from, date_to);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_fiscal_periods_open ON financial.fiscal_periods(tenant_id, status) WHERE status = 'open';
|
||||||
|
|
||||||
|
-- =====================
|
||||||
|
-- TABLA: journals
|
||||||
|
-- Diarios contables (ventas, compras, caja, banco, general)
|
||||||
|
-- =====================
|
||||||
|
CREATE TABLE IF NOT EXISTS financial.journals (
|
||||||
|
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,
|
||||||
|
|
||||||
|
-- Identificacion
|
||||||
|
name VARCHAR(255) NOT NULL,
|
||||||
|
code VARCHAR(20) NOT NULL,
|
||||||
|
|
||||||
|
-- Tipo de diario
|
||||||
|
journal_type financial.journal_type_enum NOT NULL,
|
||||||
|
|
||||||
|
-- Cuenta por defecto (para asientos automaticos)
|
||||||
|
default_account_id UUID REFERENCES financial.accounts(id) ON DELETE SET NULL,
|
||||||
|
|
||||||
|
-- Secuencia para numeracion
|
||||||
|
sequence_id UUID, -- FK a sistema de secuencias si existe
|
||||||
|
|
||||||
|
-- Moneda preferida
|
||||||
|
currency_id UUID,
|
||||||
|
|
||||||
|
-- Estado
|
||||||
|
active BOOLEAN DEFAULT TRUE,
|
||||||
|
|
||||||
|
-- Audit columns con soft delete
|
||||||
|
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),
|
||||||
|
deleted_at TIMESTAMPTZ,
|
||||||
|
|
||||||
|
-- Unicidad por tenant
|
||||||
|
UNIQUE(tenant_id, code)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Indices para journals
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_journals_tenant ON financial.journals(tenant_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_journals_company ON financial.journals(company_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_journals_code ON financial.journals(code);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_journals_type ON financial.journals(journal_type);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_journals_default_account ON financial.journals(default_account_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_journals_active ON financial.journals(tenant_id) WHERE active = TRUE AND deleted_at IS NULL;
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_journals_by_type_active ON financial.journals(tenant_id, journal_type) WHERE active = TRUE AND deleted_at IS NULL;
|
||||||
|
|
||||||
|
-- =====================
|
||||||
|
-- COMENTARIOS
|
||||||
|
-- =====================
|
||||||
|
COMMENT ON TABLE financial.fiscal_years IS 'Anos fiscales para organizacion contable';
|
||||||
|
COMMENT ON COLUMN financial.fiscal_years.code IS 'Codigo unico del ano fiscal (ej: FY2026)';
|
||||||
|
COMMENT ON COLUMN financial.fiscal_years.status IS 'Estado: open (permite movimientos), closed (no permite movimientos)';
|
||||||
|
|
||||||
|
COMMENT ON TABLE financial.fiscal_periods IS 'Periodos fiscales (meses o trimestres) dentro de un ano fiscal';
|
||||||
|
COMMENT ON COLUMN financial.fiscal_periods.code IS 'Codigo del periodo (ej: 2026-01, Q1-2026)';
|
||||||
|
COMMENT ON COLUMN financial.fiscal_periods.closed_at IS 'Fecha y hora de cierre del periodo';
|
||||||
|
COMMENT ON COLUMN financial.fiscal_periods.closed_by IS 'Usuario que cerro el periodo';
|
||||||
|
|
||||||
|
COMMENT ON TABLE financial.journals IS 'Diarios contables para agrupar asientos por tipo de operacion';
|
||||||
|
COMMENT ON COLUMN financial.journals.code IS 'Codigo unico del diario (ej: VTAS, COMP, CAJA, BCO)';
|
||||||
|
COMMENT ON COLUMN financial.journals.journal_type IS 'Tipo: sale (ventas), purchase (compras), cash (caja), bank (banco), general';
|
||||||
|
COMMENT ON COLUMN financial.journals.default_account_id IS 'Cuenta por defecto para asientos automaticos';
|
||||||
|
COMMENT ON COLUMN financial.journals.sequence_id IS 'Referencia a secuencia para numeracion automatica';
|
||||||
175
ddl/53-financial-entries.sql
Normal file
175
ddl/53-financial-entries.sql
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
-- =============================================================
|
||||||
|
-- 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)';
|
||||||
167
ddl/54-financial-invoices.sql
Normal file
167
ddl/54-financial-invoices.sql
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
-- =============================================================
|
||||||
|
-- ARCHIVO: 54-financial-invoices.sql
|
||||||
|
-- DESCRIPCION: Facturas contables (cliente/proveedor) y lineas de factura
|
||||||
|
-- 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, 53-financial-entries.sql
|
||||||
|
-- NOTA: Este modulo es para facturas desde perspectiva CONTABLE.
|
||||||
|
-- Para facturacion operativa ver 24-invoices.sql (schema billing)
|
||||||
|
-- =============================================================
|
||||||
|
|
||||||
|
-- =====================
|
||||||
|
-- TABLA: invoices
|
||||||
|
-- Facturas contables (cliente y proveedor)
|
||||||
|
-- =====================
|
||||||
|
CREATE TABLE IF NOT EXISTS financial.invoices (
|
||||||
|
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,
|
||||||
|
|
||||||
|
-- Partner (cliente o proveedor)
|
||||||
|
partner_id UUID NOT NULL, -- FK a partners.partners
|
||||||
|
|
||||||
|
-- Tipo de factura
|
||||||
|
invoice_type financial.invoice_type_enum NOT NULL,
|
||||||
|
|
||||||
|
-- Identificacion
|
||||||
|
number VARCHAR(100) NOT NULL, -- Numero de factura
|
||||||
|
ref VARCHAR(255), -- Referencia externa
|
||||||
|
|
||||||
|
-- Fechas
|
||||||
|
invoice_date DATE NOT NULL,
|
||||||
|
due_date DATE,
|
||||||
|
|
||||||
|
-- Moneda
|
||||||
|
currency_id UUID, -- FK a catalogo de monedas
|
||||||
|
|
||||||
|
-- Montos
|
||||||
|
amount_untaxed DECIMAL(15, 2) DEFAULT 0, -- Subtotal sin impuestos
|
||||||
|
amount_tax DECIMAL(15, 2) DEFAULT 0, -- Total impuestos
|
||||||
|
amount_total DECIMAL(15, 2) DEFAULT 0, -- Total de la factura
|
||||||
|
amount_paid DECIMAL(15, 2) DEFAULT 0, -- Monto pagado
|
||||||
|
amount_residual DECIMAL(15, 2) GENERATED ALWAYS AS (amount_total - COALESCE(amount_paid, 0)) STORED, -- Saldo pendiente
|
||||||
|
|
||||||
|
-- Estado
|
||||||
|
status financial.invoice_status_enum DEFAULT 'draft',
|
||||||
|
|
||||||
|
-- Terminos de pago
|
||||||
|
payment_term_id UUID, -- FK a terminos de pago si existe
|
||||||
|
|
||||||
|
-- Relacion con contabilidad
|
||||||
|
journal_id UUID REFERENCES financial.journals(id) ON DELETE RESTRICT,
|
||||||
|
journal_entry_id UUID REFERENCES financial.journal_entries(id) ON DELETE SET NULL,
|
||||||
|
|
||||||
|
-- Notas
|
||||||
|
notes TEXT,
|
||||||
|
|
||||||
|
-- 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),
|
||||||
|
|
||||||
|
-- Validacion
|
||||||
|
validated_at TIMESTAMPTZ,
|
||||||
|
validated_by UUID REFERENCES auth.users(id),
|
||||||
|
|
||||||
|
-- Cancelacion
|
||||||
|
cancelled_at TIMESTAMPTZ,
|
||||||
|
cancelled_by UUID REFERENCES auth.users(id),
|
||||||
|
|
||||||
|
-- Unicidad
|
||||||
|
UNIQUE(tenant_id, number, invoice_type)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Indices para invoices
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_invoices_tenant ON financial.invoices(tenant_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_invoices_company ON financial.invoices(company_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_invoices_partner ON financial.invoices(partner_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_invoices_type ON financial.invoices(invoice_type);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_invoices_number ON financial.invoices(number);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_invoices_date ON financial.invoices(invoice_date);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_invoices_due_date ON financial.invoices(due_date);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_invoices_status ON financial.invoices(status);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_invoices_journal ON financial.invoices(journal_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_invoices_entry ON financial.invoices(journal_entry_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_invoices_open ON financial.invoices(tenant_id, status) WHERE status = 'open';
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_invoices_unpaid ON financial.invoices(tenant_id, due_date) WHERE status = 'open' AND amount_paid < amount_total;
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_invoices_customer ON financial.invoices(tenant_id, partner_id, invoice_type) WHERE invoice_type = 'customer';
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_invoices_supplier ON financial.invoices(tenant_id, partner_id, invoice_type) WHERE invoice_type = 'supplier';
|
||||||
|
|
||||||
|
-- =====================
|
||||||
|
-- TABLA: invoice_lines
|
||||||
|
-- Lineas de factura contable
|
||||||
|
-- =====================
|
||||||
|
CREATE TABLE IF NOT EXISTS financial.invoice_lines (
|
||||||
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||||
|
|
||||||
|
-- Relacion con factura (cascade delete)
|
||||||
|
invoice_id UUID NOT NULL REFERENCES financial.invoices(id) ON DELETE CASCADE,
|
||||||
|
|
||||||
|
-- Multi-tenant (denormalizado para queries rapidas)
|
||||||
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
|
||||||
|
|
||||||
|
-- Producto (opcional)
|
||||||
|
product_id UUID, -- FK a products.products
|
||||||
|
|
||||||
|
-- Descripcion
|
||||||
|
description TEXT,
|
||||||
|
|
||||||
|
-- Cantidad y unidad
|
||||||
|
quantity DECIMAL(15, 4) NOT NULL DEFAULT 1,
|
||||||
|
uom_id UUID, -- FK a unidades de medida
|
||||||
|
|
||||||
|
-- Precio
|
||||||
|
price_unit DECIMAL(15, 2) NOT NULL DEFAULT 0,
|
||||||
|
|
||||||
|
-- Impuestos aplicables (array de UUIDs de taxes)
|
||||||
|
tax_ids UUID[] DEFAULT '{}',
|
||||||
|
|
||||||
|
-- Montos calculados
|
||||||
|
amount_untaxed DECIMAL(15, 2) DEFAULT 0, -- subtotal linea
|
||||||
|
amount_tax DECIMAL(15, 2) DEFAULT 0, -- impuestos linea
|
||||||
|
amount_total DECIMAL(15, 2) DEFAULT 0, -- total linea
|
||||||
|
|
||||||
|
-- Cuenta contable
|
||||||
|
account_id UUID REFERENCES financial.accounts(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)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Indices para invoice_lines
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_invoice_lines_invoice ON financial.invoice_lines(invoice_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_invoice_lines_tenant ON financial.invoice_lines(tenant_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_invoice_lines_product ON financial.invoice_lines(product_id) WHERE product_id IS NOT NULL;
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_invoice_lines_account ON financial.invoice_lines(account_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_invoice_lines_tax_ids ON financial.invoice_lines USING GIN(tax_ids);
|
||||||
|
|
||||||
|
-- =====================
|
||||||
|
-- COMENTARIOS
|
||||||
|
-- =====================
|
||||||
|
COMMENT ON TABLE financial.invoices IS 'Facturas contables (perspectiva financiera)';
|
||||||
|
COMMENT ON COLUMN financial.invoices.invoice_type IS 'Tipo: customer (venta a cliente), supplier (compra a proveedor)';
|
||||||
|
COMMENT ON COLUMN financial.invoices.number IS 'Numero unico de factura';
|
||||||
|
COMMENT ON COLUMN financial.invoices.ref IS 'Referencia externa (numero de factura del proveedor, etc.)';
|
||||||
|
COMMENT ON COLUMN financial.invoices.amount_untaxed IS 'Subtotal sin impuestos';
|
||||||
|
COMMENT ON COLUMN financial.invoices.amount_tax IS 'Total de impuestos';
|
||||||
|
COMMENT ON COLUMN financial.invoices.amount_total IS 'Total de la factura (subtotal + impuestos)';
|
||||||
|
COMMENT ON COLUMN financial.invoices.amount_paid IS 'Monto pagado hasta el momento';
|
||||||
|
COMMENT ON COLUMN financial.invoices.amount_residual IS 'Saldo pendiente de pago (calculado)';
|
||||||
|
COMMENT ON COLUMN financial.invoices.status IS 'Estado: draft, open (validada), paid, cancelled';
|
||||||
|
COMMENT ON COLUMN financial.invoices.journal_entry_id IS 'Asiento contable generado al validar la factura';
|
||||||
|
COMMENT ON COLUMN financial.invoices.validated_at IS 'Fecha y hora de validacion/apertura';
|
||||||
|
COMMENT ON COLUMN financial.invoices.cancelled_at IS 'Fecha y hora de cancelacion';
|
||||||
|
|
||||||
|
COMMENT ON TABLE financial.invoice_lines IS 'Lineas de detalle de facturas contables';
|
||||||
|
COMMENT ON COLUMN financial.invoice_lines.product_id IS 'Producto asociado (opcional)';
|
||||||
|
COMMENT ON COLUMN financial.invoice_lines.quantity IS 'Cantidad facturada';
|
||||||
|
COMMENT ON COLUMN financial.invoice_lines.price_unit IS 'Precio unitario';
|
||||||
|
COMMENT ON COLUMN financial.invoice_lines.tax_ids IS 'Array de IDs de impuestos aplicables';
|
||||||
|
COMMENT ON COLUMN financial.invoice_lines.account_id IS 'Cuenta contable para el asiento';
|
||||||
174
ddl/55-financial-payments.sql
Normal file
174
ddl/55-financial-payments.sql
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
-- =============================================================
|
||||||
|
-- ARCHIVO: 55-financial-payments.sql
|
||||||
|
-- DESCRIPCION: Pagos contables (cobros y pagos)
|
||||||
|
-- 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, 53-financial-entries.sql
|
||||||
|
-- NOTA: Este modulo es para pagos desde perspectiva CONTABLE.
|
||||||
|
-- Para pagos operativos ver 24-invoices.sql (schema billing)
|
||||||
|
-- =============================================================
|
||||||
|
|
||||||
|
-- =====================
|
||||||
|
-- TABLA: payments
|
||||||
|
-- Pagos contables (cobros entrantes y pagos salientes)
|
||||||
|
-- =====================
|
||||||
|
CREATE TABLE IF NOT EXISTS financial.payments (
|
||||||
|
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,
|
||||||
|
|
||||||
|
-- Partner (cliente o proveedor)
|
||||||
|
partner_id UUID NOT NULL, -- FK a partners.partners
|
||||||
|
|
||||||
|
-- Tipo de pago
|
||||||
|
payment_type financial.payment_type_enum NOT NULL, -- inbound (cobro), outbound (pago)
|
||||||
|
|
||||||
|
-- Metodo de pago
|
||||||
|
payment_method financial.payment_method_enum NOT NULL,
|
||||||
|
|
||||||
|
-- Monto
|
||||||
|
amount DECIMAL(15, 2) NOT NULL CHECK (amount > 0),
|
||||||
|
|
||||||
|
-- Moneda
|
||||||
|
currency_id UUID, -- FK a catalogo de monedas
|
||||||
|
|
||||||
|
-- Fecha de pago
|
||||||
|
payment_date DATE NOT NULL,
|
||||||
|
|
||||||
|
-- Referencia
|
||||||
|
ref VARCHAR(255), -- Numero de cheque, referencia bancaria, etc.
|
||||||
|
|
||||||
|
-- Estado
|
||||||
|
status financial.payment_status_enum DEFAULT 'draft',
|
||||||
|
|
||||||
|
-- Relacion con contabilidad
|
||||||
|
journal_id UUID REFERENCES financial.journals(id) ON DELETE RESTRICT,
|
||||||
|
journal_entry_id UUID REFERENCES financial.journal_entries(id) ON DELETE SET NULL,
|
||||||
|
|
||||||
|
-- Notas
|
||||||
|
notes TEXT,
|
||||||
|
|
||||||
|
-- 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/Contabilizacion
|
||||||
|
posted_at TIMESTAMPTZ,
|
||||||
|
posted_by UUID REFERENCES auth.users(id)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Indices para payments
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_payments_tenant ON financial.payments(tenant_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_payments_company ON financial.payments(company_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_payments_partner ON financial.payments(partner_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_payments_type ON financial.payments(payment_type);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_payments_method ON financial.payments(payment_method);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_payments_date ON financial.payments(payment_date);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_payments_status ON financial.payments(status);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_payments_journal ON financial.payments(journal_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_payments_entry ON financial.payments(journal_entry_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_payments_ref ON financial.payments(ref) WHERE ref IS NOT NULL;
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_payments_posted ON financial.payments(tenant_id, status) WHERE status = 'posted';
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_payments_inbound ON financial.payments(tenant_id, partner_id, payment_type) WHERE payment_type = 'inbound';
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_payments_outbound ON financial.payments(tenant_id, partner_id, payment_type) WHERE payment_type = 'outbound';
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_payments_date_range ON financial.payments(tenant_id, payment_date, status);
|
||||||
|
|
||||||
|
-- =====================
|
||||||
|
-- TABLA: payment_invoice_allocations
|
||||||
|
-- Aplicacion de pagos a facturas
|
||||||
|
-- =====================
|
||||||
|
CREATE TABLE IF NOT EXISTS financial.payment_invoice_allocations (
|
||||||
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||||
|
|
||||||
|
-- Pago
|
||||||
|
payment_id UUID NOT NULL REFERENCES financial.payments(id) ON DELETE CASCADE,
|
||||||
|
|
||||||
|
-- Factura
|
||||||
|
invoice_id UUID NOT NULL REFERENCES financial.invoices(id) ON DELETE CASCADE,
|
||||||
|
|
||||||
|
-- Monto aplicado a esta factura
|
||||||
|
amount DECIMAL(15, 2) NOT NULL CHECK (amount > 0),
|
||||||
|
|
||||||
|
-- Fecha de aplicacion
|
||||||
|
allocation_date DATE NOT NULL DEFAULT CURRENT_DATE,
|
||||||
|
|
||||||
|
-- Audit columns
|
||||||
|
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
created_by UUID REFERENCES auth.users(id),
|
||||||
|
|
||||||
|
-- Un pago solo puede aplicarse una vez a cada factura
|
||||||
|
UNIQUE(payment_id, invoice_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Indices para payment_invoice_allocations
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_payment_allocations_payment ON financial.payment_invoice_allocations(payment_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_payment_allocations_invoice ON financial.payment_invoice_allocations(invoice_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_payment_allocations_date ON financial.payment_invoice_allocations(allocation_date);
|
||||||
|
|
||||||
|
-- =====================
|
||||||
|
-- FUNCION: Actualizar amount_paid en factura
|
||||||
|
-- =====================
|
||||||
|
CREATE OR REPLACE FUNCTION financial.update_invoice_amount_paid()
|
||||||
|
RETURNS TRIGGER AS $$
|
||||||
|
DECLARE
|
||||||
|
v_invoice_id UUID;
|
||||||
|
v_total_paid DECIMAL(15, 2);
|
||||||
|
BEGIN
|
||||||
|
-- Determinar la factura afectada
|
||||||
|
IF TG_OP = 'DELETE' THEN
|
||||||
|
v_invoice_id := OLD.invoice_id;
|
||||||
|
ELSE
|
||||||
|
v_invoice_id := NEW.invoice_id;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- Calcular total pagado para la factura
|
||||||
|
SELECT COALESCE(SUM(amount), 0)
|
||||||
|
INTO v_total_paid
|
||||||
|
FROM financial.payment_invoice_allocations
|
||||||
|
WHERE invoice_id = v_invoice_id;
|
||||||
|
|
||||||
|
-- Actualizar factura
|
||||||
|
UPDATE financial.invoices
|
||||||
|
SET
|
||||||
|
amount_paid = v_total_paid,
|
||||||
|
status = CASE
|
||||||
|
WHEN v_total_paid >= amount_total THEN 'paid'::financial.invoice_status_enum
|
||||||
|
WHEN v_total_paid > 0 THEN 'open'::financial.invoice_status_enum
|
||||||
|
ELSE status
|
||||||
|
END,
|
||||||
|
updated_at = CURRENT_TIMESTAMP
|
||||||
|
WHERE id = v_invoice_id;
|
||||||
|
|
||||||
|
RETURN COALESCE(NEW, OLD);
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
-- Trigger para actualizar amount_paid automaticamente
|
||||||
|
DROP TRIGGER IF EXISTS trg_update_invoice_amount_paid ON financial.payment_invoice_allocations;
|
||||||
|
CREATE TRIGGER trg_update_invoice_amount_paid
|
||||||
|
AFTER INSERT OR UPDATE OR DELETE ON financial.payment_invoice_allocations
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE FUNCTION financial.update_invoice_amount_paid();
|
||||||
|
|
||||||
|
-- =====================
|
||||||
|
-- COMENTARIOS
|
||||||
|
-- =====================
|
||||||
|
COMMENT ON TABLE financial.payments IS 'Pagos contables (cobros y pagos a proveedores)';
|
||||||
|
COMMENT ON COLUMN financial.payments.payment_type IS 'Tipo: inbound (cobro de cliente), outbound (pago a proveedor)';
|
||||||
|
COMMENT ON COLUMN financial.payments.payment_method IS 'Metodo: cash, bank_transfer, check, card, other';
|
||||||
|
COMMENT ON COLUMN financial.payments.amount IS 'Monto del pago (siempre positivo)';
|
||||||
|
COMMENT ON COLUMN financial.payments.ref IS 'Referencia: numero de cheque, referencia bancaria, etc.';
|
||||||
|
COMMENT ON COLUMN financial.payments.status IS 'Estado: draft, posted (contabilizado), reconciled, cancelled';
|
||||||
|
COMMENT ON COLUMN financial.payments.journal_entry_id IS 'Asiento contable generado al publicar el pago';
|
||||||
|
COMMENT ON COLUMN financial.payments.posted_at IS 'Fecha y hora de publicacion/contabilizacion';
|
||||||
|
|
||||||
|
COMMENT ON TABLE financial.payment_invoice_allocations IS 'Aplicacion de pagos a facturas especificas';
|
||||||
|
COMMENT ON COLUMN financial.payment_invoice_allocations.amount IS 'Monto del pago aplicado a esta factura';
|
||||||
|
COMMENT ON COLUMN financial.payment_invoice_allocations.allocation_date IS 'Fecha de aplicacion del pago';
|
||||||
|
|
||||||
|
COMMENT ON FUNCTION financial.update_invoice_amount_paid() IS 'Actualiza automaticamente amount_paid en facturas cuando se aplican pagos';
|
||||||
155
ddl/56-financial-taxes.sql
Normal file
155
ddl/56-financial-taxes.sql
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
-- =============================================================
|
||||||
|
-- ARCHIVO: 56-financial-taxes.sql
|
||||||
|
-- DESCRIPCION: Impuestos contables (IVA, retenciones, etc.)
|
||||||
|
-- VERSION: 1.0.0
|
||||||
|
-- PROYECTO: ERP-Core V2
|
||||||
|
-- FECHA: 2026-01-20
|
||||||
|
-- DEPENDE DE: 50-financial-schema.sql, 51-financial-accounts.sql
|
||||||
|
-- =============================================================
|
||||||
|
|
||||||
|
-- =====================
|
||||||
|
-- TABLA: taxes
|
||||||
|
-- Catalogo de impuestos
|
||||||
|
-- =====================
|
||||||
|
CREATE TABLE IF NOT EXISTS financial.taxes (
|
||||||
|
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,
|
||||||
|
|
||||||
|
-- Identificacion
|
||||||
|
name VARCHAR(100) NOT NULL, -- Ej: "IVA 16%", "Retencion ISR 10%"
|
||||||
|
code VARCHAR(20) NOT NULL, -- Ej: "IVA16", "RET_ISR10"
|
||||||
|
|
||||||
|
-- Tipo de impuesto
|
||||||
|
tax_type financial.tax_type_enum NOT NULL DEFAULT 'all',
|
||||||
|
|
||||||
|
-- Tasa
|
||||||
|
amount DECIMAL(5, 2) NOT NULL, -- Porcentaje (ej: 16.00 para 16%)
|
||||||
|
|
||||||
|
-- Configuracion
|
||||||
|
included_in_price BOOLEAN DEFAULT FALSE, -- TRUE si el precio ya incluye el impuesto
|
||||||
|
|
||||||
|
-- Estado
|
||||||
|
active BOOLEAN DEFAULT TRUE,
|
||||||
|
|
||||||
|
-- Cuentas contables asociadas (opcional)
|
||||||
|
account_id UUID REFERENCES financial.accounts(id) ON DELETE SET NULL, -- Cuenta de impuesto
|
||||||
|
refund_account_id UUID REFERENCES financial.accounts(id) ON DELETE SET NULL, -- Cuenta para devoluciones
|
||||||
|
|
||||||
|
-- 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),
|
||||||
|
|
||||||
|
-- Unicidad por tenant
|
||||||
|
UNIQUE(tenant_id, code)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Indices para taxes
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_taxes_tenant ON financial.taxes(tenant_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_taxes_company ON financial.taxes(company_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_taxes_code ON financial.taxes(code);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_taxes_type ON financial.taxes(tax_type);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_taxes_active ON financial.taxes(tenant_id) WHERE active = TRUE;
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_taxes_sales ON financial.taxes(tenant_id, tax_type) WHERE tax_type IN ('sales', 'all') AND active = TRUE;
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_taxes_purchase ON financial.taxes(tenant_id, tax_type) WHERE tax_type IN ('purchase', 'all') AND active = TRUE;
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_taxes_account ON financial.taxes(account_id) WHERE account_id IS NOT NULL;
|
||||||
|
|
||||||
|
-- =====================
|
||||||
|
-- TABLA: tax_groups (opcional, para agrupar impuestos)
|
||||||
|
-- Grupos de impuestos para aplicacion conjunta
|
||||||
|
-- =====================
|
||||||
|
CREATE TABLE IF NOT EXISTS financial.tax_groups (
|
||||||
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||||
|
|
||||||
|
-- Multi-tenant
|
||||||
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
|
||||||
|
|
||||||
|
-- Identificacion
|
||||||
|
name VARCHAR(100) NOT NULL,
|
||||||
|
code VARCHAR(20) NOT NULL,
|
||||||
|
|
||||||
|
-- Descripcion
|
||||||
|
description TEXT,
|
||||||
|
|
||||||
|
-- Impuestos en el grupo (array de IDs)
|
||||||
|
tax_ids UUID[] DEFAULT '{}',
|
||||||
|
|
||||||
|
-- Estado
|
||||||
|
active BOOLEAN DEFAULT TRUE,
|
||||||
|
|
||||||
|
-- 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),
|
||||||
|
|
||||||
|
UNIQUE(tenant_id, code)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Indices para tax_groups
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_tax_groups_tenant ON financial.tax_groups(tenant_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_tax_groups_code ON financial.tax_groups(code);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_tax_groups_active ON financial.tax_groups(tenant_id) WHERE active = TRUE;
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_tax_groups_tax_ids ON financial.tax_groups USING GIN(tax_ids);
|
||||||
|
|
||||||
|
-- =====================
|
||||||
|
-- DATOS SEMILLA: Impuestos comunes de Mexico
|
||||||
|
-- =====================
|
||||||
|
-- Nota: Estos se insertan condicionalmente. En produccion, los impuestos
|
||||||
|
-- se crean por tenant desde la aplicacion.
|
||||||
|
|
||||||
|
-- Funcion para insertar impuestos semilla
|
||||||
|
CREATE OR REPLACE FUNCTION financial.seed_default_taxes(p_tenant_id UUID)
|
||||||
|
RETURNS void AS $$
|
||||||
|
BEGIN
|
||||||
|
-- IVA 16% (tasa general)
|
||||||
|
INSERT INTO financial.taxes (tenant_id, code, name, tax_type, amount, included_in_price, active)
|
||||||
|
VALUES (p_tenant_id, 'IVA16', 'IVA 16%', 'all', 16.00, FALSE, TRUE)
|
||||||
|
ON CONFLICT (tenant_id, code) DO NOTHING;
|
||||||
|
|
||||||
|
-- IVA 8% (frontera)
|
||||||
|
INSERT INTO financial.taxes (tenant_id, code, name, tax_type, amount, included_in_price, active)
|
||||||
|
VALUES (p_tenant_id, 'IVA8', 'IVA 8% (Frontera)', 'all', 8.00, FALSE, TRUE)
|
||||||
|
ON CONFLICT (tenant_id, code) DO NOTHING;
|
||||||
|
|
||||||
|
-- IVA 0% (tasa cero)
|
||||||
|
INSERT INTO financial.taxes (tenant_id, code, name, tax_type, amount, included_in_price, active)
|
||||||
|
VALUES (p_tenant_id, 'IVA0', 'IVA 0%', 'all', 0.00, FALSE, TRUE)
|
||||||
|
ON CONFLICT (tenant_id, code) DO NOTHING;
|
||||||
|
|
||||||
|
-- IVA Exento
|
||||||
|
INSERT INTO financial.taxes (tenant_id, code, name, tax_type, amount, included_in_price, active)
|
||||||
|
VALUES (p_tenant_id, 'EXENTO', 'Exento de IVA', 'all', 0.00, FALSE, TRUE)
|
||||||
|
ON CONFLICT (tenant_id, code) DO NOTHING;
|
||||||
|
|
||||||
|
-- Retencion ISR 10%
|
||||||
|
INSERT INTO financial.taxes (tenant_id, code, name, tax_type, amount, included_in_price, active)
|
||||||
|
VALUES (p_tenant_id, 'RET_ISR10', 'Retencion ISR 10%', 'purchase', -10.00, FALSE, TRUE)
|
||||||
|
ON CONFLICT (tenant_id, code) DO NOTHING;
|
||||||
|
|
||||||
|
-- Retencion IVA 10.67%
|
||||||
|
INSERT INTO financial.taxes (tenant_id, code, name, tax_type, amount, included_in_price, active)
|
||||||
|
VALUES (p_tenant_id, 'RET_IVA', 'Retencion IVA 2/3', 'purchase', -10.67, FALSE, TRUE)
|
||||||
|
ON CONFLICT (tenant_id, code) DO NOTHING;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
-- =====================
|
||||||
|
-- COMENTARIOS
|
||||||
|
-- =====================
|
||||||
|
COMMENT ON TABLE financial.taxes IS 'Catalogo de impuestos (IVA, retenciones, etc.)';
|
||||||
|
COMMENT ON COLUMN financial.taxes.code IS 'Codigo unico del impuesto (ej: IVA16, RET_ISR10)';
|
||||||
|
COMMENT ON COLUMN financial.taxes.tax_type IS 'Aplicacion: sales (solo ventas), purchase (solo compras), all (ambos)';
|
||||||
|
COMMENT ON COLUMN financial.taxes.amount IS 'Tasa del impuesto en porcentaje (ej: 16.00 para 16%). Negativo para retenciones.';
|
||||||
|
COMMENT ON COLUMN financial.taxes.included_in_price IS 'TRUE si el precio del producto ya incluye este impuesto';
|
||||||
|
COMMENT ON COLUMN financial.taxes.account_id IS 'Cuenta contable donde se registra el impuesto';
|
||||||
|
COMMENT ON COLUMN financial.taxes.refund_account_id IS 'Cuenta contable para devoluciones/notas de credito';
|
||||||
|
|
||||||
|
COMMENT ON TABLE financial.tax_groups IS 'Grupos de impuestos para aplicacion conjunta (ej: IVA + Retenciones)';
|
||||||
|
COMMENT ON COLUMN financial.tax_groups.tax_ids IS 'Array de IDs de impuestos que componen el grupo';
|
||||||
|
|
||||||
|
COMMENT ON FUNCTION financial.seed_default_taxes(UUID) IS 'Inserta impuestos predeterminados de Mexico para un tenant';
|
||||||
254
ddl/57-financial-bank-reconciliation.sql
Normal file
254
ddl/57-financial-bank-reconciliation.sql
Normal file
@ -0,0 +1,254 @@
|
|||||||
|
-- =============================================================
|
||||||
|
-- ARCHIVO: 57-financial-bank-reconciliation.sql
|
||||||
|
-- DESCRIPCION: Conciliacion bancaria - extractos, lineas y reglas de match
|
||||||
|
-- VERSION: 1.0.0
|
||||||
|
-- PROYECTO: ERP-Core V2
|
||||||
|
-- FECHA: 2026-01-20
|
||||||
|
-- DEPENDE DE: 50-financial-schema.sql, 51-financial-accounts.sql, 53-financial-entries.sql
|
||||||
|
-- =============================================================
|
||||||
|
|
||||||
|
-- =====================
|
||||||
|
-- TIPO ENUMERADO: Estado de extracto bancario
|
||||||
|
-- =====================
|
||||||
|
DO $$ BEGIN
|
||||||
|
CREATE TYPE financial.bank_statement_status_enum AS ENUM (
|
||||||
|
'draft', -- Borrador
|
||||||
|
'reconciling', -- En proceso de conciliacion
|
||||||
|
'reconciled' -- Conciliado
|
||||||
|
);
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
-- =====================
|
||||||
|
-- TIPO ENUMERADO: Tipo de regla de match
|
||||||
|
-- =====================
|
||||||
|
DO $$ BEGIN
|
||||||
|
CREATE TYPE financial.reconciliation_match_type_enum AS ENUM (
|
||||||
|
'exact_amount', -- Monto exacto
|
||||||
|
'reference_contains', -- Referencia contiene texto
|
||||||
|
'partner_name' -- Nombre de partner
|
||||||
|
);
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
-- =====================
|
||||||
|
-- TABLA: bank_statements
|
||||||
|
-- Extractos bancarios importados
|
||||||
|
-- =====================
|
||||||
|
CREATE TABLE IF NOT EXISTS financial.bank_statements (
|
||||||
|
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,
|
||||||
|
|
||||||
|
-- Cuenta bancaria asociada (referencia a cuenta contable tipo banco)
|
||||||
|
bank_account_id UUID REFERENCES financial.accounts(id) ON DELETE RESTRICT,
|
||||||
|
|
||||||
|
-- Datos del extracto
|
||||||
|
statement_date DATE NOT NULL,
|
||||||
|
opening_balance DECIMAL(15, 2) NOT NULL DEFAULT 0,
|
||||||
|
closing_balance DECIMAL(15, 2) NOT NULL DEFAULT 0,
|
||||||
|
|
||||||
|
-- Estado
|
||||||
|
status financial.bank_statement_status_enum DEFAULT 'draft',
|
||||||
|
|
||||||
|
-- Importacion
|
||||||
|
imported_at TIMESTAMPTZ,
|
||||||
|
imported_by UUID REFERENCES auth.users(id),
|
||||||
|
|
||||||
|
-- Conciliacion
|
||||||
|
reconciled_at TIMESTAMPTZ,
|
||||||
|
reconciled_by UUID REFERENCES auth.users(id),
|
||||||
|
|
||||||
|
-- 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)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Indices para bank_statements
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_bank_statements_tenant ON financial.bank_statements(tenant_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_bank_statements_company ON financial.bank_statements(company_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_bank_statements_bank_account ON financial.bank_statements(bank_account_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_bank_statements_date ON financial.bank_statements(statement_date);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_bank_statements_status ON financial.bank_statements(status);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_bank_statements_tenant_date ON financial.bank_statements(tenant_id, statement_date DESC);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_bank_statements_draft ON financial.bank_statements(tenant_id, status) WHERE status = 'draft';
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_bank_statements_reconciling ON financial.bank_statements(tenant_id, status) WHERE status = 'reconciling';
|
||||||
|
|
||||||
|
-- =====================
|
||||||
|
-- TABLA: bank_statement_lines
|
||||||
|
-- Lineas de extracto bancario (movimientos)
|
||||||
|
-- =====================
|
||||||
|
CREATE TABLE IF NOT EXISTS financial.bank_statement_lines (
|
||||||
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||||
|
|
||||||
|
-- Relacion con extracto (cascade delete)
|
||||||
|
statement_id UUID NOT NULL REFERENCES financial.bank_statements(id) ON DELETE CASCADE,
|
||||||
|
|
||||||
|
-- Multi-tenant (denormalizado para queries rapidas)
|
||||||
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
|
||||||
|
|
||||||
|
-- Datos del movimiento
|
||||||
|
transaction_date DATE NOT NULL,
|
||||||
|
value_date DATE,
|
||||||
|
description VARCHAR(500),
|
||||||
|
reference VARCHAR(100),
|
||||||
|
amount DECIMAL(15, 2) NOT NULL, -- Positivo = deposito, Negativo = retiro
|
||||||
|
|
||||||
|
-- Estado de conciliacion
|
||||||
|
is_reconciled BOOLEAN DEFAULT false,
|
||||||
|
reconciled_entry_id UUID REFERENCES financial.journal_entry_lines(id) ON DELETE SET NULL,
|
||||||
|
reconciled_at TIMESTAMPTZ,
|
||||||
|
reconciled_by UUID REFERENCES auth.users(id),
|
||||||
|
|
||||||
|
-- Partner detectado (automatico o manual)
|
||||||
|
partner_id UUID, -- FK a partners si existe
|
||||||
|
|
||||||
|
-- Notas adicionales
|
||||||
|
notes TEXT,
|
||||||
|
|
||||||
|
-- Audit columns
|
||||||
|
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Indices para bank_statement_lines
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_bank_statement_lines_statement ON financial.bank_statement_lines(statement_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_bank_statement_lines_tenant ON financial.bank_statement_lines(tenant_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_bank_statement_lines_date ON financial.bank_statement_lines(transaction_date);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_bank_statement_lines_reconciled ON financial.bank_statement_lines(is_reconciled);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_bank_statement_lines_entry ON financial.bank_statement_lines(reconciled_entry_id) WHERE reconciled_entry_id IS NOT NULL;
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_bank_statement_lines_partner ON financial.bank_statement_lines(partner_id) WHERE partner_id IS NOT NULL;
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_bank_statement_lines_reference ON financial.bank_statement_lines(reference) WHERE reference IS NOT NULL;
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_bank_statement_lines_unreconciled ON financial.bank_statement_lines(tenant_id, statement_id) WHERE is_reconciled = false;
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_bank_statement_lines_amount ON financial.bank_statement_lines(amount);
|
||||||
|
|
||||||
|
-- =====================
|
||||||
|
-- TABLA: bank_reconciliation_rules
|
||||||
|
-- Reglas de conciliacion automatica
|
||||||
|
-- =====================
|
||||||
|
CREATE TABLE IF NOT EXISTS financial.bank_reconciliation_rules (
|
||||||
|
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,
|
||||||
|
|
||||||
|
-- Identificacion de la regla
|
||||||
|
name VARCHAR(255) NOT NULL,
|
||||||
|
|
||||||
|
-- Tipo y valor del match
|
||||||
|
match_type financial.reconciliation_match_type_enum NOT NULL,
|
||||||
|
match_value VARCHAR(255) NOT NULL, -- Valor a buscar segun el tipo
|
||||||
|
|
||||||
|
-- Cuenta destino para auto-crear asiento
|
||||||
|
auto_account_id UUID REFERENCES financial.accounts(id) ON DELETE SET NULL,
|
||||||
|
|
||||||
|
-- Estado y prioridad
|
||||||
|
is_active BOOLEAN DEFAULT true,
|
||||||
|
priority INTEGER DEFAULT 0, -- Mayor prioridad = se evalua primero
|
||||||
|
|
||||||
|
-- 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)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Indices para bank_reconciliation_rules
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_bank_reconciliation_rules_tenant ON financial.bank_reconciliation_rules(tenant_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_bank_reconciliation_rules_company ON financial.bank_reconciliation_rules(company_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_bank_reconciliation_rules_active ON financial.bank_reconciliation_rules(tenant_id, is_active) WHERE is_active = true;
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_bank_reconciliation_rules_priority ON financial.bank_reconciliation_rules(tenant_id, priority DESC);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_financial_bank_reconciliation_rules_match_type ON financial.bank_reconciliation_rules(match_type);
|
||||||
|
|
||||||
|
-- =====================
|
||||||
|
-- FUNCION: Calcular balance calculado del extracto
|
||||||
|
-- Verifica que opening_balance + sum(lines) = closing_balance
|
||||||
|
-- =====================
|
||||||
|
CREATE OR REPLACE FUNCTION financial.check_statement_balance(p_statement_id UUID)
|
||||||
|
RETURNS BOOLEAN AS $$
|
||||||
|
DECLARE
|
||||||
|
v_opening_balance DECIMAL(15, 2);
|
||||||
|
v_closing_balance DECIMAL(15, 2);
|
||||||
|
v_lines_total DECIMAL(15, 2);
|
||||||
|
v_calculated_closing DECIMAL(15, 2);
|
||||||
|
BEGIN
|
||||||
|
-- Obtener balances del extracto
|
||||||
|
SELECT opening_balance, closing_balance
|
||||||
|
INTO v_opening_balance, v_closing_balance
|
||||||
|
FROM financial.bank_statements
|
||||||
|
WHERE id = p_statement_id;
|
||||||
|
|
||||||
|
-- Sumar todas las lineas
|
||||||
|
SELECT COALESCE(SUM(amount), 0)
|
||||||
|
INTO v_lines_total
|
||||||
|
FROM financial.bank_statement_lines
|
||||||
|
WHERE statement_id = p_statement_id;
|
||||||
|
|
||||||
|
-- Calcular balance de cierre esperado
|
||||||
|
v_calculated_closing := v_opening_balance + v_lines_total;
|
||||||
|
|
||||||
|
-- Retornar si coincide (con tolerancia de 0.01)
|
||||||
|
RETURN ABS(v_calculated_closing - v_closing_balance) < 0.01;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
-- =====================
|
||||||
|
-- FUNCION: Obtener progreso de conciliacion
|
||||||
|
-- Retorna porcentaje de lineas conciliadas
|
||||||
|
-- =====================
|
||||||
|
CREATE OR REPLACE FUNCTION financial.get_reconciliation_progress(p_statement_id UUID)
|
||||||
|
RETURNS NUMERIC AS $$
|
||||||
|
DECLARE
|
||||||
|
v_total_lines INTEGER;
|
||||||
|
v_reconciled_lines INTEGER;
|
||||||
|
BEGIN
|
||||||
|
SELECT COUNT(*), COUNT(*) FILTER (WHERE is_reconciled = true)
|
||||||
|
INTO v_total_lines, v_reconciled_lines
|
||||||
|
FROM financial.bank_statement_lines
|
||||||
|
WHERE statement_id = p_statement_id;
|
||||||
|
|
||||||
|
IF v_total_lines = 0 THEN
|
||||||
|
RETURN 100;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
RETURN ROUND((v_reconciled_lines::NUMERIC / v_total_lines::NUMERIC) * 100, 2);
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
-- =====================
|
||||||
|
-- COMENTARIOS
|
||||||
|
-- =====================
|
||||||
|
COMMENT ON TYPE financial.bank_statement_status_enum IS 'Estado del extracto bancario: borrador, en conciliacion, conciliado';
|
||||||
|
COMMENT ON TYPE financial.reconciliation_match_type_enum IS 'Tipo de regla de match: monto exacto, referencia contiene, nombre de partner';
|
||||||
|
|
||||||
|
COMMENT ON TABLE financial.bank_statements IS 'Extractos bancarios importados para conciliacion';
|
||||||
|
COMMENT ON COLUMN financial.bank_statements.bank_account_id IS 'Cuenta contable tipo banco asociada';
|
||||||
|
COMMENT ON COLUMN financial.bank_statements.statement_date IS 'Fecha del extracto bancario';
|
||||||
|
COMMENT ON COLUMN financial.bank_statements.opening_balance IS 'Saldo inicial del extracto';
|
||||||
|
COMMENT ON COLUMN financial.bank_statements.closing_balance IS 'Saldo final del extracto';
|
||||||
|
COMMENT ON COLUMN financial.bank_statements.status IS 'Estado: draft, reconciling, reconciled';
|
||||||
|
COMMENT ON COLUMN financial.bank_statements.imported_at IS 'Fecha y hora de importacion';
|
||||||
|
COMMENT ON COLUMN financial.bank_statements.reconciled_at IS 'Fecha y hora de cierre de conciliacion';
|
||||||
|
|
||||||
|
COMMENT ON TABLE financial.bank_statement_lines IS 'Lineas/movimientos del extracto bancario';
|
||||||
|
COMMENT ON COLUMN financial.bank_statement_lines.transaction_date IS 'Fecha de la transaccion';
|
||||||
|
COMMENT ON COLUMN financial.bank_statement_lines.value_date IS 'Fecha valor (cuando aplica el movimiento)';
|
||||||
|
COMMENT ON COLUMN financial.bank_statement_lines.amount IS 'Monto del movimiento (positivo=deposito, negativo=retiro)';
|
||||||
|
COMMENT ON COLUMN financial.bank_statement_lines.is_reconciled IS 'Indica si la linea ha sido conciliada';
|
||||||
|
COMMENT ON COLUMN financial.bank_statement_lines.reconciled_entry_id IS 'Linea de asiento contable con la que se concilio';
|
||||||
|
COMMENT ON COLUMN financial.bank_statement_lines.partner_id IS 'Partner detectado o asignado manualmente';
|
||||||
|
|
||||||
|
COMMENT ON TABLE financial.bank_reconciliation_rules IS 'Reglas para conciliacion automatica de movimientos';
|
||||||
|
COMMENT ON COLUMN financial.bank_reconciliation_rules.match_type IS 'Tipo de coincidencia: exact_amount, reference_contains, partner_name';
|
||||||
|
COMMENT ON COLUMN financial.bank_reconciliation_rules.match_value IS 'Valor a buscar segun el tipo de match';
|
||||||
|
COMMENT ON COLUMN financial.bank_reconciliation_rules.auto_account_id IS 'Cuenta contable para auto-generar asiento';
|
||||||
|
COMMENT ON COLUMN financial.bank_reconciliation_rules.priority IS 'Prioridad de evaluacion (mayor = primero)';
|
||||||
|
|
||||||
|
COMMENT ON FUNCTION financial.check_statement_balance(UUID) IS 'Verifica que el balance calculado coincida con opening + lines = closing';
|
||||||
|
COMMENT ON FUNCTION financial.get_reconciliation_progress(UUID) IS 'Retorna porcentaje de lineas conciliadas (0-100)';
|
||||||
Loading…
Reference in New Issue
Block a user