Files

12 KiB
Raw Permalink Blame History

Cambio de negocio a portar — Picking diurno/nocturno

Requerimiento de negocio sin equivalente en este .NET: las notificaciones de Picking durante el día ya no deben crear documentos transaccionales en SAP inmediatamente — eso causaba que el WMS reportara éxito sin que SAP reflejara nada. Especificación traducida 1:1 de la implementación ya validada en Python (itca_api_sap_wms_python/app/services/picking_service.py, app/schemas/picking.py, app/api/routes/picking.py).

Ver también PORTE_FUNCIONALIDAD_NUEVA.md para el detalle de un bug de SQL injection real (HelperProcesoPiciking.Set) que hay que corregir como parte de este mismo trabajo, porque es exactamente el código que este cambio va a aislar y reusar.

Flujo de negocio

La hoja de ruta contiene varias facturas. El repartidor va notificando el estado de cada factura a medida que llega a cada punto de entrega (preliminar, sin crear nada en SAP). Al volver a la oficina, el tercero recorre esas mismas facturas con un FOR de su lado llamando otra vez al mismo endpoint pero en modo cierre (ahí sí se crean los documentos SAP), y al final cierra la hoja de ruta completa una sola vez.

Regla Proceso + Notificacion (versión 2.1.2.0)

Notificacion solo se valida cuando Proceso = "ULTIMA MILLA". En los demás procesos el campo se ignora.

Proceso Notificacion Resultado
ULTIMA MILLA "cierre" Lógica completa: crea documentos en SAP (solicitud de devolución, estado 23).
ULTIMA MILLA ausente u otro valor Solo actualiza estado en HANA; no crea nada en SAP.
DESPACHO cualquiera / ausente Lógica completa siempre: NC y transferencias automáticas (estados 5 y 6).
PICKING cualquiera / ausente Lógica completa siempre: NC y transferencias automáticas (estado 13).

Historial: en 2.1.1.0 el filtro se aplicaba a todos los procesos, por lo que DESPACHO y PICKING no generaban la NC si no llegaba "cierre". Corregido en 2.1.2.0.

Lotes de la nota de crédito en DESPACHO (versión 2.1.4.0)

Se resuelve línea por línea según el documento origen de la línea de la factura:

La línea de la factura tiene... Los lotes salen de...
Entrega como origen (BaseType = 15) La línea de la Entrega (DeliveryNotes(BaseEntry), línea BaseLine).
Otro origen o ninguno La propia línea de la factura (LineNum).

Si hay lotes de origen y no cubren la cantidad a devolver, o no se puede leer la Entrega, la API responde un error con la línea y el artículo en vez de enviar lotes incompletos a SAP. Si no hay lotes de origen (artículo sin control por lote) la línea se envía sin lotes. PICKING (estado 13) no cambia: genera un lote nuevo por fecha y hora.

Cantidades de la NC y de las transferencias (versión 2.1.6.0)

  • Cantidad de la línea de NC (DESPACHO y PICKING estado 13): cantidad facturada − CantidadPedida; solo entran las líneas con resultado mayor a 0.
  • Transferencia automática (cuando U_ITC_ALMDES difiere del almacén de la línea): una línea por artículo, con la cantidad sumada de las líneas de la NC de ese par origen/destino y los lotes de todas esas líneas (el mismo lote en varias líneas se suma).

Resumen del contrato

Método Ruta Cambia hoy Qué hace
POST /api/picking Sí — agrega campo Notificacion Solo con Proceso = "ULTIMA MILLA": "cierre" → lógica completa (crea documento SAP); cualquier otro valor/ausente → solo actualiza estado en HANA, nunca toca SAP. Con DESPACHO, PICKING y demás procesos el campo se ignora y la lógica corre siempre (NC y transferencias).
POST /api/picking/ruta No Sin cambios — CreateHojaRuta ya existe y sigue igual.
POST /api/picking/hojaruta/{docEntry}/cerrar Nuevo Cierra el UDO ARD_PHR completo en SAP. Sin body.

1. Campo Notificacion en POST /api/Picking

DTO/Entidad — agregar una propiedad

Archivo: Net.Business.Entities/WMS/Picking/BE_PickingRequest.cs — agregar:

public string Notificacion { get; set; }  // solo se evalua con Proceso "ULTIMA MILLA": "cierre" | otro/ausente = preliminar

