9.3 KiB
MODELO DE DOMINIO: Inventario y Compras
Módulos: MGN-005 (Inventario), MGN-006 (Compras) Fecha: 2025-11-24 Referencia Odoo: stock, purchase Referencia Gamilit: inventory_management, purchasing_management
Diagrama de Entidades (Texto UML)
[Product]
- id: UUID (PK)
- tenant_id: UUID (FK)
- name: String
- code: String
- barcode: String
- product_type: ENUM (storable, consumable, service)
- category_id: UUID (FK)
- uom_id: UUID (FK)
- cost_price: Decimal
- sale_price: Decimal
1 <----> * [StockMove]
1 <----> * [PurchaseOrderLine]
[Warehouse]
- id: UUID (PK)
- tenant_id: UUID (FK)
- company_id: UUID (FK)
- name: String
- code: String
1 <----> * [Location]
[Location]
- id: UUID (PK)
- warehouse_id: UUID (FK)
- name: String
- parent_id: UUID (FK self)
- location_type: ENUM (physical, virtual, supplier, customer)
1 <----> * [StockQuant]
1 <----> * [StockMove] (origen/destino)
[StockQuant]
- id: UUID (PK)
- product_id: UUID (FK)
- location_id: UUID (FK)
- quantity: Decimal
- lot_id: UUID (FK)
- cost: Decimal
[StockMove]
- id: UUID (PK)
- tenant_id: UUID (FK)
- product_id: UUID (FK)
- location_src_id: UUID (FK)
- location_dest_id: UUID (FK)
- quantity: Decimal
- uom_id: UUID (FK)
- status: ENUM (draft, confirmed, done, cancelled)
- picking_id: UUID (FK)
[Picking]
- id: UUID (PK)
- tenant_id: UUID (FK)
- company_id: UUID (FK)
- name: String
- picking_type: ENUM (incoming, outgoing, internal)
- partner_id: UUID (FK)
- date: Date
- status: ENUM (draft, ready, done, cancelled)
1 <----> * [StockMove]
[Lot]
- id: UUID (PK)
- product_id: UUID (FK)
- name: String
- lot_date: Date
- expiration_date: Date
[PurchaseOrder]
- id: UUID (PK)
- tenant_id: UUID (FK)
- company_id: UUID (FK)
- partner_id: UUID (FK)
- name: String
- order_date: Date
- expected_date: Date
- status: ENUM (draft, confirmed, received, billed, cancelled)
- amount_total: Decimal
1 <----> * [PurchaseOrderLine]
1 <----> * [Picking]
[PurchaseOrderLine]
- id: UUID (PK)
- order_id: UUID (FK)
- product_id: UUID (FK)
- description: String
- quantity: Decimal
- price_unit: Decimal
- tax_ids: UUID[]
- subtotal: Decimal
- total: Decimal
- analytic_account_id: UUID (FK)
Entidades Principales
1. Product (Producto)
Descripción: Producto almacenable, consumible o servicio.
Atributos:
id: UUIDname: Nombre del productocode: Código internobarcode: Código de barrasproduct_type: storable, consumable, servicecategory_id: Categoríauom_id: Unidad de medidacost_price: Precio de costosale_price: Precio de venta
Relaciones:
- 1 Product → N StockMoves
- 1 Product → N PurchaseOrderLines
- 1 Product → N StockQuants
Patrón Odoo: product.product + product.template Tipos:
- storable: Productos físicos con inventario
- consumable: Productos que no se trackean (ej: tornillos)
- service: Servicios (no tienen stock)
2. Warehouse (Almacén)
Descripción: Almacén físico de la empresa.
Atributos:
id: UUIDcompany_id: Empresa propietarianame: Nombre del almacéncode: Código (ej: "WH-MEX")
Relaciones:
- 1 Warehouse → N Locations
Patrón Odoo: stock.warehouse Almacenes típicos:
- Almacén Principal
- Almacén de Tránsito
- Almacén de Obra (construcción)
3. Location (Ubicación)
Descripción: Ubicación física o virtual dentro de almacén.
Atributos:
id: UUIDwarehouse_id: Almacén propietarioname: Nombre de ubicaciónparent_id: Ubicación padre (jerarquía)location_type: physical, virtual, supplier, customer
Relaciones:
- 1 Location → N StockQuants
- 1 Location → N StockMoves (origen/destino)
Patrón Odoo: stock.location Tipos:
- physical: Ubicaciones físicas
- virtual: Ubicaciones virtuales (proveedores, clientes, producción)
- supplier: Stock de proveedores
- customer: Stock de clientes
4. StockQuant (Cantidad en Stock)
Descripción: Cantidad de producto en ubicación específica.
Atributos:
id: UUIDproduct_id: Productolocation_id: Ubicaciónquantity: Cantidad disponiblelot_id: Lote (opcional)cost: Costo unitario
Relaciones:
- N StockQuants → 1 Product
- N StockQuants → 1 Location
Patrón Odoo: stock.quant Nota: Se actualiza automáticamente con cada movimiento
5. StockMove (Movimiento de Inventario)
Descripción: Movimiento de stock de una ubicación a otra.
Atributos:
id: UUIDproduct_id: Producto movidolocation_src_id: Ubicación origenlocation_dest_id: Ubicación destinoquantity: Cantidad movidastatus: draft, confirmed, done, cancelledpicking_id: Picking asociado
Relaciones:
- N StockMoves → 1 Product
- N StockMoves → 1 Picking
Patrón Odoo: stock.move Flujo: draft → confirmed → done
6. Picking (Albarán)
Descripción: Agrupación de movimientos de stock.
Atributos:
id: UUIDname: Número de albaránpicking_type: incoming (recepción), outgoing (entrega), internal (transferencia)partner_id: Partner asociadodate: Fechastatus: draft, ready, done, cancelled
Relaciones:
- 1 Picking → N StockMoves
Patrón Odoo: stock.picking Tipos:
- incoming: Recepción de proveedores
- outgoing: Entrega a clientes
- internal: Transferencias internas
7. Lot (Lote)
Descripción: Lote de producción o número de serie.
Atributos:
id: UUIDproduct_id: Productoname: Número de lote/serielot_date: Fecha de producciónexpiration_date: Fecha de caducidad
Relaciones:
- N Lots → 1 Product
Patrón Odoo: stock.production.lot Uso: Trazabilidad completa
8. PurchaseOrder (Orden de Compra)
Descripción: Orden de compra a proveedor.
Atributos:
id: UUIDpartner_id: Proveedorname: Número de ordenorder_date: Fecha de ordenexpected_date: Fecha esperada de entregastatus: draft, confirmed, received, billed, cancelledamount_total: Monto total
Relaciones:
- 1 PurchaseOrder → N PurchaseOrderLines
- 1 PurchaseOrder → N Pickings (recepciones)
Patrón Odoo: purchase.order Flujo: draft → confirmed → received → billed
9. PurchaseOrderLine (Línea de Orden de Compra)
Descripción: Línea de producto en orden de compra.
Atributos:
id: UUIDorder_id: Orden propietariaproduct_id: Productodescription: Descripciónquantity: Cantidad ordenadaprice_unit: Precio unitariotax_ids: Impuestosanalytic_account_id: Cuenta analítica (opcional)
Relaciones:
- N PurchaseOrderLines → 1 PurchaseOrder
Patrón Odoo: purchase.order.line
Reglas de Negocio
RN-INV-001: Movimientos Atómicos
- TODO movimiento de stock debe tener origen y destino
- Cantidad debe ser > 0
- Producto debe ser tipo 'storable' para afectar stock
RN-INV-002: Actualización Automática de Quants
- Al confirmar StockMove (status=done), actualizar StockQuants
- Decrementar en location_src
- Incrementar en location_dest
RN-INV-003: Valoración de Inventario
- Método FIFO por defecto
- Calcular costo promedio automáticamente
- Generar asientos contables si valoración automática activa
RN-INV-004: Trazabilidad
- Productos con tracking='lot' requieren lot_id en movimientos
- Productos con tracking='serial' requieren quantity=1 por movimiento
RN-INV-005: Integración Compras-Inventario
- Al confirmar PO, generar Picking automáticamente
- Al validar Picking, marcar PO como 'received'
RN-INV-006: 3-Way Match
- Validar: PO quantity = Picking quantity = Invoice quantity
- Permitir tolerancias configurables (ej: +/- 5%)
Casos de Uso Principales
- UC-INV-001: Usuario crea producto almacenable
- UC-INV-002: Usuario crea almacén con ubicaciones
- UC-INV-003: Usuario realiza transferencia interna
- UC-INV-004: Usuario crea orden de compra
- UC-INV-005: Sistema genera picking al confirmar PO
- UC-INV-006: Usuario valida recepción de productos
- UC-INV-007: Sistema actualiza stock automáticamente
- UC-INV-008: Usuario consulta inventario por ubicación
Validaciones y Constraints
-- Product code único por tenant
UNIQUE (tenant_id, code)
-- Cantidad > 0 en movimientos
CHECK (quantity > 0)
-- Ubicaciones no pueden ser su propio padre
CHECK (parent_id != id)
-- Status transitions válidos
-- (state machine)
Índices Requeridos
CREATE INDEX idx_products_code ON inventory.products(code);
CREATE INDEX idx_products_barcode ON inventory.products(barcode);
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_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_purchase_orders_partner_id ON purchase.purchase_orders(partner_id);
CREATE INDEX idx_purchase_orders_status ON purchase.purchase_orders(status);