From 3111bb38427cf58a1bb8e557d5ac91a6844fb634 Mon Sep 17 00:00:00 2001 From: George Date: Fri, 16 Oct 2026 07:38:01 -0500 Subject: [PATCH] Documentar la transferencia desde ALM99 (docs/TRANSFERENCIA_IMPORTACION.md) y comentar el codigo Co-Authored-By: Claude Sonnet 5.5 --- CLAUDE.md | 4 +- Net.Data/HelperData/Invoices/HelperInvoice.cs | 4 +- .../WMS/Transferencia/EntradaImportacion.cs | 3 + .../Transferencia/TransferenciaRepository.cs | 6 ++ docs/TRANSFERENCIA_IMPORTACION.md | 102 ++++++++++++++++++ 5 files changed, 117 insertions(+), 2 deletions(-) create mode 100644 docs/TRANSFERENCIA_IMPORTACION.md diff --git a/CLAUDE.md b/CLAUDE.md index 954c9aa..ea305c2 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -121,7 +121,9 @@ transferencias siempre, sin depender de `Notificacion`. Detalle y tabla en sin amarre). Con `DocEntryEM` informado se respeta (antes reventaba con referencia nula si la entrada o la línea no existían). Sin probar contra SAP: validar la consulta, que `isIns='Y'` sea factura de reserva y que SAP acepte `BaseType "PurchaseDeliveryNotes"` en - la línea de `StockTransfers`. + la línea de `StockTransfers`. Detalle, errores y diagnóstico en + `docs/TRANSFERENCIA_IMPORTACION.md`. Probado con datos reales (SQL Server) solo la consulta: entradas + de 1 y 2 líneas, y una transferencia cancelada libera la entrada. - Sin probar contra SAP: confirmar con la factura 292410 que SAP devuelve `BaseType`/`BaseEntry` en las líneas y `BatchNumbers` en las de la Entrega. diff --git a/Net.Data/HelperData/Invoices/HelperInvoice.cs b/Net.Data/HelperData/Invoices/HelperInvoice.cs index 9934030..6f2fdf5 100644 --- a/Net.Data/HelperData/Invoices/HelperInvoice.cs +++ b/Net.Data/HelperData/Invoices/HelperInvoice.cs @@ -35,7 +35,9 @@ namespace Net.Data.HelperData.Invoices // Lineas de todas las Entradas de Mercaderia (no anuladas) enlazadas (BaseType 18) a la Factura de Reserva // de proveedores con ese DocNum (isIns = 'Y'). Si una linea ya tiene transferencia de stock (TargetType 67) - // y esa transferencia no esta anulada, TransferenciaDocEntry trae su DocEntry; si no, 0. + // y esa transferencia no esta anulada, TransferenciaDocEntry trae su DocEntry; si no, 0 (una transferencia cancelada + // libera la entrada). Lo usa TransferenciaRepository (origen ALM99); ver docs/TRANSFERENCIA_IMPORTACION.md. + // OJO: Get() (procedimiento ITCA_WMS_SAP_GetEntradaPorImportacion) ya no se usa; devolvia un solo DocEntry. public static List GetEntradasPorFactura(string DocNum) { var lista = new List(); diff --git a/Net.Data/WMS/Transferencia/EntradaImportacion.cs b/Net.Data/WMS/Transferencia/EntradaImportacion.cs index df2a115..acc26ce 100644 --- a/Net.Data/WMS/Transferencia/EntradaImportacion.cs +++ b/Net.Data/WMS/Transferencia/EntradaImportacion.cs @@ -8,6 +8,9 @@ namespace Net.Data.WMS.Transferencia // Elige la Entrada de Mercaderia de una Factura de Reserva que corresponde a una transferencia desde ALM99. // WMS recibe por partes: cada entrada tiene su propio juego de articulos/cantidades, y WMS notifica una // transferencia por entrada con ese mismo juego (se compara solo ItemCode y cantidad, no el numero de item). + // Reglas: coincidencia exacta de articulos y cantidades (sumadas por articulo); entre las que coinciden se toma la mas antigua + // (menor DocEntry) sin transferencia; si todas las que coinciden ya estan transferidas se rechaza para no duplicar. + // Ver docs/TRANSFERENCIA_IMPORTACION.md. public static class EntradaImportacion { private const decimal Tolerancia = 0.0001m; diff --git a/Net.Data/WMS/Transferencia/TransferenciaRepository.cs b/Net.Data/WMS/Transferencia/TransferenciaRepository.cs index dbe2a6c..0723d17 100644 --- a/Net.Data/WMS/Transferencia/TransferenciaRepository.cs +++ b/Net.Data/WMS/Transferencia/TransferenciaRepository.cs @@ -112,6 +112,12 @@ namespace Net.Data.WMS.Transferencia }); //=== SI EL ALMACEN ES ALM99 (almacen de costeo de importaciones), AMARRA CON LA ENTRADA DE MERCANCIA + // Proceso: factura de reserva -> WMS recibe por partes y crea una entrada por recepcion (todas a ALM99) -> costeo en SAP -> + // WMS notifica UNA transferencia por entrada, con numorden = DocNum de la factura de reserva y en el detalle los + // articulos/cantidades de esa entrada. Detalle completo y diagnostico en docs/TRANSFERENCIA_IMPORTACION.md. + // Casos: (1) DocEntryEM informado -> se usa esa entrada; (2) sin DocEntryEM -> se busca la entrada de la factura que + // coincida con el detalle y no tenga transferencia (EntradaImportacion.Elegir); (3) la factura no tiene entradas -> + // como antes, lotes del stock de HANA sin amarre. if (entidad.CodigoSubAlmacenOrigen == "ALM99") { StockTransfer.U_SYP_MDMT = "21"; diff --git a/docs/TRANSFERENCIA_IMPORTACION.md b/docs/TRANSFERENCIA_IMPORTACION.md new file mode 100644 index 0000000..330df5a --- /dev/null +++ b/docs/TRANSFERENCIA_IMPORTACION.md @@ -0,0 +1,102 @@ +# Transferencia de stock desde ALM99 (importaciones) — v2.1.9.0 + +Documento de soporte. Código: `Net.Data/WMS/Transferencia/TransferenciaRepository.cs` (bloque `ALM99`), +`Net.Data/WMS/Transferencia/EntradaImportacion.cs` (elección de la entrada) y +`Net.Data/HelperData/Invoices/HelperInvoice.GetEntradasPorFactura` (consulta HANA). +Endpoint: `POST /api/Transferencia`. + +## Proceso de negocio + +1. En SAP se crea una **Factura de Reserva de proveedores** y se envía al WMS para su recepción. +2. El WMS recibe **por partes**: por cada recepción crea una **Entrada de Mercadería** + (`PurchaseDeliveryNotes`) enlazada a la factura (`BaseType 18`, `BaseEntry`, `BaseLine`). + Hay una o varias entradas por factura y todas ingresan al almacén **ALM99** (costeo). + El WMS no guarda el `DocEntry` de las entradas, solo sabe qué artículos recibió en cada una. +3. En SAP se hace el costeo (precio de entrega) de las entradas. +4. Terminado el costeo, el WMS notifica **una transferencia por entrada** hacia el almacén final. + +## JSON que envía el WMS + +- `numorden` = **DocNum de la factura de reserva** (único). +- `codigoSubAlmacenOrigen` = `ALM99`; `codigoSubAlmacenDestino` = almacén final (p. ej. `ALM21`). +- `docEntry` = 0 (no hay solicitud de traslado); `docEntryEM` no se envía. +- `detalle` = los artículos y cantidades **de una sola entrada**. Solo se usan `codigoarticulo` + y `cantidadpedida`; `item` y `loteproducto` se ignoran en este caso. + +## Qué hace la API (origen = ALM99 y sin `docEntryEM`) + +1. Consulta HANA (`HelperInvoice.GetEntradasPorFactura`): factura `OPCH` con `DocNum = numorden` + y `isIns = 'Y'` (= factura de reserva) → líneas `PDN1` con `BaseType 18` y `BaseEntry` = factura + → entrada `OPDN` no anulada (`CANCELED = 'N'`). +2. Una entrada **ya tiene transferencia** si la línea tiene `TargetType = 67` y su `TrgetEntry` + apunta a una transferencia `OWTR` con `CANCELED = 'N'`. Una transferencia anulada libera la entrada. +3. `EntradaImportacion.Elegir` compara, por entrada, el conjunto `ItemCode` → cantidad (sumada por + artículo) contra el detalle del JSON (también sumado). Debe coincidir **exacto** (mismos artículos, + mismas cantidades, tolerancia 0.0001). Se toma la **entrada más antigua** (menor `DocEntry`) que + coincida y no tenga transferencia. +4. Se lee la entrada por Service Layer (`PurchaseDeliveryNotes(docEntry)`) y las líneas de la + transferencia salen **de la entrada**: artículo, cantidad, lotes reales y base + (`BaseType "PurchaseDeliveryNotes"`, `BaseEntry`, `BaseLine` = línea de la entrada). Si los lotes + de una línea no suman su cantidad, responde error antes de enviar a SAP. +5. Se envía `StockTransfers` con `U_SYP_MDMT = "21"`. + +## Respuestas de error (400, mensaje en la respuesta y en `ITCA_LOG_INTEGRACION`) + +| Mensaje | Causa | +|---|---| +| `SAP: Ninguna entrada de la factura de reserva X coincide con los articulos y cantidades enviados. Entradas pendientes de transferir (DocNum): ...` | El JSON no es igual a ninguna entrada (falta/sobra un artículo o cambia una cantidad). Lista las entradas aún sin transferir para comparar. | +| `SAP: Todas las entradas de la factura de reserva X con esos articulos y cantidades ya estan transferidas: entrada N ya transferida en la transferencia M` | Reintento del mismo JSON o entrada ya transferida (evita duplicados). | +| `SAP: Los lotes de la linea L (item) de la entrada N no cubren la cantidad Q` | La entrada tiene lotes que no suman la cantidad de la línea. | +| `SAP: La entrada de mercaderia N no se puede leer` | Falló el GET al Service Layer. | +| Error de SAP al crear `StockTransfers` | Ver la fila `PROCESO='ServiceLayer Error'` (JSON enviado + mensaje de SAP). | + +## Otros casos del mismo endpoint (no cambiaron) + +- **Transferencia simple** (origen distinto de ALM99, `docEntry = 0`): lotes por stock HANA (FEFO), sin base. +- **Con solicitud de traslado** (`docEntry ≠ 0`): la solicitud es la base y se cierra al terminar. +- **ALM99 con `docEntryEM` informado**: se respeta; lotes y base de esa entrada, amarrando + `BaseLine` del JSON con `BaseLine` de la entrada. Si la entrada o la línea no existen responde error claro. +- **ALM99 cuando la factura no tiene entradas** (o `numorden` no es una factura de reserva): se mantiene el + comportamiento anterior (lotes del stock de HANA, sin amarre). + +## Verificado y pendiente + +- Verificado con datos reales (SQL Server): la consulta encuentra las entradas por `BaseType 18` y + `isIns='Y'`; muestra el estado de transferencia por línea (entradas con 1 y con 2 líneas) y una + transferencia **cancelada** vuelve a dejar la entrada libre (`TransferenciaDocEntry = 0`). +- Verificado con simulación: la lógica de `Elegir` (coincidencia, entrada más antigua, ya transferida, no coincide). +- **Pendiente de confirmar contra SAP/HANA reales**: que la consulta corra en HANA (la API usa HANA); + que SAP acepte `BaseType "PurchaseDeliveryNotes"` en las líneas de `StockTransfers`; y el caso en que + una entrada de varias líneas tenga transferida solo una línea (hoy cuenta como ya transferida). + +## Cómo diagnosticar ("no se genera la transferencia") + +1. Ver la fila en `"ITC_WMS_SAP"."ITCA_LOG_INTEGRACION"` con `PROCESO = 'Transferencia'` y + `NRO_ORDEN = `: trae el JSON del WMS y la respuesta (el mensaje de la tabla de arriba). +2. Ejecutar la consulta de `HelperInvoice.GetEntradasPorFactura` (versión SQL Server/HANA al final) con el + `DocNum`: si no devuelve filas, la factura no existe, no es de reserva (`isIns`) o sus entradas no tienen + `BaseType 18`; si `TransferenciaDocEntry ≠ 0`, esa entrada ya fue transferida. +3. Comparar artículos y cantidades del JSON contra las filas de esa entrada. + +```sql +-- HANA (reemplazar y el DocNum de la factura de reserva) +SELECT E."DocEntry", E."DocNum", L."LineNum", L."ItemCode", L."Quantity", + CASE WHEN L."TargetType" = 67 AND W."DocEntry" IS NOT NULL AND W."CANCELED" = 'N' + THEN W."DocEntry" ELSE 0 END AS "TransferenciaDocEntry" +FROM ""."OPCH" F +INNER JOIN ""."PDN1" L ON L."BaseType" = 18 AND L."BaseEntry" = F."DocEntry" +INNER JOIN ""."OPDN" E ON E."DocEntry" = L."DocEntry" +LEFT JOIN ""."OWTR" W ON L."TargetType" = 67 AND W."DocEntry" = L."TrgetEntry" +WHERE F."DocNum" = 262000082 AND F."isIns" = 'Y' AND E."CANCELED" = 'N' +ORDER BY E."DocEntry", L."LineNum"; +``` + +En SQL Server es la misma consulta sin comillas dobles y con `[].dbo.OPCH`, etc. + +## Historia + +Antes (también en el código del desarrollador anterior y en el commit inicial) el bloque dependía de +`DocEntryEM`, que el WMS no envía: nunca se amarraba a la entrada y los lotes salían solo del stock de HANA +(sin validar que cubrieran la cantidad). La versión original buscaba la entrada por `NumOrden` con +`HelperInvoice.Get` (procedimiento `ITCA_WMS_SAP_GetEntradaPorImportacion`, un solo `DocEntry`), que quedó +comentado; no servía para varias entradas por factura. Esa función sigue en el código sin llamadores.