Archivo: Net.Business.DTO/WMS/Picking/Dto_PickingRequest.cs — agregar la misma propiedad y copiarla en RetornaModelo():

public string Notificacion { get; set; }

public BE_PickingRequest RetornaModelo()
{
    return new BE_PickingRequest()
    {
        // ... resto de los campos existentes, sin tocar ...
        Notificacion = Notificacion,
    };
}

Dónde cortar la lógica — PickingRepository.Create

Archivo: Net.Data/WMS/Picking/PickingRepository.cs, método Create.

La buena noticia: el corte es un único early-return, justo después de la línea que ya actualiza el estado en HANA — no hace falta duplicar ninguna de las validaciones de arriba (a diferencia de la versión Python, que sí duplicó el bloque de validación en una función separada porque Python separa actualizar_estado()/create() en dos funciones independientes; acá alcanza con un if adentro del mismo método).

// ... validaciones existentes sin cambios (lineas 51-93: DocEntry, Proceso,
//     IdEstadoCubico, HelperProcesoPermitido.Get, desambiguacion Existe > 1) ...

//=== REALIZA PROCESO COMPLETO DE ACTUALIZACION
HelperProcesoPiciking.Set(entidad.Proceso, entidad.IdEstadoCubico, entidad.DocEntry, entidad.CodMotivo, Proceso.Code);
// <-- linea 96 actual, sin tocar

// === Solo ULTIMA MILLA: notificacion preliminar (diurna) - nunca toca SAP.
// DESPACHO / PICKING no dependen de Notificacion.
if (string.Equals(entidad.Proceso, "ULTIMA MILLA", StringComparison.OrdinalIgnoreCase)
    && !string.Equals(entidad.Notificacion, "cierre", StringComparison.OrdinalIgnoreCase))
{
    return resultadoTransaccion.GetInformacionResultado(entidad.DocEntry, entidad.NumOrden, "Estado actualizado correctamente", entidad);
}

#region PROCESO DE NOTA DE CREDITO
// ... todo el resto del metodo (lineas 98-333 actuales) sin ninguna modificacion ...
#endregion

Es decir: todo lo que hoy hace Create() (líneas 45-341 completas) sigue siendo exactamente la lógica de cierre — no se toca nada de ahí. Solo se agrega el if de corte después de la línea 96, y únicamente para ULTIMA MILLA. Para DESPACHO y PICKING la NC se genera sin importar Notificacion.

Nota de contrato: detalle ya es obligatorio hoy en este endpoint (la validación de arriba lo necesita para desambiguar Existe > 1), así que no hace falta agregar ninguna validación nueva ahí — la versión Python solo lo documentó explícito porque separó la función, acá ya viene gratis al reusar el mismo método.

Bodies de ejemplo

Notificación preliminar (solo ULTIMA MILLA, reparto en curso — Notificacion ausente o distinto de "cierre"):

{
  "numOrden": "OT-2026-001",
  "docEntry": 500,
  "proceso": "ULTIMA MILLA",
  "codMotivo": "M01",
  "idEstadoCubico": 23,
  "codigoAlmacen": "ALM01",
  "detalle": [
    { "item": 0, "codigoArticulo": "ART-001", "cantidadPedida": 3, "estado": "C" }
  ]
}

Cierre (ULTIMA MILLA, mismo shape, agrega notificacion: "cierre" — crea el documento SAP real, misma respuesta que hoy):

{
  "numOrden": "OT-2026-001",
  "docEntry": 500,
  "proceso": "ULTIMA MILLA",
  "codMotivo": "M01",
  "idEstadoCubico": 23,
  "codigoAlmacen": "ALM01",
  "notificacion": "cierre",
  "detalle": [
    { "item": 0, "codigoArticulo": "ART-001", "cantidadPedida": 3, "estado": "C" }
  ]
}

Respuesta — notificación preliminar (éxito)

{
  "docEntry": 500,
  "docNum": "OT-2026-001",
  "statusCode": 200,
  "message": "Estado actualizado correctamente",
  "error": null,
  "appName": "PickingRepository",
  "methodName": "Create",
  "listData": null,
  "data": { "...": "eco del PickingRequest enviado" },
  "nombreEstacion": "SERVER-WMS"
}

(La respuesta de cierre no cambia respecto a la que ya devuelve Create() hoy — ver el controller existente.)

