-- =====================================================
-- SCHEMA: inventory
-- PROPÓSITO: Gestión de inventarios, productos, almacenes, movimientos
-- MÓDULOS: MGN-005 (Inventario Básico)
-- FECHA: 2025-11-24
-- =====================================================

-- Crear schema
CREATE SCHEMA IF NOT EXISTS inventory;

-- =====================================================
-- TYPES (ENUMs)
-- =====================================================

CREATE TYPE inventory.product_type AS ENUM (
  'storable',
  'consumable',
  'service'
);

CREATE TYPE inventory.tracking_type AS ENUM (
  'none',
  'lot',
  'serial'
);

CREATE TYPE inventory.location_type AS ENUM (
  'internal',
  'customer',
  'supplier',
  'inventory',
  'production',
  'transit'
);

CREATE TYPE inventory.picking_type AS ENUM (
  'incoming',
  'outgoing',
  'internal'
);

CREATE TYPE inventory.move_status AS ENUM (
  'draft',
  'confirmed',
  'assigned',
  'done',
  'cancelled'
);

CREATE TYPE inventory.valuation_method AS ENUM (
  'fifo',
  'average',
  'standard'
);

-- =====================================================
-- TABLES
-- =====================================================

-- Tabla: products (Productos)
CREATE TABLE inventory.products (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,

  -- Identificación
  name VARCHAR(255) NOT NULL,
  code VARCHAR(100),
  barcode VARCHAR(100),
  description TEXT,

  -- Tipo
  product_type inventory.product_type NOT NULL DEFAULT 'storable',
  tracking inventory.tracking_type NOT NULL DEFAULT 'none',

  -- Categoría
  category_id UUID REFERENCES core.product_categories(id),

  -- Unidades de medida
  uom_id UUID NOT NULL REFERENCES core.uom(id), -- UoM de venta/uso
  purchase_uom_id UUID REFERENCES core.uom(id), -- UoM de compra

  -- Precios
  cost_price DECIMAL(15, 4) DEFAULT 0,
  list_price DECIMAL(15, 4) DEFAULT 0,

  -- Configuración de inventario
  valuation_method inventory.valuation_method DEFAULT 'fifo',
  is_storable BOOLEAN GENERATED ALWAYS AS (product_type = 'storable') STORED,

  -- Pesos y dimensiones
  weight DECIMAL(12, 4),
  volume DECIMAL(12, 4),

  -- Proveedores y clientes
  can_be_sold BOOLEAN DEFAULT TRUE,
  can_be_purchased BOOLEAN DEFAULT TRUE,

  -- Imagen
  image_url VARCHAR(500),

  -- Control
  active BOOLEAN NOT NULL DEFAULT TRUE,

  -- Auditoría
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  created_by UUID REFERENCES auth.users(id),
  updated_at TIMESTAMP,
  updated_by UUID REFERENCES auth.users(id),
  deleted_at TIMESTAMP,
  deleted_by UUID REFERENCES auth.users(id),

  CONSTRAINT uq_products_code_tenant UNIQUE (tenant_id, code),
  CONSTRAINT uq_products_barcode UNIQUE (barcode)
);

-- Tabla: product_variants (Variantes de producto)
CREATE TABLE inventory.product_variants (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  product_template_id UUID NOT NULL REFERENCES inventory.products(id) ON DELETE CASCADE,

  -- Atributos (JSON)
  -- Ejemplo: {"color": "red", "size": "XL"}
  attribute_values JSONB NOT NULL DEFAULT '{}',

  -- Identificación
  name VARCHAR(255),
  code VARCHAR(100),
  barcode VARCHAR(100),

  -- Precio diferencial
  price_extra DECIMAL(15, 4) DEFAULT 0,

  -- Control
  active BOOLEAN NOT NULL DEFAULT TRUE,

  -- Auditoría
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  created_by UUID REFERENCES auth.users(id),

  CONSTRAINT uq_product_variants_barcode UNIQUE (barcode)
);

