diff --git a/CLAUDE.md b/CLAUDE.md index bdd4473..d07033a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -98,7 +98,12 @@ transferencias siempre, sin depender de `Notificacion`. Detalle y tabla en exactas sin llamadores: `HelperHojaRuta.GetConsolidado` (queda `HelperStock.GetConsolidado`) y `HRRepository.UpdateCreateCreditNotes` (queda `CreditNotesRepository`). Sigue pendiente: código comentado, `PatchAsyncSBA`, repositorios de compras sin uso - (`SetCreatePurchaseInvoices` duplicado en `PurchaseOrdersRepository`) y la redundancia de los métodos POST/PATCH del cliente. + (`SetCreatePurchaseInvoices` duplicado en `PurchaseOrdersRepository`) y el resto de la redundancia (serie `BC10`/`FC10` y cabecera de la NC). + `PostAsync`, `PostReOpen` y `PostClose` de `ConnectionServiceLayer` comparten ahora un + núcleo privado (`EnviarPostAsync`: con/sin cuerpo, acepta o no el 204); se verificó con un + servidor simulado que el resultado es idéntico al anterior (reintento por 401, 204 sin + cuerpo, rechazo y log). `PatchAsyncSBA` sigue aparte: su manejo de errores es distinto y + mantiene su bug del 204 (sin marcar éxito; el único llamador ignora el resultado). La transferencia automática de stock de la NC (3 copias en Picking x2 y Recibo) pasó a un solo método: `Net.Data/ServicesLayer/StockTransfer/TransferenciaAutomaticaNC.Crear`. - Sin probar contra SAP: confirmar con la factura 292410 que SAP devuelve diff --git a/Net.Connection.ServiceLayer/ConnectionServiceLayer.cs b/Net.Connection.ServiceLayer/ConnectionServiceLayer.cs index 767ccca..db7634f 100644 --- a/Net.Connection.ServiceLayer/ConnectionServiceLayer.cs +++ b/Net.Connection.ServiceLayer/ConnectionServiceLayer.cs @@ -218,140 +218,29 @@ namespace Net.Connection.ServiceLayer // PASAMOS LA EXCEPCIÓN A LA APP. } } - // POST con cuerpo JSON. Reintenta una vez si la sesion expiro (401). Si SAP rechaza, devuelve Exito = false y guarda - // el JSON enviado con ErrorLogger (tabla ITCA_LOG_INTEGRACION) - public async Task PostAsync(string Query, object obj) + // POST con cuerpo JSON. Crea un documento en SAP. Si SAP rechaza, devuelve Exito = false y guarda el JSON enviado + // con ErrorLogger (tabla ITCA_LOG_INTEGRACION) + public Task PostAsync(string Query, object obj) { - - try - { - // CONSTRUIMOS LA URL DE LA ACCIÓN - var urlBuilder_ = new StringBuilder(); - urlBuilder_.Append(_url != null ? _url.TrimEnd('/') : "") - .Append("/" + Query); - var url_ = urlBuilder_.ToString(); - - // RECUPERAMOS EL HttpClient - var client_ = _httpClient.CreateClient("bypass-ssl-validation"); - - int NumError = 0; - - band: - - using (var request_ = new HttpRequestMessage()) - { - /////////////////////////////////////// - // CONSTRUIMOS LA PETICIÓN (REQUEST) // - /////////////////////////////////////// - // DEFINIMOS EL MÉTODO HTTP GET - request_.Method = new HttpMethod("POST"); - - // DEFINIMOS LA URI - request_.RequestUri = new Uri(url_, System.UriKind.RelativeOrAbsolute); - - // DEFINIMOS EL Accept, EN ESTE CASO ES "application/json" - request_.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/json")); - //request_.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("odata=minimalmetadata")); - //request_.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("charset=utf-8")); - var a = JsonConvert.SerializeObject(obj); - request_.Content = new StringContent(JsonConvert.SerializeObject(obj), System.Text.Encoding.UTF8, "application/json"); - - // ASIGNAMOS A LA CABECERA DE LA PETICIÓN EL TOKEN JWT. - if (_responseLoginServiceLayer.ServicioActivo) - request_.Headers.Add("Cookie", "B1SESSION=" + _responseLoginServiceLayer.SessionId); - - ///////////////////////////////////////// - // CONSTRUIMOS LA RESPUESTA (RESPONSE) // - ///////////////////////////////////////// - // Utilizamos ConfigureAwait(false) para evitar el DeadLock. - var response_ = await client_.SendAsync(request_, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false); - - // OBTENEMOS EL Content DEL RESPONSE como un String - // Utilizamos ConfigureAwait(false) para evitar el DeadLock. - var responseText_ = await response_.Content.ReadAsStringAsync().ConfigureAwait(false); - - // SI ES LA RESPUESTA ESPERADA !! ... - if (response_.StatusCode == System.Net.HttpStatusCode.OK || response_.StatusCode == System.Net.HttpStatusCode.Created) // 200 or Created - { - // DESERIALIZAMOS Content DEL RESPONSE - dynamic responseBody = JsonConvert.DeserializeObject(responseText_); - responseBody.Exito = true; - var estado = (int)response_.StatusCode; - responseBody.CodigoHttp = estado.ToString(); - responseBody.Mensaje = "SE ENVIO CORRECTAMENTE A SAP BO "; - return responseBody; - } - else - // SI NO SE ESTÁ AUTORIZADO ... - if (response_.StatusCode == System.Net.HttpStatusCode.Unauthorized) // 401 - { - - NumError += 1; - - _responseLoginServiceLayer = JsonConvert.DeserializeObject(responseText_); - _responseLoginServiceLayer.ServicioActivo = false; - - if (NumError == 2) - { - dynamic responseBody = JsonConvert.DeserializeObject(responseText_); - responseBody.Exito = false; - var estado = (int)response_.StatusCode; - responseBody.CodigoHttp = estado.ToString(); - responseBody.Mensaje = "Services Unauthorized : " + _responseLoginServiceLayer.error.message.ToString(); - LogErrorEnvio(Query, obj, responseBody.Mensaje); - return responseBody; - } - - - if (_responseLoginServiceLayer.error.code.ToString() == "301") - { - await Login(); - } - - goto band; - } - else - // CUALQUIER OTRA RESPUESTA ... - if (response_.StatusCode != System.Net.HttpStatusCode.OK && // 200 - response_.StatusCode != System.Net.HttpStatusCode.NoContent) // 204 - { - - NumError += 1; - - _responseLoginServiceLayer = JsonConvert.DeserializeObject(responseText_); - _responseLoginServiceLayer.ServicioActivo = false; - - if (NumError == 2) - { - dynamic responseBody = JsonConvert.DeserializeObject(responseText_); - responseBody.Exito = false; - var estado = (int)response_.StatusCode; - responseBody.CodigoHttp = estado.ToString(); - responseBody.Mensaje = "Error Services : " + _responseLoginServiceLayer.error.message.ToString(); - LogErrorEnvio(Query, obj, responseBody.Mensaje); - return responseBody; - } - - if (_responseLoginServiceLayer.error.code.ToString() == "301") - { - await Login(); - } - - goto band; - } - - // RETORNAMOS EL OBJETO POR DEFECTO ESPERADO - return default(T); - } - } - finally - { - // NO UTILIZAMOS CATCH, - // PASAMOS LA EXCEPCIÓN A LA APP. - } + return EnviarPostAsync(Query, obj, true, false); } + // POST sin cuerpo para reabrir un documento (ej. Invoices(n)/Reopen). SAP responde 204 sin contenido y se toma como exito - public async Task PostReOpen(string Query) + public Task PostReOpen(string Query) + { + return EnviarPostAsync(Query, null, false, true); + } + + // POST sin cuerpo para cerrar un documento (.../Close). SAP responde 204 sin contenido y se toma como exito + public Task PostClose(string Query) + { + return EnviarPostAsync(Query, null, false, true); + } + + // Nucleo comun de los envios POST al Service Layer (PostAsync, PostReOpen, PostClose). Reintenta una vez si la sesion + // expiro (401). conCuerpo: envia obj como JSON y, si SAP rechaza, guarda el JSON enviado con ErrorLogger. + // aceptaSinContenido: toma el 204 (sin cuerpo) como exito. + private async Task EnviarPostAsync(string Query, object obj, bool conCuerpo, bool aceptaSinContenido) { try @@ -384,8 +273,8 @@ namespace Net.Connection.ServiceLayer request_.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/json")); //request_.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("odata=minimalmetadata")); //request_.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("charset=utf-8")); - //var a = JsonConvert.SerializeObject(obj); - //request_.Content = new StringContent(JsonConvert.SerializeObject(obj), System.Text.Encoding.UTF8, "application/json"); + if (conCuerpo) + request_.Content = new StringContent(JsonConvert.SerializeObject(obj), System.Text.Encoding.UTF8, "application/json"); // ASIGNAMOS A LA CABECERA DE LA PETICIÓN EL TOKEN JWT. if (_responseLoginServiceLayer.ServicioActivo) @@ -402,12 +291,11 @@ namespace Net.Connection.ServiceLayer var responseText_ = await response_.Content.ReadAsStringAsync().ConfigureAwait(false); // SI ES LA RESPUESTA ESPERADA !! ... - if (response_.StatusCode == System.Net.HttpStatusCode.OK || response_.StatusCode == System.Net.HttpStatusCode.Created || response_.StatusCode == HttpStatusCode.NoContent) // 200 or Created + if (response_.StatusCode == System.Net.HttpStatusCode.OK || response_.StatusCode == System.Net.HttpStatusCode.Created || (aceptaSinContenido && response_.StatusCode == System.Net.HttpStatusCode.NoContent)) // 200, Created o 204 { // DESERIALIZAMOS Content DEL RESPONSE - // Reopen responde 204 sin cuerpo: DeserializeObject devuelve null, asi que - // se crea una instancia vacia para poder marcar el exito. - dynamic responseBody = string.IsNullOrWhiteSpace(responseText_) + // Un 204 (Reopen/Close) viene sin cuerpo: Deserialize devolveria null, asi que se crea una instancia vacia + dynamic responseBody = (aceptaSinContenido && string.IsNullOrWhiteSpace(responseText_)) ? Activator.CreateInstance() : JsonConvert.DeserializeObject(responseText_); responseBody.Exito = true; @@ -433,6 +321,7 @@ namespace Net.Connection.ServiceLayer var estado = (int)response_.StatusCode; responseBody.CodigoHttp = estado.ToString(); responseBody.Mensaje = "Services Unauthorized : " + _responseLoginServiceLayer.error.message.ToString(); + if (conCuerpo) LogErrorEnvio(Query, obj, responseBody.Mensaje); return responseBody; } @@ -462,135 +351,7 @@ namespace Net.Connection.ServiceLayer var estado = (int)response_.StatusCode; responseBody.CodigoHttp = estado.ToString(); responseBody.Mensaje = "Error Services : " + _responseLoginServiceLayer.error.message.ToString(); - return responseBody; - } - - if (_responseLoginServiceLayer.error.code.ToString() == "301") - { - await Login(); - } - - goto band; - } - - // RETORNAMOS EL OBJETO POR DEFECTO ESPERADO - return default(T); - } - } - finally - { - // NO UTILIZAMOS CATCH, - // PASAMOS LA EXCEPCIÓN A LA APP. - } - } - // POST sin cuerpo para cerrar un documento (.../Close) - public async Task PostClose(string Query) - { - - try - { - // CONSTRUIMOS LA URL DE LA ACCIÓN - var urlBuilder_ = new StringBuilder(); - urlBuilder_.Append(_url != null ? _url.TrimEnd('/') : "") - .Append("/" + Query); - var url_ = urlBuilder_.ToString(); - - // RECUPERAMOS EL HttpClient - var client_ = _httpClient.CreateClient("bypass-ssl-validation"); - - int NumError = 0; - - band: - - using (var request_ = new HttpRequestMessage()) - { - /////////////////////////////////////// - // CONSTRUIMOS LA PETICIÓN (REQUEST) // - /////////////////////////////////////// - // DEFINIMOS EL MÉTODO HTTP GET - request_.Method = new HttpMethod("POST"); - - // DEFINIMOS LA URI - request_.RequestUri = new Uri(url_, System.UriKind.RelativeOrAbsolute); - - // DEFINIMOS EL Accept, EN ESTE CASO ES "application/json" - request_.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/json")); - //request_.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("odata=minimalmetadata")); - //request_.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("charset=utf-8")); - //var a = JsonConvert.SerializeObject(obj); - //request_.Content = new StringContent(JsonConvert.SerializeObject(obj), System.Text.Encoding.UTF8, "application/json"); - - // ASIGNAMOS A LA CABECERA DE LA PETICIÓN EL TOKEN JWT. - if (_responseLoginServiceLayer.ServicioActivo) - request_.Headers.Add("Cookie", "B1SESSION=" + _responseLoginServiceLayer.SessionId); - - ///////////////////////////////////////// - // CONSTRUIMOS LA RESPUESTA (RESPONSE) // - ///////////////////////////////////////// - // Utilizamos ConfigureAwait(false) para evitar el DeadLock. - var response_ = await client_.SendAsync(request_, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false); - - // OBTENEMOS EL Content DEL RESPONSE como un String - // Utilizamos ConfigureAwait(false) para evitar el DeadLock. - var responseText_ = await response_.Content.ReadAsStringAsync().ConfigureAwait(false); - - // SI ES LA RESPUESTA ESPERADA !! ... - if (response_.StatusCode == System.Net.HttpStatusCode.OK || response_.StatusCode == System.Net.HttpStatusCode.Created || response_.StatusCode == HttpStatusCode.NoContent) // 200 or Created - { - // DESERIALIZAMOS Content DEL RESPONSE - dynamic responseBody = new BE_BaseResponseSap(); //JsonConvert.DeserializeObject(responseText_); - responseBody.Exito = true; - var estado = (int)response_.StatusCode; - responseBody.CodigoHttp = estado.ToString(); - responseBody.Mensaje = "SE ENVIO CORRECTAMENTE A SAP BO "; - return responseBody; - } - else - // SI NO SE ESTÁ AUTORIZADO ... - if (response_.StatusCode == System.Net.HttpStatusCode.Unauthorized) // 401 - { - - NumError += 1; - - _responseLoginServiceLayer = JsonConvert.DeserializeObject(responseText_); - _responseLoginServiceLayer.ServicioActivo = false; - - if (NumError == 2) - { - dynamic responseBody = JsonConvert.DeserializeObject(responseText_); - responseBody.Exito = false; - var estado = (int)response_.StatusCode; - responseBody.CodigoHttp = estado.ToString(); - responseBody.Mensaje = "Services Unauthorized : " + _responseLoginServiceLayer.error.message.ToString(); - return responseBody; - } - - - if (_responseLoginServiceLayer.error.code.ToString() == "301") - { - await Login(); - } - - goto band; - } - else - // CUALQUIER OTRA RESPUESTA ... - if (response_.StatusCode != System.Net.HttpStatusCode.OK && // 200 - response_.StatusCode != System.Net.HttpStatusCode.NoContent) // 204 - { - - NumError += 1; - - _responseLoginServiceLayer = JsonConvert.DeserializeObject(responseText_); - _responseLoginServiceLayer.ServicioActivo = false; - - if (NumError == 2) - { - dynamic responseBody = JsonConvert.DeserializeObject(responseText_); - responseBody.Exito = false; - var estado = (int)response_.StatusCode; - responseBody.CodigoHttp = estado.ToString(); - responseBody.Mensaje = "Error Services : " + _responseLoginServiceLayer.error.message.ToString(); + if (conCuerpo) LogErrorEnvio(Query, obj, responseBody.Mensaje); return responseBody; }