-- =====================================================
-- SCHEMA: purchase
-- PROPÓSITO: Gestión de compras, proveedores, órdenes de compra
-- MÓDULOS: MGN-006 (Compras Básico)
-- FECHA: 2025-11-24
-- =====================================================

-- Crear schema
CREATE SCHEMA IF NOT EXISTS purchase;

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

CREATE TYPE purchase.order_status AS ENUM (
  'draft',
  'sent',
  'confirmed',
  'received',
  'billed',
  'cancelled'
);

CREATE TYPE purchase.rfq_status AS ENUM (
  'draft',
  'sent',
  'responded',
  'accepted',
  'rejected',
  'cancelled'
);

CREATE TYPE purchase.agreement_type AS ENUM (
  'price',
  'discount',
  'blanket'
);

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

-- Tabla: purchase_orders (Órdenes de compra)
CREATE TABLE purchase.purchase_orders (
  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,

  -- Numeración
  name VARCHAR(100) NOT NULL,
  ref VARCHAR(100), -- Referencia del proveedor

  -- Proveedor
  partner_id UUID NOT NULL REFERENCES core.partners(id),

  -- Fechas
  order_date DATE NOT NULL,
  expected_date DATE,
  effective_date DATE,

  -- Configuración
  currency_id UUID NOT NULL REFERENCES core.currencies(id),
  payment_term_id UUID REFERENCES financial.payment_terms(id),

  -- Montos
  amount_untaxed DECIMAL(15, 2) NOT NULL DEFAULT 0,
  amount_tax DECIMAL(15, 2) NOT NULL DEFAULT 0,
  amount_total DECIMAL(15, 2) NOT NULL DEFAULT 0,

  -- Estado
  status purchase.order_status NOT NULL DEFAULT 'draft',

  -- Recepciones y facturación
  receipt_status VARCHAR(20) DEFAULT 'pending', -- pending, partial, received
  invoice_status VARCHAR(20) DEFAULT 'pending', -- pending, partial, billed

  -- Relaciones
  picking_id UUID REFERENCES inventory.pickings(id), -- Recepción generada
  invoice_id UUID REFERENCES financial.invoices(id), -- Factura generada

  -- 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),
  confirmed_at TIMESTAMP,
  confirmed_by UUID REFERENCES auth.users(id),
  cancelled_at TIMESTAMP,
  cancelled_by UUID REFERENCES auth.users(id),

  CONSTRAINT uq_purchase_orders_name_company UNIQUE (company_id, name)
);

-- Tabla: purchase_order_lines (Líneas de orden de compra)
CREATE TABLE purchase.purchase_order_lines (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  order_id UUID NOT NULL REFERENCES purchase.purchase_orders(id) ON DELETE CASCADE,

  product_id UUID NOT NULL REFERENCES inventory.products(id),
  description TEXT NOT NULL,

  -- Cantidades
  quantity DECIMAL(12, 4) NOT NULL,
  qty_received DECIMAL(12, 4) DEFAULT 0,
  qty_invoiced DECIMAL(12, 4) DEFAULT 0,
  uom_id UUID NOT NULL REFERENCES core.uom(id),

  -- Precios
  price_unit DECIMAL(15, 4) NOT NULL,
  discount DECIMAL(5, 2) DEFAULT 0, -- Porcentaje de descuento

  -- Impuestos
  tax_ids UUID[] DEFAULT '{}',

  -- Montos
  amount_untaxed DECIMAL(15, 2) NOT NULL,
  amount_tax DECIMAL(15, 2) NOT NULL,
  amount_total DECIMAL(15, 2) NOT NULL,

  -- Fechas esperadas
  expected_date DATE,

  -- 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,
  updated_at TIMESTAMP,

  CONSTRAINT chk_purchase_order_lines_quantity CHECK (quantity > 0),
  CONSTRAINT chk_purchase_order_lines_discount CHECK (discount >= 0 AND discount <= 100)
);

