Saltar a contenido

Contrato — Enriquecimiento de líneas contra SAP (extender serials-with-price/batch)

Fecha: 2026-07-20. Audiencia: agente de backend (NestJS) + frontend. Estado: contrato acordado para implementar. Hechos SAP verificados en dev ZZ_COMPU_3.


1. Problema que resuelve

Al agregar/cargar líneas de pedido, el frontend necesita, contra SAP: existencia, ItemCode real, stock por depósito, seriales disponibles, precio lista 1 y flags. Hoy eso está repartido y duplicado en dos endpoints, con un bug:

  • POST /items/serials-with-price/batch → resuelve bien (por ItemCode y si no por SupplierCatalogNo), trae nombre, precio lista 1 y seriales. No trae stock ni flags.
  • GET /items/currentStock/:itemCode → trae stock, pero resuelve solo por ItemCode (Items('code')), así que con un SKU publicado (que en SAP vive en SupplierCatalogNo) da 404 falso → el ítem se marca como inexistente aunque exista.
  • POST /items/currentStock/batch → lo consumía el frontend pero nunca se implementó.

Verificado en SAP: el SKU del integrador vive en SupplierCatalogNo, no en ItemCode.

CPI-437320  → ItemCode SAP = 06544 (SupplierCatalogNo='CPI-437320')
CPI-1705    → no existe (ni por ItemCode ni por SupplierCatalogNo)

2. Decisión

No se crea ningún endpoint nuevo. Se extiende serials-with-price/batch (método getSerialsWithPriceList1, items.service.ts), que ya resuelve por ambos campos. Solo le faltan 3 datos, y todos salen de la query Items que ya hace — cero llamadas nuevas a SAP.

  • POST /items/currentStock/batch: descartado (su función queda acá).
  • GET /items/currentStock/:itemCode: se mantiene (lo usa ProductShow para stock+garantía); su bug de resolución por-ItemCode es tema aparte y de menor prioridad.

3. Cambios en el backend (getSerialsWithPriceList1)

  1. Extender el $select de la query Items (hoy en las líneas ~330 y ~343):

    antes:   ItemCode,ItemName,SupplierCatalogNo,ItemPrices
    después: ItemCode,ItemName,SupplierCatalogNo,ItemPrices,ItemWarehouseInfoCollection,ManageSerialNumbers,InventoryItem
    
    Verificado en dev: una sola query con $filter=(ItemCode eq X or SupplierCatalogNo eq X) + ese $select devuelve existencia, ItemCode real, stock por depósito y flags, para una mezcla de ItemCodes y SKUs.

  2. found: boolean explícito. Hoy el modo soft devuelve un stub con ItemName: undefined; agregar un booleano claro (found) en vez de que el cliente lo infiera.

  3. No filtrar depósitos. El single de stock descarta InStock > 0 (items.service.ts:269). Acá NO — se necesita el Available aunque sea ≤ 0 para poder decir "sin stock". Devolver todos los depósitos con InStock y Committed crudos (el frontend calcula Available = InStock − Committed).

  4. Saltear V_SERIES_ART si no maneja serie (perf). Hoy se llama siempre, aun para ítems sin serie. Con ManageSerialNumbers ya en la primera query, hacer la segunda llamada solo si ManageSerialNumbers === 'tYES'. La mayoría no maneja serie → baja fuerte las llamadas a SAP en el batch.

4. Reglas de resolución

  • Resolver cada código pedido por ItemCode eq X OR SupplierCatalogNo eq X (ya lo hace, mantener). El código pedido puede ser un ItemCode SAP o un SKU publicado.
  • SupplierCatalogNo NO es único (verificado: CPI-373138 → ItemCodes 06471, 06472, 06473…). Elegir uno determinísticamente, con la MISMA regla que processOrderLines usa al crear la línea del pedido, para que el stock/serie mostrado sea el del ítem que efectivamente se vende. Documentar cuál se devuelve.
  • Batch grande: trocear el $filter por límite de URL del Service Layer (p.ej. lotes de ~40 códigos) y unir resultados. Un pedido con muchas líneas puede superar el límite.
  • Degradación: si SAP falla para un código, devolver found:false / campos vacíos para ese código (modo soft), sin voltear todo el batch.