-- Tabla: warehouses (Almacenes)
CREATE TABLE inventory.warehouses (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
  company_id UUID NOT NULL REFERENCES auth.companies(id) ON DELETE CASCADE,

  name VARCHAR(255) NOT NULL,
  code VARCHAR(20) NOT NULL,

  -- Dirección
  address_id UUID REFERENCES core.addresses(id),

  -- Configuración
  is_default BOOLEAN DEFAULT FALSE,

  -- Control
  active BOOLEAN NOT NULL DEFAULT TRUE,

  -- Auditoría
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  created_by UUID REFERENCES auth.users(id),
  updated_at TIMESTAMP,
  updated_by UUID REFERENCES auth.users(id),

  CONSTRAINT uq_warehouses_code_company UNIQUE (company_id, code)
);

-- Tabla: locations (Ubicaciones de inventario)
CREATE TABLE inventory.locations (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,

  warehouse_id UUID REFERENCES inventory.warehouses(id),
  name VARCHAR(255) NOT NULL,
  complete_name TEXT, -- Generado: "Warehouse / Zone A / Shelf 1"
  location_type inventory.location_type NOT NULL DEFAULT 'internal',

  -- Jerarquía
  parent_id UUID REFERENCES inventory.locations(id),

  -- Configuración
  is_scrap_location BOOLEAN DEFAULT FALSE,
  is_return_location BOOLEAN DEFAULT FALSE,

  -- Control
  active BOOLEAN NOT NULL DEFAULT TRUE,

  -- Auditoría
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  created_by UUID REFERENCES auth.users(id),
  updated_at TIMESTAMP,
  updated_by UUID REFERENCES auth.users(id),

  CONSTRAINT chk_locations_no_self_parent CHECK (id != parent_id)
);

-- Tabla: lots (Lotes/Series) - DEBE IR ANTES DE stock_quants por FK
CREATE TABLE inventory.lots (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,

  product_id UUID NOT NULL REFERENCES inventory.products(id),
  name VARCHAR(100) NOT NULL,
  ref VARCHAR(100), -- Referencia externa

  -- Fechas
  manufacture_date DATE,
  expiration_date DATE,
  removal_date DATE,
  alert_date DATE,

  -- Notas
  notes TEXT,

  -- Auditoría
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  created_by UUID REFERENCES auth.users(id),

  CONSTRAINT uq_lots_name_product UNIQUE (product_id, name),
  CONSTRAINT chk_lots_expiration CHECK (expiration_date IS NULL OR expiration_date > manufacture_date)
);

-- Tabla: stock_quants (Cantidades en stock)
CREATE TABLE inventory.stock_quants (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,

  product_id UUID NOT NULL REFERENCES inventory.products(id),
  location_id UUID NOT NULL REFERENCES inventory.locations(id),
  lot_id UUID REFERENCES inventory.lots(id),

  -- Cantidades
  quantity DECIMAL(12, 4) NOT NULL DEFAULT 0,
  reserved_quantity DECIMAL(12, 4) NOT NULL DEFAULT 0,
  available_quantity DECIMAL(12, 4) GENERATED ALWAYS AS (quantity - reserved_quantity) STORED,

  -- Valoración
  cost DECIMAL(15, 4) DEFAULT 0,

  -- Auditoría
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP,

  CONSTRAINT chk_stock_quants_reserved CHECK (reserved_quantity >= 0 AND reserved_quantity <= quantity)
);

-- Unique index for stock_quants (allows expressions unlike UNIQUE constraint)
CREATE UNIQUE INDEX uq_stock_quants_product_location_lot
ON inventory.stock_quants (tenant_id, product_id, location_id, COALESCE(lot_id, '00000000-0000-0000-0000-000000000000'::UUID));

-- Índices para stock_quants
CREATE INDEX idx_stock_quants_tenant_id ON inventory.stock_quants(tenant_id);
CREATE INDEX idx_stock_quants_product_location ON inventory.stock_quants(product_id, location_id);

-- RLS para stock_quants
ALTER TABLE inventory.stock_quants ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation_stock_quants ON inventory.stock_quants
  USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid);

