erp-mecanicas-diesel/docs/03-modelo-datos/SCHEMA-PARTS-MANAGEMENT.md

14 KiB

Schema: parts_management

Descripcion

Schema para gestion de inventario, refacciones, movimientos de almacen y control de stock.

NOTA: Este schema depende de erp-core para autenticacion y tenants. La columna tenant_id referencia a core.tenants(id).

Tablas

part_categories

Categorias de refacciones.

CREATE TABLE parts_management.part_categories (
    id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id       UUID NOT NULL,

    name            VARCHAR(100) NOT NULL,
    description     VARCHAR(300),

    parent_id       UUID REFERENCES parts_management.part_categories(id),

    sort_order      INTEGER DEFAULT 0,
    created_at      TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    updated_at      TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

CREATE INDEX idx_part_categories_tenant ON parts_management.part_categories(tenant_id);
CREATE INDEX idx_part_categories_parent ON parts_management.part_categories(parent_id);

SELECT create_tenant_rls_policies('parts_management', 'part_categories');

suppliers

Proveedores.

CREATE TABLE parts_management.suppliers (
    id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id       UUID NOT NULL,

    name            VARCHAR(200) NOT NULL,
    legal_name      VARCHAR(300),
    rfc             VARCHAR(13),

    -- Contacto
    contact_name    VARCHAR(200),
    email           VARCHAR(200),
    phone           VARCHAR(20),

    -- Direccion
    address         TEXT,

    -- Condiciones
    credit_days     INTEGER DEFAULT 0 CHECK (credit_days >= 0),
    discount_pct    DECIMAL(5,2) DEFAULT 0 CHECK (discount_pct >= 0 AND discount_pct <= 100),

    -- Calificacion
    rating          DECIMAL(3,2) CHECK (rating >= 0 AND rating <= 5),

    notes           TEXT,
    is_active       BOOLEAN DEFAULT TRUE,

    created_at      TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    updated_at      TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

CREATE INDEX idx_suppliers_tenant ON parts_management.suppliers(tenant_id);
CREATE INDEX idx_suppliers_name ON parts_management.suppliers(name);

SELECT create_tenant_rls_policies('parts_management', 'suppliers');

warehouse_locations

Ubicaciones de almacen.

CREATE TABLE parts_management.warehouse_locations (
    id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id       UUID NOT NULL,

    code            VARCHAR(20) NOT NULL,
    name            VARCHAR(100),
    description     VARCHAR(200),

    -- Jerarquia
    zone            VARCHAR(10),
    aisle           VARCHAR(10),
    level           VARCHAR(10),

    -- Capacidad
    max_weight      DECIMAL(10,2) CHECK (max_weight > 0),
    max_volume      DECIMAL(10,2) CHECK (max_volume > 0),

    is_active       BOOLEAN DEFAULT TRUE,

    created_at      TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    updated_at      TIMESTAMP WITH TIME ZONE DEFAULT NOW(),

    CONSTRAINT uq_location_code UNIQUE(tenant_id, code)
);

CREATE INDEX idx_locations_tenant ON parts_management.warehouse_locations(tenant_id);
CREATE INDEX idx_locations_zone ON parts_management.warehouse_locations(zone);

SELECT create_tenant_rls_policies('parts_management', 'warehouse_locations');

parts

Catalogo de refacciones.

CREATE TABLE parts_management.parts (
    id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id       UUID NOT NULL,

    -- Identificacion
    sku             VARCHAR(50) NOT NULL,
    name            VARCHAR(300) NOT NULL,
    description     TEXT,

    -- Categoria
    category_id     UUID REFERENCES parts_management.part_categories(id),

    -- Marca/Fabricante
    brand           VARCHAR(100),
    manufacturer    VARCHAR(100),

    -- Compatibilidad
    compatible_engines TEXT[],

    -- Precios
    cost            DECIMAL(12,2) CHECK (cost >= 0),
    price           DECIMAL(12,2) NOT NULL CHECK (price >= 0),

    -- Inventario
    current_stock   DECIMAL(10,3) DEFAULT 0 CHECK (current_stock >= 0),
    reserved_stock  DECIMAL(10,3) DEFAULT 0 CHECK (reserved_stock >= 0),
    min_stock       DECIMAL(10,3) DEFAULT 0 CHECK (min_stock >= 0),
    max_stock       DECIMAL(10,3) CHECK (max_stock > 0),
    reorder_point   DECIMAL(10,3) CHECK (reorder_point >= 0),

    -- Ubicacion principal
    location_id     UUID REFERENCES parts_management.warehouse_locations(id),

    -- Unidad
    unit            VARCHAR(20) DEFAULT 'pza',

    -- Codigo de barras
    barcode         VARCHAR(50),

    -- Proveedor preferido
    preferred_supplier_id UUID REFERENCES parts_management.suppliers(id),

    -- Estado
    is_active       BOOLEAN DEFAULT TRUE,

    -- Audit
    created_at      TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    updated_at      TIMESTAMP WITH TIME ZONE DEFAULT NOW(),

    CONSTRAINT uq_part_sku UNIQUE(tenant_id, sku),
    CONSTRAINT chk_min_max_stock CHECK (max_stock IS NULL OR max_stock >= min_stock)
);

CREATE INDEX idx_parts_tenant ON parts_management.parts(tenant_id);
CREATE INDEX idx_parts_sku ON parts_management.parts(sku);
CREATE INDEX idx_parts_barcode ON parts_management.parts(barcode);
CREATE INDEX idx_parts_category ON parts_management.parts(category_id);
CREATE INDEX idx_parts_supplier ON parts_management.parts(preferred_supplier_id);
CREATE INDEX idx_parts_location ON parts_management.parts(location_id);

SELECT create_tenant_rls_policies('parts_management', 'parts');

part_alternates

Codigos alternos/equivalencias.

CREATE TABLE parts_management.part_alternates (
    id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    part_id         UUID NOT NULL REFERENCES parts_management.parts(id) ON DELETE CASCADE,

    alternate_code  VARCHAR(50) NOT NULL,
    manufacturer    VARCHAR(100),

    is_preferred    BOOLEAN DEFAULT FALSE,

    created_at      TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

CREATE UNIQUE INDEX idx_alternates_code ON parts_management.part_alternates(alternate_code);
CREATE INDEX idx_alternates_part ON parts_management.part_alternates(part_id);

part_locations

Ubicaciones multiples por refaccion.

CREATE TABLE parts_management.part_locations (
    id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    part_id         UUID NOT NULL REFERENCES parts_management.parts(id) ON DELETE CASCADE,
    location_id     UUID NOT NULL REFERENCES parts_management.warehouse_locations(id),

    quantity        DECIMAL(10,3) DEFAULT 0 CHECK (quantity >= 0),
    is_primary      BOOLEAN DEFAULT FALSE,

    created_at      TIMESTAMP WITH TIME ZONE DEFAULT NOW(),

    CONSTRAINT uq_part_location UNIQUE(part_id, location_id)
);

CREATE INDEX idx_part_locations_part ON parts_management.part_locations(part_id);
CREATE INDEX idx_part_locations_location ON parts_management.part_locations(location_id);

inventory_movements

Movimientos de inventario (kardex).

CREATE TABLE parts_management.inventory_movements (
    id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id       UUID NOT NULL,

    part_id         UUID NOT NULL REFERENCES parts_management.parts(id),

    -- Tipo de movimiento
    movement_type   VARCHAR(30) NOT NULL
        CHECK (movement_type IN ('purchase', 'consumption', 'adjustment_in',
                                  'adjustment_out', 'return', 'transfer')),

    -- Referencia
    reference_type  VARCHAR(30)
        CHECK (reference_type IN ('service_order', 'purchase_order', 'adjustment', 'return')),
    reference_id    UUID,
    reference_number VARCHAR(50),

    -- Cantidades
    quantity        DECIMAL(10,3) NOT NULL,
    previous_stock  DECIMAL(10,3) NOT NULL,
    new_stock       DECIMAL(10,3) NOT NULL,

    -- Costo
    unit_cost       DECIMAL(12,2) CHECK (unit_cost >= 0),
    total_cost      DECIMAL(12,2) CHECK (total_cost >= 0),

    -- Ubicacion
    location_id     UUID REFERENCES parts_management.warehouse_locations(id),

    notes           TEXT,

    created_by      UUID,
    created_at      TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

CREATE INDEX idx_movements_tenant ON parts_management.inventory_movements(tenant_id);
CREATE INDEX idx_movements_part ON parts_management.inventory_movements(part_id);
CREATE INDEX idx_movements_date ON parts_management.inventory_movements(created_at DESC);
CREATE INDEX idx_movements_reference ON parts_management.inventory_movements(reference_type, reference_id);

SELECT create_tenant_rls_policies('parts_management', 'inventory_movements');

inventory_adjustments

Ajustes de inventario.

CREATE TABLE parts_management.inventory_adjustments (
    id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id       UUID NOT NULL,

    adjustment_number VARCHAR(20) NOT NULL,

    -- Tipo
    adjustment_type VARCHAR(30) NOT NULL
        CHECK (adjustment_type IN ('count', 'damage', 'expiry', 'correction', 'other')),

    -- Estado
    status          VARCHAR(20) DEFAULT 'pending'
        CHECK (status IN ('pending', 'approved', 'applied', 'rejected')),

    -- Totales
    total_items     INTEGER DEFAULT 0,
    total_value     DECIMAL(12,2) DEFAULT 0,

    reason          TEXT NOT NULL,
    notes           TEXT,

    -- Aprobacion
    approved_by     UUID,
    approved_at     TIMESTAMP WITH TIME ZONE,

    created_by      UUID,
    created_at      TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    updated_at      TIMESTAMP WITH TIME ZONE DEFAULT NOW(),

    CONSTRAINT uq_adjustment_number UNIQUE(tenant_id, adjustment_number)
);

CREATE INDEX idx_adjustments_tenant ON parts_management.inventory_adjustments(tenant_id);
CREATE INDEX idx_adjustments_status ON parts_management.inventory_adjustments(status);

SELECT create_tenant_rls_policies('parts_management', 'inventory_adjustments');

adjustment_items

Items del ajuste.

CREATE TABLE parts_management.adjustment_items (
    id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    adjustment_id   UUID NOT NULL REFERENCES parts_management.inventory_adjustments(id) ON DELETE CASCADE,

    part_id         UUID NOT NULL REFERENCES parts_management.parts(id),

    system_qty      DECIMAL(10,3) NOT NULL,
    physical_qty    DECIMAL(10,3) NOT NULL,
    difference      DECIMAL(10,3) NOT NULL,

    unit_cost       DECIMAL(12,2) CHECK (unit_cost >= 0),
    value_impact    DECIMAL(12,2),

    notes           TEXT,

    created_at      TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

CREATE INDEX idx_adjustment_items_adjustment ON parts_management.adjustment_items(adjustment_id);
CREATE INDEX idx_adjustment_items_part ON parts_management.adjustment_items(part_id);

stock_alerts

Alertas de stock.

CREATE TABLE parts_management.stock_alerts (
    id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id       UUID NOT NULL,

    part_id         UUID NOT NULL REFERENCES parts_management.parts(id),

    alert_type      VARCHAR(30) NOT NULL
        CHECK (alert_type IN ('low_stock', 'out_of_stock', 'overstock')),

    current_stock   DECIMAL(10,3),
    threshold       DECIMAL(10,3),

    status          VARCHAR(20) DEFAULT 'active'
        CHECK (status IN ('active', 'acknowledged', 'resolved')),

    acknowledged_by UUID,
    acknowledged_at TIMESTAMP WITH TIME ZONE,

    created_at      TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

CREATE INDEX idx_alerts_tenant ON parts_management.stock_alerts(tenant_id);
CREATE INDEX idx_alerts_status ON parts_management.stock_alerts(status);
CREATE INDEX idx_alerts_part ON parts_management.stock_alerts(part_id);

SELECT create_tenant_rls_policies('parts_management', 'stock_alerts');

physical_inventory

Sesiones de inventario fisico.

CREATE TABLE parts_management.physical_inventory (
    id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id       UUID NOT NULL,

    inventory_number VARCHAR(20) NOT NULL,

    -- Tipo
    inventory_type  VARCHAR(30) NOT NULL
        CHECK (inventory_type IN ('full', 'zone', 'category', 'cyclic')),

    -- Estado
    status          VARCHAR(20) DEFAULT 'in_progress'
        CHECK (status IN ('in_progress', 'completed', 'cancelled')),

    -- Filtros
    zones           TEXT[],
    categories      UUID[],

    -- Estadisticas
    total_items     INTEGER DEFAULT 0,
    counted_items   INTEGER DEFAULT 0,
    with_difference INTEGER DEFAULT 0,

    -- Fechas
    started_at      TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    completed_at    TIMESTAMP WITH TIME ZONE,

    -- Ajuste generado
    adjustment_id   UUID REFERENCES parts_management.inventory_adjustments(id),

    created_by      UUID,
    created_at      TIMESTAMP WITH TIME ZONE DEFAULT NOW(),

    CONSTRAINT uq_inventory_number UNIQUE(tenant_id, inventory_number)
);

CREATE INDEX idx_physical_inv_tenant ON parts_management.physical_inventory(tenant_id);
CREATE INDEX idx_physical_inv_status ON parts_management.physical_inventory(status);

SELECT create_tenant_rls_policies('parts_management', 'physical_inventory');

inventory_counts

Conteos del inventario fisico.

CREATE TABLE parts_management.inventory_counts (
    id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    inventory_id    UUID NOT NULL REFERENCES parts_management.physical_inventory(id) ON DELETE CASCADE,

    part_id         UUID NOT NULL REFERENCES parts_management.parts(id),
    location_id     UUID REFERENCES parts_management.warehouse_locations(id),

    system_qty      DECIMAL(10,3) NOT NULL,
    counted_qty     DECIMAL(10,3),
    difference      DECIMAL(10,3),

    counted_by      UUID,
    counted_at      TIMESTAMP WITH TIME ZONE,

    notes           TEXT,

    created_at      TIMESTAMP WITH TIME ZONE DEFAULT NOW(),

    CONSTRAINT uq_count_part_location UNIQUE(inventory_id, part_id, location_id)
);

CREATE INDEX idx_inventory_counts_inventory ON parts_management.inventory_counts(inventory_id);
CREATE INDEX idx_inventory_counts_part ON parts_management.inventory_counts(part_id);

Relacion con erp-core

Este schema utiliza las siguientes referencias a erp-core:

Columna Referencia erp-core
tenant_id core.tenants(id)
created_by, approved_by, acknowledged_by, counted_by auth.users(id)

Creado por: Requirements-Analyst Fecha: 2025-12-06 Actualizado: 2025-12-06 (Correccion tenant_id, CHECK constraints, RLS completo)