5. Contrato de respuesta

POST /items/serials-with-price/batch — body { itemCodes: string[] } Respuesta: Record<códigoPedido, EnrichedItem>

interface EnrichedItem {
  found: boolean;                 // ← NUEVO. false = no existe en SAP (dropship: se crea al guardar)
  ItemCode: string;               // ItemCode REAL de SAP (resuelto). Si !found, eco del código pedido.
  SupplierCatalogNo?: string;     // SKU del integrador
  ItemName?: string;
  InventoryItem?: 'tYES' | 'tNO'; // ← NUEVO. ¿mueve stock? (combo padre / servicios = tNO)
  ManageSerialNumbers?: 'tYES' | 'tNO'; // ← NUEVO
  ItemPrice: { PriceList: number; Price: number; Currency: string } | null; // lista 1 (igual que hoy)
  // ← NUEVO: stock por depósito, crudo (Available lo calcula el front)
  ItemWarehouseInfoCollection: { WarehouseCode: string; InStock: number; Committed: number }[];
  // Igual que hoy; VACÍO si ManageSerialNumbers !== 'tYES' (no se consulta la vista)
  V_SERIES_ART: { value: { ItemCode: string; SysSerial: number; IntrSerial?: string; Status: number; WhsCode?: string }[]; '@odata.count'?: number };
  error?: boolean;                // ya existe: true si falló la resolución de ese código
}

Es aditivo sobre la respuesta actual (ItemCode, SupplierCatalogNo, ItemName, ItemPrice, V_SERIES_ART) → no rompe a los consumidores existentes.

6. Dos modos de consumo (frontend)

Mismo endpoint, dos momentos con necesidades distintas:

Momento Qué importa Nota
Agregar (borrador) found + ItemCode real + stock + serie + precio Incremental: 1 código nuevo por agregado
Cargar (pedido guardado) stock + seriales disponibles La existencia ya está garantizada y el ItemCode ya es el real (viene del GET del pedido)

Aparte — validación al facturar: debe ser un chequeo fresco (sin caché) de stock; es el único que bloquea. No usar el valor cacheado del enriquecimiento. Puede pegar a este mismo endpoint bypasseando el TTL, o quedar como consulta puntual.

7. Refactor del frontend (resumen)

  • Un solo hook de enriquecimiento: useCurrentStockDict se elimina y su lógica de stock
  • existencia se pliega en useSerialsWithPriceDict, que pasa a devolver también found, warehouseStocks (con Available), InventoryItem, ManageSerialNumbers.
  • Se elimina el fallback de singles (getCurrentStockAndWarranty por ítem) y con él el bug del SKU-como-ItemCode.
  • OrderLinesPanel deja de combinar dos dicts: warehouseStocks, newInSap (= !found para simples), Serials y ReferencePriceL1 salen del mismo dict enriquecido.
  • Bonus: al venir el ItemCode real, el frontend puede fijarlo en la línea al resolver (cuidando el update-en-render, React #185, como con seriales).
  • La validación de factura (OrderPaymentPanel.findStockShortages) usa el batch extendido en modo fresco.
  • Helpers vigentes (lineStockStatus, lineMovesStock, lineSapPlan) no cambian de firma.

8. Efecto neto

  • De 2–3 requests por agregado a 1. Menos llamadas a SAP (además, seriales solo donde aplica).
  • Se arregla el bug de existencia (el endpoint ya resuelve por SupplierCatalogNo).
  • Un solo hook en el frontend; se borra useCurrentStockDict y el endpoint fantasma currentStock/batch.