# RF-CATALOG-004: Unidades de Medida (UoM) ## Identificacion | Campo | Valor | |-------|-------| | **ID** | RF-CATALOG-004 | | **Modulo** | MGN-005 Catalogs | | **Titulo** | Unidades de Medida (UoM) | | **Prioridad** | Media | | **Estado** | Draft | | **Fecha** | 2025-12-05 | --- ## Descripcion El sistema debe permitir gestionar unidades de medida agrupadas por categorias, con factores de conversion entre unidades de la misma categoria. **Referencia Odoo:** - `uom.category` (addons/uom/models/uom_uom.py) - `uom.uom` (mismo archivo) --- ## Arquitectura ### Por Tenant A diferencia de paises y monedas, las unidades de medida son **por tenant**: ```sql core_catalogs.uom_categories (tenant_id UUID NOT NULL) core_catalogs.uom (tenant_id UUID NOT NULL) ``` **Rationale:** Cada tenant puede necesitar unidades personalizadas segun su industria. --- ## Requisitos Funcionales ### RF-CATALOG-004.1: Categorias de UoM Las unidades se agrupan en categorias. Solo se pueden convertir unidades de la misma categoria. | Campo | Tipo | Descripcion | |-------|------|-------------| | id | UUID | Identificador unico | | tenant_id | FK | Tenant propietario | | name | string | Nombre de categoria | | is_active | boolean | Disponible para uso | **Categorias por defecto (seed):** | Categoria | Descripcion | |-----------|-------------| | Unit | Unidades discretas (piezas, docenas) | | Weight | Peso (kg, g, lb, oz) | | Volume | Volumen (L, mL, gal) | | Length | Longitud (m, cm, ft, in) | | Time | Tiempo (hora, dia, semana) | | Area | Area (m2, ha, acre) | ### RF-CATALOG-004.2: Unidades de Medida | Campo | Tipo | Descripcion | |-------|------|-------------| | id | UUID | Identificador unico | | tenant_id | FK | Tenant propietario | | category_id | FK | Categoria de UoM | | name | string | Nombre de unidad | | uom_type | enum | reference/bigger/smaller | | factor | decimal | Factor de conversion | | rounding | decimal | Precision de redondeo | | is_active | boolean | Disponible para uso | ### RF-CATALOG-004.3: Tipos de Unidad Cada categoria tiene exactamente UNA unidad de referencia: | Tipo | Descripcion | Factor | |------|-------------|--------| | `reference` | Unidad base de la categoria | 1.0 | | `bigger` | Mayor que la referencia | < 1.0 | | `smaller` | Menor que la referencia | > 1.0 | **Ejemplo categoria "Weight":** | Unidad | Tipo | Factor | Equivalencia | |--------|------|--------|--------------| | kg | reference | 1.0 | 1 kg = 1 kg | | g | smaller | 1000 | 1 kg = 1000 g | | lb | bigger | 0.453592 | 1 lb = 0.453592 kg | | oz | smaller | 35.274 | 1 kg = 35.274 oz | ### RF-CATALOG-004.4: Conversion de Unidades El sistema debe proporcionar funciones de conversion: ```typescript // Convertir cantidad de una unidad a otra convertUom( quantity: number, fromUom: UUID, toUom: UUID ): number // Redondear segun unidad roundUom(quantity: number, uom: UUID): number ``` **Formula de conversion:** ``` quantity_to = quantity_from * (factor_from / factor_to) ``` **Validacion:** Solo se permite conversion entre unidades de la misma categoria. ### RF-CATALOG-004.5: Constraints de Categoria | Constraint | Descripcion | |------------|-------------| | Una referencia | Exactamente 1 unidad `reference` por categoria | | Factor referencia = 1 | La unidad reference debe tener factor = 1.0 | | Factor > 0 | El factor de conversion debe ser positivo | ### RF-CATALOG-004.6: Unidades Protegidas Algunas unidades del seed no deben modificarse: ```sql -- Unidades protegidas is_protected BOOLEAN DEFAULT false ``` | Unidad | Categoria | Protegida | Razon | |--------|-----------|-----------|-------| | pcs | Unit | Si | Referencia universal | | kg | Weight | Si | SI standard | | L | Volume | Si | SI standard | | m | Length | Si | SI standard | | hr | Time | Si | Referencia de tiempo | --- ## Operaciones CRUD ### Listar Categorias ```typescript GET /api/v1/catalogs/uom-categories Response: { "data": [ { "id": "uuid", "name": "Unit", "uomCount": 3 }, { "id": "uuid", "name": "Weight", "uomCount": 5 }, ... ] } ``` ### Listar Unidades por Categoria ```typescript GET /api/v1/catalogs/uom?categoryId=uuid-weight Response: { "data": [ { "id": "uuid", "name": "kg", "uomType": "reference", "factor": 1.0, "rounding": 0.001 }, { "id": "uuid", "name": "g", "uomType": "smaller", "factor": 1000, "rounding": 1 } ] } ``` ### Crear Unidad ```typescript POST /api/v1/catalogs/uom { "categoryId": "uuid-weight", "name": "Tonelada", "uomType": "bigger", "factor": 0.001, // 1 ton = 0.001 kg^-1 = 1000 kg "rounding": 0.001 } ``` ### Convertir Cantidad ```typescript GET /api/v1/catalogs/uom/convert?from=uuid-kg&to=uuid-lb&quantity=10 Response: { "from": { "uom": "kg", "quantity": 10 }, "to": { "uom": "lb", "quantity": 22.0462 }, "factor": 2.20462 } ``` --- ## Data Seed ### Categoria: Unit | Nombre | Tipo | Factor | Rounding | |--------|------|--------|----------| | pcs (Pieces) | reference | 1 | 1 | | dozen | bigger | 0.0833 | 1 | | pair | bigger | 0.5 | 1 | ### Categoria: Weight | Nombre | Tipo | Factor | Rounding | |--------|------|--------|----------| | kg | reference | 1 | 0.001 | | g | smaller | 1000 | 1 | | lb | bigger | 0.453592 | 0.01 | | oz | smaller | 35.274 | 0.01 | | ton | bigger | 0.001 | 0.001 | ### Categoria: Volume | Nombre | Tipo | Factor | Rounding | |--------|------|--------|----------| | L | reference | 1 | 0.001 | | mL | smaller | 1000 | 1 | | gal (US) | bigger | 0.264172 | 0.001 | | m3 | bigger | 0.001 | 0.001 | ### Categoria: Length | Nombre | Tipo | Factor | Rounding | |--------|------|--------|----------| | m | reference | 1 | 0.001 | | cm | smaller | 100 | 1 | | ft | bigger | 0.3048 | 0.01 | | in | smaller | 39.3701 | 0.1 | ### Categoria: Time | Nombre | Tipo | Factor | Rounding | |--------|------|--------|----------| | hour | reference | 1 | 0.01 | | day | bigger | 0.0417 | 0.01 | | week | bigger | 0.00595 | 0.01 | --- ## Reglas de Negocio | ID | Regla | Severidad | |----|-------|-----------| | BR-001 | Una unidad reference por categoria | Error | | BR-002 | Reference factor = 1.0 | Error | | BR-003 | Factor > 0 | Error | | BR-004 | Solo convertir misma categoria | Error | | BR-005 | No eliminar unidad con productos | Error | | BR-006 | No modificar unidades protegidas | Error | --- ## Casos de Prueba | ID | Escenario | Resultado Esperado | |----|-----------|-------------------| | TC-001 | Crear categoria nueva | Categoria creada | | TC-002 | Crear unidad reference | Factor = 1.0 validado | | TC-003 | Crear segunda reference | Error: ya existe | | TC-004 | Convertir kg a lb | 10 kg = 22.0462 lb | | TC-005 | Convertir kg a L (diferente categoria) | Error | | TC-006 | Redondear 10.12345 kg (rounding=0.001) | 10.123 | | TC-007 | Eliminar unidad con productos | Error | --- ## Criterios de Aceptacion - [ ] Categorias CRUD funcional - [ ] Unidades CRUD funcional - [ ] Constraint de una reference - [ ] Conversion dentro de categoria - [ ] Redondeo segun precision - [ ] Seed de unidades comunes - [ ] Proteccion de unidades base --- ## Notas Tecnicas ### Funcion de Conversion SQL ```sql CREATE OR REPLACE FUNCTION core_catalogs.convert_uom( p_quantity DECIMAL, p_from_uom UUID, p_to_uom UUID ) RETURNS DECIMAL AS $$ DECLARE v_from_factor DECIMAL; v_to_factor DECIMAL; v_from_category UUID; v_to_category UUID; BEGIN -- Obtener datos de origen SELECT factor, category_id INTO v_from_factor, v_from_category FROM core_catalogs.uom WHERE id = p_from_uom; -- Obtener datos de destino SELECT factor, category_id INTO v_to_factor, v_to_category FROM core_catalogs.uom WHERE id = p_to_uom; -- Validar misma categoria IF v_from_category != v_to_category THEN RAISE EXCEPTION 'Cannot convert between different UoM categories'; END IF; -- Convertir RETURN p_quantity * (v_from_factor / v_to_factor); END; $$ LANGUAGE plpgsql STABLE; ``` --- ## Historial | Version | Fecha | Autor | Cambios | |---------|-------|-------|---------| | 1.0 | 2025-12-05 | System | Creacion inicial |