-- Tabla: pickings (Albaranes/Transferencias)
CREATE TABLE inventory.pickings (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
  company_id UUID NOT NULL REFERENCES auth.companies(id) ON DELETE CASCADE,

  name VARCHAR(100) NOT NULL,
  picking_type inventory.picking_type NOT NULL,

  -- Ubicaciones
  location_id UUID NOT NULL REFERENCES inventory.locations(id), -- Origen
  location_dest_id UUID NOT NULL REFERENCES inventory.locations(id), -- Destino

  -- Partner (cliente/proveedor)
  partner_id UUID REFERENCES core.partners(id),

  -- Fechas
  scheduled_date TIMESTAMP,
  date_done TIMESTAMP,

  -- Origen
  origin VARCHAR(255), -- Referencia al documento origen (PO, SO, etc.)

  -- Estado
  status inventory.move_status NOT NULL DEFAULT 'draft',

  -- Notas
  notes TEXT,

  -- Auditoría
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  created_by UUID REFERENCES auth.users(id),
  updated_at TIMESTAMP,
  updated_by UUID REFERENCES auth.users(id),
  validated_at TIMESTAMP,
  validated_by UUID REFERENCES auth.users(id),

  CONSTRAINT uq_pickings_name_company UNIQUE (company_id, name)
);

-- Tabla: stock_moves (Movimientos de inventario)
CREATE TABLE inventory.stock_moves (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,

  product_id UUID NOT NULL REFERENCES inventory.products(id),
  product_uom_id UUID NOT NULL REFERENCES core.uom(id),

  -- Ubicaciones
  location_id UUID NOT NULL REFERENCES inventory.locations(id), -- Origen
  location_dest_id UUID NOT NULL REFERENCES inventory.locations(id), -- Destino

  -- Cantidades
  product_qty DECIMAL(12, 4) NOT NULL,
  quantity_done DECIMAL(12, 4) DEFAULT 0,

  -- Lote/Serie
  lot_id UUID REFERENCES inventory.lots(id),

  -- Relación con picking
  picking_id UUID REFERENCES inventory.pickings(id) ON DELETE CASCADE,

  -- Origen del movimiento
  origin VARCHAR(255),
  ref VARCHAR(255),

  -- Estado
  status inventory.move_status NOT NULL DEFAULT 'draft',

  -- Fechas
  date_expected TIMESTAMP,
  date TIMESTAMP,

  -- Precio (para valoración)
  price_unit DECIMAL(15, 4) DEFAULT 0,

  -- Analítica
  analytic_account_id UUID REFERENCES analytics.analytic_accounts(id), -- Distribución analítica

  -- Auditoría
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  created_by UUID REFERENCES auth.users(id),
  updated_at TIMESTAMP,
  updated_by UUID REFERENCES auth.users(id),

  CONSTRAINT chk_stock_moves_quantity CHECK (product_qty > 0),
  CONSTRAINT chk_stock_moves_quantity_done CHECK (quantity_done >= 0)
);

-- Tabla: inventory_adjustments (Ajustes de inventario)
CREATE TABLE inventory.inventory_adjustments (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
  company_id UUID NOT NULL REFERENCES auth.companies(id) ON DELETE CASCADE,

  name VARCHAR(100) NOT NULL,

  -- Ubicación a ajustar
  location_id UUID NOT NULL REFERENCES inventory.locations(id),

  -- Fecha de conteo
  date DATE NOT NULL,

  -- Estado
  status inventory.move_status NOT NULL DEFAULT 'draft',

  -- Notas
  notes TEXT,

  -- Auditoría
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  created_by UUID REFERENCES auth.users(id),
  updated_at TIMESTAMP,
  updated_by UUID REFERENCES auth.users(id),
  validated_at TIMESTAMP,
  validated_by UUID REFERENCES auth.users(id),

  CONSTRAINT uq_inventory_adjustments_name_company UNIQUE (company_id, name)
);

-- Tabla: inventory_adjustment_lines (Líneas de ajuste)
CREATE TABLE inventory.inventory_adjustment_lines (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
  adjustment_id UUID NOT NULL REFERENCES inventory.inventory_adjustments(id) ON DELETE CASCADE,

  product_id UUID NOT NULL REFERENCES inventory.products(id),
  location_id UUID NOT NULL REFERENCES inventory.locations(id),
  lot_id UUID REFERENCES inventory.lots(id),

  -- Cantidades
  theoretical_qty DECIMAL(12, 4) NOT NULL DEFAULT 0, -- Cantidad teórica del sistema
  counted_qty DECIMAL(12, 4) NOT NULL, -- Cantidad contada físicamente
  difference_qty DECIMAL(12, 4) GENERATED ALWAYS AS (counted_qty - theoretical_qty) STORED,

  -- Auditoría
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);

