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 (porItemCodey si no porSupplierCatalogNo), trae nombre, precio lista 1 y seriales. No trae stock ni flags.GET /items/currentStock/:itemCode→ trae stock, pero resuelve solo porItemCode(Items('code')), así que con un SKU publicado (que en SAP vive enSupplierCatalogNo) 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 usaProductShowpara stock+garantía); su bug de resolución por-ItemCode es tema aparte y de menor prioridad.
3. Cambios en el backend (getSerialsWithPriceList1)¶
-
Extender el
$selectde la queryItems(hoy en las líneas ~330 y ~343):Verificado en dev: una sola query conantes: ItemCode,ItemName,SupplierCatalogNo,ItemPrices después: ItemCode,ItemName,SupplierCatalogNo,ItemPrices,ItemWarehouseInfoCollection,ManageSerialNumbers,InventoryItem$filter=(ItemCode eq X or SupplierCatalogNo eq X)+ ese$selectdevuelve existencia, ItemCode real, stock por depósito y flags, para una mezcla de ItemCodes y SKUs. -
found: booleanexplícito. Hoy el modosoftdevuelve un stub conItemName: undefined; agregar un booleano claro (found) en vez de que el cliente lo infiera. -
No filtrar depósitos. El single de stock descarta
InStock > 0(items.service.ts:269). Acá NO — se necesita elAvailableaunque sea ≤ 0 para poder decir "sin stock". Devolver todos los depósitos conInStockyCommittedcrudos (el frontend calculaAvailable = InStock − Committed). -
Saltear
V_SERIES_ARTsi no maneja serie (perf). Hoy se llama siempre, aun para ítems sin serie. ConManageSerialNumbersya en la primera query, hacer la segunda llamada solo siManageSerialNumbers === '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 XORSupplierCatalogNo eq X(ya lo hace, mantener). El código pedido puede ser un ItemCode SAP o un SKU publicado. SupplierCatalogNoNO es único (verificado:CPI-373138→ ItemCodes 06471, 06472, 06473…). Elegir uno determinísticamente, con la MISMA regla queprocessOrderLinesusa 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
$filterpor 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 (modosoft), 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:
useCurrentStockDictse elimina y su lógica de stock - existencia se pliega en
useSerialsWithPriceDict, que pasa a devolver tambiénfound,warehouseStocks(conAvailable),InventoryItem,ManageSerialNumbers. - Se elimina el fallback de singles (
getCurrentStockAndWarrantypor ítem) y con él el bug del SKU-como-ItemCode. OrderLinesPaneldeja de combinar dos dicts:warehouseStocks,newInSap(=!foundpara simples),SerialsyReferencePriceL1salen del mismo dict enriquecido.- Bonus: al venir el
ItemCodereal, 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
useCurrentStockDicty el endpoint fantasmacurrentStock/batch.