erp-core-database-v2/ddl/19-product-attributes.sql
Adrian Flores Cortes c9ddeb8b5a [TASK-2026-02-03] feat: Add RLS policies to 19-product-attributes.sql
- Added Row Level Security for product_attributes, product_variants
- Added tenant isolation policies for related tables
- Prepared for execution in WSL

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 17:49:41 -06:00

185 lines
7.9 KiB
SQL

-- =============================================================
-- ARCHIVO: 19-product-attributes.sql
-- DESCRIPCION: Atributos de productos (color, talla, material)
-- VERSION: 1.0.0
-- PROYECTO: ERP-Core V2
-- FECHA: 2026-01-24
-- =============================================================
-- =====================
-- TABLA: product_attributes
-- Atributos configurables de productos
-- =====================
CREATE TABLE IF NOT EXISTS products.product_attributes (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
-- Identificacion
code VARCHAR(50) NOT NULL,
name VARCHAR(100) NOT NULL,
description TEXT,
-- Tipo de visualizacion
display_type VARCHAR(20) DEFAULT 'radio', -- radio, select, color, pills
-- Estado
is_active BOOLEAN DEFAULT TRUE,
sort_order INTEGER DEFAULT 0,
-- Metadata
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
created_by UUID REFERENCES auth.users(id),
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
updated_by UUID REFERENCES auth.users(id),
UNIQUE(tenant_id, code)
);
-- Indices para product_attributes
CREATE INDEX IF NOT EXISTS idx_product_attributes_tenant ON products.product_attributes(tenant_id);
CREATE INDEX IF NOT EXISTS idx_product_attributes_code ON products.product_attributes(code);
CREATE INDEX IF NOT EXISTS idx_product_attributes_active ON products.product_attributes(is_active) WHERE is_active = TRUE;
-- =====================
-- TABLA: product_attribute_values
-- Valores posibles para cada atributo
-- =====================
CREATE TABLE IF NOT EXISTS products.product_attribute_values (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
attribute_id UUID NOT NULL REFERENCES products.product_attributes(id) ON DELETE CASCADE,
-- Identificacion
code VARCHAR(50),
name VARCHAR(100) NOT NULL,
-- Visualizacion
html_color VARCHAR(20), -- Para atributos de tipo color (#FF0000)
image_url VARCHAR(500), -- Imagen del valor
-- Estado
is_active BOOLEAN DEFAULT TRUE,
sort_order INTEGER DEFAULT 0,
-- Metadata
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
-- Indices para product_attribute_values
CREATE INDEX IF NOT EXISTS idx_product_attribute_values_attribute ON products.product_attribute_values(attribute_id);
CREATE INDEX IF NOT EXISTS idx_product_attribute_values_active ON products.product_attribute_values(is_active) WHERE is_active = TRUE;
-- =====================
-- TABLA: product_variants
-- Variantes de productos (combinaciones de atributos)
-- =====================
CREATE TABLE IF NOT EXISTS products.product_variants (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
product_id UUID NOT NULL REFERENCES products.products(id) ON DELETE CASCADE,
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
-- Identificacion
sku VARCHAR(50) NOT NULL,
barcode VARCHAR(50),
name VARCHAR(200) NOT NULL, -- Nombre de la variante (ej: "Camisa Azul - Talla M")
-- Precios (override del producto base)
price_extra DECIMAL(15, 4) DEFAULT 0, -- Ajuste de precio sobre el producto base
cost_extra DECIMAL(15, 4) DEFAULT 0, -- Ajuste de costo sobre el producto base
-- Inventario
stock_qty DECIMAL(15, 4) DEFAULT 0,
-- Imagen
image_url VARCHAR(500),
-- Estado
is_active BOOLEAN DEFAULT TRUE,
-- Metadata
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
created_by UUID REFERENCES auth.users(id),
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
updated_by UUID REFERENCES auth.users(id),
UNIQUE(tenant_id, sku)
);
-- Indices para product_variants
CREATE INDEX IF NOT EXISTS idx_product_variants_product ON products.product_variants(product_id);
CREATE INDEX IF NOT EXISTS idx_product_variants_tenant ON products.product_variants(tenant_id);
CREATE INDEX IF NOT EXISTS idx_product_variants_sku ON products.product_variants(sku);
CREATE INDEX IF NOT EXISTS idx_product_variants_barcode ON products.product_variants(barcode);
CREATE INDEX IF NOT EXISTS idx_product_variants_active ON products.product_variants(is_active) WHERE is_active = TRUE;
-- =====================
-- TABLA: product_variant_attributes
-- Relacion M:N entre variantes y valores de atributos
-- =====================
CREATE TABLE IF NOT EXISTS products.product_variant_attributes (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
variant_id UUID NOT NULL REFERENCES products.product_variants(id) ON DELETE CASCADE,
attribute_value_id UUID NOT NULL REFERENCES products.product_attribute_values(id) ON DELETE CASCADE,
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
UNIQUE(variant_id, attribute_value_id)
);
-- Indices para product_variant_attributes
CREATE INDEX IF NOT EXISTS idx_product_variant_attributes_variant ON products.product_variant_attributes(variant_id);
CREATE INDEX IF NOT EXISTS idx_product_variant_attributes_value ON products.product_variant_attributes(attribute_value_id);
-- =====================
-- COMENTARIOS
-- =====================
COMMENT ON TABLE products.product_attributes IS 'Definicion de atributos de productos (color, talla, material, etc.)';
COMMENT ON COLUMN products.product_attributes.display_type IS 'Tipo de visualizacion: radio (botones radio), select (dropdown), color (muestra de colores), pills (etiquetas)';
COMMENT ON TABLE products.product_attribute_values IS 'Valores posibles para cada atributo (ej: Rojo, Verde, Azul para atributo Color)';
COMMENT ON COLUMN products.product_attribute_values.html_color IS 'Color HTML para visualizar en UI (#FF0000)';
COMMENT ON TABLE products.product_variants IS 'Variantes de productos generadas a partir de combinaciones de atributos';
COMMENT ON COLUMN products.product_variants.price_extra IS 'Ajuste de precio sobre el precio base del producto';
COMMENT ON COLUMN products.product_variants.cost_extra IS 'Ajuste de costo sobre el costo base del producto';
COMMENT ON TABLE products.product_variant_attributes IS 'Tabla de union entre variantes y valores de atributos';
-- =====================
-- RLS (Row Level Security)
-- =====================
ALTER TABLE products.product_attributes ENABLE ROW LEVEL SECURITY;
ALTER TABLE products.product_attribute_values ENABLE ROW LEVEL SECURITY;
ALTER TABLE products.product_variants ENABLE ROW LEVEL SECURITY;
ALTER TABLE products.product_variant_attributes ENABLE ROW LEVEL SECURITY;
-- Politicas RLS para product_attributes
DROP POLICY IF EXISTS product_attributes_tenant_isolation ON products.product_attributes;
CREATE POLICY product_attributes_tenant_isolation ON products.product_attributes
FOR ALL USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid);
-- Politicas RLS para product_variants
DROP POLICY IF EXISTS product_variants_tenant_isolation ON products.product_variants;
CREATE POLICY product_variants_tenant_isolation ON products.product_variants
FOR ALL USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid);
-- Politicas RLS para product_attribute_values (via JOIN con attribute)
DROP POLICY IF EXISTS product_attribute_values_tenant_isolation ON products.product_attribute_values;
CREATE POLICY product_attribute_values_tenant_isolation ON products.product_attribute_values
FOR ALL USING (
attribute_id IN (
SELECT id FROM products.product_attributes
WHERE tenant_id = current_setting('app.current_tenant_id', true)::uuid
)
);
-- Politicas RLS para product_variant_attributes (via JOIN con variant)
DROP POLICY IF EXISTS product_variant_attributes_tenant_isolation ON products.product_variant_attributes;
CREATE POLICY product_variant_attributes_tenant_isolation ON products.product_variant_attributes
FOR ALL USING (
variant_id IN (
SELECT id FROM products.product_variants
WHERE tenant_id = current_setting('app.current_tenant_id', true)::uuid
)
);