-- Índices para inventory_adjustment_lines
CREATE INDEX idx_inventory_adjustment_lines_tenant_id ON inventory.inventory_adjustment_lines(tenant_id);

-- RLS para inventory_adjustment_lines
ALTER TABLE inventory.inventory_adjustment_lines ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation_inventory_adjustment_lines ON inventory.inventory_adjustment_lines
  USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid);

-- =====================================================
-- INDICES
-- =====================================================

-- Products
CREATE INDEX idx_products_tenant_id ON inventory.products(tenant_id);
CREATE INDEX idx_products_code ON inventory.products(code);
CREATE INDEX idx_products_barcode ON inventory.products(barcode);
CREATE INDEX idx_products_category_id ON inventory.products(category_id);
CREATE INDEX idx_products_type ON inventory.products(product_type);
CREATE INDEX idx_products_active ON inventory.products(active) WHERE active = TRUE;

-- Product Variants
CREATE INDEX idx_product_variants_template_id ON inventory.product_variants(product_template_id);
CREATE INDEX idx_product_variants_barcode ON inventory.product_variants(barcode);

-- Warehouses
CREATE INDEX idx_warehouses_tenant_id ON inventory.warehouses(tenant_id);
CREATE INDEX idx_warehouses_company_id ON inventory.warehouses(company_id);
CREATE INDEX idx_warehouses_code ON inventory.warehouses(code);

-- Locations
CREATE INDEX idx_locations_tenant_id ON inventory.locations(tenant_id);
CREATE INDEX idx_locations_warehouse_id ON inventory.locations(warehouse_id);
CREATE INDEX idx_locations_parent_id ON inventory.locations(parent_id);
CREATE INDEX idx_locations_type ON inventory.locations(location_type);

-- Stock Quants
CREATE INDEX idx_stock_quants_product_id ON inventory.stock_quants(product_id);
CREATE INDEX idx_stock_quants_location_id ON inventory.stock_quants(location_id);
CREATE INDEX idx_stock_quants_lot_id ON inventory.stock_quants(lot_id);
CREATE INDEX idx_stock_quants_available ON inventory.stock_quants(product_id, location_id)
  WHERE available_quantity > 0;

-- Lots
CREATE INDEX idx_lots_tenant_id ON inventory.lots(tenant_id);
CREATE INDEX idx_lots_product_id ON inventory.lots(product_id);
CREATE INDEX idx_lots_name ON inventory.lots(name);
CREATE INDEX idx_lots_expiration_date ON inventory.lots(expiration_date);

-- Pickings
CREATE INDEX idx_pickings_tenant_id ON inventory.pickings(tenant_id);
CREATE INDEX idx_pickings_company_id ON inventory.pickings(company_id);
CREATE INDEX idx_pickings_name ON inventory.pickings(name);
CREATE INDEX idx_pickings_type ON inventory.pickings(picking_type);
CREATE INDEX idx_pickings_status ON inventory.pickings(status);
CREATE INDEX idx_pickings_partner_id ON inventory.pickings(partner_id);
CREATE INDEX idx_pickings_origin ON inventory.pickings(origin);
CREATE INDEX idx_pickings_scheduled_date ON inventory.pickings(scheduled_date);

-- Stock Moves
CREATE INDEX idx_stock_moves_tenant_id ON inventory.stock_moves(tenant_id);
CREATE INDEX idx_stock_moves_product_id ON inventory.stock_moves(product_id);
CREATE INDEX idx_stock_moves_picking_id ON inventory.stock_moves(picking_id);
CREATE INDEX idx_stock_moves_location_id ON inventory.stock_moves(location_id);
CREATE INDEX idx_stock_moves_location_dest_id ON inventory.stock_moves(location_dest_id);
CREATE INDEX idx_stock_moves_status ON inventory.stock_moves(status);
CREATE INDEX idx_stock_moves_lot_id ON inventory.stock_moves(lot_id);
CREATE INDEX idx_stock_moves_analytic_account_id ON inventory.stock_moves(analytic_account_id) WHERE analytic_account_id IS NOT NULL;

