-- ============================================================================ -- INVENTORY EXTENSION Schema DDL - Extensiones de Inventario para Construcción -- Modulos: MAI-004 (Compras e Inventarios) -- Version: 1.0.0 -- Fecha: 2025-12-08 -- ============================================================================ -- TIPO: Extensión del ERP Core (MGN-005 Inventory) -- NOTA: Contiene SOLO extensiones específicas de construcción. -- Las tablas base están en el ERP Core. -- ============================================================================ -- PREREQUISITOS: -- 1. ERP-Core instalado (auth.tenants, auth.users) -- 2. Schema construction instalado (fraccionamientos, conceptos, lotes, departamentos) -- 3. Schema inventory de ERP-Core instalado (opcional, para FKs) -- ============================================================================ -- Verificar prerequisitos DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_namespace WHERE nspname = 'auth') THEN RAISE EXCEPTION 'Schema auth no existe. Ejecutar primero ERP-Core DDL'; END IF; IF NOT EXISTS (SELECT 1 FROM pg_namespace WHERE nspname = 'construction') THEN RAISE EXCEPTION 'Schema construction no existe. Ejecutar primero construction DDL'; END IF; END $$; -- Crear schema si no existe (puede ya existir desde ERP-Core) CREATE SCHEMA IF NOT EXISTS inventory; -- ============================================================================ -- TYPES (ENUMs) ADICIONALES -- ============================================================================ DO $$ BEGIN CREATE TYPE inventory.warehouse_type_construction AS ENUM ( 'central', 'obra', 'temporal', 'transito' ); EXCEPTION WHEN duplicate_object THEN NULL; END $$; DO $$ BEGIN CREATE TYPE inventory.requisition_status AS ENUM ( 'draft', 'submitted', 'approved', 'partially_served', 'served', 'cancelled' ); EXCEPTION WHEN duplicate_object THEN NULL; END $$; -- ============================================================================ -- TABLES - EXTENSIONES CONSTRUCCIÓN -- ============================================================================ -- Tabla: almacenes_proyecto (almacén por proyecto/obra) -- Extiende: inventory.warehouses (ERP Core) CREATE TABLE IF NOT EXISTS inventory.almacenes_proyecto ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE, warehouse_id UUID NOT NULL, -- FK a inventory.warehouses (ERP Core) fraccionamiento_id UUID NOT NULL REFERENCES construction.fraccionamientos(id), warehouse_type inventory.warehouse_type_construction NOT NULL DEFAULT 'obra', location_description TEXT, location GEOMETRY(POINT, 4326), responsible_id UUID REFERENCES auth.users(id), is_active BOOLEAN NOT NULL DEFAULT TRUE, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), created_by UUID REFERENCES auth.users(id), updated_at TIMESTAMPTZ, updated_by UUID REFERENCES auth.users(id), deleted_at TIMESTAMPTZ, deleted_by UUID REFERENCES auth.users(id), CONSTRAINT uq_almacenes_proyecto_warehouse UNIQUE (warehouse_id) ); -- Tabla: requisiciones_obra (requisiciones desde obra) CREATE TABLE IF NOT EXISTS inventory.requisiciones_obra ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE, fraccionamiento_id UUID NOT NULL REFERENCES construction.fraccionamientos(id), requisition_number VARCHAR(30) NOT NULL, requisition_date DATE NOT NULL, required_date DATE NOT NULL, status inventory.requisition_status NOT NULL DEFAULT 'draft', priority VARCHAR(20) DEFAULT 'medium', requested_by UUID NOT NULL REFERENCES auth.users(id), destination_warehouse_id UUID, -- FK a inventory.warehouses (ERP Core) approved_by UUID REFERENCES auth.users(id), approved_at TIMESTAMPTZ, rejection_reason TEXT, purchase_order_id UUID, -- FK a purchase.purchase_orders (ERP Core) notes TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), created_by UUID REFERENCES auth.users(id), updated_at TIMESTAMPTZ, updated_by UUID REFERENCES auth.users(id), deleted_at TIMESTAMPTZ, deleted_by UUID REFERENCES auth.users(id), CONSTRAINT uq_requisiciones_obra_number UNIQUE (tenant_id, requisition_number) ); -- Tabla: requisicion_lineas (líneas de requisición) CREATE TABLE IF NOT EXISTS inventory.requisicion_lineas ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE, requisicion_id UUID NOT NULL REFERENCES inventory.requisiciones_obra(id) ON DELETE CASCADE, product_id UUID NOT NULL, -- FK a inventory.products (ERP Core) concepto_id UUID REFERENCES construction.conceptos(id), lote_id UUID REFERENCES construction.lotes(id), quantity_requested DECIMAL(12,4) NOT NULL, quantity_approved DECIMAL(12,4), quantity_served DECIMAL(12,4) DEFAULT 0, unit_id UUID, -- FK a core.uom (ERP Core) notes TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), created_by UUID REFERENCES auth.users(id), updated_at TIMESTAMPTZ, updated_by UUID REFERENCES auth.users(id), deleted_at TIMESTAMPTZ, deleted_by UUID REFERENCES auth.users(id) ); -- Tabla: consumos_obra (consumos de materiales por obra/lote) CREATE TABLE IF NOT EXISTS inventory.consumos_obra ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE, stock_move_id UUID, -- FK a inventory.stock_moves (ERP Core) fraccionamiento_id UUID NOT NULL REFERENCES construction.fraccionamientos(id), lote_id UUID REFERENCES construction.lotes(id), departamento_id UUID REFERENCES construction.departamentos(id), concepto_id UUID REFERENCES construction.conceptos(id), product_id UUID NOT NULL, -- FK a inventory.products (ERP Core) quantity DECIMAL(12,4) NOT NULL, unit_cost DECIMAL(12,4), total_cost DECIMAL(14,2) GENERATED ALWAYS AS (quantity * unit_cost) STORED, consumption_date DATE NOT NULL, registered_by UUID NOT NULL REFERENCES auth.users(id), notes TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), created_by UUID REFERENCES auth.users(id), updated_at TIMESTAMPTZ, updated_by UUID REFERENCES auth.users(id), deleted_at TIMESTAMPTZ, deleted_by UUID REFERENCES auth.users(id) ); -- ============================================================================ -- INDICES -- ============================================================================ CREATE INDEX IF NOT EXISTS idx_almacenes_proyecto_tenant_id ON inventory.almacenes_proyecto(tenant_id); CREATE INDEX IF NOT EXISTS idx_almacenes_proyecto_warehouse_id ON inventory.almacenes_proyecto(warehouse_id); CREATE INDEX IF NOT EXISTS idx_almacenes_proyecto_fraccionamiento_id ON inventory.almacenes_proyecto(fraccionamiento_id); CREATE INDEX IF NOT EXISTS idx_requisiciones_obra_tenant_id ON inventory.requisiciones_obra(tenant_id); CREATE INDEX IF NOT EXISTS idx_requisiciones_obra_fraccionamiento_id ON inventory.requisiciones_obra(fraccionamiento_id); CREATE INDEX IF NOT EXISTS idx_requisiciones_obra_status ON inventory.requisiciones_obra(status); CREATE INDEX IF NOT EXISTS idx_requisiciones_obra_date ON inventory.requisiciones_obra(requisition_date); CREATE INDEX IF NOT EXISTS idx_requisiciones_obra_required_date ON inventory.requisiciones_obra(required_date); CREATE INDEX IF NOT EXISTS idx_requisicion_lineas_tenant_id ON inventory.requisicion_lineas(tenant_id); CREATE INDEX IF NOT EXISTS idx_requisicion_lineas_requisicion_id ON inventory.requisicion_lineas(requisicion_id); CREATE INDEX IF NOT EXISTS idx_requisicion_lineas_product_id ON inventory.requisicion_lineas(product_id); CREATE INDEX IF NOT EXISTS idx_consumos_obra_tenant_id ON inventory.consumos_obra(tenant_id); CREATE INDEX IF NOT EXISTS idx_consumos_obra_fraccionamiento_id ON inventory.consumos_obra(fraccionamiento_id); CREATE INDEX IF NOT EXISTS idx_consumos_obra_lote_id ON inventory.consumos_obra(lote_id); CREATE INDEX IF NOT EXISTS idx_consumos_obra_concepto_id ON inventory.consumos_obra(concepto_id); CREATE INDEX IF NOT EXISTS idx_consumos_obra_product_id ON inventory.consumos_obra(product_id); CREATE INDEX IF NOT EXISTS idx_consumos_obra_date ON inventory.consumos_obra(consumption_date); -- ============================================================================ -- ROW LEVEL SECURITY (RLS) -- ============================================================================ ALTER TABLE inventory.almacenes_proyecto ENABLE ROW LEVEL SECURITY; ALTER TABLE inventory.requisiciones_obra ENABLE ROW LEVEL SECURITY; ALTER TABLE inventory.requisicion_lineas ENABLE ROW LEVEL SECURITY; ALTER TABLE inventory.consumos_obra ENABLE ROW LEVEL SECURITY; DO $$ BEGIN DROP POLICY IF EXISTS tenant_isolation_almacenes_proyecto ON inventory.almacenes_proyecto; CREATE POLICY tenant_isolation_almacenes_proyecto ON inventory.almacenes_proyecto FOR ALL USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID); EXCEPTION WHEN undefined_object THEN NULL; END $$; DO $$ BEGIN DROP POLICY IF EXISTS tenant_isolation_requisiciones_obra ON inventory.requisiciones_obra; CREATE POLICY tenant_isolation_requisiciones_obra ON inventory.requisiciones_obra FOR ALL USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID); EXCEPTION WHEN undefined_object THEN NULL; END $$; DO $$ BEGIN DROP POLICY IF EXISTS tenant_isolation_requisicion_lineas ON inventory.requisicion_lineas; CREATE POLICY tenant_isolation_requisicion_lineas ON inventory.requisicion_lineas FOR ALL USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID); EXCEPTION WHEN undefined_object THEN NULL; END $$; DO $$ BEGIN DROP POLICY IF EXISTS tenant_isolation_consumos_obra ON inventory.consumos_obra; CREATE POLICY tenant_isolation_consumos_obra ON inventory.consumos_obra FOR ALL USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID); EXCEPTION WHEN undefined_object THEN NULL; END $$; -- ============================================================================ -- COMENTARIOS -- ============================================================================ COMMENT ON TABLE inventory.almacenes_proyecto IS 'Extensión: almacenes por proyecto de construcción'; COMMENT ON TABLE inventory.requisiciones_obra IS 'Extensión: requisiciones de material desde obra'; COMMENT ON TABLE inventory.requisicion_lineas IS 'Extensión: líneas de requisición de obra'; COMMENT ON TABLE inventory.consumos_obra IS 'Extensión: consumos de materiales por obra/lote'; -- ============================================================================ -- FIN DE EXTENSIONES INVENTORY -- Total tablas: 4 -- ============================================================================