erp-core-database/ddl/24-invoices.sql
rckrdmrd 5043a640e4 refactor: Restructure DDL with numbered schema files
- Replace old DDL structure with new numbered files (01-24)
- Update migrations and seeds for new schema
- Clean up deprecated files

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 00:40:32 -06:00

251 lines
9.5 KiB
SQL

-- =============================================================
-- ARCHIVO: 24-invoices.sql
-- DESCRIPCION: Facturas de venta/compra y pagos
-- VERSION: 1.0.0
-- PROYECTO: ERP-Core V2
-- FECHA: 2026-01-13
-- DEPENDE DE: 16-partners.sql, 22-sales.sql, 23-purchases.sql
-- =============================================================
-- =====================
-- SCHEMA: billing
-- =====================
CREATE SCHEMA IF NOT EXISTS billing;
-- =====================
-- TABLA: invoices
-- Facturas de venta y compra
-- =====================
CREATE TABLE IF NOT EXISTS billing.invoices (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
-- Identificacion
invoice_number VARCHAR(30) NOT NULL,
invoice_type VARCHAR(20) NOT NULL DEFAULT 'sale', -- sale (venta), purchase (compra), credit_note, debit_note
-- Referencia a origen
sales_order_id UUID REFERENCES sales.sales_orders(id),
purchase_order_id UUID REFERENCES purchases.purchase_orders(id),
-- Partner
partner_id UUID NOT NULL REFERENCES partners.partners(id) ON DELETE RESTRICT,
partner_name VARCHAR(200),
partner_tax_id VARCHAR(50),
-- Direcciones
billing_address JSONB,
-- Fechas
invoice_date DATE NOT NULL DEFAULT CURRENT_DATE,
due_date DATE,
payment_date DATE, -- Fecha real de pago
-- Totales
currency VARCHAR(3) DEFAULT 'MXN',
exchange_rate DECIMAL(10, 6) DEFAULT 1,
subtotal DECIMAL(15, 2) NOT NULL DEFAULT 0,
tax_amount DECIMAL(15, 2) NOT NULL DEFAULT 0,
withholding_tax DECIMAL(15, 2) DEFAULT 0, -- Retenciones
discount_amount DECIMAL(15, 2) NOT NULL DEFAULT 0,
total DECIMAL(15, 2) NOT NULL DEFAULT 0,
-- Pagos
amount_paid DECIMAL(15, 2) DEFAULT 0,
amount_due DECIMAL(15, 2) GENERATED ALWAYS AS (total - COALESCE(amount_paid, 0)) STORED,
-- Terminos
payment_term_days INTEGER DEFAULT 0,
payment_method VARCHAR(50),
-- Estado
status VARCHAR(20) NOT NULL DEFAULT 'draft', -- draft, validated, sent, partial, paid, cancelled, voided
-- CFDI (Facturacion electronica Mexico)
cfdi_uuid VARCHAR(40), -- UUID del CFDI
cfdi_status VARCHAR(20), -- pending, stamped, cancelled
cfdi_xml TEXT, -- XML del CFDI
cfdi_pdf_url VARCHAR(500),
-- Notas
notes TEXT,
internal_notes TEXT,
-- Metadata
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,
UNIQUE(tenant_id, invoice_number)
);
-- Indices para invoices
CREATE INDEX IF NOT EXISTS idx_invoices_tenant ON billing.invoices(tenant_id);
CREATE INDEX IF NOT EXISTS idx_invoices_number ON billing.invoices(invoice_number);
CREATE INDEX IF NOT EXISTS idx_invoices_type ON billing.invoices(invoice_type);
CREATE INDEX IF NOT EXISTS idx_invoices_partner ON billing.invoices(partner_id);
CREATE INDEX IF NOT EXISTS idx_invoices_sales_order ON billing.invoices(sales_order_id);
CREATE INDEX IF NOT EXISTS idx_invoices_purchase_order ON billing.invoices(purchase_order_id);
CREATE INDEX IF NOT EXISTS idx_invoices_status ON billing.invoices(status);
CREATE INDEX IF NOT EXISTS idx_invoices_date ON billing.invoices(invoice_date);
CREATE INDEX IF NOT EXISTS idx_invoices_due_date ON billing.invoices(due_date);
CREATE INDEX IF NOT EXISTS idx_invoices_cfdi ON billing.invoices(cfdi_uuid);
CREATE INDEX IF NOT EXISTS idx_invoices_unpaid ON billing.invoices(status) WHERE status IN ('validated', 'sent', 'partial');
-- =====================
-- TABLA: invoice_items
-- Lineas de factura
-- =====================
CREATE TABLE IF NOT EXISTS billing.invoice_items (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
invoice_id UUID NOT NULL REFERENCES billing.invoices(id) ON DELETE CASCADE,
product_id UUID REFERENCES products.products(id) ON DELETE SET NULL,
-- Linea
line_number INTEGER NOT NULL DEFAULT 1,
-- Producto
product_sku VARCHAR(50),
product_name VARCHAR(200) NOT NULL,
description TEXT,
-- SAT (Mexico)
sat_product_code VARCHAR(20), -- Clave de producto SAT
sat_unit_code VARCHAR(10), -- Clave de unidad SAT
-- Cantidad y precio
quantity DECIMAL(15, 4) NOT NULL DEFAULT 1,
uom VARCHAR(20) DEFAULT 'PZA',
unit_price DECIMAL(15, 4) NOT NULL DEFAULT 0,
-- Descuentos
discount_percent DECIMAL(5, 2) DEFAULT 0,
discount_amount DECIMAL(15, 2) DEFAULT 0,
-- Impuestos
tax_rate DECIMAL(5, 2) DEFAULT 16.00,
tax_amount DECIMAL(15, 2) DEFAULT 0,
withholding_rate DECIMAL(5, 2) DEFAULT 0,
withholding_amount DECIMAL(15, 2) DEFAULT 0,
-- Totales
subtotal DECIMAL(15, 2) NOT NULL DEFAULT 0,
total DECIMAL(15, 2) NOT NULL DEFAULT 0,
-- Metadata
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
-- Indices para invoice_items
CREATE INDEX IF NOT EXISTS idx_invoice_items_invoice ON billing.invoice_items(invoice_id);
CREATE INDEX IF NOT EXISTS idx_invoice_items_product ON billing.invoice_items(product_id);
CREATE INDEX IF NOT EXISTS idx_invoice_items_line ON billing.invoice_items(invoice_id, line_number);
-- =====================
-- TABLA: payments
-- Pagos recibidos y realizados
-- =====================
CREATE TABLE IF NOT EXISTS billing.payments (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
-- Identificacion
payment_number VARCHAR(30) NOT NULL,
payment_type VARCHAR(20) NOT NULL DEFAULT 'received', -- received (cobro), made (pago)
-- Partner
partner_id UUID NOT NULL REFERENCES partners.partners(id) ON DELETE RESTRICT,
partner_name VARCHAR(200),
-- Monto
currency VARCHAR(3) DEFAULT 'MXN',
amount DECIMAL(15, 2) NOT NULL,
exchange_rate DECIMAL(10, 6) DEFAULT 1,
-- Fecha
payment_date DATE NOT NULL DEFAULT CURRENT_DATE,
-- Metodo de pago
payment_method VARCHAR(50) NOT NULL, -- cash, transfer, check, credit_card, debit_card
reference VARCHAR(100), -- Numero de referencia, cheque, etc.
-- Cuenta bancaria
bank_account_id UUID REFERENCES partners.partner_bank_accounts(id),
-- Estado
status VARCHAR(20) NOT NULL DEFAULT 'draft', -- draft, confirmed, reconciled, cancelled
-- Notas
notes TEXT,
-- CFDI de pago (Mexico)
cfdi_uuid VARCHAR(40),
cfdi_status VARCHAR(20),
-- Metadata
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
created_by UUID REFERENCES auth.users(id),
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMPTZ,
UNIQUE(tenant_id, payment_number)
);
-- Indices para payments
CREATE INDEX IF NOT EXISTS idx_payments_tenant ON billing.payments(tenant_id);
CREATE INDEX IF NOT EXISTS idx_payments_number ON billing.payments(payment_number);
CREATE INDEX IF NOT EXISTS idx_payments_type ON billing.payments(payment_type);
CREATE INDEX IF NOT EXISTS idx_payments_partner ON billing.payments(partner_id);
CREATE INDEX IF NOT EXISTS idx_payments_status ON billing.payments(status);
CREATE INDEX IF NOT EXISTS idx_payments_date ON billing.payments(payment_date);
CREATE INDEX IF NOT EXISTS idx_payments_method ON billing.payments(payment_method);
-- =====================
-- TABLA: payment_allocations
-- Aplicacion de pagos a facturas
-- =====================
CREATE TABLE IF NOT EXISTS billing.payment_allocations (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
payment_id UUID NOT NULL REFERENCES billing.payments(id) ON DELETE CASCADE,
invoice_id UUID NOT NULL REFERENCES billing.invoices(id) ON DELETE CASCADE,
-- Monto aplicado
amount DECIMAL(15, 2) NOT NULL,
-- Fecha de aplicacion
allocation_date DATE NOT NULL DEFAULT CURRENT_DATE,
-- Metadata
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
created_by UUID REFERENCES auth.users(id),
UNIQUE(payment_id, invoice_id)
);
-- Indices para payment_allocations
CREATE INDEX IF NOT EXISTS idx_payment_allocations_payment ON billing.payment_allocations(payment_id);
CREATE INDEX IF NOT EXISTS idx_payment_allocations_invoice ON billing.payment_allocations(invoice_id);
-- =====================
-- COMENTARIOS
-- =====================
COMMENT ON TABLE billing.invoices IS 'Facturas de venta y compra';
COMMENT ON COLUMN billing.invoices.invoice_type IS 'Tipo: sale (venta), purchase (compra), credit_note (nota credito), debit_note (nota debito)';
COMMENT ON COLUMN billing.invoices.status IS 'Estado: draft, validated, sent, partial (pago parcial), paid, cancelled, voided';
COMMENT ON COLUMN billing.invoices.cfdi_uuid IS 'UUID del CFDI para facturacion electronica en Mexico';
COMMENT ON COLUMN billing.invoices.amount_due IS 'Saldo pendiente de pago (calculado)';
COMMENT ON TABLE billing.invoice_items IS 'Lineas de detalle de facturas';
COMMENT ON COLUMN billing.invoice_items.sat_product_code IS 'Clave de producto del catalogo SAT (Mexico)';
COMMENT ON COLUMN billing.invoice_items.sat_unit_code IS 'Clave de unidad del catalogo SAT (Mexico)';
COMMENT ON TABLE billing.payments IS 'Registro de pagos recibidos y realizados';
COMMENT ON COLUMN billing.payments.payment_type IS 'Tipo: received (cobro a cliente), made (pago a proveedor)';
COMMENT ON COLUMN billing.payments.status IS 'Estado: draft, confirmed, reconciled, cancelled';
COMMENT ON TABLE billing.payment_allocations IS 'Aplicacion de pagos a facturas especificas';
COMMENT ON COLUMN billing.payment_allocations.amount IS 'Monto del pago aplicado a esta factura';