Saltar a contenido

Hallazgos verificados: Sales BOM vía Service Layer

Fecha de pruebas: 2026-07-13 Ambiente: Service Layer https://sapsl.compulandia.com.py/b1s/v2, base dev ZZ_COMPU_3 (lecturas de contraste contra prod COMPU). Herramienta: sl-helper.js (en _borradores/, no publicado) (ver §9).

Todo lo descrito abajo fue probado contra el servidor, no es teoría de manual.


1. Estructura del producto compuesto

  • Ítem padre: ítem normal de Items con InventoryItem = tNO, SalesItem = tYES. No mueve stock nunca.
  • BOM: entidad ProductTrees, TreeCode = ItemCode del padre, TreeType = iSalesTree, ProductTreeLines = componentes con cantidad y almacén.
  • Al crear el ProductTree, SAP estampa automáticamente TreeType = iSalesTree en el ítem padre.
// POST ProductTrees
{
  "TreeCode": "TESTBOM-PC",          // = ItemCode del padre
  "TreeType": "iSalesTree",
  "Quantity": 1,
  "Warehouse": "CEN",
  "PriceList": 1,
  "ProductTreeLines": [
    { "ItemCode": "TESTBOM-RAM", "Quantity": 2, "Warehouse": "CEN" },
    { "ItemCode": "TESTBOM-CPU", "Quantity": 1, "Warehouse": "CEN" },
    { "ItemCode": "TESTBOM-MB",  "Quantity": 1, "Warehouse": "CEN" },
    { "ItemCode": "TESTBOM-PSU", "Quantity": 1, "Warehouse": "CEN" }
  ]
}

Campo relevante del árbol: HideBOMComponentsInPrintout (tYES/tNO) — controla si el layout de impresión muestra los componentes. Es por BOM. El combo de prod 09379 lo tiene en tYES (cliente ve solo el combo); el requerimiento pide componentes visibles ⇒ tNO.

2. Crear pedido: explosión automática

POST Orders enviando una sola línea con el ItemCode del padre. SAP explota los componentes solo — pedido de prueba 33246: 1 línea enviada → 5 líneas devueltas.

// POST Orders  (payload mínimo verificado)
{
  "CardCode": "C32857",
  "DocDueDate": "2026-07-13",
  "DocumentLines": [
    {
      "ItemCode": "TESTBOM-PC",
      "Quantity": 1,
      "WarehouseCode": "CEN",
      "TaxCode": "IVA_10",          // ⚠ OBLIGATORIO acá — ver §5
      "PriceAfterVAT": 4950000      // opcional: precio manual del combo — ver §4
    }
  ]
}

Respuesta (líneas):

LineNum ItemCode TreeType Significado
0 TESTBOM-PC iSalesTree Línea padre — lleva precio e impuesto
1..4 componentes iIngredient Explotadas por SAP — precio 0
— cualquier otra iNotATree Línea normal, ajena al kit

⚠ NUNCA enviar las líneas de componentes explícitas en el POST: SAP explota el kit igual y además agrega las líneas enviadas como independientes (iNotATree) ⇒ productos duplicados en el pedido (verificado, pedido 33243, cancelado).

Las cantidades escalan solas: PATCH de Quantity del padre 1→2 duplicó las cantidades de los 4 hijos y el total (verificado, pedido 33241).

3. Modelo de precios: depende de una configuración de EMPRESA

Hallazgo central de la investigación. No es una regla del Service Layer — es el checkbox:

Cliente B1 → Gestión → Inicialización del sistema → Parametrizaciones de documentos → pestaña General → "Visualizar precios de artículos componentes de lista de materiales"

Checkbox Comportamiento
☑ Marcado (estaba así en dev hasta 2026-07-13) Precios en las líneas de componentes (auto-valorizados desde lista de precios); línea padre siempre 0 y su precio no editable (error ODBC -1029)
☐ Desmarcado (prod, y dev desde 2026-07-13) Precio en la línea padre (de su precio de lista); componentes explotan en 0
  • No se puede leer ni cambiar por Service Layer (verificado: no existe objeto DocumentSettings en $metadata; AdminInfo y CompanyInfo no lo incluyen — se diffearon completos entre dev y prod). Tampoco lo expone la DI API. Solo cliente B1.
  • Afecta solo documentos nuevos.
  • Ambas bases quedaron alineadas: precio en el padre, que es lo que pide el requerimiento.

4. Precio del combo (config actual: precio en padre) — verificado

  • Precio de lista: si el ítem padre tiene precio en la lista del cliente, la línea padre lo toma sola (pedido 33250: lista Gs. 5.500.000 IVA inc → UnitPrice 5.000.000 neto).
  • Precio manual: enviar PriceAfterVAT en la línea padre del POST. SAP calcula el neto y marca PriceSource: "dpsManual" (pedido 33252). Es el mismo campo que el OMS ya usa en líneas normales.
  • Edición posterior: PriceAfterVAT y DiscountPercent de la línea padre son editables por PATCH. ⚠ Si se cambian precio y descuento, mandarlos juntos en un solo PATCH: en llamadas separadas SAP recalcula el descuento sobre el precio unitario original, no sobre el último parcheado (verificado).
  • En prod se ve el patrón del cliente B1: precio manual tipeado ⇒ UnitPrice de lista + DiscountPercent negativo (ej. pedido prod 72649: -143.58% de "descuento" = recargo). Por SL no hace falta imitar eso; PriceAfterVAT directo funciona.