-- Tabla: rfqs (Request for Quotation - Solicitudes de cotización)
CREATE TABLE purchase.rfqs (
  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,

  -- Proveedores (puede ser enviada a múltiples proveedores)
  partner_ids UUID[] NOT NULL,

  -- Fechas
  request_date DATE NOT NULL,
  deadline_date DATE,
  response_date DATE,

  -- Estado
  status purchase.rfq_status NOT NULL DEFAULT 'draft',

  -- Descripción
  description TEXT,
  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),

  CONSTRAINT uq_rfqs_name_company UNIQUE (company_id, name)
);

-- Tabla: rfq_lines (Líneas de RFQ)
CREATE TABLE purchase.rfq_lines (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  rfq_id UUID NOT NULL REFERENCES purchase.rfqs(id) ON DELETE CASCADE,

  product_id UUID REFERENCES inventory.products(id),
  description TEXT NOT NULL,
  quantity DECIMAL(12, 4) NOT NULL,
  uom_id UUID NOT NULL REFERENCES core.uom(id),

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

  CONSTRAINT chk_rfq_lines_quantity CHECK (quantity > 0)
);

-- Tabla: vendor_pricelists (Listas de precios de proveedores)
CREATE TABLE purchase.vendor_pricelists (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,

  partner_id UUID NOT NULL REFERENCES core.partners(id),
  product_id UUID NOT NULL REFERENCES inventory.products(id),

  -- Precio
  price DECIMAL(15, 4) NOT NULL,
  currency_id UUID NOT NULL REFERENCES core.currencies(id),

  -- Cantidad mínima
  min_quantity DECIMAL(12, 4) DEFAULT 1,

  -- Validez
  valid_from DATE,
  valid_to DATE,

  -- 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_vendor_pricelists_price CHECK (price >= 0),
  CONSTRAINT chk_vendor_pricelists_min_qty CHECK (min_quantity > 0),
  CONSTRAINT chk_vendor_pricelists_dates CHECK (valid_to IS NULL OR valid_to >= valid_from)
);

-- Tabla: purchase_agreements (Acuerdos de compra / Contratos)
CREATE TABLE purchase.purchase_agreements (
  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(50),
  agreement_type purchase.agreement_type NOT NULL,

  -- Proveedor
  partner_id UUID NOT NULL REFERENCES core.partners(id),

  -- Vigencia
  start_date DATE NOT NULL,
  end_date DATE NOT NULL,

  -- Montos (para contratos blanket)
  amount_max DECIMAL(15, 2),
  currency_id UUID REFERENCES core.currencies(id),

  -- Estado
  is_active BOOLEAN DEFAULT TRUE,

  -- Términos
  terms TEXT,
  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),

  CONSTRAINT uq_purchase_agreements_code_company UNIQUE (company_id, code),
  CONSTRAINT chk_purchase_agreements_dates CHECK (end_date > start_date)
);

