erp-core-database-v2/ddl/55-financial-payments.sql
rckrdmrd 4b6240311d [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>
2026-01-20 03:47:04 -06:00

175 lines
7.4 KiB
PL/PgSQL

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