- 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>
251 lines
9.5 KiB
SQL
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';
|