-- Inventory Adjustments
CREATE INDEX idx_inventory_adjustments_tenant_id ON inventory.inventory_adjustments(tenant_id);
CREATE INDEX idx_inventory_adjustments_company_id ON inventory.inventory_adjustments(company_id);
CREATE INDEX idx_inventory_adjustments_location_id ON inventory.inventory_adjustments(location_id);
CREATE INDEX idx_inventory_adjustments_status ON inventory.inventory_adjustments(status);
CREATE INDEX idx_inventory_adjustments_date ON inventory.inventory_adjustments(date);

-- Inventory Adjustment Lines
CREATE INDEX idx_inventory_adjustment_lines_adjustment_id ON inventory.inventory_adjustment_lines(adjustment_id);
CREATE INDEX idx_inventory_adjustment_lines_product_id ON inventory.inventory_adjustment_lines(product_id);

-- =====================================================
-- FUNCTIONS
-- =====================================================

-- Función: update_stock_quant
-- Actualiza la cantidad en stock de un producto en una ubicación
CREATE OR REPLACE FUNCTION inventory.update_stock_quant(
  p_product_id UUID,
  p_location_id UUID,
  p_lot_id UUID,
  p_quantity DECIMAL
)
RETURNS VOID AS $$
BEGIN
  INSERT INTO inventory.stock_quants (product_id, location_id, lot_id, quantity)
  VALUES (p_product_id, p_location_id, p_lot_id, p_quantity)
  ON CONFLICT (product_id, location_id, COALESCE(lot_id, '00000000-0000-0000-0000-000000000000'::UUID))
  DO UPDATE SET
    quantity = inventory.stock_quants.quantity + EXCLUDED.quantity,
    updated_at = CURRENT_TIMESTAMP;
END;
$$ LANGUAGE plpgsql;

COMMENT ON FUNCTION inventory.update_stock_quant IS 'Actualiza la cantidad en stock de un producto en una ubicación';

-- Función: reserve_quantity
-- Reserva cantidad de un producto en una ubicación
CREATE OR REPLACE FUNCTION inventory.reserve_quantity(
  p_product_id UUID,
  p_location_id UUID,
  p_lot_id UUID,
  p_quantity DECIMAL
)
RETURNS BOOLEAN AS $$
DECLARE
  v_available DECIMAL;
BEGIN
  -- Verificar disponibilidad
  SELECT available_quantity INTO v_available
  FROM inventory.stock_quants
  WHERE product_id = p_product_id
    AND location_id = p_location_id
    AND (lot_id = p_lot_id OR (lot_id IS NULL AND p_lot_id IS NULL));

  IF v_available IS NULL OR v_available < p_quantity THEN
    RETURN FALSE;
  END IF;

  -- Reservar
  UPDATE inventory.stock_quants
  SET reserved_quantity = reserved_quantity + p_quantity,
      updated_at = CURRENT_TIMESTAMP
  WHERE product_id = p_product_id
    AND location_id = p_location_id
    AND (lot_id = p_lot_id OR (lot_id IS NULL AND p_lot_id IS NULL));

  RETURN TRUE;
END;
$$ LANGUAGE plpgsql;

COMMENT ON FUNCTION inventory.reserve_quantity IS 'Reserva cantidad de un producto en una ubicación';

-- Función: get_product_stock
-- Obtiene el stock disponible de un producto
CREATE OR REPLACE FUNCTION inventory.get_product_stock(
  p_product_id UUID,
  p_location_id UUID DEFAULT NULL
)
RETURNS TABLE(
  location_id UUID,
  location_name VARCHAR,
  quantity DECIMAL,
  reserved_quantity DECIMAL,
  available_quantity DECIMAL
) AS $$
BEGIN
  RETURN QUERY
  SELECT
    sq.location_id,
    l.name AS location_name,
    sq.quantity,
    sq.reserved_quantity,
    sq.available_quantity
  FROM inventory.stock_quants sq
  JOIN inventory.locations l ON sq.location_id = l.id
  WHERE sq.product_id = p_product_id
    AND (p_location_id IS NULL OR sq.location_id = p_location_id)
    AND sq.quantity > 0;
END;
$$ LANGUAGE plpgsql STABLE;

COMMENT ON FUNCTION inventory.get_product_stock IS 'Obtiene el stock disponible de un producto por ubicación';

