-- ============================================================= -- 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';