2. POST /api/Picking/HojaRuta/{docEntry}/Cerrar (nuevo)

Cierra la Hoja de Ruta completa en SAP — se llama una sola vez, después de que el tercero terminó el FOR de cierre por cada factura.

  • Hoja de Ruta es un UDO en SAP: ARD_PHR.
  • SAP Service Layer: POST ARD_PHR({docEntry})/Close — mismo patrón que SolicitudTransferencia/{docEntry}/cerrar (ver SOLICITUD_TRANSFERENCIA.md para el gap de infraestructura compartido: hace falta un método para POST .../Close con body vacío, no existe ninguno hoy en ConnectionServiceLayer).
  • Body del request: ninguno (solo docEntry en la ruta).
  • Body enviado a SAP: {}.
  • Interfaz: agregar a IPickingRepository.cs:
    Task<BE_ResultadoTransaccion<object>> CerrarHojaRuta(int docEntry);
    
public async Task<BE_ResultadoTransaccion<object>> CerrarHojaRuta(int docEntry)
{
    _metodoName = "CerrarHojaRuta";
    ResultadoTransaccion<object> resultadoTransaccion = new ResultadoTransaccion<object>(_metodoName, _aplicacionName);

    try
    {
        var connectServiceLayer = new ConnectionServiceLayer(_configuration, _clientFactory);
        await connectServiceLayer.PostAsync<object>($"ARD_PHR({docEntry})/Close", new { });
        return null;
    }
    catch (Exception ex)
    {
        return resultadoTransaccion.GetInformacionResultado(ex.Message.ToString(), null);
    }
}

Controller (PickingController.cs, agregar junto a Ruta):

[HttpPost]
[Route("HojaRuta/{docEntry}/Cerrar")]
public async Task<IActionResult> CerrarHojaRuta(int docEntry)
{
    var error = await _repository.Picking.CerrarHojaRuta(docEntry);

    var result = error ?? new BE_ResultadoTransaccion<object>
    {
        DocEntry = docEntry,
        StatusCode = 200,
        Message = "Cerrado Correctamente",
        AppName = "PickingRepository",
        MethodName = "CerrarHojaRuta",
    };

    B_Log.Add(/* mismo patron que el resto - proceso "Hoja Ruta" */);

    return error == null ? Ok(result) : BadRequest(result);
}

Por qué 200 con body y no 204: mismo motivo que SolicitudTransferencia — un 204 no puede llevar body (RFC 7231 §6.3.5) y muchos clientes HTTP, incluido HttpClient de .NET, no lo leen. 201 se descarta porque no se crea nada nuevo.

Respuesta éxito (200)

{
  "docEntry": 77,
  "docNum": null,
  "statusCode": 200,
  "message": "Cerrado Correctamente",
  "error": null,
  "appName": "PickingRepository",
  "methodName": "CerrarHojaRuta",
  "listData": null,
  "data": null,
  "nombreEstacion": "SERVER-WMS"
}

Respuesta error (400)

{
  "docEntry": null,
  "docNum": null,
  "statusCode": 500,
  "message": null,
  "error": "La hoja de ruta ya esta cerrada",
  "appName": "PickingRepository",
  "methodName": "CerrarHojaRuta",
  "listData": null,
  "data": null,
  "nombreEstacion": "SERVER-WMS"
}

Checklist de integración para el WMS

  1. Nunca mandar un array a POST /api/Picking. Un documento (una factura) por llamada — el FOR por factura es responsabilidad del WMS.
  2. notificacion ausente o distinto de "cierre" = preliminar (default seguro, nunca crea nada en SAP).
  3. El orden importa: primero el FOR de cierre por cada factura (notificacion: "cierre"), y recién al final —una sola vez— el POST /api/Picking/HojaRuta/{docEntry}/Cerrar.
  4. docEntry en POST /api/Picking es el de la Factura, no el de la Hoja de Ruta.

Pendiente de validar en QA

  • Confirmar en QA si el nuevo Close del UDO ARD_PHR efectivamente bloquea modificaciones futuras sobre el DocEntry una vez cerrado (no confirmado todavía ni en la versión Python).
  • Correr los mismos casos contra el endpoint .NET levantado antes de dar por válido este port — en Python tampoco se probó esto todavía end-to-end contra Service Layer real (ver CLAUDE.md del proyecto Python, sección "Pruebas contra HANA/SAP reales").