-- Función: process_stock_move
-- Procesa un movimiento de inventario (actualiza quants)
CREATE OR REPLACE FUNCTION inventory.process_stock_move(p_move_id UUID)
RETURNS VOID AS $$
DECLARE
  v_move RECORD;
BEGIN
  -- Obtener datos del movimiento
  SELECT * INTO v_move
  FROM inventory.stock_moves
  WHERE id = p_move_id;

  IF NOT FOUND THEN
    RAISE EXCEPTION 'Stock move % not found', p_move_id;
  END IF;

  IF v_move.status != 'confirmed' THEN
    RAISE EXCEPTION 'Stock move % is not in confirmed status', p_move_id;
  END IF;

  -- Decrementar en ubicación origen
  PERFORM inventory.update_stock_quant(
    v_move.product_id,
    v_move.location_id,
    v_move.lot_id,
    -v_move.quantity_done
  );

  -- Incrementar en ubicación destino
  PERFORM inventory.update_stock_quant(
    v_move.product_id,
    v_move.location_dest_id,
    v_move.lot_id,
    v_move.quantity_done
  );

  -- Actualizar estado del movimiento
  UPDATE inventory.stock_moves
  SET status = 'done',
      date = CURRENT_TIMESTAMP,
      updated_at = CURRENT_TIMESTAMP,
      updated_by = get_current_user_id()
  WHERE id = p_move_id;
END;
$$ LANGUAGE plpgsql;

COMMENT ON FUNCTION inventory.process_stock_move IS 'Procesa un movimiento de inventario y actualiza los quants';

-- Función: update_location_complete_name
-- Actualiza el nombre completo de una ubicación
CREATE OR REPLACE FUNCTION inventory.update_location_complete_name()
RETURNS TRIGGER AS $$
DECLARE
  v_parent_name TEXT;
BEGIN
  IF NEW.parent_id IS NULL THEN
    NEW.complete_name := NEW.name;
  ELSE
    SELECT complete_name INTO v_parent_name
    FROM inventory.locations
    WHERE id = NEW.parent_id;

    NEW.complete_name := v_parent_name || ' / ' || NEW.name;
  END IF;

  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

COMMENT ON FUNCTION inventory.update_location_complete_name IS 'Actualiza el nombre completo de la ubicación';

-- =====================================================
-- TRIGGERS
-- =====================================================

-- Trigger: Actualizar updated_at
CREATE TRIGGER trg_products_updated_at
  BEFORE UPDATE ON inventory.products
  FOR EACH ROW
  EXECUTE FUNCTION auth.update_updated_at_column();

CREATE TRIGGER trg_warehouses_updated_at
  BEFORE UPDATE ON inventory.warehouses
  FOR EACH ROW
  EXECUTE FUNCTION auth.update_updated_at_column();

CREATE TRIGGER trg_locations_updated_at
  BEFORE UPDATE ON inventory.locations
  FOR EACH ROW
  EXECUTE FUNCTION auth.update_updated_at_column();

CREATE TRIGGER trg_pickings_updated_at
  BEFORE UPDATE ON inventory.pickings
  FOR EACH ROW
  EXECUTE FUNCTION auth.update_updated_at_column();

CREATE TRIGGER trg_stock_moves_updated_at
  BEFORE UPDATE ON inventory.stock_moves
  FOR EACH ROW
  EXECUTE FUNCTION auth.update_updated_at_column();

CREATE TRIGGER trg_inventory_adjustments_updated_at
  BEFORE UPDATE ON inventory.inventory_adjustments
  FOR EACH ROW
  EXECUTE FUNCTION auth.update_updated_at_column();

-- Trigger: Actualizar complete_name de ubicación
CREATE TRIGGER trg_locations_update_complete_name
  BEFORE INSERT OR UPDATE OF name, parent_id ON inventory.locations
  FOR EACH ROW
  EXECUTE FUNCTION inventory.update_location_complete_name();

-- =====================================================
-- TRACKING AUTOMÁTICO (mail.thread pattern)
-- =====================================================

-- Trigger: Tracking automático para movimientos de stock
CREATE TRIGGER track_stock_move_changes
  AFTER INSERT OR UPDATE OR DELETE ON inventory.stock_moves
  FOR EACH ROW EXECUTE FUNCTION system.track_field_changes();

