Compare commits
No commits in common. "57f41859de2761d1b13de7c0aa95c963695c813e" and "1cd8996d192869ea2cfb4b861f457c3ccd1f7686" have entirely different histories.
57f41859de
...
1cd8996d19
23
.gitignore
vendored
Normal file
23
.gitignore
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
# Environment
|
||||
.env
|
||||
.env.local
|
||||
.env.*
|
||||
|
||||
# Backups
|
||||
*.bak
|
||||
*.backup
|
||||
*.dump
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
|
||||
# IDE
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
.DS_Store
|
||||
|
||||
# Temp
|
||||
tmp/
|
||||
temp/
|
||||
3
README.md
Normal file
3
README.md
Normal file
@ -0,0 +1,3 @@
|
||||
# erp-core-database-v2
|
||||
|
||||
Database de erp-core - Workspace V2
|
||||
@ -1,467 +0,0 @@
|
||||
-- =============================================================
|
||||
-- ARCHIVO: 20-core-catalogs.sql
|
||||
-- DESCRIPCION: Catalogos maestros - paises, estados, monedas, UoM
|
||||
-- VERSION: 1.0.0
|
||||
-- PROYECTO: ERP-Core V2
|
||||
-- FECHA: 2026-01-18
|
||||
-- =============================================================
|
||||
|
||||
-- =====================
|
||||
-- SCHEMA: core (si no existe)
|
||||
-- =====================
|
||||
CREATE SCHEMA IF NOT EXISTS core;
|
||||
|
||||
-- =====================
|
||||
-- TABLA: countries
|
||||
-- Paises ISO 3166-1
|
||||
-- =====================
|
||||
CREATE TABLE IF NOT EXISTS core.countries (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
code VARCHAR(2) NOT NULL UNIQUE, -- ISO 3166-1 alpha-2
|
||||
code_alpha3 VARCHAR(3), -- ISO 3166-1 alpha-3
|
||||
name VARCHAR(255) NOT NULL,
|
||||
phone_code VARCHAR(10),
|
||||
currency_code VARCHAR(3), -- Default currency
|
||||
|
||||
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_countries_code ON core.countries(code);
|
||||
CREATE INDEX IF NOT EXISTS idx_countries_name ON core.countries(name);
|
||||
|
||||
COMMENT ON TABLE core.countries IS 'Catalogo de paises ISO 3166-1';
|
||||
|
||||
-- =====================
|
||||
-- TABLA: states
|
||||
-- Estados/Provincias/Regiones
|
||||
-- =====================
|
||||
CREATE TABLE IF NOT EXISTS core.states (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
country_id UUID NOT NULL REFERENCES core.countries(id) ON DELETE CASCADE,
|
||||
code VARCHAR(10) NOT NULL, -- Codigo del estado (ej: JAL, NL, QRO)
|
||||
name VARCHAR(255) NOT NULL,
|
||||
|
||||
-- Datos adicionales
|
||||
timezone VARCHAR(50), -- Zona horaria predominante
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
|
||||
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
UNIQUE(country_id, code)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_states_country ON core.states(country_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_states_code ON core.states(code);
|
||||
CREATE INDEX IF NOT EXISTS idx_states_active ON core.states(is_active) WHERE is_active = TRUE;
|
||||
|
||||
COMMENT ON TABLE core.states IS 'Catalogo de estados/provincias/regiones por pais';
|
||||
|
||||
-- =====================
|
||||
-- TABLA: currencies
|
||||
-- Monedas ISO 4217
|
||||
-- =====================
|
||||
CREATE TABLE IF NOT EXISTS core.currencies (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
code VARCHAR(3) NOT NULL UNIQUE, -- ISO 4217
|
||||
name VARCHAR(100) NOT NULL,
|
||||
symbol VARCHAR(10) NOT NULL,
|
||||
decimals INTEGER NOT NULL DEFAULT 2,
|
||||
rounding DECIMAL(12, 6) DEFAULT 0.01,
|
||||
active BOOLEAN DEFAULT TRUE,
|
||||
|
||||
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_currencies_code ON core.currencies(code);
|
||||
CREATE INDEX IF NOT EXISTS idx_currencies_active ON core.currencies(active) WHERE active = TRUE;
|
||||
|
||||
COMMENT ON TABLE core.currencies IS 'Catalogo de monedas ISO 4217';
|
||||
|
||||
-- =====================
|
||||
-- TABLA: currency_rates
|
||||
-- Tipos de cambio historicos
|
||||
-- =====================
|
||||
CREATE TABLE IF NOT EXISTS core.currency_rates (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID REFERENCES auth.tenants(id) ON DELETE CASCADE, -- NULL = global
|
||||
|
||||
from_currency_id UUID NOT NULL REFERENCES core.currencies(id),
|
||||
to_currency_id UUID NOT NULL REFERENCES core.currencies(id),
|
||||
|
||||
rate DECIMAL(18, 8) NOT NULL, -- Tipo de cambio
|
||||
rate_date DATE NOT NULL, -- Fecha del tipo de cambio
|
||||
|
||||
-- Fuente del tipo de cambio
|
||||
source VARCHAR(50) DEFAULT 'manual', -- manual, banxico, xe, openexchange
|
||||
|
||||
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
||||
created_by UUID REFERENCES auth.users(id),
|
||||
|
||||
UNIQUE(tenant_id, from_currency_id, to_currency_id, rate_date)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_currency_rates_tenant ON core.currency_rates(tenant_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_currency_rates_from ON core.currency_rates(from_currency_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_currency_rates_to ON core.currency_rates(to_currency_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_currency_rates_date ON core.currency_rates(rate_date DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_currency_rates_lookup ON core.currency_rates(from_currency_id, to_currency_id, rate_date DESC);
|
||||
|
||||
COMMENT ON TABLE core.currency_rates IS 'Historico de tipos de cambio entre monedas';
|
||||
|
||||
-- =====================
|
||||
-- TABLA: uom_categories
|
||||
-- Categorias de unidades de medida
|
||||
-- =====================
|
||||
CREATE TABLE IF NOT EXISTS core.uom_categories (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID REFERENCES auth.tenants(id) ON DELETE CASCADE, -- NULL = global
|
||||
|
||||
name VARCHAR(100) NOT NULL,
|
||||
description TEXT,
|
||||
|
||||
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
UNIQUE(tenant_id, name)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_uom_categories_tenant ON core.uom_categories(tenant_id);
|
||||
|
||||
COMMENT ON TABLE core.uom_categories IS 'Categorias de unidades de medida (peso, volumen, longitud, etc.)';
|
||||
|
||||
-- =====================
|
||||
-- TABLA: uom
|
||||
-- Unidades de medida
|
||||
-- =====================
|
||||
CREATE TABLE IF NOT EXISTS core.uom (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID REFERENCES auth.tenants(id) ON DELETE CASCADE, -- NULL = global
|
||||
category_id UUID NOT NULL REFERENCES core.uom_categories(id) ON DELETE CASCADE,
|
||||
|
||||
name VARCHAR(100) NOT NULL,
|
||||
symbol VARCHAR(20) NOT NULL,
|
||||
|
||||
-- Tipo: reference = unidad base de la categoria
|
||||
uom_type VARCHAR(20) NOT NULL DEFAULT 'reference', -- reference, bigger, smaller
|
||||
|
||||
-- Factor de conversion respecto a la unidad de referencia de la categoria
|
||||
factor DECIMAL(18, 8) NOT NULL DEFAULT 1,
|
||||
rounding DECIMAL(12, 6) DEFAULT 0.01,
|
||||
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
|
||||
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
UNIQUE(tenant_id, category_id, name)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_uom_tenant ON core.uom(tenant_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_uom_category ON core.uom(category_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_uom_active ON core.uom(is_active) WHERE is_active = TRUE;
|
||||
|
||||
COMMENT ON TABLE core.uom IS 'Unidades de medida con factores de conversion';
|
||||
|
||||
-- =====================
|
||||
-- TABLA: product_categories
|
||||
-- Categorias jerarquicas de productos
|
||||
-- =====================
|
||||
CREATE TABLE IF NOT EXISTS core.product_categories (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
|
||||
parent_id UUID REFERENCES core.product_categories(id) ON DELETE SET NULL,
|
||||
|
||||
code VARCHAR(50),
|
||||
name VARCHAR(255) NOT NULL,
|
||||
description TEXT,
|
||||
|
||||
-- Jerarquia
|
||||
hierarchy_path TEXT,
|
||||
hierarchy_level INTEGER DEFAULT 0,
|
||||
|
||||
-- Configuracion
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
|
||||
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted_at TIMESTAMPTZ,
|
||||
|
||||
UNIQUE(tenant_id, code)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_product_categories_tenant ON core.product_categories(tenant_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_product_categories_parent ON core.product_categories(parent_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_product_categories_path ON core.product_categories(hierarchy_path);
|
||||
CREATE INDEX IF NOT EXISTS idx_product_categories_active ON core.product_categories(is_active) WHERE is_active = TRUE;
|
||||
|
||||
COMMENT ON TABLE core.product_categories IS 'Categorias jerarquicas de productos';
|
||||
|
||||
-- =====================
|
||||
-- TABLA: sequences
|
||||
-- Secuencias para numeracion automatica
|
||||
-- =====================
|
||||
CREATE TABLE IF NOT EXISTS core.sequences (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
|
||||
|
||||
code VARCHAR(50) NOT NULL,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
description TEXT,
|
||||
|
||||
-- Formato
|
||||
prefix VARCHAR(20),
|
||||
suffix VARCHAR(20),
|
||||
padding INTEGER DEFAULT 5,
|
||||
|
||||
-- Contador
|
||||
next_number BIGINT DEFAULT 1,
|
||||
|
||||
-- Reset
|
||||
reset_period VARCHAR(20) DEFAULT 'never', -- never, daily, monthly, yearly
|
||||
last_reset_at TIMESTAMPTZ,
|
||||
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
|
||||
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
UNIQUE(tenant_id, code)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_sequences_tenant ON core.sequences(tenant_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_sequences_code ON core.sequences(code);
|
||||
|
||||
COMMENT ON TABLE core.sequences IS 'Secuencias para numeracion automatica de documentos';
|
||||
|
||||
-- =====================
|
||||
-- TABLA: payment_terms
|
||||
-- Condiciones de pago
|
||||
-- =====================
|
||||
CREATE TABLE IF NOT EXISTS core.payment_terms (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
|
||||
|
||||
code VARCHAR(50) NOT NULL,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
description TEXT,
|
||||
|
||||
-- Configuracion
|
||||
is_immediate BOOLEAN DEFAULT FALSE, -- Pago inmediato/contado
|
||||
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
|
||||
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted_at TIMESTAMPTZ,
|
||||
|
||||
UNIQUE(tenant_id, code)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_payment_terms_tenant ON core.payment_terms(tenant_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_payment_terms_code ON core.payment_terms(code);
|
||||
|
||||
-- =====================
|
||||
-- TABLA: payment_term_lines
|
||||
-- Lineas de condiciones de pago
|
||||
-- =====================
|
||||
CREATE TABLE IF NOT EXISTS core.payment_term_lines (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
payment_term_id UUID NOT NULL REFERENCES core.payment_terms(id) ON DELETE CASCADE,
|
||||
|
||||
sequence INTEGER DEFAULT 0,
|
||||
|
||||
line_type VARCHAR(20) NOT NULL DEFAULT 'balance', -- percent, fixed, balance
|
||||
value_percent DECIMAL(5, 2), -- Para type=percent
|
||||
value_fixed DECIMAL(12, 2), -- Para type=fixed
|
||||
|
||||
days INTEGER DEFAULT 0, -- Dias para vencimiento
|
||||
day_of_month INTEGER, -- Dia especifico del mes (1-31)
|
||||
|
||||
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_payment_term_lines_term ON core.payment_term_lines(payment_term_id);
|
||||
|
||||
COMMENT ON TABLE core.payment_term_lines IS 'Lineas que componen una condicion de pago';
|
||||
|
||||
-- =====================
|
||||
-- TABLA: discount_rules
|
||||
-- Reglas de descuento
|
||||
-- =====================
|
||||
CREATE TABLE IF NOT EXISTS core.discount_rules (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
|
||||
|
||||
code VARCHAR(50) NOT NULL,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
description TEXT,
|
||||
|
||||
-- Tipo de descuento
|
||||
discount_type VARCHAR(20) NOT NULL DEFAULT 'percent', -- percent, fixed
|
||||
value DECIMAL(12, 2) NOT NULL,
|
||||
|
||||
-- A que aplica
|
||||
applies_to VARCHAR(30) DEFAULT 'all', -- all, product, category, customer, order
|
||||
|
||||
-- Condiciones (JSONB para flexibilidad)
|
||||
conditions JSONB DEFAULT '{}',
|
||||
-- Ejemplo: {"min_qty": 10, "min_amount": 1000, "product_ids": [...]}
|
||||
|
||||
-- Vigencia
|
||||
valid_from TIMESTAMPTZ,
|
||||
valid_until TIMESTAMPTZ,
|
||||
|
||||
-- Prioridad (para resolver conflictos)
|
||||
priority INTEGER DEFAULT 0,
|
||||
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
|
||||
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted_at TIMESTAMPTZ,
|
||||
|
||||
UNIQUE(tenant_id, code)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_discount_rules_tenant ON core.discount_rules(tenant_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_discount_rules_code ON core.discount_rules(code);
|
||||
CREATE INDEX IF NOT EXISTS idx_discount_rules_active ON core.discount_rules(is_active) WHERE is_active = TRUE;
|
||||
CREATE INDEX IF NOT EXISTS idx_discount_rules_validity ON core.discount_rules(valid_from, valid_until);
|
||||
|
||||
COMMENT ON TABLE core.discount_rules IS 'Reglas de descuento configurables';
|
||||
|
||||
-- =====================
|
||||
-- RLS POLICIES
|
||||
-- =====================
|
||||
|
||||
-- Currency rates: tenant isolation (NULL tenant = global, available to all)
|
||||
ALTER TABLE core.currency_rates ENABLE ROW LEVEL SECURITY;
|
||||
CREATE POLICY tenant_isolation_currency_rates ON core.currency_rates
|
||||
USING (
|
||||
tenant_id IS NULL OR
|
||||
tenant_id = current_setting('app.current_tenant_id', true)::uuid
|
||||
);
|
||||
|
||||
-- UoM Categories: tenant isolation
|
||||
ALTER TABLE core.uom_categories ENABLE ROW LEVEL SECURITY;
|
||||
CREATE POLICY tenant_isolation_uom_categories ON core.uom_categories
|
||||
USING (
|
||||
tenant_id IS NULL OR
|
||||
tenant_id = current_setting('app.current_tenant_id', true)::uuid
|
||||
);
|
||||
|
||||
-- UoM: tenant isolation
|
||||
ALTER TABLE core.uom ENABLE ROW LEVEL SECURITY;
|
||||
CREATE POLICY tenant_isolation_uom ON core.uom
|
||||
USING (
|
||||
tenant_id IS NULL OR
|
||||
tenant_id = current_setting('app.current_tenant_id', true)::uuid
|
||||
);
|
||||
|
||||
-- Product Categories: tenant isolation
|
||||
ALTER TABLE core.product_categories ENABLE ROW LEVEL SECURITY;
|
||||
CREATE POLICY tenant_isolation_product_categories ON core.product_categories
|
||||
USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid);
|
||||
|
||||
-- Sequences: tenant isolation
|
||||
ALTER TABLE core.sequences ENABLE ROW LEVEL SECURITY;
|
||||
CREATE POLICY tenant_isolation_sequences ON core.sequences
|
||||
USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid);
|
||||
|
||||
-- Payment Terms: tenant isolation
|
||||
ALTER TABLE core.payment_terms ENABLE ROW LEVEL SECURITY;
|
||||
CREATE POLICY tenant_isolation_payment_terms ON core.payment_terms
|
||||
USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid);
|
||||
|
||||
-- Discount Rules: tenant isolation
|
||||
ALTER TABLE core.discount_rules ENABLE ROW LEVEL SECURITY;
|
||||
CREATE POLICY tenant_isolation_discount_rules ON core.discount_rules
|
||||
USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid);
|
||||
|
||||
-- =====================
|
||||
-- FUNCIONES DE UTILIDAD
|
||||
-- =====================
|
||||
|
||||
-- Funcion para obtener tipo de cambio mas reciente
|
||||
CREATE OR REPLACE FUNCTION core.get_currency_rate(
|
||||
p_from_currency VARCHAR(3),
|
||||
p_to_currency VARCHAR(3),
|
||||
p_date DATE DEFAULT CURRENT_DATE,
|
||||
p_tenant_id UUID DEFAULT NULL
|
||||
)
|
||||
RETURNS DECIMAL(18, 8) AS $$
|
||||
DECLARE
|
||||
v_rate DECIMAL(18, 8);
|
||||
BEGIN
|
||||
-- Si son la misma moneda, retornar 1
|
||||
IF p_from_currency = p_to_currency THEN
|
||||
RETURN 1.0;
|
||||
END IF;
|
||||
|
||||
-- Buscar tipo de cambio directo (mas reciente hasta la fecha dada)
|
||||
SELECT cr.rate INTO v_rate
|
||||
FROM core.currency_rates cr
|
||||
JOIN core.currencies fc ON fc.id = cr.from_currency_id AND fc.code = p_from_currency
|
||||
JOIN core.currencies tc ON tc.id = cr.to_currency_id AND tc.code = p_to_currency
|
||||
WHERE cr.rate_date <= p_date
|
||||
AND (cr.tenant_id IS NULL OR cr.tenant_id = p_tenant_id)
|
||||
ORDER BY cr.rate_date DESC, cr.tenant_id DESC NULLS LAST
|
||||
LIMIT 1;
|
||||
|
||||
IF v_rate IS NOT NULL THEN
|
||||
RETURN v_rate;
|
||||
END IF;
|
||||
|
||||
-- Buscar tipo de cambio inverso
|
||||
SELECT 1.0 / cr.rate INTO v_rate
|
||||
FROM core.currency_rates cr
|
||||
JOIN core.currencies fc ON fc.id = cr.from_currency_id AND fc.code = p_to_currency
|
||||
JOIN core.currencies tc ON tc.id = cr.to_currency_id AND tc.code = p_from_currency
|
||||
WHERE cr.rate_date <= p_date
|
||||
AND (cr.tenant_id IS NULL OR cr.tenant_id = p_tenant_id)
|
||||
ORDER BY cr.rate_date DESC, cr.tenant_id DESC NULLS LAST
|
||||
LIMIT 1;
|
||||
|
||||
RETURN v_rate; -- NULL si no se encuentra
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- Funcion para convertir cantidad entre UoM
|
||||
CREATE OR REPLACE FUNCTION core.convert_uom(
|
||||
p_quantity DECIMAL,
|
||||
p_from_uom_id UUID,
|
||||
p_to_uom_id UUID
|
||||
)
|
||||
RETURNS DECIMAL(18, 8) AS $$
|
||||
DECLARE
|
||||
v_from_factor DECIMAL(18, 8);
|
||||
v_to_factor DECIMAL(18, 8);
|
||||
v_from_category UUID;
|
||||
v_to_category UUID;
|
||||
BEGIN
|
||||
-- Si son la misma UoM, retornar la cantidad
|
||||
IF p_from_uom_id = p_to_uom_id THEN
|
||||
RETURN p_quantity;
|
||||
END IF;
|
||||
|
||||
-- Obtener factores y categorias
|
||||
SELECT factor, category_id INTO v_from_factor, v_from_category
|
||||
FROM core.uom WHERE id = p_from_uom_id;
|
||||
|
||||
SELECT factor, category_id INTO v_to_factor, v_to_category
|
||||
FROM core.uom WHERE id = p_to_uom_id;
|
||||
|
||||
-- Validar que sean de la misma categoria
|
||||
IF v_from_category != v_to_category THEN
|
||||
RAISE EXCEPTION 'Cannot convert between different UoM categories';
|
||||
END IF;
|
||||
|
||||
-- Convertir: primero a unidad de referencia, luego a destino
|
||||
RETURN p_quantity * v_from_factor / v_to_factor;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- =====================
|
||||
-- FIN DEL ARCHIVO
|
||||
-- =====================
|
||||
@ -1,295 +0,0 @@
|
||||
-- =============================================================
|
||||
-- ARCHIVO: 21-fiscal-catalogs.sql
|
||||
-- DESCRIPCION: Catalogos fiscales - SAT Mexico, regimenes, CFDI
|
||||
-- VERSION: 1.0.0
|
||||
-- PROYECTO: ERP-Core V2
|
||||
-- FECHA: 2026-01-18
|
||||
-- =============================================================
|
||||
|
||||
-- =====================
|
||||
-- SCHEMA: fiscal
|
||||
-- =====================
|
||||
CREATE SCHEMA IF NOT EXISTS fiscal;
|
||||
|
||||
-- =====================
|
||||
-- TABLA: tax_categories
|
||||
-- Categorias de impuestos (IVA, ISR, IEPS, etc.)
|
||||
-- =====================
|
||||
CREATE TABLE IF NOT EXISTS fiscal.tax_categories (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
|
||||
code VARCHAR(20) NOT NULL UNIQUE, -- IVA, ISR, IEPS, etc.
|
||||
name VARCHAR(100) NOT NULL,
|
||||
description TEXT,
|
||||
|
||||
-- Tipo de impuesto
|
||||
tax_nature VARCHAR(20) NOT NULL DEFAULT 'tax', -- tax, withholding, both
|
||||
|
||||
-- Configuracion SAT Mexico
|
||||
sat_code VARCHAR(10), -- Codigo SAT (002=IVA, 001=ISR, etc.)
|
||||
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
|
||||
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_tax_categories_code ON fiscal.tax_categories(code);
|
||||
CREATE INDEX IF NOT EXISTS idx_tax_categories_sat ON fiscal.tax_categories(sat_code);
|
||||
CREATE INDEX IF NOT EXISTS idx_tax_categories_active ON fiscal.tax_categories(is_active) WHERE is_active = TRUE;
|
||||
|
||||
COMMENT ON TABLE fiscal.tax_categories IS 'Categorias de impuestos (IVA, ISR, IEPS, etc.)';
|
||||
|
||||
-- =====================
|
||||
-- TABLA: fiscal_regimes
|
||||
-- Regimenes fiscales SAT Mexico (Catalogo c_RegimenFiscal)
|
||||
-- =====================
|
||||
CREATE TABLE IF NOT EXISTS fiscal.fiscal_regimes (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
|
||||
code VARCHAR(10) NOT NULL UNIQUE, -- Codigo SAT (601, 603, 612, etc.)
|
||||
name VARCHAR(255) NOT NULL,
|
||||
description TEXT,
|
||||
|
||||
-- Aplica a persona fisica o moral
|
||||
applies_to VARCHAR(20) NOT NULL DEFAULT 'both', -- natural (fisica), legal (moral), both
|
||||
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
|
||||
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_fiscal_regimes_code ON fiscal.fiscal_regimes(code);
|
||||
CREATE INDEX IF NOT EXISTS idx_fiscal_regimes_applies ON fiscal.fiscal_regimes(applies_to);
|
||||
CREATE INDEX IF NOT EXISTS idx_fiscal_regimes_active ON fiscal.fiscal_regimes(is_active) WHERE is_active = TRUE;
|
||||
|
||||
COMMENT ON TABLE fiscal.fiscal_regimes IS 'Catalogo de regimenes fiscales SAT (c_RegimenFiscal)';
|
||||
|
||||
-- =====================
|
||||
-- TABLA: cfdi_uses
|
||||
-- Uso del CFDI SAT Mexico (Catalogo c_UsoCFDI)
|
||||
-- =====================
|
||||
CREATE TABLE IF NOT EXISTS fiscal.cfdi_uses (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
|
||||
code VARCHAR(10) NOT NULL UNIQUE, -- Codigo SAT (G01, G02, G03, etc.)
|
||||
name VARCHAR(255) NOT NULL,
|
||||
description TEXT,
|
||||
|
||||
-- Aplica a persona fisica o moral
|
||||
applies_to VARCHAR(20) NOT NULL DEFAULT 'both', -- natural, legal, both
|
||||
|
||||
-- Regimenes fiscales permitidos (NULL = todos)
|
||||
allowed_regimes VARCHAR[] DEFAULT NULL,
|
||||
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
|
||||
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_cfdi_uses_code ON fiscal.cfdi_uses(code);
|
||||
CREATE INDEX IF NOT EXISTS idx_cfdi_uses_applies ON fiscal.cfdi_uses(applies_to);
|
||||
CREATE INDEX IF NOT EXISTS idx_cfdi_uses_active ON fiscal.cfdi_uses(is_active) WHERE is_active = TRUE;
|
||||
|
||||
COMMENT ON TABLE fiscal.cfdi_uses IS 'Catalogo de uso del CFDI SAT (c_UsoCFDI)';
|
||||
|
||||
-- =====================
|
||||
-- TABLA: payment_methods
|
||||
-- Formas de pago SAT Mexico (Catalogo c_FormaPago)
|
||||
-- =====================
|
||||
CREATE TABLE IF NOT EXISTS fiscal.payment_methods (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
|
||||
code VARCHAR(10) NOT NULL UNIQUE, -- Codigo SAT (01, 02, 03, etc.)
|
||||
name VARCHAR(100) NOT NULL,
|
||||
description TEXT,
|
||||
|
||||
-- Configuracion
|
||||
requires_bank_info BOOLEAN DEFAULT FALSE, -- Requiere info bancaria
|
||||
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
|
||||
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_payment_methods_code ON fiscal.payment_methods(code);
|
||||
CREATE INDEX IF NOT EXISTS idx_payment_methods_active ON fiscal.payment_methods(is_active) WHERE is_active = TRUE;
|
||||
|
||||
COMMENT ON TABLE fiscal.payment_methods IS 'Catalogo de formas de pago SAT (c_FormaPago)';
|
||||
|
||||
-- =====================
|
||||
-- TABLA: payment_types
|
||||
-- Metodos de pago SAT Mexico (Catalogo c_MetodoPago)
|
||||
-- =====================
|
||||
CREATE TABLE IF NOT EXISTS fiscal.payment_types (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
|
||||
code VARCHAR(10) NOT NULL UNIQUE, -- PUE, PPD
|
||||
name VARCHAR(100) NOT NULL,
|
||||
description TEXT,
|
||||
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
|
||||
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_payment_types_code ON fiscal.payment_types(code);
|
||||
CREATE INDEX IF NOT EXISTS idx_payment_types_active ON fiscal.payment_types(is_active) WHERE is_active = TRUE;
|
||||
|
||||
COMMENT ON TABLE fiscal.payment_types IS 'Catalogo de metodos de pago SAT (c_MetodoPago)';
|
||||
|
||||
-- =====================
|
||||
-- TABLA: withholding_types
|
||||
-- Tipos de retencion
|
||||
-- =====================
|
||||
CREATE TABLE IF NOT EXISTS fiscal.withholding_types (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
|
||||
code VARCHAR(20) NOT NULL UNIQUE,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
description TEXT,
|
||||
|
||||
-- Tasa de retencion por defecto
|
||||
default_rate DECIMAL(5, 2) NOT NULL DEFAULT 0,
|
||||
|
||||
-- Categoria de impuesto asociada
|
||||
tax_category_id UUID REFERENCES fiscal.tax_categories(id),
|
||||
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
|
||||
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_withholding_types_code ON fiscal.withholding_types(code);
|
||||
CREATE INDEX IF NOT EXISTS idx_withholding_types_category ON fiscal.withholding_types(tax_category_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_withholding_types_active ON fiscal.withholding_types(is_active) WHERE is_active = TRUE;
|
||||
|
||||
COMMENT ON TABLE fiscal.withholding_types IS 'Tipos de retencion fiscal';
|
||||
|
||||
-- =====================
|
||||
-- DATOS INICIALES: tax_categories
|
||||
-- =====================
|
||||
INSERT INTO fiscal.tax_categories (code, name, description, tax_nature, sat_code) VALUES
|
||||
('IVA', 'Impuesto al Valor Agregado', 'Impuesto al consumo aplicado a bienes y servicios', 'tax', '002'),
|
||||
('ISR', 'Impuesto Sobre la Renta', 'Impuesto a los ingresos', 'withholding', '001'),
|
||||
('IEPS', 'Impuesto Especial sobre Produccion y Servicios', 'Impuesto a productos especiales', 'tax', '003'),
|
||||
('IVA_RET', 'IVA Retenido', 'Retencion de IVA', 'withholding', '002'),
|
||||
('ISR_RET', 'ISR Retenido', 'Retencion de ISR', 'withholding', '001'),
|
||||
('CEDULAR', 'Impuesto Cedular', 'Impuesto local sobre ingresos', 'tax', NULL),
|
||||
('ISH', 'Impuesto Sobre Hospedaje', 'Impuesto estatal al hospedaje', 'tax', NULL)
|
||||
ON CONFLICT (code) DO NOTHING;
|
||||
|
||||
-- =====================
|
||||
-- DATOS INICIALES: fiscal_regimes (SAT Mexico c_RegimenFiscal)
|
||||
-- =====================
|
||||
INSERT INTO fiscal.fiscal_regimes (code, name, applies_to) VALUES
|
||||
('601', 'General de Ley Personas Morales', 'legal'),
|
||||
('603', 'Personas Morales con Fines no Lucrativos', 'legal'),
|
||||
('605', 'Sueldos y Salarios e Ingresos Asimilados a Salarios', 'natural'),
|
||||
('606', 'Arrendamiento', 'natural'),
|
||||
('607', 'Régimen de Enajenación o Adquisición de Bienes', 'natural'),
|
||||
('608', 'Demás ingresos', 'natural'),
|
||||
('609', 'Consolidación', 'legal'),
|
||||
('610', 'Residentes en el Extranjero sin Establecimiento Permanente en México', 'both'),
|
||||
('611', 'Ingresos por Dividendos (socios y accionistas)', 'natural'),
|
||||
('612', 'Personas Físicas con Actividades Empresariales y Profesionales', 'natural'),
|
||||
('614', 'Ingresos por intereses', 'natural'),
|
||||
('615', 'Régimen de los ingresos por obtención de premios', 'natural'),
|
||||
('616', 'Sin obligaciones fiscales', 'both'),
|
||||
('620', 'Sociedades Cooperativas de Producción que optan por diferir sus ingresos', 'legal'),
|
||||
('621', 'Incorporación Fiscal', 'natural'),
|
||||
('622', 'Actividades Agrícolas, Ganaderas, Silvícolas y Pesqueras', 'both'),
|
||||
('623', 'Opcional para Grupos de Sociedades', 'legal'),
|
||||
('624', 'Coordinados', 'legal'),
|
||||
('625', 'Régimen de las Actividades Empresariales con ingresos a través de Plataformas Tecnológicas', 'natural'),
|
||||
('626', 'Régimen Simplificado de Confianza', 'both')
|
||||
ON CONFLICT (code) DO NOTHING;
|
||||
|
||||
-- =====================
|
||||
-- DATOS INICIALES: cfdi_uses (SAT Mexico c_UsoCFDI)
|
||||
-- =====================
|
||||
INSERT INTO fiscal.cfdi_uses (code, name, applies_to) VALUES
|
||||
('G01', 'Adquisición de mercancías', 'both'),
|
||||
('G02', 'Devoluciones, descuentos o bonificaciones', 'both'),
|
||||
('G03', 'Gastos en general', 'both'),
|
||||
('I01', 'Construcciones', 'both'),
|
||||
('I02', 'Mobiliario y equipo de oficina por inversiones', 'both'),
|
||||
('I03', 'Equipo de transporte', 'both'),
|
||||
('I04', 'Equipo de cómputo y accesorios', 'both'),
|
||||
('I05', 'Dados, troqueles, moldes, matrices y herramental', 'both'),
|
||||
('I06', 'Comunicaciones telefónicas', 'both'),
|
||||
('I07', 'Comunicaciones satelitales', 'both'),
|
||||
('I08', 'Otra maquinaria y equipo', 'both'),
|
||||
('D01', 'Honorarios médicos, dentales y gastos hospitalarios', 'natural'),
|
||||
('D02', 'Gastos médicos por incapacidad o discapacidad', 'natural'),
|
||||
('D03', 'Gastos funerales', 'natural'),
|
||||
('D04', 'Donativos', 'natural'),
|
||||
('D05', 'Intereses reales efectivamente pagados por créditos hipotecarios (casa habitación)', 'natural'),
|
||||
('D06', 'Aportaciones voluntarias al SAR', 'natural'),
|
||||
('D07', 'Primas por seguros de gastos médicos', 'natural'),
|
||||
('D08', 'Gastos de transportación escolar obligatoria', 'natural'),
|
||||
('D09', 'Depósitos en cuentas para el ahorro, primas que tengan como base planes de pensiones', 'natural'),
|
||||
('D10', 'Pagos por servicios educativos (colegiaturas)', 'natural'),
|
||||
('S01', 'Sin efectos fiscales', 'both'),
|
||||
('CP01', 'Pagos', 'both'),
|
||||
('CN01', 'Nómina', 'natural')
|
||||
ON CONFLICT (code) DO NOTHING;
|
||||
|
||||
-- =====================
|
||||
-- DATOS INICIALES: payment_methods (SAT Mexico c_FormaPago)
|
||||
-- =====================
|
||||
INSERT INTO fiscal.payment_methods (code, name, requires_bank_info) VALUES
|
||||
('01', 'Efectivo', false),
|
||||
('02', 'Cheque nominativo', true),
|
||||
('03', 'Transferencia electrónica de fondos', true),
|
||||
('04', 'Tarjeta de crédito', true),
|
||||
('05', 'Monedero electrónico', false),
|
||||
('06', 'Dinero electrónico', false),
|
||||
('08', 'Vales de despensa', false),
|
||||
('12', 'Dación en pago', false),
|
||||
('13', 'Pago por subrogación', false),
|
||||
('14', 'Pago por consignación', false),
|
||||
('15', 'Condonación', false),
|
||||
('17', 'Compensación', false),
|
||||
('23', 'Novación', false),
|
||||
('24', 'Confusión', false),
|
||||
('25', 'Remisión de deuda', false),
|
||||
('26', 'Prescripción o caducidad', false),
|
||||
('27', 'A satisfacción del acreedor', false),
|
||||
('28', 'Tarjeta de débito', true),
|
||||
('29', 'Tarjeta de servicios', true),
|
||||
('30', 'Aplicación de anticipos', false),
|
||||
('31', 'Intermediario pagos', false),
|
||||
('99', 'Por definir', false)
|
||||
ON CONFLICT (code) DO NOTHING;
|
||||
|
||||
-- =====================
|
||||
-- DATOS INICIALES: payment_types (SAT Mexico c_MetodoPago)
|
||||
-- =====================
|
||||
INSERT INTO fiscal.payment_types (code, name, description) VALUES
|
||||
('PUE', 'Pago en una sola exhibición', 'El pago se realiza en una sola exhibición al momento de emitir el CFDI'),
|
||||
('PPD', 'Pago en parcialidades o diferido', 'El pago se realiza en parcialidades o de forma diferida')
|
||||
ON CONFLICT (code) DO NOTHING;
|
||||
|
||||
-- =====================
|
||||
-- DATOS INICIALES: withholding_types
|
||||
-- =====================
|
||||
INSERT INTO fiscal.withholding_types (code, name, description, default_rate, tax_category_id) VALUES
|
||||
('ISR_10', 'Retención ISR 10%', 'Retención de ISR al 10% para servicios profesionales', 10.00,
|
||||
(SELECT id FROM fiscal.tax_categories WHERE code = 'ISR')),
|
||||
('ISR_10.67', 'Retención ISR 10.67%', 'Retención de ISR al 10.67% para arrendamiento', 10.67,
|
||||
(SELECT id FROM fiscal.tax_categories WHERE code = 'ISR')),
|
||||
('IVA_RET_10.67', 'Retención IVA 10.67%', 'Retención de IVA para servicios profesionales persona moral', 10.67,
|
||||
(SELECT id FROM fiscal.tax_categories WHERE code = 'IVA_RET')),
|
||||
('IVA_RET_4', 'Retención IVA 4%', 'Retención de IVA al 4% para autotransporte', 4.00,
|
||||
(SELECT id FROM fiscal.tax_categories WHERE code = 'IVA_RET'))
|
||||
ON CONFLICT (code) DO NOTHING;
|
||||
|
||||
-- =====================
|
||||
-- FIN DEL ARCHIVO
|
||||
-- =====================
|
||||
@ -1,140 +0,0 @@
|
||||
-- =============================================================
|
||||
-- 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';
|
||||
@ -1,154 +0,0 @@
|
||||
-- =============================================================
|
||||
-- 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';
|
||||
@ -1,143 +0,0 @@
|
||||
-- =============================================================
|
||||
-- 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';
|
||||
@ -1,162 +0,0 @@
|
||||
-- =============================================================
|
||||
-- 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';
|
||||
@ -1,175 +0,0 @@
|
||||
-- =============================================================
|
||||
-- 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)';
|
||||
@ -1,167 +0,0 @@
|
||||
-- =============================================================
|
||||
-- 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';
|
||||
@ -1,174 +0,0 @@
|
||||
-- =============================================================
|
||||
-- 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';
|
||||
@ -1,155 +0,0 @@
|
||||
-- =============================================================
|
||||
-- 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';
|
||||
@ -1,254 +0,0 @@
|
||||
-- =============================================================
|
||||
-- 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)';
|
||||
@ -1,381 +0,0 @@
|
||||
-- =============================================================
|
||||
-- ARCHIVO: 60-projects-timesheets.sql
|
||||
-- DESCRIPCION: Schema de proyectos con soporte para timesheets
|
||||
-- VERSION: 1.0.0
|
||||
-- PROYECTO: ERP-Core V2
|
||||
-- FECHA: 2026-01-20
|
||||
-- DEPENDE DE: auth schema (tenants, users, companies)
|
||||
-- =============================================================
|
||||
|
||||
-- =====================
|
||||
-- SCHEMA: projects
|
||||
-- Schema para modulo de gestion de proyectos
|
||||
-- =====================
|
||||
CREATE SCHEMA IF NOT EXISTS projects;
|
||||
|
||||
-- =====================
|
||||
-- EXTENSIONES REQUERIDAS
|
||||
-- =====================
|
||||
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
||||
|
||||
-- =====================
|
||||
-- TIPOS ENUMERADOS
|
||||
-- =====================
|
||||
|
||||
-- Estado del proyecto
|
||||
DO $$ BEGIN
|
||||
CREATE TYPE projects.project_status_enum AS ENUM (
|
||||
'draft', -- Borrador
|
||||
'active', -- Activo
|
||||
'completed', -- Completado
|
||||
'cancelled', -- Cancelado
|
||||
'on_hold' -- En espera
|
||||
);
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
-- Privacidad del proyecto
|
||||
DO $$ BEGIN
|
||||
CREATE TYPE projects.project_privacy_enum AS ENUM (
|
||||
'public', -- Publico (visible para todos)
|
||||
'private', -- Privado (solo miembros)
|
||||
'followers' -- Solo seguidores
|
||||
);
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
-- Estado de la tarea
|
||||
DO $$ BEGIN
|
||||
CREATE TYPE projects.task_status_enum AS ENUM (
|
||||
'todo', -- Por hacer
|
||||
'in_progress', -- En progreso
|
||||
'review', -- En revision
|
||||
'done', -- Completada
|
||||
'cancelled' -- Cancelada
|
||||
);
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
-- Prioridad de la tarea
|
||||
DO $$ BEGIN
|
||||
CREATE TYPE projects.task_priority_enum AS ENUM (
|
||||
'low', -- Baja
|
||||
'normal', -- Normal
|
||||
'high', -- Alta
|
||||
'urgent' -- Urgente
|
||||
);
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
-- Estado del timesheet
|
||||
DO $$ BEGIN
|
||||
CREATE TYPE projects.timesheet_status_enum AS ENUM (
|
||||
'draft', -- Borrador
|
||||
'submitted', -- Enviado para aprobacion
|
||||
'approved', -- Aprobado
|
||||
'rejected' -- Rechazado
|
||||
);
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
-- Estado del milestone
|
||||
DO $$ BEGIN
|
||||
CREATE TYPE projects.milestone_status_enum AS ENUM (
|
||||
'pending', -- Pendiente
|
||||
'completed', -- Completado
|
||||
'cancelled' -- Cancelado
|
||||
);
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
-- =====================
|
||||
-- TABLA: projects
|
||||
-- Proyectos
|
||||
-- =====================
|
||||
CREATE TABLE IF NOT EXISTS projects.projects (
|
||||
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 NOT NULL,
|
||||
|
||||
-- Identificacion
|
||||
name VARCHAR(255) NOT NULL,
|
||||
code VARCHAR(50),
|
||||
description TEXT,
|
||||
|
||||
-- Responsables
|
||||
manager_id UUID REFERENCES auth.users(id) ON DELETE SET NULL,
|
||||
partner_id UUID, -- Cliente asociado al proyecto
|
||||
|
||||
-- Cuenta analitica (para integracion contable)
|
||||
analytic_account_id UUID,
|
||||
|
||||
-- Fechas
|
||||
date_start DATE,
|
||||
date_end DATE,
|
||||
|
||||
-- Estado y configuracion
|
||||
status projects.project_status_enum DEFAULT 'draft',
|
||||
privacy projects.project_privacy_enum DEFAULT 'public',
|
||||
allow_timesheets BOOLEAN DEFAULT TRUE,
|
||||
color VARCHAR(20),
|
||||
|
||||
-- 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,
|
||||
deleted_by UUID REFERENCES auth.users(id),
|
||||
|
||||
-- Constraint de unicidad por codigo dentro de company
|
||||
UNIQUE(company_id, code)
|
||||
);
|
||||
|
||||
-- Indices para projects
|
||||
CREATE INDEX IF NOT EXISTS idx_projects_tenant ON projects.projects(tenant_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_projects_company ON projects.projects(company_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_projects_manager ON projects.projects(manager_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_projects_partner ON projects.projects(partner_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_projects_status ON projects.projects(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_projects_active ON projects.projects(tenant_id) WHERE deleted_at IS NULL;
|
||||
CREATE INDEX IF NOT EXISTS idx_projects_code ON projects.projects(code);
|
||||
|
||||
-- =====================
|
||||
-- TABLA: project_stages
|
||||
-- Etapas/columnas del tablero Kanban
|
||||
-- =====================
|
||||
CREATE TABLE IF NOT EXISTS projects.project_stages (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
|
||||
-- Multi-tenant
|
||||
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
|
||||
|
||||
-- Puede ser global o por proyecto
|
||||
project_id UUID REFERENCES projects.projects(id) ON DELETE CASCADE,
|
||||
|
||||
-- Identificacion
|
||||
name VARCHAR(100) NOT NULL,
|
||||
sequence INT DEFAULT 0,
|
||||
|
||||
-- Configuracion
|
||||
fold BOOLEAN DEFAULT FALSE, -- Columna plegada por defecto
|
||||
is_closed BOOLEAN DEFAULT FALSE, -- Indica etapa de cierre
|
||||
|
||||
-- Audit columns
|
||||
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Indices para project_stages
|
||||
CREATE INDEX IF NOT EXISTS idx_project_stages_tenant ON projects.project_stages(tenant_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_project_stages_project ON projects.project_stages(project_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_project_stages_sequence ON projects.project_stages(sequence);
|
||||
|
||||
-- =====================
|
||||
-- TABLA: tasks
|
||||
-- Tareas del proyecto
|
||||
-- =====================
|
||||
CREATE TABLE IF NOT EXISTS projects.tasks (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
|
||||
-- Multi-tenant
|
||||
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
|
||||
|
||||
-- Relaciones
|
||||
project_id UUID NOT NULL REFERENCES projects.projects(id) ON DELETE CASCADE,
|
||||
stage_id UUID REFERENCES projects.project_stages(id) ON DELETE SET NULL,
|
||||
parent_id UUID REFERENCES projects.tasks(id) ON DELETE SET NULL,
|
||||
|
||||
-- Identificacion
|
||||
name VARCHAR(255) NOT NULL,
|
||||
description TEXT,
|
||||
|
||||
-- Asignacion
|
||||
assigned_to UUID REFERENCES auth.users(id) ON DELETE SET NULL,
|
||||
|
||||
-- Fechas
|
||||
date_deadline DATE,
|
||||
|
||||
-- Estimacion y seguimiento
|
||||
estimated_hours DECIMAL(10,2) DEFAULT 0,
|
||||
|
||||
-- Estado y prioridad
|
||||
priority projects.task_priority_enum DEFAULT 'normal',
|
||||
status projects.task_status_enum DEFAULT 'todo',
|
||||
|
||||
-- Ordenamiento
|
||||
sequence INT DEFAULT 0,
|
||||
color VARCHAR(20),
|
||||
|
||||
-- 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,
|
||||
deleted_by UUID REFERENCES auth.users(id)
|
||||
);
|
||||
|
||||
-- Indices para tasks
|
||||
CREATE INDEX IF NOT EXISTS idx_tasks_tenant ON projects.tasks(tenant_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_tasks_project ON projects.tasks(project_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_tasks_stage ON projects.tasks(stage_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_tasks_parent ON projects.tasks(parent_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_tasks_assigned ON projects.tasks(assigned_to);
|
||||
CREATE INDEX IF NOT EXISTS idx_tasks_status ON projects.tasks(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_tasks_priority ON projects.tasks(priority);
|
||||
CREATE INDEX IF NOT EXISTS idx_tasks_deadline ON projects.tasks(date_deadline);
|
||||
CREATE INDEX IF NOT EXISTS idx_tasks_active ON projects.tasks(tenant_id, project_id) WHERE deleted_at IS NULL;
|
||||
CREATE INDEX IF NOT EXISTS idx_tasks_sequence ON projects.tasks(project_id, sequence);
|
||||
|
||||
-- =====================
|
||||
-- TABLA: milestones
|
||||
-- Hitos del proyecto
|
||||
-- =====================
|
||||
CREATE TABLE IF NOT EXISTS projects.milestones (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
|
||||
-- Multi-tenant
|
||||
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
|
||||
|
||||
-- Relacion
|
||||
project_id UUID NOT NULL REFERENCES projects.projects(id) ON DELETE CASCADE,
|
||||
|
||||
-- Identificacion
|
||||
name VARCHAR(255) NOT NULL,
|
||||
description TEXT,
|
||||
|
||||
-- Fecha objetivo
|
||||
date_deadline DATE,
|
||||
|
||||
-- Estado
|
||||
status projects.milestone_status_enum DEFAULT 'pending',
|
||||
|
||||
-- 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 milestones
|
||||
CREATE INDEX IF NOT EXISTS idx_milestones_tenant ON projects.milestones(tenant_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_milestones_project ON projects.milestones(project_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_milestones_status ON projects.milestones(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_milestones_deadline ON projects.milestones(date_deadline);
|
||||
|
||||
-- =====================
|
||||
-- TABLA: timesheets
|
||||
-- Registro de horas trabajadas (estilo Odoo)
|
||||
-- =====================
|
||||
CREATE TABLE IF NOT EXISTS projects.timesheets (
|
||||
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 NOT NULL,
|
||||
|
||||
-- Relaciones
|
||||
project_id UUID NOT NULL REFERENCES projects.projects(id) ON DELETE CASCADE,
|
||||
task_id UUID REFERENCES projects.tasks(id) ON DELETE SET NULL,
|
||||
user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
|
||||
|
||||
-- Datos del registro
|
||||
date DATE NOT NULL,
|
||||
hours DECIMAL(5,2) NOT NULL CHECK (hours >= 0 AND hours <= 24),
|
||||
description TEXT,
|
||||
|
||||
-- Facturacion
|
||||
billable BOOLEAN DEFAULT TRUE,
|
||||
invoiced BOOLEAN DEFAULT FALSE,
|
||||
invoice_id UUID, -- FK a factura cuando se facture
|
||||
|
||||
-- Aprobacion
|
||||
status projects.timesheet_status_enum DEFAULT 'draft',
|
||||
approved_by UUID REFERENCES auth.users(id),
|
||||
approved_at TIMESTAMPTZ,
|
||||
|
||||
-- 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 timesheets
|
||||
CREATE INDEX IF NOT EXISTS idx_timesheets_tenant ON projects.timesheets(tenant_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_timesheets_company ON projects.timesheets(company_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_timesheets_project ON projects.timesheets(project_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_timesheets_task ON projects.timesheets(task_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_timesheets_user ON projects.timesheets(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_timesheets_user_date ON projects.timesheets(user_id, date);
|
||||
CREATE INDEX IF NOT EXISTS idx_timesheets_date ON projects.timesheets(date);
|
||||
CREATE INDEX IF NOT EXISTS idx_timesheets_status ON projects.timesheets(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_timesheets_billable ON projects.timesheets(billable) WHERE billable = TRUE;
|
||||
CREATE INDEX IF NOT EXISTS idx_timesheets_not_invoiced ON projects.timesheets(project_id, invoiced) WHERE invoiced = FALSE;
|
||||
CREATE INDEX IF NOT EXISTS idx_timesheets_invoice ON projects.timesheets(invoice_id);
|
||||
|
||||
-- =====================
|
||||
-- TABLA: project_members
|
||||
-- Miembros del equipo del proyecto
|
||||
-- =====================
|
||||
CREATE TABLE IF NOT EXISTS projects.project_members (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
|
||||
-- Multi-tenant
|
||||
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
|
||||
|
||||
-- Relaciones
|
||||
project_id UUID NOT NULL REFERENCES projects.projects(id) ON DELETE CASCADE,
|
||||
user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
|
||||
|
||||
-- Rol en el proyecto
|
||||
role VARCHAR(50) DEFAULT 'member', -- member, contributor, viewer
|
||||
|
||||
-- Audit columns
|
||||
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
-- Un usuario solo puede ser miembro una vez por proyecto
|
||||
UNIQUE(project_id, user_id)
|
||||
);
|
||||
|
||||
-- Indices para project_members
|
||||
CREATE INDEX IF NOT EXISTS idx_project_members_tenant ON projects.project_members(tenant_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_project_members_project ON projects.project_members(project_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_project_members_user ON projects.project_members(user_id);
|
||||
|
||||
-- =====================
|
||||
-- COMENTARIOS
|
||||
-- =====================
|
||||
COMMENT ON SCHEMA projects IS 'Schema para gestion de proyectos: proyectos, tareas, timesheets, milestones';
|
||||
|
||||
COMMENT ON TABLE projects.projects IS 'Proyectos con seguimiento de tareas y timesheets';
|
||||
COMMENT ON COLUMN projects.projects.allow_timesheets IS 'TRUE si el proyecto permite registro de horas';
|
||||
COMMENT ON COLUMN projects.projects.analytic_account_id IS 'Cuenta analitica para integracion contable';
|
||||
|
||||
COMMENT ON TABLE projects.project_stages IS 'Etapas del tablero Kanban (columnas)';
|
||||
COMMENT ON COLUMN projects.project_stages.fold IS 'TRUE si la columna debe mostrarse plegada';
|
||||
COMMENT ON COLUMN projects.project_stages.is_closed IS 'TRUE si representa una etapa de cierre/completado';
|
||||
|
||||
COMMENT ON TABLE projects.tasks IS 'Tareas asignables a proyectos';
|
||||
COMMENT ON COLUMN projects.tasks.estimated_hours IS 'Horas estimadas para completar la tarea';
|
||||
COMMENT ON COLUMN projects.tasks.parent_id IS 'Referencia a tarea padre para subtareas';
|
||||
|
||||
COMMENT ON TABLE projects.milestones IS 'Hitos importantes del proyecto';
|
||||
|
||||
COMMENT ON TABLE projects.timesheets IS 'Registro de horas trabajadas estilo Odoo';
|
||||
COMMENT ON COLUMN projects.timesheets.hours IS 'Horas trabajadas (0-24 por dia)';
|
||||
COMMENT ON COLUMN projects.timesheets.billable IS 'TRUE si las horas son facturables al cliente';
|
||||
COMMENT ON COLUMN projects.timesheets.invoiced IS 'TRUE si ya fue incluido en una factura';
|
||||
COMMENT ON COLUMN projects.timesheets.status IS 'Flujo de aprobacion: draft -> submitted -> approved/rejected';
|
||||
|
||||
COMMENT ON TABLE projects.project_members IS 'Miembros del equipo del proyecto';
|
||||
COMMENT ON COLUMN projects.project_members.role IS 'Rol: member (puede editar), contributor (puede agregar), viewer (solo lectura)';
|
||||
@ -1,259 +0,0 @@
|
||||
-- =============================================================
|
||||
-- ARCHIVO: 04-seed-catalogs.sql
|
||||
-- DESCRIPCION: Datos semilla para catalogos maestros
|
||||
-- VERSION: 1.0.0
|
||||
-- PROYECTO: ERP-Core V2
|
||||
-- FECHA: 2026-01-18
|
||||
-- =============================================================
|
||||
|
||||
-- =====================
|
||||
-- PAISES (ISO 3166-1)
|
||||
-- =====================
|
||||
|
||||
INSERT INTO core.countries (code, code_alpha3, name, phone_code, currency_code) VALUES
|
||||
-- America del Norte
|
||||
('MX', 'MEX', 'México', '+52', 'MXN'),
|
||||
('US', 'USA', 'Estados Unidos', '+1', 'USD'),
|
||||
('CA', 'CAN', 'Canadá', '+1', 'CAD'),
|
||||
-- America Central
|
||||
('GT', 'GTM', 'Guatemala', '+502', 'GTQ'),
|
||||
('BZ', 'BLZ', 'Belice', '+501', 'BZD'),
|
||||
('SV', 'SLV', 'El Salvador', '+503', 'USD'),
|
||||
('HN', 'HND', 'Honduras', '+504', 'HNL'),
|
||||
('NI', 'NIC', 'Nicaragua', '+505', 'NIO'),
|
||||
('CR', 'CRI', 'Costa Rica', '+506', 'CRC'),
|
||||
('PA', 'PAN', 'Panamá', '+507', 'PAB'),
|
||||
-- America del Sur
|
||||
('CO', 'COL', 'Colombia', '+57', 'COP'),
|
||||
('VE', 'VEN', 'Venezuela', '+58', 'VES'),
|
||||
('EC', 'ECU', 'Ecuador', '+593', 'USD'),
|
||||
('PE', 'PER', 'Perú', '+51', 'PEN'),
|
||||
('BO', 'BOL', 'Bolivia', '+591', 'BOB'),
|
||||
('CL', 'CHL', 'Chile', '+56', 'CLP'),
|
||||
('AR', 'ARG', 'Argentina', '+54', 'ARS'),
|
||||
('UY', 'URY', 'Uruguay', '+598', 'UYU'),
|
||||
('PY', 'PRY', 'Paraguay', '+595', 'PYG'),
|
||||
('BR', 'BRA', 'Brasil', '+55', 'BRL'),
|
||||
-- Europa
|
||||
('ES', 'ESP', 'España', '+34', 'EUR'),
|
||||
('DE', 'DEU', 'Alemania', '+49', 'EUR'),
|
||||
('FR', 'FRA', 'Francia', '+33', 'EUR'),
|
||||
('IT', 'ITA', 'Italia', '+39', 'EUR'),
|
||||
('GB', 'GBR', 'Reino Unido', '+44', 'GBP'),
|
||||
('PT', 'PRT', 'Portugal', '+351', 'EUR'),
|
||||
('NL', 'NLD', 'Países Bajos', '+31', 'EUR'),
|
||||
('BE', 'BEL', 'Bélgica', '+32', 'EUR'),
|
||||
('CH', 'CHE', 'Suiza', '+41', 'CHF'),
|
||||
-- Asia
|
||||
('CN', 'CHN', 'China', '+86', 'CNY'),
|
||||
('JP', 'JPN', 'Japón', '+81', 'JPY'),
|
||||
('KR', 'KOR', 'Corea del Sur', '+82', 'KRW'),
|
||||
('IN', 'IND', 'India', '+91', 'INR')
|
||||
ON CONFLICT (code) DO NOTHING;
|
||||
|
||||
-- =====================
|
||||
-- ESTADOS DE MEXICO
|
||||
-- =====================
|
||||
|
||||
-- Obtener ID de México
|
||||
DO $$
|
||||
DECLARE
|
||||
mexico_id UUID;
|
||||
BEGIN
|
||||
SELECT id INTO mexico_id FROM core.countries WHERE code = 'MX';
|
||||
|
||||
IF mexico_id IS NOT NULL THEN
|
||||
INSERT INTO core.states (country_id, code, name, timezone, is_active) VALUES
|
||||
(mexico_id, 'AGU', 'Aguascalientes', 'America/Mexico_City', true),
|
||||
(mexico_id, 'BCN', 'Baja California', 'America/Tijuana', true),
|
||||
(mexico_id, 'BCS', 'Baja California Sur', 'America/Mazatlan', true),
|
||||
(mexico_id, 'CAM', 'Campeche', 'America/Merida', true),
|
||||
(mexico_id, 'CHP', 'Chiapas', 'America/Mexico_City', true),
|
||||
(mexico_id, 'CHH', 'Chihuahua', 'America/Chihuahua', true),
|
||||
(mexico_id, 'CMX', 'Ciudad de México', 'America/Mexico_City', true),
|
||||
(mexico_id, 'COA', 'Coahuila', 'America/Monterrey', true),
|
||||
(mexico_id, 'COL', 'Colima', 'America/Mexico_City', true),
|
||||
(mexico_id, 'DUR', 'Durango', 'America/Monterrey', true),
|
||||
(mexico_id, 'GUA', 'Guanajuato', 'America/Mexico_City', true),
|
||||
(mexico_id, 'GRO', 'Guerrero', 'America/Mexico_City', true),
|
||||
(mexico_id, 'HID', 'Hidalgo', 'America/Mexico_City', true),
|
||||
(mexico_id, 'JAL', 'Jalisco', 'America/Mexico_City', true),
|
||||
(mexico_id, 'MEX', 'Estado de México', 'America/Mexico_City', true),
|
||||
(mexico_id, 'MIC', 'Michoacán', 'America/Mexico_City', true),
|
||||
(mexico_id, 'MOR', 'Morelos', 'America/Mexico_City', true),
|
||||
(mexico_id, 'NAY', 'Nayarit', 'America/Mazatlan', true),
|
||||
(mexico_id, 'NLE', 'Nuevo León', 'America/Monterrey', true),
|
||||
(mexico_id, 'OAX', 'Oaxaca', 'America/Mexico_City', true),
|
||||
(mexico_id, 'PUE', 'Puebla', 'America/Mexico_City', true),
|
||||
(mexico_id, 'QUE', 'Querétaro', 'America/Mexico_City', true),
|
||||
(mexico_id, 'ROO', 'Quintana Roo', 'America/Cancun', true),
|
||||
(mexico_id, 'SLP', 'San Luis Potosí', 'America/Mexico_City', true),
|
||||
(mexico_id, 'SIN', 'Sinaloa', 'America/Mazatlan', true),
|
||||
(mexico_id, 'SON', 'Sonora', 'America/Hermosillo', true),
|
||||
(mexico_id, 'TAB', 'Tabasco', 'America/Mexico_City', true),
|
||||
(mexico_id, 'TAM', 'Tamaulipas', 'America/Monterrey', true),
|
||||
(mexico_id, 'TLA', 'Tlaxcala', 'America/Mexico_City', true),
|
||||
(mexico_id, 'VER', 'Veracruz', 'America/Mexico_City', true),
|
||||
(mexico_id, 'YUC', 'Yucatán', 'America/Merida', true),
|
||||
(mexico_id, 'ZAC', 'Zacatecas', 'America/Mexico_City', true)
|
||||
ON CONFLICT (country_id, code) DO NOTHING;
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- =====================
|
||||
-- MONEDAS (ISO 4217)
|
||||
-- =====================
|
||||
|
||||
INSERT INTO core.currencies (code, name, symbol, decimals, rounding, active) VALUES
|
||||
-- Americas
|
||||
('MXN', 'Peso Mexicano', '$', 2, 0.01, true),
|
||||
('USD', 'Dólar Estadounidense', '$', 2, 0.01, true),
|
||||
('CAD', 'Dólar Canadiense', '$', 2, 0.01, true),
|
||||
('BRL', 'Real Brasileño', 'R$', 2, 0.01, true),
|
||||
('ARS', 'Peso Argentino', '$', 2, 0.01, true),
|
||||
('CLP', 'Peso Chileno', '$', 0, 1, true),
|
||||
('COP', 'Peso Colombiano', '$', 0, 1, true),
|
||||
('PEN', 'Sol Peruano', 'S/', 2, 0.01, true),
|
||||
('GTQ', 'Quetzal', 'Q', 2, 0.01, true),
|
||||
-- Europa
|
||||
('EUR', 'Euro', '€', 2, 0.01, true),
|
||||
('GBP', 'Libra Esterlina', '£', 2, 0.01, true),
|
||||
('CHF', 'Franco Suizo', 'Fr', 2, 0.01, true),
|
||||
-- Asia
|
||||
('JPY', 'Yen Japonés', '¥', 0, 1, true),
|
||||
('CNY', 'Yuan Chino', '¥', 2, 0.01, true),
|
||||
('KRW', 'Won Surcoreano', '₩', 0, 1, true),
|
||||
('INR', 'Rupia India', '₹', 2, 0.01, true)
|
||||
ON CONFLICT (code) DO NOTHING;
|
||||
|
||||
-- =====================
|
||||
-- TIPOS DE CAMBIO INICIALES (MXN base)
|
||||
-- =====================
|
||||
|
||||
DO $$
|
||||
DECLARE
|
||||
mxn_id UUID;
|
||||
usd_id UUID;
|
||||
eur_id UUID;
|
||||
cad_id UUID;
|
||||
BEGIN
|
||||
SELECT id INTO mxn_id FROM core.currencies WHERE code = 'MXN';
|
||||
SELECT id INTO usd_id FROM core.currencies WHERE code = 'USD';
|
||||
SELECT id INTO eur_id FROM core.currencies WHERE code = 'EUR';
|
||||
SELECT id INTO cad_id FROM core.currencies WHERE code = 'CAD';
|
||||
|
||||
IF mxn_id IS NOT NULL AND usd_id IS NOT NULL THEN
|
||||
INSERT INTO core.currency_rates (tenant_id, from_currency_id, to_currency_id, rate, rate_date, source)
|
||||
VALUES
|
||||
(NULL, usd_id, mxn_id, 17.50, CURRENT_DATE, 'manual'),
|
||||
(NULL, eur_id, mxn_id, 19.20, CURRENT_DATE, 'manual'),
|
||||
(NULL, cad_id, mxn_id, 13.10, CURRENT_DATE, 'manual')
|
||||
ON CONFLICT DO NOTHING;
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- =====================
|
||||
-- CATEGORIAS DE UOM (Globales)
|
||||
-- =====================
|
||||
|
||||
INSERT INTO core.uom_categories (tenant_id, name, description) VALUES
|
||||
(NULL, 'Unidad', 'Unidades contables'),
|
||||
(NULL, 'Peso', 'Unidades de peso/masa'),
|
||||
(NULL, 'Longitud', 'Unidades de longitud'),
|
||||
(NULL, 'Volumen', 'Unidades de volumen'),
|
||||
(NULL, 'Área', 'Unidades de área'),
|
||||
(NULL, 'Tiempo', 'Unidades de tiempo')
|
||||
ON CONFLICT (tenant_id, name) DO NOTHING;
|
||||
|
||||
-- =====================
|
||||
-- UOM (Globales)
|
||||
-- =====================
|
||||
|
||||
DO $$
|
||||
DECLARE
|
||||
cat_unit UUID;
|
||||
cat_peso UUID;
|
||||
cat_longitud UUID;
|
||||
cat_volumen UUID;
|
||||
cat_area UUID;
|
||||
cat_tiempo UUID;
|
||||
BEGIN
|
||||
SELECT id INTO cat_unit FROM core.uom_categories WHERE name = 'Unidad' AND tenant_id IS NULL;
|
||||
SELECT id INTO cat_peso FROM core.uom_categories WHERE name = 'Peso' AND tenant_id IS NULL;
|
||||
SELECT id INTO cat_longitud FROM core.uom_categories WHERE name = 'Longitud' AND tenant_id IS NULL;
|
||||
SELECT id INTO cat_volumen FROM core.uom_categories WHERE name = 'Volumen' AND tenant_id IS NULL;
|
||||
SELECT id INTO cat_area FROM core.uom_categories WHERE name = 'Área' AND tenant_id IS NULL;
|
||||
SELECT id INTO cat_tiempo FROM core.uom_categories WHERE name = 'Tiempo' AND tenant_id IS NULL;
|
||||
|
||||
-- Unidad
|
||||
IF cat_unit IS NOT NULL THEN
|
||||
INSERT INTO core.uom (tenant_id, category_id, name, symbol, uom_type, factor, is_active) VALUES
|
||||
(NULL, cat_unit, 'Pieza', 'pz', 'reference', 1, true),
|
||||
(NULL, cat_unit, 'Docena', 'doc', 'bigger', 12, true),
|
||||
(NULL, cat_unit, 'Ciento', 'cto', 'bigger', 100, true),
|
||||
(NULL, cat_unit, 'Millar', 'mil', 'bigger', 1000, true),
|
||||
(NULL, cat_unit, 'Par', 'par', 'bigger', 2, true)
|
||||
ON CONFLICT (tenant_id, category_id, name) DO NOTHING;
|
||||
END IF;
|
||||
|
||||
-- Peso
|
||||
IF cat_peso IS NOT NULL THEN
|
||||
INSERT INTO core.uom (tenant_id, category_id, name, symbol, uom_type, factor, is_active) VALUES
|
||||
(NULL, cat_peso, 'Kilogramo', 'kg', 'reference', 1, true),
|
||||
(NULL, cat_peso, 'Gramo', 'g', 'smaller', 0.001, true),
|
||||
(NULL, cat_peso, 'Miligramo', 'mg', 'smaller', 0.000001, true),
|
||||
(NULL, cat_peso, 'Tonelada', 't', 'bigger', 1000, true),
|
||||
(NULL, cat_peso, 'Libra', 'lb', 'smaller', 0.453592, true),
|
||||
(NULL, cat_peso, 'Onza', 'oz', 'smaller', 0.0283495, true)
|
||||
ON CONFLICT (tenant_id, category_id, name) DO NOTHING;
|
||||
END IF;
|
||||
|
||||
-- Longitud
|
||||
IF cat_longitud IS NOT NULL THEN
|
||||
INSERT INTO core.uom (tenant_id, category_id, name, symbol, uom_type, factor, is_active) VALUES
|
||||
(NULL, cat_longitud, 'Metro', 'm', 'reference', 1, true),
|
||||
(NULL, cat_longitud, 'Centímetro', 'cm', 'smaller', 0.01, true),
|
||||
(NULL, cat_longitud, 'Milímetro', 'mm', 'smaller', 0.001, true),
|
||||
(NULL, cat_longitud, 'Kilómetro', 'km', 'bigger', 1000, true),
|
||||
(NULL, cat_longitud, 'Pulgada', 'in', 'smaller', 0.0254, true),
|
||||
(NULL, cat_longitud, 'Pie', 'ft', 'smaller', 0.3048, true),
|
||||
(NULL, cat_longitud, 'Yarda', 'yd', 'smaller', 0.9144, true)
|
||||
ON CONFLICT (tenant_id, category_id, name) DO NOTHING;
|
||||
END IF;
|
||||
|
||||
-- Volumen
|
||||
IF cat_volumen IS NOT NULL THEN
|
||||
INSERT INTO core.uom (tenant_id, category_id, name, symbol, uom_type, factor, is_active) VALUES
|
||||
(NULL, cat_volumen, 'Litro', 'L', 'reference', 1, true),
|
||||
(NULL, cat_volumen, 'Mililitro', 'mL', 'smaller', 0.001, true),
|
||||
(NULL, cat_volumen, 'Metro cúbico', 'm³', 'bigger', 1000, true),
|
||||
(NULL, cat_volumen, 'Galón US', 'gal', 'bigger', 3.78541, true),
|
||||
(NULL, cat_volumen, 'Onza líquida', 'fl oz', 'smaller', 0.0295735, true)
|
||||
ON CONFLICT (tenant_id, category_id, name) DO NOTHING;
|
||||
END IF;
|
||||
|
||||
-- Área
|
||||
IF cat_area IS NOT NULL THEN
|
||||
INSERT INTO core.uom (tenant_id, category_id, name, symbol, uom_type, factor, is_active) VALUES
|
||||
(NULL, cat_area, 'Metro cuadrado', 'm²', 'reference', 1, true),
|
||||
(NULL, cat_area, 'Centímetro cuadrado', 'cm²', 'smaller', 0.0001, true),
|
||||
(NULL, cat_area, 'Kilómetro cuadrado', 'km²', 'bigger', 1000000, true),
|
||||
(NULL, cat_area, 'Hectárea', 'ha', 'bigger', 10000, true),
|
||||
(NULL, cat_area, 'Pie cuadrado', 'ft²', 'smaller', 0.092903, true)
|
||||
ON CONFLICT (tenant_id, category_id, name) DO NOTHING;
|
||||
END IF;
|
||||
|
||||
-- Tiempo
|
||||
IF cat_tiempo IS NOT NULL THEN
|
||||
INSERT INTO core.uom (tenant_id, category_id, name, symbol, uom_type, factor, is_active) VALUES
|
||||
(NULL, cat_tiempo, 'Hora', 'h', 'reference', 1, true),
|
||||
(NULL, cat_tiempo, 'Minuto', 'min', 'smaller', 0.0166667, true),
|
||||
(NULL, cat_tiempo, 'Día', 'd', 'bigger', 24, true),
|
||||
(NULL, cat_tiempo, 'Semana', 'sem', 'bigger', 168, true)
|
||||
ON CONFLICT (tenant_id, category_id, name) DO NOTHING;
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- =====================
|
||||
-- FIN DEL ARCHIVO
|
||||
-- =====================
|
||||
Loading…
Reference in New Issue
Block a user