En las primeras pruebas con SAP Service Layer y su cliente OData para .Net me he encontrado con un problema que, por otra parte, me esperaba. Quiero decir que la actualización en OData se basa en enviar el conjunto de los cambios y no toda la entidad, para tener un flujo de datos lo más reducido posible. En Service Layer esto implica que si, por ejemplo, quiero actualizar un pedido de ventas, enviaré los campos de la cabecera que cambian, así como las líneas que se cambian o se añaden.
Pero, ¿y si lo que quiero es eliminar una línea? En Di Server/B1WS debíamos enviar todas las líneas, por lo que, si faltaba alguna, SAP entendía que la queríamos eliminar.
¿Es posible hacer eso en Service Layer?
Atendiendo al manual del usuario que tenemos en https://<miservidor>:50000/Working_with_SAP_Business_One_Service_Layer.pdf, no.
Pero en los foros de SAP podemos encontrar referencias a un extraño parámetro, B1S-ReplaceCollectionsOnPatch. Este parámetro de la cabecera de nuestra petición indica a SAP que se envían las colecciones al completo, igual que en Di Server, y así él se encargará de eliminar lo que falte.
Este parámetro lo añadiríamos a la cabecera en el BuildingRequest de nuestro context. Por ejemplo, controlando el evento:
Private Sub context_BuildingRequest(sender As Object, e As BuildingRequestEventArgs) Handles context.BuildingRequest e.Headers.Add("B1S-ReplaceCollectionsOnPatch", "true") End Sub
Si hemos tomado el código que nos ofrece SAP y que mencionaba en mi anterior entrada, ya tenemos gestionado el BuildingRequest dentro de nuestro método InitializeContext:
private void InitializeContext() { // Get the cookie if the response header containing Set-Cookie. this.ReceivingResponse += (sender, eventArgs) => { (...) }; // Set the cookie for each request. this.BuildingRequest += (sender, eventArgs) => { (...) }; // Ignore the SSL/TLS certificate check for the HTTPS connection to Service Layer. ServicePointManager.ServerCertificateValidationCallback = TLSCertificateValidate; // Filter out the null properties for some cases like entity creation. this.Configurations.RequestPipeline.OnEntryStarting((arg) => { arg.Entry.Properties = FilterNullValues(arg.Entry); }); }
Así que podemos crearnos una propiedad pública para gestionar este valor:
/// <summary> /// Por defecto, en updates vía Patch sólo se actualizan (modificar y añadir) las filas /// que se pasan de una colección (p.ej.: líneas de pedido), /// por lo que no es posible eliminar filas. /// Si se pone a True esta propiedad, las filas que no se envíen en el update son eliminadas. /// /// Internamente, añade B1S-ReplaceCollectionsOnPatch=true en la cabecera cuando es True. /// </summary> public bool ProcesarColeccionesCompletasEnUpdate { get; set; } = false;
Y meterlo en el BuildingRequest:
private void InitializeContext() { (...) // Set the cookie for each request. this.BuildingRequest += (sender, eventArgs) => { (...) // <bool>.ToString devuelve True o False (primera letra en mayúscula) y puede dar error. if(eventArgs.Headers.ContainsKey("B1S-ReplaceCollectionsOnPatch")) { eventArgs.Headers["B1S-ReplaceCollectionsOnPatch"] = ProcesarColeccionesCompletasEnUpdate ? "true" : "false" ; } else if(ProcesarColeccionesCompletasEnUpdate) { eventArgs.Headers.Add("B1S-ReplaceCollectionsOnPatch", "true"); } }; (...) }
Y ya podemos eliminar líneas sin problemas cuando toque. Por ejemplo, en pedidos u ofertas de ventas, pero también en órdenes de fabricación, en nuestros UDOs, etc.