-- Tabla: purchase_agreement_lines (Líneas de acuerdo)
CREATE TABLE purchase.purchase_agreement_lines (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  agreement_id UUID NOT NULL REFERENCES purchase.purchase_agreements(id) ON DELETE CASCADE,

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

  -- Cantidades
  quantity DECIMAL(12, 4),
  qty_ordered DECIMAL(12, 4) DEFAULT 0,

  -- Precio acordado
  price_unit DECIMAL(15, 4),
  discount DECIMAL(5, 2) DEFAULT 0,

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

-- Tabla: vendor_evaluations (Evaluaciones de proveedores)
CREATE TABLE purchase.vendor_evaluations (
  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,

  partner_id UUID NOT NULL REFERENCES core.partners(id),

  -- Período de evaluación
  evaluation_date DATE NOT NULL,
  period_start DATE NOT NULL,
  period_end DATE NOT NULL,

  -- Calificaciones (1-5)
  quality_rating INTEGER,
  delivery_rating INTEGER,
  service_rating INTEGER,
  price_rating INTEGER,
  overall_rating DECIMAL(3, 2),

  -- Métricas
  on_time_delivery_rate DECIMAL(5, 2), -- Porcentaje
  defect_rate DECIMAL(5, 2), -- Porcentaje

  -- Comentarios
  comments TEXT,

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

  CONSTRAINT chk_vendor_evaluations_quality CHECK (quality_rating >= 1 AND quality_rating <= 5),
  CONSTRAINT chk_vendor_evaluations_delivery CHECK (delivery_rating >= 1 AND delivery_rating <= 5),
  CONSTRAINT chk_vendor_evaluations_service CHECK (service_rating >= 1 AND service_rating <= 5),
  CONSTRAINT chk_vendor_evaluations_price CHECK (price_rating >= 1 AND price_rating <= 5),
  CONSTRAINT chk_vendor_evaluations_overall CHECK (overall_rating >= 1 AND overall_rating <= 5),
  CONSTRAINT chk_vendor_evaluations_dates CHECK (period_end >= period_start)
);

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

-- Purchase Orders
CREATE INDEX idx_purchase_orders_tenant_id ON purchase.purchase_orders(tenant_id);
CREATE INDEX idx_purchase_orders_company_id ON purchase.purchase_orders(company_id);
CREATE INDEX idx_purchase_orders_partner_id ON purchase.purchase_orders(partner_id);
CREATE INDEX idx_purchase_orders_name ON purchase.purchase_orders(name);
CREATE INDEX idx_purchase_orders_status ON purchase.purchase_orders(status);
CREATE INDEX idx_purchase_orders_order_date ON purchase.purchase_orders(order_date);
CREATE INDEX idx_purchase_orders_expected_date ON purchase.purchase_orders(expected_date);

-- Purchase Order Lines
CREATE INDEX idx_purchase_order_lines_order_id ON purchase.purchase_order_lines(order_id);
CREATE INDEX idx_purchase_order_lines_product_id ON purchase.purchase_order_lines(product_id);
CREATE INDEX idx_purchase_order_lines_analytic_account_id ON purchase.purchase_order_lines(analytic_account_id) WHERE analytic_account_id IS NOT NULL;

-- RFQs
CREATE INDEX idx_rfqs_tenant_id ON purchase.rfqs(tenant_id);
CREATE INDEX idx_rfqs_company_id ON purchase.rfqs(company_id);
CREATE INDEX idx_rfqs_status ON purchase.rfqs(status);
CREATE INDEX idx_rfqs_request_date ON purchase.rfqs(request_date);

-- RFQ Lines
CREATE INDEX idx_rfq_lines_rfq_id ON purchase.rfq_lines(rfq_id);
CREATE INDEX idx_rfq_lines_product_id ON purchase.rfq_lines(product_id);

-- Vendor Pricelists
CREATE INDEX idx_vendor_pricelists_tenant_id ON purchase.vendor_pricelists(tenant_id);
CREATE INDEX idx_vendor_pricelists_partner_id ON purchase.vendor_pricelists(partner_id);
CREATE INDEX idx_vendor_pricelists_product_id ON purchase.vendor_pricelists(product_id);
CREATE INDEX idx_vendor_pricelists_active ON purchase.vendor_pricelists(active) WHERE active = TRUE;

-- Purchase Agreements
CREATE INDEX idx_purchase_agreements_tenant_id ON purchase.purchase_agreements(tenant_id);
CREATE INDEX idx_purchase_agreements_company_id ON purchase.purchase_agreements(company_id);
CREATE INDEX idx_purchase_agreements_partner_id ON purchase.purchase_agreements(partner_id);
CREATE INDEX idx_purchase_agreements_dates ON purchase.purchase_agreements(start_date, end_date);
CREATE INDEX idx_purchase_agreements_active ON purchase.purchase_agreements(is_active) WHERE is_active = TRUE;

-- Purchase Agreement Lines
CREATE INDEX idx_purchase_agreement_lines_agreement_id ON purchase.purchase_agreement_lines(agreement_id);
CREATE INDEX idx_purchase_agreement_lines_product_id ON purchase.purchase_agreement_lines(product_id);

-- Vendor Evaluations
CREATE INDEX idx_vendor_evaluations_tenant_id ON purchase.vendor_evaluations(tenant_id);
CREATE INDEX idx_vendor_evaluations_partner_id ON purchase.vendor_evaluations(partner_id);
CREATE INDEX idx_vendor_evaluations_date ON purchase.vendor_evaluations(evaluation_date);

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

-- Función: calculate_purchase_order_totals
CREATE OR REPLACE FUNCTION purchase.calculate_purchase_order_totals(p_order_id UUID)
RETURNS VOID AS $$
DECLARE
  v_amount_untaxed DECIMAL;
  v_amount_tax DECIMAL;
  v_amount_total DECIMAL;
BEGIN
  SELECT
    COALESCE(SUM(amount_untaxed), 0),
    COALESCE(SUM(amount_tax), 0),
    COALESCE(SUM(amount_total), 0)
  INTO v_amount_untaxed, v_amount_tax, v_amount_total
  FROM purchase.purchase_order_lines
  WHERE order_id = p_order_id;

  UPDATE purchase.purchase_orders
  SET amount_untaxed = v_amount_untaxed,
      amount_tax = v_amount_tax,
      amount_total = v_amount_total,
      updated_at = CURRENT_TIMESTAMP,
      updated_by = get_current_user_id()
  WHERE id = p_order_id;
END;
$$ LANGUAGE plpgsql;

COMMENT ON FUNCTION purchase.calculate_purchase_order_totals IS 'Calcula los totales de una orden de compra';

-- Función: create_picking_from_po
CREATE OR REPLACE FUNCTION purchase.create_picking_from_po(p_order_id UUID)
RETURNS UUID AS $$
DECLARE
  v_order RECORD;
  v_picking_id UUID;
  v_location_supplier UUID;
  v_location_stock UUID;
BEGIN
  -- Obtener datos de la orden
  SELECT * INTO v_order
  FROM purchase.purchase_orders
  WHERE id = p_order_id;

  IF NOT FOUND THEN
    RAISE EXCEPTION 'Purchase order % not found', p_order_id;
  END IF;

  -- Obtener ubicaciones (simplificado - en producción obtener de configuración)
  SELECT id INTO v_location_supplier
  FROM inventory.locations
  WHERE location_type = 'supplier'
  LIMIT 1;

  SELECT id INTO v_location_stock
  FROM inventory.locations
  WHERE location_type = 'internal'
  LIMIT 1;

  -- Crear picking
  INSERT INTO inventory.pickings (
    tenant_id,
    company_id,
    name,
    picking_type,
    location_id,
    location_dest_id,
    partner_id,
    origin,
    scheduled_date
  ) VALUES (
    v_order.tenant_id,
    v_order.company_id,
    'IN/' || v_order.name,
    'incoming',
    v_location_supplier,
    v_location_stock,
    v_order.partner_id,
    v_order.name,
    v_order.expected_date
  ) RETURNING id INTO v_picking_id;

  -- Actualizar la PO con el picking_id
  UPDATE purchase.purchase_orders
  SET picking_id = v_picking_id
  WHERE id = p_order_id;

  RETURN v_picking_id;
END;
$$ LANGUAGE plpgsql;

COMMENT ON FUNCTION purchase.create_picking_from_po IS 'Crea un picking de recepción a partir de una orden de compra';

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

CREATE TRIGGER trg_purchase_orders_updated_at
  BEFORE UPDATE ON purchase.purchase_orders
  FOR EACH ROW
  EXECUTE FUNCTION auth.update_updated_at_column();

CREATE TRIGGER trg_rfqs_updated_at
  BEFORE UPDATE ON purchase.rfqs
  FOR EACH ROW
  EXECUTE FUNCTION auth.update_updated_at_column();

CREATE TRIGGER trg_vendor_pricelists_updated_at
  BEFORE UPDATE ON purchase.vendor_pricelists
  FOR EACH ROW
  EXECUTE FUNCTION auth.update_updated_at_column();

CREATE TRIGGER trg_purchase_agreements_updated_at
  BEFORE UPDATE ON purchase.purchase_agreements
  FOR EACH ROW
  EXECUTE FUNCTION auth.update_updated_at_column();

-- Trigger: Actualizar totales de PO al cambiar líneas
CREATE OR REPLACE FUNCTION purchase.trg_update_po_totals()
RETURNS TRIGGER AS $$
BEGIN
  IF TG_OP = 'DELETE' THEN
    PERFORM purchase.calculate_purchase_order_totals(OLD.order_id);
  ELSE
    PERFORM purchase.calculate_purchase_order_totals(NEW.order_id);
  END IF;
  RETURN NULL;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trg_purchase_order_lines_update_totals
  AFTER INSERT OR UPDATE OR DELETE ON purchase.purchase_order_lines
  FOR EACH ROW
  EXECUTE FUNCTION purchase.trg_update_po_totals();

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

-- Trigger: Tracking automático para órdenes de compra
CREATE TRIGGER track_purchase_order_changes
  AFTER INSERT OR UPDATE OR DELETE ON purchase.purchase_orders
  FOR EACH ROW EXECUTE FUNCTION system.track_field_changes();

COMMENT ON TRIGGER track_purchase_order_changes ON purchase.purchase_orders IS
'Registra automáticamente cambios en órdenes de compra (estado, proveedor, monto, fecha)';

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

ALTER TABLE purchase.purchase_orders ENABLE ROW LEVEL SECURITY;
ALTER TABLE purchase.rfqs ENABLE ROW LEVEL SECURITY;
ALTER TABLE purchase.vendor_pricelists ENABLE ROW LEVEL SECURITY;
ALTER TABLE purchase.purchase_agreements ENABLE ROW LEVEL SECURITY;
ALTER TABLE purchase.vendor_evaluations ENABLE ROW LEVEL SECURITY;

CREATE POLICY tenant_isolation_purchase_orders ON purchase.purchase_orders
  USING (tenant_id = get_current_tenant_id());

CREATE POLICY tenant_isolation_rfqs ON purchase.rfqs
  USING (tenant_id = get_current_tenant_id());

CREATE POLICY tenant_isolation_vendor_pricelists ON purchase.vendor_pricelists
  USING (tenant_id = get_current_tenant_id());

CREATE POLICY tenant_isolation_purchase_agreements ON purchase.purchase_agreements
  USING (tenant_id = get_current_tenant_id());

CREATE POLICY tenant_isolation_vendor_evaluations ON purchase.vendor_evaluations
  USING (tenant_id = get_current_tenant_id());

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

COMMENT ON SCHEMA purchase IS 'Schema de gestión de compras y proveedores';
COMMENT ON TABLE purchase.purchase_orders IS 'Órdenes de compra a proveedores';
COMMENT ON TABLE purchase.purchase_order_lines IS 'Líneas de órdenes de compra';
COMMENT ON TABLE purchase.rfqs IS 'Solicitudes de cotización (RFQ)';
COMMENT ON TABLE purchase.rfq_lines IS 'Líneas de solicitudes de cotización';
COMMENT ON TABLE purchase.vendor_pricelists IS 'Listas de precios de proveedores';
COMMENT ON TABLE purchase.purchase_agreements IS 'Acuerdos/contratos de compra con proveedores';
COMMENT ON TABLE purchase.purchase_agreement_lines IS 'Líneas de acuerdos de compra';
COMMENT ON TABLE purchase.vendor_evaluations IS 'Evaluaciones de desempeño de proveedores';

-- =====================================================
-- FIN DEL SCHEMA PURCHASE
-- =====================================================
