2026-10-05 17:53:41 -05:00
using Microsoft.Extensions.Configuration ;
using Net.Business.Entities.ServicesLayer ;
using Newtonsoft.Json ;
using Newtonsoft.Json.Linq ;
using System ;
using System.Collections.Generic ;
using System.Net ;
using System.Net.Http ;
using System.Net.Http.Headers ;
using System.Net.Security ;
using System.Security.Cryptography.X509Certificates ;
using System.Text ;
using System.Threading.Tasks ;
namespace Net.Connection.ServiceLayer
{
public class ConnectionServiceLayer
{
public static bool ServicioActivo = false ;
public static string SLSession ;
2026-10-14 17:06:07 -05:00
// Se invoca solo cuando SAP rechaza un envio: (ruta del recurso, JSON enviado, respuesta de SAP).
// Lo asigna Startup (esta capa no conoce HANA).
public static Action < string , object , string > ErrorLogger ;
private static void LogErrorEnvio ( string query , object jsonEnviado , string respuesta )
{
try { ErrorLogger ?. Invoke ( query , jsonEnviado , respuesta ); }
catch { /* el log nunca debe romper el flujo principal */ }
}
2026-10-05 17:53:41 -05:00
private readonly string _url ;
private readonly string _company ;
private readonly string _user ;
private readonly string _password ;
private readonly string _language ;
private readonly IHttpClientFactory _httpClient ;
private ResponseLoginServiceLayer _responseLoginServiceLayer ;
public ConnectionServiceLayer ( IConfiguration configuration , IHttpClientFactory clientFactory )
{
_url = configuration [ "SapServiceLayer:url" ];
_company = configuration [ "SapServiceLayer:company" ];
_user = configuration [ "SapServiceLayer:user" ];
_password = configuration [ "SapServiceLayer:password" ];
_language = configuration [ "SapServiceLayer:language" ];
_httpClient = clientFactory ;
_responseLoginServiceLayer = new ResponseLoginServiceLayer ();
}
2026-10-14 19:31:06 -05:00
// Inicia sesion en el Service Layer (cookie B1SESSION). Usa SapServiceLayer:company/user/password de appsettings.json
2026-10-05 17:53:41 -05:00
public async Task < ResponseLoginServiceLayer > Login ()
{
try
{
// CONSTRUIMOS LA URL DE LA ACCIÓN
var urlBuilder_ = new StringBuilder ();
urlBuilder_ . Append ( _url != null ? _url . TrimEnd ( '/' ) : "" )
. Append ( "/Login" );
var url_ = urlBuilder_ . ToString ();
// RECUPERAMOS EL HttpClient
var client_ = _httpClient . CreateClient ( "bypass-ssl-validation" );
using ( var request_ = new HttpRequestMessage ())
{
///////////////////////////////////////
// CONSTRUIMOS LA PETICIÓN (REQUEST) //
///////////////////////////////////////
// DEFINIMOS EL Content CON EL OBJETO A ENVIAR SERIALIZADO.
request_ . Content = new StringContent ( "{\"CompanyDB\":\"" + _company + "\",\"Password\":\"" + _password + "\",\"UserName\":\"" + _user + "\"}" );
// DEFINIMOS EL ContentType, EN ESTE CASO ES "application/json"
request_ . Content . Headers . ContentType = MediaTypeHeaderValue . Parse ( "application/json" );
request_ . Headers . Add ( "cache-control" , "no-cache" );
// DEFINIMOS EL MÉTODO HTTP
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" ));
/////////////////////////////////////////
// CONSTRUIMOS LA RESPUESTA (RESPONSE) //
/////////////////////////////////////////
// Utilizamos ConfigureAwait(false) para evitar el DeadLock.
var response_ = await client_ . SendAsync ( request_ ). 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 ) // 200
{
// DESERIALIZAMOS Content DEL RESPONSE
_responseLoginServiceLayer = JsonConvert . DeserializeObject < ResponseLoginServiceLayer >( responseText_ );
_responseLoginServiceLayer . ServicioActivo = true ;
_responseLoginServiceLayer . MensajeLogin = "Autenticación Correcta" ;
return _responseLoginServiceLayer ;
}
else
// SI NO SE ESTÁ AUTORIZADO ...
if ( response_ . StatusCode == System . Net . HttpStatusCode . Unauthorized ) // 401
{
_responseLoginServiceLayer = JsonConvert . DeserializeObject < ResponseLoginServiceLayer >( responseText_ );
_responseLoginServiceLayer . ServicioActivo = false ;
_responseLoginServiceLayer . MensajeLogin = "401 Unauthorized. Las credenciales de acceso del usuario son incorrectas." ;
return _responseLoginServiceLayer ;
}
else
// CUALQUIER OTRA RESPUESTA ...
if ( response_ . StatusCode != System . Net . HttpStatusCode . OK && // 200
response_ . StatusCode != System . Net . HttpStatusCode . NoContent ) // 204
{
_responseLoginServiceLayer = JsonConvert . DeserializeObject < ResponseLoginServiceLayer >( responseText_ );
_responseLoginServiceLayer . ServicioActivo = false ;
_responseLoginServiceLayer . MensajeLogin = "401 Unauthorized. Las credenciales de acceso del usuario son incorrectas." ;
return _responseLoginServiceLayer ;
}
// RETORNAMOS EL OBJETO POR DEFECTO ESPERADO
return _responseLoginServiceLayer ;
}
}
catch ( Exception ex )
{
_responseLoginServiceLayer . ServicioActivo = false ;
_responseLoginServiceLayer . MensajeLogin = ex . Message . ToString ();
return _responseLoginServiceLayer ;
}
}
2026-10-14 19:31:06 -05:00
// GET al Service Layer. dinamico = true: la respuesta es una sola entidad (ej. Invoices(123)); false: coleccion { value: [...] }
2026-10-05 17:53:41 -05:00
public async Task < List < T >> GetAsync < T >( string Query , int maxpagesize , bool dinamico = false )
{
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" );
//AUTENTICAMOS PARA REALIZAR LAS CONSULTAS
await Login ();
using ( var request_ = new HttpRequestMessage ())
{
///////////////////////////////////////
// CONSTRUIMOS LA PETICIÓN (REQUEST) //
///////////////////////////////////////
// DEFINIMOS EL MÉTODO HTTP
request_ . Method = new HttpMethod ( "GET" );
// 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 . Add ( "cache-control" , "no-cache" );
request_ . Headers . Add ( "Prefer" , "odata.maxpagesize=" + maxpagesize . ToString ());
// 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 ) // 200
{
// DESERIALIZAMOS Content DEL RESPONSE
if (! dinamico )
{
var responseBody_ = JsonConvert . DeserializeObject < ResponseGetServiceLayer < T >>( responseText_ );
return responseBody_ . value ;
}
if ( dinamico )
{
List < T > lista = new List < T >();
dynamic responseBody = JsonConvert . DeserializeObject < T >( responseText_ );
lista . Add ( responseBody );
return lista ;
}
}
// RETORNAMOS EL OBJETO POR DEFECTO ESPERADO
return default ( List < T >);
}
}
finally
{
// NO UTILIZAMOS CATCH,
// PASAMOS LA EXCEPCIÓN A LA APP.
}
}
2026-10-14 19:58:12 -05:00
// 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 < T > PostAsync < T >( string Query , object obj )
2026-10-05 17:53:41 -05:00
{
2026-10-14 19:58:12 -05:00
return EnviarPostAsync < T >( Query , obj , true , false );
2026-10-05 17:53:41 -05:00
}
2026-10-14 19:58:12 -05:00
2026-10-14 19:31:06 -05:00
// POST sin cuerpo para reabrir un documento (ej. Invoices(n)/Reopen). SAP responde 204 sin contenido y se toma como exito
2026-10-14 19:58:12 -05:00
public Task < T > PostReOpen < T >( string Query )
{
return EnviarPostAsync < T >( Query , null , false , true );
}
// POST sin cuerpo para cerrar un documento (.../Close). SAP responde 204 sin contenido y se toma como exito
public Task < T > PostClose < T >( string Query )
{
return EnviarPostAsync < T >( 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 < T > EnviarPostAsync < T >( string Query , object obj , bool conCuerpo , bool aceptaSinContenido )
2026-10-05 17:53:41 -05:00
{
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" ));
2026-10-14 19:58:12 -05:00
if ( conCuerpo )
request_ . Content = new StringContent ( JsonConvert . SerializeObject ( obj ), System . Text . Encoding . UTF8 , "application/json" );
2026-10-05 17:53:41 -05:00
// 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 !! ...
2026-10-14 19:58:12 -05:00
if ( response_ . StatusCode == System . Net . HttpStatusCode . OK || response_ . StatusCode == System . Net . HttpStatusCode . Created || ( aceptaSinContenido && response_ . StatusCode == System . Net . HttpStatusCode . NoContent )) // 200, Created o 204
2026-10-05 17:53:41 -05:00
{
// DESERIALIZAMOS Content DEL RESPONSE
2026-10-14 19:58:12 -05:00
// Un 204 (Reopen/Close) viene sin cuerpo: Deserialize devolveria null, asi que se crea una instancia vacia
dynamic responseBody = ( aceptaSinContenido && string . IsNullOrWhiteSpace ( responseText_ ))
2026-10-14 10:15:40 -05:00
? Activator . CreateInstance < T >()
: JsonConvert . DeserializeObject < T >( responseText_ );
2026-10-05 17:53:41 -05:00
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 < ResponseLoginServiceLayer >( responseText_ );
_responseLoginServiceLayer . ServicioActivo = false ;
if ( NumError == 2 )
{
dynamic responseBody = JsonConvert . DeserializeObject < T >( responseText_ );
responseBody . Exito = false ;
var estado = ( int ) response_ . StatusCode ;
responseBody . CodigoHttp = estado . ToString ();
responseBody . Mensaje = "Services Unauthorized : " + _responseLoginServiceLayer . error . message . ToString ();
2026-10-14 19:58:12 -05:00
if ( conCuerpo ) LogErrorEnvio ( Query , obj , responseBody . Mensaje );
2026-10-05 17:53:41 -05:00
return responseBody ;
}
if ( _responseLoginServiceLayer . error . code . ToString () == "301" )
{
2026-10-14 19:49:20 -05:00
await Login ();
2026-10-05 17:53:41 -05:00
}
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 < ResponseLoginServiceLayer >( responseText_ );
_responseLoginServiceLayer . ServicioActivo = false ;
if ( NumError == 2 )
{
dynamic responseBody = JsonConvert . DeserializeObject < T >( responseText_ );
responseBody . Exito = false ;
var estado = ( int ) response_ . StatusCode ;
responseBody . CodigoHttp = estado . ToString ();
responseBody . Mensaje = "Error Services : " + _responseLoginServiceLayer . error . message . ToString ();
2026-10-14 19:58:12 -05:00
if ( conCuerpo ) LogErrorEnvio ( Query , obj , responseBody . Mensaje );
2026-10-05 17:53:41 -05:00
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.
}
}
// === NUEVO (SolicitudTransferencia / Picking HojaRuta Cerrar) ===
//
2026-10-19 16:57:46 -05:00
// PatchNoContentAsync/PostActionAsync no devuelven un T deserializado, solo exito (sin excepcion)
// o fallo (excepcion con el mensaje real de SAP). En el Service Layer un PATCH exitoso es un 204 sin cuerpo,
// asi que no hay nada que deserializar. (v2.1.14.0: se quito PatchAsyncSBA, que con el 204 devolvia null
// sin marcar exito y con un error de red devolvia lo mismo.)
2026-10-05 17:53:41 -05:00
public async Task PatchNoContentAsync ( string Query , object obj , Dictionary < string , string > extraHeaders = null )
{
var urlBuilder_ = new StringBuilder ();
urlBuilder_ . Append ( _url != null ? _url . TrimEnd ( '/' ) : "" ). Append ( "/" + Query );
var url_ = urlBuilder_ . ToString ();
var client_ = _httpClient . CreateClient ( "bypass-ssl-validation" );
await Login ();
using ( var request_ = new HttpRequestMessage ())
{
request_ . Method = new HttpMethod ( "PATCH" );
request_ . RequestUri = new Uri ( url_ , UriKind . RelativeOrAbsolute );
request_ . Headers . Accept . Add ( MediaTypeWithQualityHeaderValue . Parse ( "application/json" ));
request_ . Content = new StringContent ( JsonConvert . SerializeObject ( obj ), Encoding . UTF8 , "application/json" );
if ( _responseLoginServiceLayer . ServicioActivo )
request_ . Headers . Add ( "Cookie" , "B1SESSION=" + _responseLoginServiceLayer . SessionId );
if ( extraHeaders != null )
{
foreach ( var kv in extraHeaders )
request_ . Headers . Add ( kv . Key , kv . Value );
}
var response_ = await client_ . SendAsync ( request_ , HttpCompletionOption . ResponseHeadersRead ). ConfigureAwait ( false );
var responseText_ = await response_ . Content . ReadAsStringAsync (). ConfigureAwait ( false );
if ( response_ . StatusCode == HttpStatusCode . OK || response_ . StatusCode == HttpStatusCode . NoContent )
{
return ; // exito real - sin body que parsear
}
2026-10-14 17:06:07 -05:00
var mensajeError = ExtraerMensajeErrorSap ( responseText_ );
LogErrorEnvio ( Query , obj , mensajeError );
throw new Exception ( mensajeError );
2026-10-05 17:53:41 -05:00
}
}
public async Task PostActionAsync ( string Query )
{
var urlBuilder_ = new StringBuilder ();
urlBuilder_ . Append ( _url != null ? _url . TrimEnd ( '/' ) : "" ). Append ( "/" + Query );
var url_ = urlBuilder_ . ToString ();
var client_ = _httpClient . CreateClient ( "bypass-ssl-validation" );
await Login ();
using ( var request_ = new HttpRequestMessage ())
{
request_ . Method = new HttpMethod ( "POST" );
request_ . RequestUri = new Uri ( url_ , UriKind . RelativeOrAbsolute );
request_ . Headers . Accept . Add ( MediaTypeWithQualityHeaderValue . Parse ( "application/json" ));
request_ . Content = new StringContent ( "{}" , Encoding . UTF8 , "application/json" );
if ( _responseLoginServiceLayer . ServicioActivo )
request_ . Headers . Add ( "Cookie" , "B1SESSION=" + _responseLoginServiceLayer . SessionId );
var response_ = await client_ . SendAsync ( request_ , HttpCompletionOption . ResponseHeadersRead ). ConfigureAwait ( false );
var responseText_ = await response_ . Content . ReadAsStringAsync (). ConfigureAwait ( false );
if ( response_ . StatusCode == HttpStatusCode . OK || response_ . StatusCode == HttpStatusCode . Created || response_ . StatusCode == HttpStatusCode . NoContent )
{
return ; // exito real - sin body que parsear
}
throw new Exception ( ExtraerMensajeErrorSap ( responseText_ ));
}
}
private static string ExtraerMensajeErrorSap ( string responseText )
{
try
{
var parsed = JsonConvert . DeserializeObject < ResponseLoginServiceLayer >( responseText );
var mensaje = parsed ?. error ?. message ;
if ( mensaje == null ) return responseText ;
// "message" esta tipado como object en ErrorServiceLayer (ver
// ResponseLoginServiceLayer.cs) - Json.NET lo deja como JObject.
if ( mensaje is JObject jMensaje ) return jMensaje . Value < string >( "value" ) ?? responseText ;
return mensaje . ToString ();
}
catch
{
return responseText ;
}
}
}
}