COMMENT ON TRIGGER track_stock_move_changes ON inventory.stock_moves IS
'Registra automáticamente cambios en movimientos de stock (estado, producto, cantidad, ubicaciones)';

-- =====================================================
-- ROW LEVEL SECURITY (RLS)
-- =====================================================

ALTER TABLE inventory.products ENABLE ROW LEVEL SECURITY;
ALTER TABLE inventory.warehouses ENABLE ROW LEVEL SECURITY;
ALTER TABLE inventory.locations ENABLE ROW LEVEL SECURITY;
ALTER TABLE inventory.lots ENABLE ROW LEVEL SECURITY;
ALTER TABLE inventory.pickings ENABLE ROW LEVEL SECURITY;
ALTER TABLE inventory.stock_moves ENABLE ROW LEVEL SECURITY;
ALTER TABLE inventory.inventory_adjustments ENABLE ROW LEVEL SECURITY;

CREATE POLICY tenant_isolation_products ON inventory.products
  USING (tenant_id = get_current_tenant_id());

CREATE POLICY tenant_isolation_warehouses ON inventory.warehouses
  USING (tenant_id = get_current_tenant_id());

CREATE POLICY tenant_isolation_locations ON inventory.locations
  USING (tenant_id = get_current_tenant_id());

CREATE POLICY tenant_isolation_lots ON inventory.lots
  USING (tenant_id = get_current_tenant_id());

CREATE POLICY tenant_isolation_pickings ON inventory.pickings
  USING (tenant_id = get_current_tenant_id());

CREATE POLICY tenant_isolation_stock_moves ON inventory.stock_moves
  USING (tenant_id = get_current_tenant_id());

CREATE POLICY tenant_isolation_inventory_adjustments ON inventory.inventory_adjustments
  USING (tenant_id = get_current_tenant_id());

-- =====================================================
-- COMENTARIOS
-- =====================================================

COMMENT ON SCHEMA inventory IS 'Schema de gestión de inventarios, productos, almacenes y movimientos';
COMMENT ON TABLE inventory.products IS 'Productos (almacenables, consumibles, servicios)';
COMMENT ON TABLE inventory.product_variants IS 'Variantes de productos (color, talla, etc.)';
COMMENT ON TABLE inventory.warehouses IS 'Almacenes físicos';
COMMENT ON TABLE inventory.locations IS 'Ubicaciones dentro de almacenes (estantes, zonas, etc.)';
COMMENT ON TABLE inventory.stock_quants IS 'Cantidades en stock por producto/ubicación/lote';
COMMENT ON TABLE inventory.lots IS 'Lotes de producción y números de serie';
COMMENT ON TABLE inventory.pickings IS 'Albaranes de entrada, salida y transferencia';
COMMENT ON TABLE inventory.stock_moves IS 'Movimientos individuales de inventario';
COMMENT ON TABLE inventory.inventory_adjustments IS 'Ajustes de inventario (conteos físicos)';
COMMENT ON TABLE inventory.inventory_adjustment_lines IS 'Líneas de ajuste de inventario';

-- =====================================================
-- VISTAS ÚTILES
-- =====================================================

-- Vista: stock_by_product (Stock por producto)
CREATE OR REPLACE VIEW inventory.stock_by_product_view AS
SELECT
  p.id AS product_id,
  p.code AS product_code,
  p.name AS product_name,
  l.id AS location_id,
  l.complete_name AS location_name,
  COALESCE(SUM(sq.quantity), 0) AS quantity,
  COALESCE(SUM(sq.reserved_quantity), 0) AS reserved_quantity,
  COALESCE(SUM(sq.available_quantity), 0) AS available_quantity
FROM inventory.products p
CROSS JOIN inventory.locations l
LEFT JOIN inventory.stock_quants sq ON sq.product_id = p.id AND sq.location_id = l.id
WHERE p.product_type = 'storable'
  AND l.location_type = 'internal'
GROUP BY p.id, p.code, p.name, l.id, l.complete_name;

COMMENT ON VIEW inventory.stock_by_product_view IS 'Vista de stock disponible por producto y ubicación';

-- =====================================================
-- FIN DEL SCHEMA INVENTORY
-- =====================================================