5. Impuesto (IVA) — trampa crítica

  • El TaxCode (ej. IVA_10) debe ir en la línea del padre en el POST inicial. Se propaga automáticamente a los componentes explotados.
  • Si se omite: después no se puede agregar a la línea padre por PATCH (ODBC -1029) y la factura falla con 1250000103 - Row without tax was found. El pedido queda facturable solo recreándolo (verificado, pedido 33244, cancelado).
  • Los ítems creados por SL sin grupo impositivo quedan sin IVA por defecto — el alta de ítems del OMS debe asignar el grupo de IVA.

6. Edición del pedido ("predefinido + editable") — verificado

Operación Resultado
PATCH Quantity del padre ✅ Escala todos los hijos proporcionalmente
PATCH Quantity de un componente ✅ Se edita libremente, independiente del resto
PATCH ItemCode de un componente ⚠ Funciona, pero la línea se desprende del kit: pasa a iNotATree y toma su propio precio de lista
Agregar línea nueva al pedido ✅ Entra como iNotATree (independiente del kit)
PATCH precio/descuento del padre ✅ (config actual; ver §4)

Implicancia para el OMS: el "upgrade de componente" (ej. cambiar la RAM) es posible pero la línea deja de pertenecer estructuralmente al combo — la UI debe decidir cómo representarlo (p. ej. poner la línea desprendida en precio 0 si el combo mantiene su precio, o recalcular).

7. Facturación — verificado

POST Invoices con referencias al documento base, una por cada línea del pedido (padre + componentes). Referenciar solo la línea padre falla.

// POST Invoices  (pedido 33252 → factura 724250)
{
  "CardCode": "C32857",
  "DocumentLines": [
    { "BaseType": 17, "BaseEntry": 33252, "BaseLine": 0 },
    { "BaseType": 17, "BaseEntry": 33252, "BaseLine": 1 },
    { "BaseType": 17, "BaseEntry": 33252, "BaseLine": 2 },
    { "BaseType": 17, "BaseEntry": 33252, "BaseLine": 3 },
    { "BaseType": 17, "BaseEntry": 33252, "BaseLine": 4 }
  ]
}
  • La factura conserva la estructura (iSalesTree/iIngredient), el precio y descuento del padre, y cierra el pedido (bost_Close).
  • Stock: al facturar se descuenta el stock de los componentes (verificado: RAM 18→16 por un combo con RAM ×2). El padre no mueve inventario. Mientras el pedido está abierto, el compromiso (Committed) también vive en los componentes.

8. Datos de prueba que quedaron en dev (ZZ_COMPU_3)

Reutilizables para las pruebas del OMS. Cliente de prueba: C32857 ("TEST").

Objeto Detalle
Ítems TESTBOM-PC (padre, lista 1 = 5.500.000), TESTBOM-RAM, TESTBOM-CPU, TESTBOM-MB, TESTBOM-PSU (con precios en lista 1 y stock en CEN)
BOM ProductTrees('TESTBOM-PC'), RAM ×2 + CPU + MB + PSU
Pedidos cerrados por factura 33246, 33252
Facturas 724248 (precios en componentes, config vieja), 724250 (precio en padre, config actual)
Pedidos cancelados 33241, 33243, 33244, 33248, 33250

Nota: dev no es copia reciente de prod (el ítem 09379 de prod no existe en dev; feriados cargados hasta 2023).

9. Herramienta de pruebas

sl-helper.js (en _borradores/, no publicado) — CLI mínimo para el Service Layer usado en esta investigación.

node sl-helper.js GET  "ProductTrees('TESTBOM-PC')"
node sl-helper.js POST "Orders" body.json
PROD=1 node sl-helper.js GET "Orders(72649)"   # producción: SOLO lectura (bloquea escrituras)
  • Lee credenciales de backend/.env (dev) o backend/.env.save (prod, con PROD=1).
  • Maneja login y sesión (B1SESSION), reintento en 401.
  • En modo prod rechaza todo método que no sea GET (excepto getters CompanyService_Get*).

10. Pendientes para la implementación en el OMS

  1. Diseño backend: detección de TreeType = iSalesTree en processOrderLines (orders.service.ts) — enviar solo línea padre con TaxCode + PriceAfterVAT.
  2. UI: representación padre/componentes (TreeType por línea), edición de cantidades, política para el "cambio de componente" (línea desprendida, ver §6).
  3. Facturación del OMS: incluir todas las líneas base del kit al copiar pedido → factura.
  4. Alta de combos desde el OMS (opcional): POST ProductTrees ya verificado (§1).
  5. Definir HideBOMComponentsInPrintout estándar para combos nuevos (requerimiento: tNO).