Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3111bb3842 | ||
|
|
d53d5d07c5 | ||
|
|
baaf85554a | ||
|
|
31b0929342 | ||
|
|
3ea0aca535 | ||
|
|
3bce3ed117 | ||
|
|
ac725aed9b | ||
|
|
a5a4160471 | ||
|
|
57b4578f1f | ||
|
|
3d82f7380c | ||
|
|
23f1a3df44 |
No files matched your search
@@ -3,10 +3,24 @@
|
|||||||
API de integración WMS ↔ SAP Business One, **ASP.NET Core 3.1**. Para
|
API de integración WMS ↔ SAP Business One, **ASP.NET Core 3.1**. Para
|
||||||
build/publish/despliegue en IIS ver [`README.md`](./README.md).
|
build/publish/despliegue en IIS ver [`README.md`](./README.md).
|
||||||
|
|
||||||
Esta carpeta (`backend_octubre`) es **el proyecto vigente**. Antes existía
|
Esta carpeta (`ITCA_API_SAP_WMS`, antes `backend_octubre`) es **el proyecto
|
||||||
una copia paralela, `ITCA_API_SAP_WMS`, con la funcionalidad nueva portada
|
vigente**. Antes existía una copia paralela con la funcionalidad nueva portada
|
||||||
desde la reescritura Python; se portó aquí y esa copia quedó **archivada en un
|
desde la reescritura Python; se portó aquí y esa copia quedó **archivada en un
|
||||||
comprimido (versión antigua)**. No hay `git` en esta carpeta.
|
comprimido (versión antigua)**.
|
||||||
|
|
||||||
|
## Versión y control de versiones
|
||||||
|
|
||||||
|
- **Versión actual: 2.1.9.0** (rama `limpieza-2.1.8.0`; `main` = 2.1.7.0) (`Net.Business.Services.csproj`: `Version`,
|
||||||
|
`AssemblyVersion`, `FileVersion`). Cada versión tiene su commit; al publicar
|
||||||
|
se sube la versión (ver `publicar.ps1`).
|
||||||
|
- **Git**: rama `main`, remoto `origin` =
|
||||||
|
`http://100.68.242.27:30000/george/itca_api_sap_wms.git` (HTTP sin cifrar,
|
||||||
|
red interna; confirmar que el repositorio sea **privado**). Etiqueta
|
||||||
|
`v2.1.7.0` = último estado antes de la limpieza de código.
|
||||||
|
- `appsettings.json` y `appsettings.*.json` están en `.gitignore` (solo se
|
||||||
|
versiona `appsettings.example.json`); las credenciales no están en el
|
||||||
|
historial (verificado con `git log -S`).
|
||||||
|
- Los publish (`dist/`, zip del escritorio) no se versionan.
|
||||||
|
|
||||||
## Qué se portó desde `ITCA_API_SAP_WMS` (2026-10-05)
|
## Qué se portó desde `ITCA_API_SAP_WMS` (2026-10-05)
|
||||||
|
|
||||||
@@ -72,6 +86,44 @@ transferencias siempre, sin depender de `Notificacion`. Detalle y tabla en
|
|||||||
comparaba la serie origen (p. ej. `B005`) con `"03"` y siempre salía `FC10`.
|
comparaba la serie origen (p. ej. `B005`) con `"03"` y siempre salía `FC10`.
|
||||||
Pendiente: validar con contabilidad la serie de boletas, y que Recibo no
|
Pendiente: validar con contabilidad la serie de boletas, y que Recibo no
|
||||||
genera transferencias por almacén destino (nunca llena `U_ITC_ALMDES`).
|
genera transferencias por almacén destino (nunca llena `U_ITC_ALMDES`).
|
||||||
|
- **v2.1.8.0 — limpieza y comentarios** (rama `limpieza-2.1.8.0`, sin cambios de
|
||||||
|
comportamiento): se quitó código sin uso (`ValidarCertificado`, `Logout`,
|
||||||
|
`CancelAsyncSBA`, `DeleteAsyncSBA`, el `throw` inalcanzable de `PatchAsyncSBA`,
|
||||||
|
`HelperDB`, 11 propiedades de `RepositoryWrapper` sin uso, variables
|
||||||
|
`TotalDetalle_Parcial`/`_Invoice`/`SerieOrigen` y un `if` vacío en
|
||||||
|
`CreateHojaRuta`) y se agregaron comentarios en Picking, Recibo,
|
||||||
|
`ConnectionServiceLayer`, `CreditNotesRepository`, `InvoiceRepository` y
|
||||||
|
`RepositoryWrapper`. Además se unificó `LoginPost()` en `Login()` (eran idénticos;
|
||||||
|
las 4 llamadas a `LoginPost` ahora llaman a `Login`) y se quitaron dos copias
|
||||||
|
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 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`.
|
||||||
|
- **v2.1.9.0 — Transferencia desde ALM99 (importaciones)**: WMS recibe por partes; cada
|
||||||
|
entrada de mercadería (base: factura de reserva, `BaseType 18`) va a `ALM99` y, tras el
|
||||||
|
costeo, WMS notifica **una transferencia por entrada** enviando en `NumOrden` el `DocNum`
|
||||||
|
de la factura de reserva y en el detalle los artículos/cantidades de esa entrada. Si el
|
||||||
|
JSON no trae `DocEntryEM`, `TransferenciaRepository` busca por HANA
|
||||||
|
(`HelperInvoice.GetEntradasPorFactura`: `OPCH.isIns='Y'` → `PDN1` → `OPDN`, sin anuladas;
|
||||||
|
una entrada ya tiene transferencia si la línea tiene `TargetType 67` y su `OWTR` no está
|
||||||
|
anulada), compara **solo `ItemCode` y cantidad** (sumada por artículo) contra cada entrada y
|
||||||
|
transfiere la más antigua que coincida y no esté transferida (`EntradaImportacion.Elegir`).
|
||||||
|
Las líneas de la transferencia salen de la entrada (lotes reales, validando que cubran la
|
||||||
|
cantidad) con base en ella. Errores claros: ninguna coincide / todas ya transferidas. Si la
|
||||||
|
factura no tiene entradas se mantiene el comportamiento anterior (lotes del stock de HANA,
|
||||||
|
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`. 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
|
- 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.
|
`BaseType`/`BaseEntry` en las líneas y `BatchNumbers` en las de la Entrega.
|
||||||
|
|
||||||
@@ -125,15 +177,19 @@ Picking, columna `HORA` en `B_Log`. Lo que **no** se trajo: `Chatbots` y
|
|||||||
- `BE_ResultadoTransaccion<T>.NombreEstacion`: setter con recursión infinita
|
- `BE_ResultadoTransaccion<T>.NombreEstacion`: setter con recursión infinita
|
||||||
(bomba de tiempo; hoy nada lo asigna).
|
(bomba de tiempo; hoy nada lo asigna).
|
||||||
- `ConnectionServiceLayer.PatchAsyncSBA<T>`: con un `204` real devuelve `null`
|
- `ConnectionServiceLayer.PatchAsyncSBA<T>`: con un `204` real devuelve `null`
|
||||||
sin marcar éxito. `Login()`/`LoginPost()` duplicados; `throw` inalcanzable
|
sin marcar éxito.
|
||||||
tras `return` en varios `catch`.
|
- El cliente HTTP `bypass-ssl-validation` acepta cualquier certificado TLS hacia
|
||||||
- `ValidarCertificado` acepta cualquier certificado TLS (siempre `true`).
|
el Service Layer (se quitó el método `ValidarCertificado`, que no se usaba).
|
||||||
- `HelperDB.SET(string query)` ejecuta SQL arbitrario; hoy sin llamadores
|
- `appsettings.json` (fuera de git, en `.gitignore`) contiene credenciales
|
||||||
(candidato a borrar).
|
reales en texto plano (SAP Service Layer, **HANA `B1ADMIN`**, semilla JWT).
|
||||||
- `appsettings.json` versionado en texto plano con credenciales reales
|
Rotar la contraseña de `B1ADMIN` (estuvo en el código fuente) y evaluar un
|
||||||
(SAP Service Layer, **HANA `B1ADMIN`**, semilla JWT). Rotar la contraseña de
|
usuario HANA de mínimos permisos.
|
||||||
`B1ADMIN` (estuvo en el código fuente) y evaluar un usuario HANA de mínimos
|
- Los logs (`B_Log` y `HelperLogServiceLayer`) escriben en el esquema fijo
|
||||||
permisos.
|
`ITC_WMS_SAP` (`ITCA_LOG_INTEGRACION`), no en `LocalData.Schema`; si la tabla
|
||||||
|
está en otro esquema el `INSERT` falla. `B_Log.Add` no tiene `try/catch`, así
|
||||||
|
que un fallo del log puede devolver 500 tras haber creado el documento en SAP.
|
||||||
|
- Si una transferencia automática de stock falla (`TransferenciaAutomaticaNC.Crear`),
|
||||||
|
el fallo se ignora (la NC ya está creada) y no se informa al consumidor.
|
||||||
|
|
||||||
## Despliegue
|
## Despliegue
|
||||||
|
|
||||||
@@ -152,3 +208,12 @@ usa `processPath=".\ITCA_API_SAP_WMS.exe"`; el del código fuente apunta a
|
|||||||
- Verificar que existan en el esquema del sitio los procedimientos `ITCA_*`
|
- Verificar que existan en el esquema del sitio los procedimientos `ITCA_*`
|
||||||
y las tablas `@ITCA_PROWMS`, `@SYP_MOTIVOS`, `@SYP_NUMDOC`.
|
y las tablas `@ITCA_PROWMS`, `@SYP_MOTIVOS`, `@SYP_NUMDOC`.
|
||||||
- Decidir si se corrigen los bugs de la lista de arriba.
|
- Decidir si se corrigen los bugs de la lista de arriba.
|
||||||
|
- Validar con contabilidad la serie de NC de boletas (`BC10`) en Recibo y si ya
|
||||||
|
hay NC de boletas emitidas con `FC10` antes de la 2.1.7.0.
|
||||||
|
- Recibo (Devolución) no genera transferencias por almacén destino: nunca llena
|
||||||
|
`U_ITC_ALMDES` en las líneas de la NC.
|
||||||
|
- Confirmar en QA el log de errores del Service Layer (provocar un rechazo y
|
||||||
|
ver la fila con `PROCESO = 'ServiceLayer Error'`) y el esquema de la tabla de
|
||||||
|
log en cada servidor.
|
||||||
|
- Confirmar el repositorio remoto como privado; hacer la limpieza de código en
|
||||||
|
una rama aparte (2.1.8.0) partiendo de la etiqueta `v2.1.7.0`.
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
namespace Net.Business.Entities.WMS.Transferencia
|
||||||
|
{
|
||||||
|
// Una linea de una Entrada de Mercaderia enlazada a una Factura de Reserva de proveedores (importacion).
|
||||||
|
// TransferenciaDocEntry > 0 cuando la linea ya tiene una transferencia de stock vigente (TargetType 67, no anulada).
|
||||||
|
public class BE_EntradaImportacionLinea
|
||||||
|
{
|
||||||
|
public int DocEntry { get; set; }
|
||||||
|
public int DocNum { get; set; }
|
||||||
|
public int LineNum { get; set; }
|
||||||
|
public string ItemCode { get; set; }
|
||||||
|
public decimal Quantity { get; set; }
|
||||||
|
public int TransferenciaDocEntry { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,9 +3,9 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||||
<AssemblyName>ITCA_API_SAP_WMS</AssemblyName>
|
<AssemblyName>ITCA_API_SAP_WMS</AssemblyName>
|
||||||
<Version>2.1.7</Version>
|
<Version>2.1.9</Version>
|
||||||
<AssemblyVersion>2.1.7.0</AssemblyVersion>
|
<AssemblyVersion>2.1.9.0</AssemblyVersion>
|
||||||
<FileVersion>2.1.7.0</FileVersion>
|
<FileVersion>2.1.9.0</FileVersion>
|
||||||
<UserSecretsId>24763424-3300-4c85-87e3-503f8fdebd14</UserSecretsId>
|
<UserSecretsId>24763424-3300-4c85-87e3-503f8fdebd14</UserSecretsId>
|
||||||
<!-- Antes solo estaba condicionado a Debug|AnyCPU - un "dotnet publish -c Release"
|
<!-- Antes solo estaba condicionado a Debug|AnyCPU - un "dotnet publish -c Release"
|
||||||
(lo normal para IIS) se quedaba sin este archivo, y Startup.cs revienta con
|
(lo normal para IIS) se quedaba sin este archivo, y Startup.cs revienta con
|
||||||
|
|||||||
@@ -48,11 +48,7 @@ namespace Net.Connection.ServiceLayer
|
|||||||
_responseLoginServiceLayer = new ResponseLoginServiceLayer();
|
_responseLoginServiceLayer = new ResponseLoginServiceLayer();
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool ValidarCertificado(object sender, X509Certificate cert, X509Chain chain, SslPolicyErrors ssl)
|
// Inicia sesion en el Service Layer (cookie B1SESSION). Usa SapServiceLayer:company/user/password de appsettings.json
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<ResponseLoginServiceLayer> Login()
|
public async Task<ResponseLoginServiceLayer> Login()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -140,181 +136,7 @@ namespace Net.Connection.ServiceLayer
|
|||||||
return _responseLoginServiceLayer;
|
return _responseLoginServiceLayer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public async Task<ResponseLoginServiceLayer> LoginPost()
|
// GET al Service Layer. dinamico = true: la respuesta es una sola entidad (ej. Invoices(123)); false: coleccion { value: [...] }
|
||||||
{
|
|
||||||
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 + "\"}");
|
|
||||||
//request_.Content = new StringContent("{\"CompanyDB\":\"" + _company + "\",\"Password\":\"" + _password + "\",\"UserName\":\"" + _user + "\",\"Language\":\"" + _language + "\"}");
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public async Task<ResponseLoginServiceLayer> Logout()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// CONSTRUIMOS LA URL DE LA ACCIÓN
|
|
||||||
var urlBuilder_ = new StringBuilder();
|
|
||||||
urlBuilder_.Append(_url != null ? _url.TrimEnd('/') : "")
|
|
||||||
.Append("/Logout");
|
|
||||||
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.NoContent) // 204
|
|
||||||
{
|
|
||||||
// DESERIALIZAMOS Content DEL RESPONSE
|
|
||||||
_responseLoginServiceLayer = JsonConvert.DeserializeObject<ResponseLoginServiceLayer>(responseText_);
|
|
||||||
_responseLoginServiceLayer.ServicioActivo = true;
|
|
||||||
_responseLoginServiceLayer.MensajeLogin = "Cierre de Sesió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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public async Task<List<T>> GetAsync<T>(string Query, int maxpagesize,bool dinamico = false)
|
public async Task<List<T>> GetAsync<T>(string Query, int maxpagesize,bool dinamico = false)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -396,137 +218,29 @@ namespace Net.Connection.ServiceLayer
|
|||||||
// PASAMOS LA EXCEPCIÓN A LA APP.
|
// PASAMOS LA EXCEPCIÓN A LA APP.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public async Task<T> PostAsync<T>(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<T> PostAsync<T>(string Query, object obj)
|
||||||
{
|
{
|
||||||
|
return EnviarPostAsync<T>(Query, obj, true, 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");
|
|
||||||
|
|
||||||
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<T>(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<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();
|
|
||||||
LogErrorEnvio(Query, obj, responseBody.Mensaje);
|
|
||||||
return responseBody;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (_responseLoginServiceLayer.error.code.ToString() == "301")
|
|
||||||
{
|
|
||||||
await LoginPost();
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
|
||||||
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.
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
public async Task<T> PostReOpen<T>(string Query)
|
|
||||||
|
// POST sin cuerpo para reabrir un documento (ej. Invoices(n)/Reopen). SAP responde 204 sin contenido y se toma como exito
|
||||||
|
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)
|
||||||
{
|
{
|
||||||
|
|
||||||
try
|
try
|
||||||
@@ -559,8 +273,8 @@ namespace Net.Connection.ServiceLayer
|
|||||||
request_.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/json"));
|
request_.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/json"));
|
||||||
//request_.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("odata=minimalmetadata"));
|
//request_.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("odata=minimalmetadata"));
|
||||||
//request_.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("charset=utf-8"));
|
//request_.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("charset=utf-8"));
|
||||||
//var a = JsonConvert.SerializeObject(obj);
|
if (conCuerpo)
|
||||||
//request_.Content = new StringContent(JsonConvert.SerializeObject(obj), System.Text.Encoding.UTF8, "application/json");
|
request_.Content = new StringContent(JsonConvert.SerializeObject(obj), System.Text.Encoding.UTF8, "application/json");
|
||||||
|
|
||||||
// ASIGNAMOS A LA CABECERA DE LA PETICIÓN EL TOKEN JWT.
|
// ASIGNAMOS A LA CABECERA DE LA PETICIÓN EL TOKEN JWT.
|
||||||
if (_responseLoginServiceLayer.ServicioActivo)
|
if (_responseLoginServiceLayer.ServicioActivo)
|
||||||
@@ -577,12 +291,11 @@ namespace Net.Connection.ServiceLayer
|
|||||||
var responseText_ = await response_.Content.ReadAsStringAsync().ConfigureAwait(false);
|
var responseText_ = await response_.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
// SI ES LA RESPUESTA ESPERADA !! ...
|
// 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
|
// DESERIALIZAMOS Content DEL RESPONSE
|
||||||
// Reopen responde 204 sin cuerpo: DeserializeObject devuelve null, asi que
|
// Un 204 (Reopen/Close) viene sin cuerpo: Deserialize devolveria null, asi que se crea una instancia vacia
|
||||||
// se crea una instancia vacia para poder marcar el exito.
|
dynamic responseBody = (aceptaSinContenido && string.IsNullOrWhiteSpace(responseText_))
|
||||||
dynamic responseBody = string.IsNullOrWhiteSpace(responseText_)
|
|
||||||
? Activator.CreateInstance<T>()
|
? Activator.CreateInstance<T>()
|
||||||
: JsonConvert.DeserializeObject<T>(responseText_);
|
: JsonConvert.DeserializeObject<T>(responseText_);
|
||||||
responseBody.Exito = true;
|
responseBody.Exito = true;
|
||||||
@@ -608,38 +321,11 @@ namespace Net.Connection.ServiceLayer
|
|||||||
var estado = (int)response_.StatusCode;
|
var estado = (int)response_.StatusCode;
|
||||||
responseBody.CodigoHttp = estado.ToString();
|
responseBody.CodigoHttp = estado.ToString();
|
||||||
responseBody.Mensaje = "Services Unauthorized : " + _responseLoginServiceLayer.error.message.ToString();
|
responseBody.Mensaje = "Services Unauthorized : " + _responseLoginServiceLayer.error.message.ToString();
|
||||||
|
if (conCuerpo) LogErrorEnvio(Query, obj, responseBody.Mensaje);
|
||||||
return responseBody;
|
return responseBody;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (_responseLoginServiceLayer.error.code.ToString() == "301")
|
|
||||||
{
|
|
||||||
await LoginPost();
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
|
||||||
return responseBody;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_responseLoginServiceLayer.error.code.ToString() == "301")
|
if (_responseLoginServiceLayer.error.code.ToString() == "301")
|
||||||
{
|
{
|
||||||
await Login();
|
await Login();
|
||||||
@@ -647,106 +333,6 @@ namespace Net.Connection.ServiceLayer
|
|||||||
|
|
||||||
goto band;
|
goto band;
|
||||||
}
|
}
|
||||||
|
|
||||||
// RETORNAMOS EL OBJETO POR DEFECTO ESPERADO
|
|
||||||
return default(T);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
// NO UTILIZAMOS CATCH,
|
|
||||||
// PASAMOS LA EXCEPCIÓN A LA APP.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public async Task<T> PostClose<T>(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<T>(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<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();
|
|
||||||
return responseBody;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (_responseLoginServiceLayer.error.code.ToString() == "301")
|
|
||||||
{
|
|
||||||
await LoginPost();
|
|
||||||
}
|
|
||||||
|
|
||||||
goto band;
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
// CUALQUIER OTRA RESPUESTA ...
|
// CUALQUIER OTRA RESPUESTA ...
|
||||||
if (response_.StatusCode != System.Net.HttpStatusCode.OK && // 200
|
if (response_.StatusCode != System.Net.HttpStatusCode.OK && // 200
|
||||||
@@ -765,6 +351,7 @@ namespace Net.Connection.ServiceLayer
|
|||||||
var estado = (int)response_.StatusCode;
|
var estado = (int)response_.StatusCode;
|
||||||
responseBody.CodigoHttp = estado.ToString();
|
responseBody.CodigoHttp = estado.ToString();
|
||||||
responseBody.Mensaje = "Error Services : " + _responseLoginServiceLayer.error.message.ToString();
|
responseBody.Mensaje = "Error Services : " + _responseLoginServiceLayer.error.message.ToString();
|
||||||
|
if (conCuerpo) LogErrorEnvio(Query, obj, responseBody.Mensaje);
|
||||||
return responseBody;
|
return responseBody;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -786,230 +373,7 @@ namespace Net.Connection.ServiceLayer
|
|||||||
// PASAMOS LA EXCEPCIÓN A LA APP.
|
// PASAMOS LA EXCEPCIÓN A LA APP.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public async Task<T> CancelAsyncSBA<T>(string Query)
|
// PATCH con cuerpo JSON. OJO: con un 204 real devuelve null sin marcar exito (bug conocido); para PATCH sin respuesta usar PatchNoContentAsync
|
||||||
{
|
|
||||||
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"));
|
|
||||||
//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<T>(responseText_);
|
|
||||||
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_);
|
|
||||||
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 LoginPost();
|
|
||||||
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;
|
|
||||||
|
|
||||||
dynamic responseBody = JsonConvert.DeserializeObject<T>(responseText_);
|
|
||||||
var estado = (int)response_.StatusCode;
|
|
||||||
//responseBody.CodigoHttp = estado.ToString();
|
|
||||||
responseBody.Mensaje = "Error Services : " + _responseLoginServiceLayer.error.message.ToString();
|
|
||||||
return responseBody;
|
|
||||||
}
|
|
||||||
|
|
||||||
// RETORNAMOS EL OBJETO POR DEFECTO ESPERADO
|
|
||||||
return default(T);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
return default(T);
|
|
||||||
throw new Exception(ex.Message.ToString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public async Task<T> DeleteAsyncSBA<T>(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("DELETE");
|
|
||||||
|
|
||||||
// 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"));
|
|
||||||
//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<T>(responseText_);
|
|
||||||
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_);
|
|
||||||
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 LoginPost();
|
|
||||||
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;
|
|
||||||
|
|
||||||
dynamic responseBody = JsonConvert.DeserializeObject<T>(responseText_);
|
|
||||||
var estado = (int)response_.StatusCode;
|
|
||||||
//responseBody.CodigoHttp = estado.ToString();
|
|
||||||
responseBody.Mensaje = "Error Services : " + _responseLoginServiceLayer.error.message.ToString();
|
|
||||||
return responseBody;
|
|
||||||
}
|
|
||||||
|
|
||||||
// RETORNAMOS EL OBJETO POR DEFECTO ESPERADO
|
|
||||||
return default(T);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
return default(T);
|
|
||||||
throw new Exception(ex.Message.ToString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public async Task<T> PatchAsyncSBA<T>(string Query, object obj)
|
public async Task<T> PatchAsyncSBA<T>(string Query, object obj)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -1091,7 +455,7 @@ namespace Net.Connection.ServiceLayer
|
|||||||
|
|
||||||
if (_responseLoginServiceLayer.error.code.ToString() == "301")
|
if (_responseLoginServiceLayer.error.code.ToString() == "301")
|
||||||
{
|
{
|
||||||
await LoginPost();
|
await Login();
|
||||||
goto band;
|
goto band;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1127,7 +491,6 @@ namespace Net.Connection.ServiceLayer
|
|||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
return default(T);
|
return default(T);
|
||||||
throw new Exception(ex.Message.ToString());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,20 +28,9 @@ namespace Net.Data
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region SAP (SERVICES LAYER)
|
#region SAP (SERVICES LAYER)
|
||||||
IBusinessPartnersRepository BusinessPartners { get; }
|
|
||||||
ICreditNotesRepository CreditNotes { get; }
|
|
||||||
IPurchaseInvoicesRepository PurchaseInvoices { get; }
|
|
||||||
IInvoiceRepository InvoiceRepository { get; }
|
|
||||||
IPurchaseDeliveryNotesRepository PurchaseDeliveryNotes { get; }
|
|
||||||
IPurchaseOrdersRepository PurchaseOrders { get; }
|
|
||||||
IAuthenticateRepository Authenticate { get; }
|
IAuthenticateRepository Authenticate { get; }
|
||||||
IItemsRepository Items { get; }
|
IItemsRepository Items { get; }
|
||||||
IReturnRequestRepository ReturnRequest { get; }
|
|
||||||
IStockTransferRepository StockTransfer { get; }
|
|
||||||
IInventoryGenEntriesRepository InventoryGenEntries { get; }
|
|
||||||
IInventoryGenExitsRepository InventoryGenExits { get; }
|
|
||||||
IHRRepository HR { get; }
|
IHRRepository HR { get; }
|
||||||
IGlobalDataRepository GlobalData { get; }
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ using System.Net.Http;
|
|||||||
|
|
||||||
namespace Net.Data
|
namespace Net.Data
|
||||||
{
|
{
|
||||||
|
// Expone solo los repositorios que usan los controllers. Los de Service Layer (CreditNotes, Invoice, StockTransfer, ReturnRequest...)
|
||||||
|
// se instancian directamente con new donde se necesitan.
|
||||||
public class RepositoryWrapper : IRepositoryWrapper
|
public class RepositoryWrapper : IRepositoryWrapper
|
||||||
{
|
{
|
||||||
#region Variables de conexion
|
#region Variables de conexion
|
||||||
@@ -42,7 +44,6 @@ namespace Net.Data
|
|||||||
|
|
||||||
private IReciboRepository _Recibo;
|
private IReciboRepository _Recibo;
|
||||||
private ITransferenciaRepository _Transferencia;
|
private ITransferenciaRepository _Transferencia;
|
||||||
private IStockTransferRepository _StockTransfer;
|
|
||||||
private IPickingRepository _Picking;
|
private IPickingRepository _Picking;
|
||||||
private IInventarioRepository _Inventario;
|
private IInventarioRepository _Inventario;
|
||||||
private ISolicitudTransferenciaRepository _SolicitudTransferencia;
|
private ISolicitudTransferenciaRepository _SolicitudTransferencia;
|
||||||
@@ -51,19 +52,9 @@ namespace Net.Data
|
|||||||
|
|
||||||
#region SAP (SERVICES LAYER)
|
#region SAP (SERVICES LAYER)
|
||||||
|
|
||||||
private IBusinessPartnersRepository _BusinessPartners;
|
|
||||||
private IPurchaseInvoicesRepository _PurchaseInvoices;
|
|
||||||
private IInvoiceRepository _Invoice;
|
|
||||||
private ICreditNotesRepository _CreditNotes;
|
|
||||||
private IAuthenticateRepository _AuthenticateRepository;
|
private IAuthenticateRepository _AuthenticateRepository;
|
||||||
private IPurchaseDeliveryNotesRepository _PurchaseDeliveryNotes;
|
|
||||||
private IPurchaseOrdersRepository _PurchaseOrders;
|
|
||||||
private IItemsRepository _Items;
|
private IItemsRepository _Items;
|
||||||
private IReturnRequestRepository _ReturnRequest;
|
|
||||||
private IInventoryGenEntriesRepository _InventoryGenEntries;
|
|
||||||
private IInventoryGenExitsRepository _InventoryGenExits;
|
|
||||||
private IHRRepository _HR;
|
private IHRRepository _HR;
|
||||||
private IGlobalDataRepository _GlobalData;
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@@ -162,79 +153,6 @@ namespace Net.Data
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region SAP (SERVICES LAYER)
|
#region SAP (SERVICES LAYER)
|
||||||
public IBusinessPartnersRepository BusinessPartners
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (_BusinessPartners == null)
|
|
||||||
{
|
|
||||||
_repoContext._cnx = _conSQL;
|
|
||||||
_BusinessPartners = new BusinessPartnersRepository(_clientFactory, _configuration, _repoContext);
|
|
||||||
}
|
|
||||||
return _BusinessPartners;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public ICreditNotesRepository CreditNotes
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (_CreditNotes == null)
|
|
||||||
{
|
|
||||||
_repoContext._cnx = _conSQL;
|
|
||||||
_CreditNotes = new CreditNotesRepository(_clientFactory, _configuration, _repoContext);
|
|
||||||
|
|
||||||
}
|
|
||||||
return _CreditNotes;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public IInvoiceRepository InvoiceRepository
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (_Invoice == null)
|
|
||||||
{
|
|
||||||
_repoContext._cnx = _conSQL;
|
|
||||||
_Invoice = new InvoiceRepository(_clientFactory, _configuration, _repoContext);
|
|
||||||
}
|
|
||||||
return _Invoice;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public IPurchaseInvoicesRepository PurchaseInvoices
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (_PurchaseInvoices == null)
|
|
||||||
{
|
|
||||||
_repoContext._cnx = _conSQL;
|
|
||||||
_PurchaseInvoices = new PurchaseInvoicesRepository(_clientFactory, _configuration);
|
|
||||||
}
|
|
||||||
return _PurchaseInvoices;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public IPurchaseDeliveryNotesRepository PurchaseDeliveryNotes
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (_PurchaseDeliveryNotes == null)
|
|
||||||
{
|
|
||||||
_repoContext._cnx = _conSQL;
|
|
||||||
_PurchaseDeliveryNotes = new PurchaseDeliveryNotesRepository(_clientFactory, _configuration);
|
|
||||||
}
|
|
||||||
return _PurchaseDeliveryNotes;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public IPurchaseOrdersRepository PurchaseOrders
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (_PurchaseOrders == null)
|
|
||||||
{
|
|
||||||
_repoContext._cnx = _conSQL;
|
|
||||||
_PurchaseOrders = new PurchaseOrdersRepository(_clientFactory, _configuration);
|
|
||||||
}
|
|
||||||
return _PurchaseOrders;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public IAuthenticateRepository Authenticate
|
public IAuthenticateRepository Authenticate
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@@ -247,54 +165,6 @@ namespace Net.Data
|
|||||||
return _AuthenticateRepository;
|
return _AuthenticateRepository;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public IReturnRequestRepository ReturnRequest
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if(_ReturnRequest == null)
|
|
||||||
{
|
|
||||||
_repoContext._cnx = _conSQL;
|
|
||||||
_ReturnRequest = new ReturnRequestRepository(_clientFactory, _configuration);
|
|
||||||
}
|
|
||||||
return _ReturnRequest;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public IStockTransferRepository StockTransfer
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (_StockTransfer == null)
|
|
||||||
{
|
|
||||||
_repoContext._cnx = _conSQL;
|
|
||||||
_StockTransfer = new StockTransferRepository(_clientFactory, _configuration);
|
|
||||||
}
|
|
||||||
return _StockTransfer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public IInventoryGenEntriesRepository InventoryGenEntries
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (_InventoryGenEntries == null)
|
|
||||||
{
|
|
||||||
_repoContext._cnx = _conSQL;
|
|
||||||
_InventoryGenEntries = new InventoryGenEntriesRepository(_clientFactory, _configuration, _repoContext);
|
|
||||||
}
|
|
||||||
return _InventoryGenEntries;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public IInventoryGenExitsRepository InventoryGenExits
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (_InventoryGenExits == null)
|
|
||||||
{
|
|
||||||
_repoContext._cnx = _conSQL;
|
|
||||||
_InventoryGenExits = new InventoryGenExitsRepository(_clientFactory, _configuration, _repoContext);
|
|
||||||
}
|
|
||||||
return _InventoryGenExits;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public IHRRepository HR
|
public IHRRepository HR
|
||||||
{
|
{
|
||||||
@@ -309,18 +179,6 @@ namespace Net.Data
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public IGlobalDataRepository GlobalData
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (_GlobalData == null)
|
|
||||||
{
|
|
||||||
_repoContext._cnx = _conSQL;
|
|
||||||
_GlobalData = new GlobalDataRepository(_clientFactory, _configuration, _repoContext);
|
|
||||||
}
|
|
||||||
return _GlobalData;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
using Sap.Data.Hana;
|
|
||||||
using System.Data;
|
|
||||||
|
|
||||||
namespace Net.Data.HelperData
|
|
||||||
{
|
|
||||||
public class HelperDB
|
|
||||||
{
|
|
||||||
public static void SET(string query)
|
|
||||||
{
|
|
||||||
using (HanaConnection con = new HanaConnection(LocalData.ConnectionString))
|
|
||||||
{
|
|
||||||
con.Open();
|
|
||||||
HanaCommand cmd = new HanaCommand
|
|
||||||
{
|
|
||||||
CommandText = query,
|
|
||||||
Connection = con,
|
|
||||||
CommandType = CommandType.Text
|
|
||||||
};
|
|
||||||
cmd.ExecuteNonQuery();
|
|
||||||
|
|
||||||
con.Close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -56,42 +56,5 @@ namespace Net.Data.HelperData.HojaRuta
|
|||||||
return ListaStock;
|
return ListaStock;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static List<BE_ItemStock> GetConsolidado(string ItemCode, string FromWarehouseCode)
|
|
||||||
{
|
|
||||||
List<BE_ItemStock> ListaStock = new List<BE_ItemStock>();
|
|
||||||
DataTable dt = new DataTable();
|
|
||||||
using (HanaConnection con = new HanaConnection(LocalData.ConnectionString))
|
|
||||||
{
|
|
||||||
con.Open();
|
|
||||||
HanaCommand cmd = new HanaCommand($"CALL\"{LocalData.Schema}\".\"ITCA_WMS_SAP_GetStockConsolidado\"(?, ?)", con);
|
|
||||||
cmd.Parameters.Add("ItemCode", HanaDbType.VarChar).Value = ItemCode ?? "";
|
|
||||||
cmd.Parameters.Add("FromWarehouseCode", HanaDbType.VarChar).Value = FromWarehouseCode ?? "";
|
|
||||||
HanaDataAdapter da = new HanaDataAdapter(cmd);
|
|
||||||
if (da != null)
|
|
||||||
{
|
|
||||||
da.Fill(dt);
|
|
||||||
}
|
|
||||||
con.Close();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int x = 0; x < dt.Rows.Count; x++)
|
|
||||||
{
|
|
||||||
ListaStock.Add(new BE_ItemStock()
|
|
||||||
{
|
|
||||||
BinCode = (dt.Rows[x]["BinCode"] != DBNull.Value) ? Convert.ToString(dt.Rows[x]["BinCode"]) : string.Empty,
|
|
||||||
DistNumber = (dt.Rows[x]["DistNumber"] != DBNull.Value) ? Convert.ToString(dt.Rows[x]["DistNumber"]) : string.Empty,
|
|
||||||
ExpDate = (dt.Rows[x]["ExpDate"] != DBNull.Value) ? Convert.ToDateTime(dt.Rows[x]["ExpDate"]) : DateTime.Now,
|
|
||||||
ItemCode = (dt.Rows[x]["ItemCode"] != DBNull.Value) ? Convert.ToString(dt.Rows[x]["ItemCode"]) : string.Empty,
|
|
||||||
ItemName = (dt.Rows[x]["ItemName"] != DBNull.Value) ? Convert.ToString(dt.Rows[x]["ItemName"]) : string.Empty,
|
|
||||||
OnHandQty = (dt.Rows[x]["OnHandQty"] != DBNull.Value) ? Convert.ToDouble(dt.Rows[x]["OnHandQty"]) : 0,
|
|
||||||
WhsCode = (dt.Rows[x]["WhsCode"] != DBNull.Value) ? Convert.ToString(dt.Rows[x]["WhsCode"]) : string.Empty,
|
|
||||||
WhsName = (dt.Rows[x]["WhsName"] != DBNull.Value) ? Convert.ToString(dt.Rows[x]["WhsName"]) : string.Empty,
|
|
||||||
AvgPrice = (dt.Rows[x]["AvgPrice"] != DBNull.Value) ? Convert.ToDouble(dt.Rows[x]["AvgPrice"]) : 0,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return ListaStock;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
using Sap.Data.Hana;
|
using Net.Business.Entities.WMS.Transferencia;
|
||||||
|
using Sap.Data.Hana;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Data;
|
using System.Data;
|
||||||
|
|
||||||
namespace Net.Data.HelperData.Invoices
|
namespace Net.Data.HelperData.Invoices
|
||||||
@@ -30,5 +32,49 @@ namespace Net.Data.HelperData.Invoices
|
|||||||
|
|
||||||
return DocEntry;
|
return DocEntry;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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 (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<BE_EntradaImportacionLinea> GetEntradasPorFactura(string DocNum)
|
||||||
|
{
|
||||||
|
var lista = new List<BE_EntradaImportacionLinea>();
|
||||||
|
DataTable dt = new DataTable();
|
||||||
|
string esquema = LocalData.Schema;
|
||||||
|
using (HanaConnection con = new HanaConnection(LocalData.ConnectionString))
|
||||||
|
{
|
||||||
|
con.Open();
|
||||||
|
HanaCommand cmd = new HanaCommand(
|
||||||
|
$"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 \"{esquema}\".\"OPCH\" F " +
|
||||||
|
$"INNER JOIN \"{esquema}\".\"PDN1\" L ON L.\"BaseType\" = 18 AND L.\"BaseEntry\" = F.\"DocEntry\" " +
|
||||||
|
$"INNER JOIN \"{esquema}\".\"OPDN\" E ON E.\"DocEntry\" = L.\"DocEntry\" " +
|
||||||
|
$"LEFT JOIN \"{esquema}\".\"OWTR\" W ON L.\"TargetType\" = 67 AND W.\"DocEntry\" = L.\"TrgetEntry\" " +
|
||||||
|
$"WHERE F.\"DocNum\" = ? AND F.\"isIns\" = 'Y' AND E.\"CANCELED\" = 'N' " +
|
||||||
|
$"ORDER BY E.\"DocEntry\", L.\"LineNum\"", con);
|
||||||
|
cmd.Parameters.Add("DocNum", HanaDbType.VarChar).Value = DocNum ?? "";
|
||||||
|
HanaDataAdapter da = new HanaDataAdapter(cmd);
|
||||||
|
da.Fill(dt);
|
||||||
|
con.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int x = 0; x < dt.Rows.Count; x++)
|
||||||
|
{
|
||||||
|
lista.Add(new BE_EntradaImportacionLinea()
|
||||||
|
{
|
||||||
|
DocEntry = Convert.ToInt32(dt.Rows[x]["DocEntry"]),
|
||||||
|
DocNum = Convert.ToInt32(dt.Rows[x]["DocNum"]),
|
||||||
|
LineNum = Convert.ToInt32(dt.Rows[x]["LineNum"]),
|
||||||
|
ItemCode = Convert.ToString(dt.Rows[x]["ItemCode"]),
|
||||||
|
Quantity = Convert.ToDecimal(dt.Rows[x]["Quantity"]),
|
||||||
|
TransferenciaDocEntry = Convert.ToInt32(dt.Rows[x]["TransferenciaDocEntry"]),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return lista;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -36,6 +36,8 @@ namespace Net.Data.ServicesLayer.CreditNotes
|
|||||||
_context = context;
|
_context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Crea la nota de credito en SAP (POST CreditNotes). Si SAP rechaza, devuelve StatusCode -1 con el mensaje de SAP;
|
||||||
|
// el JSON enviado queda en el log (ver ConnectionServiceLayer.PostAsync)
|
||||||
public async Task<BE_ResultadoTransaccion<BE_BaseResponseSap>> SetCreateCreditNotes(BE_CreditNotesNotes entidad)
|
public async Task<BE_ResultadoTransaccion<BE_BaseResponseSap>> SetCreateCreditNotes(BE_CreditNotesNotes entidad)
|
||||||
{
|
{
|
||||||
_metodoName = regex.Match(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name).Groups[1].Value.ToString();
|
_metodoName = regex.Match(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name).Groups[1].Value.ToString();
|
||||||
@@ -62,6 +64,7 @@ namespace Net.Data.ServicesLayer.CreditNotes
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Actualiza una NC ya creada (PATCH); se usa para agregar las referencias de las transferencias automaticas
|
||||||
public async Task<BE_ResultadoTransaccion<BE_BaseResponseSap>> UpdateCreateCreditNotes(dynamic entidad, int DocEntry)
|
public async Task<BE_ResultadoTransaccion<BE_BaseResponseSap>> UpdateCreateCreditNotes(dynamic entidad, int DocEntry)
|
||||||
{
|
{
|
||||||
_metodoName = regex.Match(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name).Groups[1].Value.ToString();
|
_metodoName = regex.Match(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name).Groups[1].Value.ToString();
|
||||||
|
|||||||
@@ -116,30 +116,6 @@ namespace Net.Data.ServicesLayer.HR
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public async Task<BE_ResultadoTransaccion<BE_BaseResponseSap>> UpdateCreateCreditNotes(dynamic entidad, int DocEntry)
|
|
||||||
{
|
|
||||||
_metodoName = regex.Match(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name).Groups[1].Value.ToString();
|
|
||||||
|
|
||||||
ResultadoTransaccion<BE_BaseResponseSap> resultadoTransaccion = new ResultadoTransaccion<BE_BaseResponseSap>(_metodoName, _aplicacionName);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
//var cadena = "Drafts";
|
|
||||||
var cadena = $"CreditNotes({DocEntry})";
|
|
||||||
var data = await _connectServiceLayer.PatchAsyncSBA<BE_BaseResponseSap>(cadena, entidad);
|
|
||||||
if (!data.Exito)
|
|
||||||
{
|
|
||||||
return resultadoTransaccion.GetInformacionResultado(-1, -1, data.Mensaje);
|
|
||||||
}
|
|
||||||
|
|
||||||
return resultadoTransaccion.GetInformacionResultado(0, "", "Se Listo correctamento...!!!", data);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
return resultadoTransaccion.GetInformacionResultado(-1, -1, ex.Message.ToString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ namespace Net.Data.ServicesLayer
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GET INVOICES
|
// GET INVOICES
|
||||||
|
// Lee una factura de venta (GET Invoices(docEntry)): cabecera, campos U_SYP_*, lineas con BaseType/BaseEntry/BaseLine y lotes
|
||||||
public async Task<BE_ResultadoTransaccion<BE_InvoicesConsulta>> GetInvoice(int docEntry)
|
public async Task<BE_ResultadoTransaccion<BE_InvoicesConsulta>> GetInvoice(int docEntry)
|
||||||
{
|
{
|
||||||
_metodoName = regex.Match(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name).Groups[1].Value.ToString();
|
_metodoName = regex.Match(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name).Groups[1].Value.ToString();
|
||||||
@@ -111,6 +112,7 @@ namespace Net.Data.ServicesLayer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reabre una factura cerrada (POST Invoices(n)/Reopen); la NC solo se puede crear sobre una factura abierta
|
||||||
public async Task<BE_ResultadoTransaccion<BE_BaseResponseSap>> ReOpen(int docEntry)
|
public async Task<BE_ResultadoTransaccion<BE_BaseResponseSap>> ReOpen(int docEntry)
|
||||||
{
|
{
|
||||||
_metodoName = regex.Match(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name).Groups[1].Value.ToString();
|
_metodoName = regex.Match(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name).Groups[1].Value.ToString();
|
||||||
|
|||||||
@@ -0,0 +1,114 @@
|
|||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Net.Business.Entities.ServicesLayer.CreditNotes;
|
||||||
|
using Net.Business.Entities.ServicesLayer.StockTransfer;
|
||||||
|
using Net.Data.ServicesLayer.CreditNotes;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net.Http;
|
||||||
|
|
||||||
|
namespace Net.Data.ServicesLayer.StockTransfer
|
||||||
|
{
|
||||||
|
// Transferencias de stock automaticas de una nota de credito (Picking DESPACHO/PICKING y Recibo Devolucion).
|
||||||
|
// Antes este bloque estaba copiado 3 veces.
|
||||||
|
public static class TransferenciaAutomaticaNC
|
||||||
|
{
|
||||||
|
// Por cada almacen de origen (WarehouseCode) y almacen destino (U_ITC_ALMDES) distintos de las lineas de la NC
|
||||||
|
// crea una transferencia de stock en SAP, con las cantidades y lotes de esas lineas y la NC como referencia.
|
||||||
|
// Luego agrega a la NC una referencia por cada transferencia creada.
|
||||||
|
// OJO: si SAP rechaza una transferencia se ignora (la NC ya esta creada) y no se informa al consumidor.
|
||||||
|
public static void Crear(BE_CreditNotesNotes creditNotesNotes, int docEntryNC, IHttpClientFactory clientFactory, IConfiguration configuration, CreditNotesRepository creditNotesRepository)
|
||||||
|
{
|
||||||
|
List<string> ListaUnicaAlmacen = creditNotesNotes.DocumentLines.Select(x => x.WarehouseCode).Distinct().ToList();
|
||||||
|
List<int> ListDocEntry = new List<int>();
|
||||||
|
ListaUnicaAlmacen.ForEach(Almacen =>
|
||||||
|
{
|
||||||
|
List<string> ListaUnicaAlmDestino = creditNotesNotes.DocumentLines.Where(Linea => Linea.WarehouseCode == Almacen).ToList().Select(x => x.U_ITC_ALMDES).Distinct().ToList().Where(x => !string.IsNullOrEmpty(x)).ToList();
|
||||||
|
ListaUnicaAlmDestino.ForEach(Destino =>
|
||||||
|
{
|
||||||
|
if (Almacen != Destino)
|
||||||
|
{
|
||||||
|
BE_StockTransferAutomatico Transfer = new BE_StockTransferAutomatico();
|
||||||
|
Transfer.FromWarehouse = Almacen;
|
||||||
|
Transfer.ToWarehouse = Destino;
|
||||||
|
Transfer.DocDate = creditNotesNotes.DocDate;
|
||||||
|
Transfer.TaxDate = creditNotesNotes.DocDate;
|
||||||
|
Transfer.U_SYP_MDTS = "TSI";
|
||||||
|
Transfer.U_SYP_MDMT = "24";
|
||||||
|
Transfer.DueDate = creditNotesNotes.DocDueDate;
|
||||||
|
Transfer.PriceList = -2;
|
||||||
|
var ListaDetalle_PorAlmacen = creditNotesNotes.DocumentLines.Where(x => x.WarehouseCode == Almacen && x.U_ITC_ALMDES == Destino).ToList();
|
||||||
|
var ListaUnicaArticulo = ListaDetalle_PorAlmacen.Select(x => x.ItemCode).Distinct().ToList();
|
||||||
|
Transfer.StockTransferLines = new List<BE_StockTransferLine>();
|
||||||
|
|
||||||
|
//=== AGREGA DOCUMENTO REFERENCIA
|
||||||
|
Transfer.DocumentReferences = new List<BE_StockTransferDocumentReferences>
|
||||||
|
{
|
||||||
|
new BE_StockTransferDocumentReferences()
|
||||||
|
{
|
||||||
|
RefDocEntr = docEntryNC,
|
||||||
|
RefObjType = "rot_SalesCreditNote",
|
||||||
|
IssueDate = DateTime.Now,
|
||||||
|
Remark = "Nota de Credito",
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ListaUnicaArticulo.ForEach(x =>
|
||||||
|
{
|
||||||
|
var ListLote = new List<BE_StockTransferLineBatchNumber>();
|
||||||
|
// Lotes de todas las lineas del articulo (si el mismo lote esta en varias lineas se suma)
|
||||||
|
ListaDetalle_PorAlmacen.Where(t => t.ItemCode == x).SelectMany(t => t.BatchNumbers).GroupBy(r => r.BatchNumber).ToList().ForEach(g =>
|
||||||
|
{
|
||||||
|
ListLote.Add(new BE_StockTransferLineBatchNumber()
|
||||||
|
{
|
||||||
|
BatchNumber = g.Key,
|
||||||
|
Quantity = g.Sum(r => r.Quantity)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
Transfer.StockTransferLines.Add(new BE_StockTransferLine()
|
||||||
|
{
|
||||||
|
ItemCode = x,
|
||||||
|
ItemDescription = ListaDetalle_PorAlmacen.Where(t => t.ItemCode == x).FirstOrDefault().ItemDescription,
|
||||||
|
Quantity = Convert.ToDouble(ListaDetalle_PorAlmacen.Where(t => t.ItemCode == x).Sum(t => t.Quantity)),
|
||||||
|
WarehouseCode = Destino,
|
||||||
|
FromWarehouseCode = Almacen,
|
||||||
|
BatchNumbers = ListLote
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
//=== CREA INSTANCIA PARA CONECTAR A SAP
|
||||||
|
StockTransferRepository stockTransferRepository = new StockTransferRepository(clientFactory, configuration);
|
||||||
|
var ResultTransfer = stockTransferRepository.StockTransfer(Transfer).Result;
|
||||||
|
if (ResultTransfer.StatusCode != -1)
|
||||||
|
{
|
||||||
|
ListDocEntry.Add((int)ResultTransfer.Data.DocEntry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Referencia las transferencias creadas en la NC
|
||||||
|
if (ListDocEntry.Count > 0)
|
||||||
|
{
|
||||||
|
var listaReferencia = new List<BE_CreditNotesDocumentReferences>();
|
||||||
|
ListDocEntry.ForEach(x =>
|
||||||
|
{
|
||||||
|
listaReferencia.Add(new BE_CreditNotesDocumentReferences()
|
||||||
|
{
|
||||||
|
RefDocEntr = x,
|
||||||
|
IssueDate = DateTime.Now,
|
||||||
|
RefObjType = "rot_InventoryTransfer",
|
||||||
|
Remark = "Transferencias Automatica"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
var credit = new
|
||||||
|
{
|
||||||
|
DocumentReferences = listaReferencia
|
||||||
|
};
|
||||||
|
|
||||||
|
var Actualizacion = creditNotesRepository.UpdateCreateCreditNotes(credit, docEntryNC).Result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -52,6 +52,10 @@ namespace Net.Data.WMS.Picking
|
|||||||
_aplicacionName = GetType().Name;
|
_aplicacionName = GetType().Name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// POST /api/Picking: actualiza el estado de la factura en HANA y, segun Proceso + IdEstadoCubico, genera documentos en SAP:
|
||||||
|
// DESPACHO (estado 5/6) -> NC por lo no entregado + transferencias a los almacenes destino (U_ITC_ALMDES)
|
||||||
|
// PICKING (estado 13) -> NC con un lote nuevo por linea + transferencias
|
||||||
|
// ULTIMA MILLA (estado 23, solo con Notificacion = "cierre") -> Solicitud de Devolucion
|
||||||
public async Task<BE_ResultadoTransaccion<object>> Create(BE_PickingRequest entidad)
|
public async Task<BE_ResultadoTransaccion<object>> Create(BE_PickingRequest entidad)
|
||||||
{
|
{
|
||||||
_metodoName = regex.Match(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name).Groups[1].Value.ToString();
|
_metodoName = regex.Match(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name).Groups[1].Value.ToString();
|
||||||
@@ -67,6 +71,7 @@ namespace Net.Data.WMS.Picking
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
// Datos del motivo (@SYP_MOTIVOS): almacen destino, si es reproceso y tipo/descripcion de la NC
|
||||||
var Almacen = HelperProcesoPermitido.GetMotivoAlmacen(entidad.CodMotivo, out bool Reproceso, out string MotivoNotaCredito, out string DescripcionMotivo);
|
var Almacen = HelperProcesoPermitido.GetMotivoAlmacen(entidad.CodMotivo, out bool Reproceso, out string MotivoNotaCredito, out string DescripcionMotivo);
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(entidad.Proceso))
|
if (string.IsNullOrEmpty(entidad.Proceso))
|
||||||
@@ -88,13 +93,11 @@ namespace Net.Data.WMS.Picking
|
|||||||
|
|
||||||
var TotalDetalle = 0;
|
var TotalDetalle = 0;
|
||||||
var TotalDetalle_Anulado = 0;
|
var TotalDetalle_Anulado = 0;
|
||||||
var TotalDetalle_Parcial = 0;
|
|
||||||
var TotalDetalle_Completo = 0;
|
var TotalDetalle_Completo = 0;
|
||||||
if (Existe > 1)
|
if (Existe > 1)
|
||||||
{
|
{
|
||||||
TotalDetalle = entidad.Detalle.Count();
|
TotalDetalle = entidad.Detalle.Count();
|
||||||
TotalDetalle_Anulado = entidad.Detalle.Where(x=> x.Estado == "A").Count();
|
TotalDetalle_Anulado = entidad.Detalle.Where(x=> x.Estado == "A").Count();
|
||||||
TotalDetalle_Parcial = entidad.Detalle.Where(x => x.Estado == "P").Count();
|
|
||||||
TotalDetalle_Completo = entidad.Detalle.Where(x => x.Estado == "C").Count();
|
TotalDetalle_Completo = entidad.Detalle.Where(x => x.Estado == "C").Count();
|
||||||
if(TotalDetalle == TotalDetalle_Anulado)
|
if(TotalDetalle == TotalDetalle_Anulado)
|
||||||
{
|
{
|
||||||
@@ -113,7 +116,6 @@ namespace Net.Data.WMS.Picking
|
|||||||
{
|
{
|
||||||
TotalDetalle = entidad.Detalle.Count();
|
TotalDetalle = entidad.Detalle.Count();
|
||||||
TotalDetalle_Anulado = entidad.Detalle.Where(x => x.Estado == "A").Count();
|
TotalDetalle_Anulado = entidad.Detalle.Where(x => x.Estado == "A").Count();
|
||||||
TotalDetalle_Parcial = entidad.Detalle.Where(x => x.Estado == "P").Count();
|
|
||||||
TotalDetalle_Completo = entidad.Detalle.Where(x => x.Estado == "C").Count();
|
TotalDetalle_Completo = entidad.Detalle.Where(x => x.Estado == "C").Count();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,6 +131,7 @@ namespace Net.Data.WMS.Picking
|
|||||||
{
|
{
|
||||||
return resultadoTransaccion.GetInformacionResultado(entidad.DocEntry, entidad.NumOrden, "Estado actualizado correctamente", entidad);
|
return resultadoTransaccion.GetInformacionResultado(entidad.DocEntry, entidad.NumOrden, "Estado actualizado correctamente", entidad);
|
||||||
}
|
}
|
||||||
|
// Todas las lineas completas: no hay nada que devolver (el tipo de documento 9 sigue el flujo)
|
||||||
if(TotalDetalle == TotalDetalle_Completo && entidad.IdTipoDoc != 9)
|
if(TotalDetalle == TotalDetalle_Completo && entidad.IdTipoDoc != 9)
|
||||||
{
|
{
|
||||||
return resultadoTransaccion.GetInformacionResultado(entidad.DocEntry, entidad.NumOrden, "Se registro correctamente...!!!", entidad);
|
return resultadoTransaccion.GetInformacionResultado(entidad.DocEntry, entidad.NumOrden, "Se registro correctamente...!!!", entidad);
|
||||||
@@ -136,6 +139,7 @@ namespace Net.Data.WMS.Picking
|
|||||||
|
|
||||||
#region PROCESO DE NOTA DE CREDITO
|
#region PROCESO DE NOTA DE CREDITO
|
||||||
|
|
||||||
|
// DESPACHO (estado 5 o 6): NC desde la factura; no aplica para IdTipoDoc 9
|
||||||
if (entidad.Proceso.ToUpper() == "DESPACHO" && (entidad.IdEstadoCubico == 6 || entidad.IdEstadoCubico == 5))
|
if (entidad.Proceso.ToUpper() == "DESPACHO" && (entidad.IdEstadoCubico == 6 || entidad.IdEstadoCubico == 5))
|
||||||
{
|
{
|
||||||
if (entidad.IdTipoDoc != 9)
|
if (entidad.IdTipoDoc != 9)
|
||||||
@@ -167,6 +171,8 @@ namespace Net.Data.WMS.Picking
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Serie de la NC segun el TIPO de documento de la factura (U_SYP_MDTD): 03 = boleta -> BC10, otro -> FC10.
|
||||||
|
// El correlativo sale de @SYP_NUMDOC (HelperCorrelativo)
|
||||||
string Serie = _invoices.U_SYP_MDTD == "03" ? "BC10" : "FC10";
|
string Serie = _invoices.U_SYP_MDTD == "03" ? "BC10" : "FC10";
|
||||||
string Correlativo = HelperCorrelativo.Get(Serie);
|
string Correlativo = HelperCorrelativo.Get(Serie);
|
||||||
creditNotesNotes.DocCurrency = _invoices.DocCurrency;
|
creditNotesNotes.DocCurrency = _invoices.DocCurrency;
|
||||||
@@ -293,105 +299,8 @@ namespace Net.Data.WMS.Picking
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
List<string> ListaUnicaAlmacen = creditNotesNotes.DocumentLines.Select(x => x.WarehouseCode).Distinct().ToList();
|
// Transferencias de stock automaticas por almacen destino (U_ITC_ALMDES) y su referencia en la NC
|
||||||
List<int> ListDocEntry = new List<int>();
|
TransferenciaAutomaticaNC.Crear(creditNotesNotes, (int)result_creditNotesRepository.Data.DocEntry, _clientFactory, _configuration, _creditNotesRepository);
|
||||||
ListaUnicaAlmacen.ForEach(Almacen =>
|
|
||||||
{
|
|
||||||
List<string> ListaUnicaAlmDestino = creditNotesNotes.DocumentLines.Where(Linea => Linea.WarehouseCode == Almacen).ToList().Select(x => x.U_ITC_ALMDES).Distinct().ToList().Where(x => !string.IsNullOrEmpty(x)).ToList();
|
|
||||||
ListaUnicaAlmDestino.ForEach(Destino =>
|
|
||||||
{
|
|
||||||
if (Almacen != Destino)
|
|
||||||
{
|
|
||||||
BE_StockTransferAutomatico Transfer = new BE_StockTransferAutomatico();
|
|
||||||
Transfer.FromWarehouse = Almacen;
|
|
||||||
Transfer.ToWarehouse = Destino;
|
|
||||||
Transfer.DocDate = creditNotesNotes.DocDate;
|
|
||||||
Transfer.TaxDate = creditNotesNotes.DocDate;
|
|
||||||
Transfer.U_SYP_MDTS = "TSI";
|
|
||||||
Transfer.U_SYP_MDMT = "24";
|
|
||||||
Transfer.DueDate = creditNotesNotes.DocDueDate;
|
|
||||||
Transfer.PriceList = -2;
|
|
||||||
var ListaDetalle_PorAlmacen = creditNotesNotes.DocumentLines.Where(x => x.WarehouseCode == Almacen && x.U_ITC_ALMDES == Destino).ToList();
|
|
||||||
var ListaUnicaArticulo = ListaDetalle_PorAlmacen.Select(x => x.ItemCode).Distinct().ToList();
|
|
||||||
Transfer.StockTransferLines = new List<BE_StockTransferLine>();
|
|
||||||
Transfer.DocumentReferences = new List<BE_StockTransferDocumentReferences>();
|
|
||||||
ListaUnicaArticulo.ForEach(x =>
|
|
||||||
{
|
|
||||||
var ListLote = new List<BE_StockTransferLineBatchNumber>();
|
|
||||||
// Lotes de todas las lineas del articulo (si el mismo lote esta en varias lineas se suma)
|
|
||||||
ListaDetalle_PorAlmacen.Where(t => t.ItemCode == x).SelectMany(t => t.BatchNumbers).GroupBy(r => r.BatchNumber).ToList().ForEach(g =>
|
|
||||||
{
|
|
||||||
ListLote.Add(new BE_StockTransferLineBatchNumber()
|
|
||||||
{
|
|
||||||
BatchNumber = g.Key,
|
|
||||||
Quantity = g.Sum(r => r.Quantity)
|
|
||||||
});
|
|
||||||
});
|
|
||||||
Transfer.StockTransferLines.Add(new BE_StockTransferLine()
|
|
||||||
{
|
|
||||||
|
|
||||||
ItemCode = x,
|
|
||||||
ItemDescription = ListaDetalle_PorAlmacen.Where(t => t.ItemCode == x).FirstOrDefault().ItemDescription,
|
|
||||||
Quantity = Convert.ToDouble(ListaDetalle_PorAlmacen.Where(t => t.ItemCode == x).Sum(t => t.Quantity)),
|
|
||||||
WarehouseCode = Destino,
|
|
||||||
FromWarehouseCode = Almacen,
|
|
||||||
BatchNumbers = ListLote
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
//=== AGREGA DOCUMENTO REFERENCIA
|
|
||||||
Transfer.DocumentReferences = new List<BE_StockTransferDocumentReferences>
|
|
||||||
{
|
|
||||||
new BE_StockTransferDocumentReferences()
|
|
||||||
{
|
|
||||||
RefDocEntr = (int)result_creditNotesRepository.Data.DocEntry,
|
|
||||||
RefObjType = "rot_SalesCreditNote",
|
|
||||||
IssueDate = DateTime.Now,
|
|
||||||
Remark = "Nota de Credito",
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
//=== CREA INSTANCIA PARA CONECTAR A SAP
|
|
||||||
StockTransferRepository stockTransferRepository = new StockTransferRepository(_clientFactory, _configuration);
|
|
||||||
var ResultTransfer = stockTransferRepository.StockTransfer(Transfer).Result;
|
|
||||||
if (ResultTransfer.StatusCode == -1)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ListDocEntry.Add((int)ResultTransfer.Data.DocEntry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
if (ListDocEntry.Count > 0)
|
|
||||||
{
|
|
||||||
var listaReferencia = new List<BE_CreditNotesDocumentReferences>();
|
|
||||||
ListDocEntry.ForEach(x =>
|
|
||||||
{
|
|
||||||
listaReferencia.Add(new BE_CreditNotesDocumentReferences()
|
|
||||||
{
|
|
||||||
RefDocEntr = x,
|
|
||||||
IssueDate = DateTime.Now,
|
|
||||||
RefObjType = "rot_InventoryTransfer",
|
|
||||||
Remark = "Transferencias Automatica"
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
var credit = new
|
|
||||||
{
|
|
||||||
DocumentReferences = listaReferencia
|
|
||||||
};
|
|
||||||
|
|
||||||
var Actualizacion = _creditNotesRepository.UpdateCreateCreditNotes(credit, (int)result_creditNotesRepository.Data.DocEntry).Result;
|
|
||||||
}
|
|
||||||
return resultadoTransaccion.GetInformacionResultado((int)result_creditNotesRepository.Data.DocEntry, result_creditNotesRepository.Data.DocNum.ToString(), "Se registro correctamente...!!!", entidad);
|
return resultadoTransaccion.GetInformacionResultado((int)result_creditNotesRepository.Data.DocEntry, result_creditNotesRepository.Data.DocNum.ToString(), "Se registro correctamente...!!!", entidad);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -500,6 +409,7 @@ namespace Net.Data.WMS.Picking
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PICKING (estado 13): NC con un lote nuevo (fecha y hora) por linea, sin usar los lotes de la factura; solo lineas con diferencia
|
||||||
if (entidad.Proceso.ToUpper() == "PICKING" && entidad.IdEstadoCubico == 13)
|
if (entidad.Proceso.ToUpper() == "PICKING" && entidad.IdEstadoCubico == 13)
|
||||||
{
|
{
|
||||||
CreditNotesRepository _creditNotesRepository = new CreditNotesRepository(_clientFactory, _configuration, context);
|
CreditNotesRepository _creditNotesRepository = new CreditNotesRepository(_clientFactory, _configuration, context);
|
||||||
@@ -527,6 +437,7 @@ namespace Net.Data.WMS.Picking
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Serie segun el tipo de documento de la factura (03 = boleta -> BC10, otro -> FC10)
|
||||||
string Serie = _invoices.U_SYP_MDTD == "03" ? "BC10" : "FC10";
|
string Serie = _invoices.U_SYP_MDTD == "03" ? "BC10" : "FC10";
|
||||||
string Correlativo = HelperCorrelativo.Get(Serie);
|
string Correlativo = HelperCorrelativo.Get(Serie);
|
||||||
|
|
||||||
@@ -571,7 +482,6 @@ namespace Net.Data.WMS.Picking
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
var _Invoice = response_invoicesRepository.ListData.FirstOrDefault();
|
|
||||||
creditNotesNotes.DocumentLines.ForEach(line =>
|
creditNotesNotes.DocumentLines.ForEach(line =>
|
||||||
{
|
{
|
||||||
|
|
||||||
@@ -588,100 +498,8 @@ namespace Net.Data.WMS.Picking
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
||||||
List<string> ListaUnicaAlmacen = creditNotesNotes.DocumentLines.Select(x => x.WarehouseCode).Distinct().ToList();
|
// Transferencias de stock automaticas por almacen destino (U_ITC_ALMDES) y su referencia en la NC
|
||||||
List<int> ListDocEntry = new List<int>();
|
TransferenciaAutomaticaNC.Crear(creditNotesNotes, (int)result_creditNotesRepository.Data.DocEntry, _clientFactory, _configuration, _creditNotesRepository);
|
||||||
ListaUnicaAlmacen.ForEach(Almacen =>
|
|
||||||
{
|
|
||||||
List<string> ListaUnicaAlmDestino = creditNotesNotes.DocumentLines.Where(Linea => Linea.WarehouseCode == Almacen).ToList().Select(x => x.U_ITC_ALMDES).Distinct().ToList().Where(x => !string.IsNullOrEmpty(x)).ToList();
|
|
||||||
ListaUnicaAlmDestino.ForEach(Destino =>
|
|
||||||
{
|
|
||||||
if (Almacen != Destino)
|
|
||||||
{
|
|
||||||
BE_StockTransferAutomatico Transfer = new BE_StockTransferAutomatico();
|
|
||||||
Transfer.FromWarehouse = Almacen;
|
|
||||||
Transfer.ToWarehouse = Destino;
|
|
||||||
Transfer.DocDate = creditNotesNotes.DocDate;
|
|
||||||
Transfer.TaxDate = creditNotesNotes.DocDate;
|
|
||||||
Transfer.DueDate = creditNotesNotes.DocDueDate;
|
|
||||||
Transfer.U_SYP_MDTS = "TSI";
|
|
||||||
Transfer.U_SYP_MDMT = "24";
|
|
||||||
Transfer.PriceList = -2;
|
|
||||||
var ListaDetalle_PorAlmacen = creditNotesNotes.DocumentLines.Where(x => x.WarehouseCode == Almacen && x.U_ITC_ALMDES == Destino).ToList();
|
|
||||||
var ListaUnicaArticulo = ListaDetalle_PorAlmacen.Select(x => x.ItemCode).Distinct().ToList();
|
|
||||||
|
|
||||||
Transfer.StockTransferLines = new List<BE_StockTransferLine>();
|
|
||||||
Transfer.DocumentReferences = new List<BE_StockTransferDocumentReferences>();
|
|
||||||
|
|
||||||
ListaUnicaArticulo.ForEach(x =>
|
|
||||||
{
|
|
||||||
var ListLote = new List<BE_StockTransferLineBatchNumber>();
|
|
||||||
// Lotes de todas las lineas del articulo (si el mismo lote esta en varias lineas se suma)
|
|
||||||
ListaDetalle_PorAlmacen.Where(t => t.ItemCode == x).SelectMany(t => t.BatchNumbers).GroupBy(r => r.BatchNumber).ToList().ForEach(g =>
|
|
||||||
{
|
|
||||||
ListLote.Add(new BE_StockTransferLineBatchNumber()
|
|
||||||
{
|
|
||||||
BatchNumber = g.Key,
|
|
||||||
Quantity = g.Sum(r => r.Quantity)
|
|
||||||
});
|
|
||||||
});
|
|
||||||
Transfer.StockTransferLines.Add(new BE_StockTransferLine()
|
|
||||||
{
|
|
||||||
ItemCode = x,
|
|
||||||
ItemDescription = ListaDetalle_PorAlmacen.Where(t => t.ItemCode == x).FirstOrDefault().ItemDescription,
|
|
||||||
Quantity = Convert.ToDouble(ListaDetalle_PorAlmacen.Where(t => t.ItemCode == x).Sum(t => t.Quantity)),
|
|
||||||
WarehouseCode = Destino,
|
|
||||||
FromWarehouseCode = Almacen,
|
|
||||||
BatchNumbers = ListLote
|
|
||||||
});
|
|
||||||
|
|
||||||
//=== AGREGA DOCUMENTO REFERENCIA
|
|
||||||
Transfer.DocumentReferences = new List<BE_StockTransferDocumentReferences>
|
|
||||||
{
|
|
||||||
new BE_StockTransferDocumentReferences()
|
|
||||||
{
|
|
||||||
RefDocEntr = (int)result_creditNotesRepository.Data.DocEntry,
|
|
||||||
RefObjType = "rot_SalesCreditNote",
|
|
||||||
IssueDate = DateTime.Now,
|
|
||||||
Remark = "Nota de Credito",
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
//=== CREA INSTANCIA PARA CONECTAR A SAP
|
|
||||||
StockTransferRepository stockTransferRepository = new StockTransferRepository(_clientFactory, _configuration);
|
|
||||||
var ResultTransfer = stockTransferRepository.StockTransfer(Transfer).Result;
|
|
||||||
if (ResultTransfer.StatusCode == -1)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ListDocEntry.Add((int)ResultTransfer.Data.DocEntry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
if (ListDocEntry.Count > 0)
|
|
||||||
{
|
|
||||||
var listaReferencia = new List<BE_CreditNotesDocumentReferences>();
|
|
||||||
ListDocEntry.ForEach(x =>
|
|
||||||
{
|
|
||||||
listaReferencia.Add(new BE_CreditNotesDocumentReferences()
|
|
||||||
{
|
|
||||||
RefDocEntr = x,
|
|
||||||
IssueDate = DateTime.Now,
|
|
||||||
RefObjType = "rot_InventoryTransfer",
|
|
||||||
Remark = "Transferencias Automatica"
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
var credit = new
|
|
||||||
{
|
|
||||||
DocumentReferences = listaReferencia
|
|
||||||
};
|
|
||||||
var Actualizacion = _creditNotesRepository.UpdateCreateCreditNotes(credit, (int)result_creditNotesRepository.Data.DocEntry).Result;
|
|
||||||
}
|
|
||||||
return resultadoTransaccion.GetInformacionResultado((int)result_creditNotesRepository.Data.DocEntry, result_creditNotesRepository.Data.DocNum.ToString(), "Se registro correctamente...!!!", entidad);
|
return resultadoTransaccion.GetInformacionResultado((int)result_creditNotesRepository.Data.DocEntry, result_creditNotesRepository.Data.DocNum.ToString(), "Se registro correctamente...!!!", entidad);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -690,6 +508,8 @@ namespace Net.Data.WMS.Picking
|
|||||||
return resultadoTransaccion.GetInformacionResultado((int)entidad.DocEntry, entidad.NumOrden.ToString(), "Se registro correctamente...!!!", entidad);
|
return resultadoTransaccion.GetInformacionResultado((int)entidad.DocEntry, entidad.NumOrden.ToString(), "Se registro correctamente...!!!", entidad);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// ULTIMA MILLA (estado 23): con Notificacion = "cierre" (el filtro esta arriba) y el proceso configurado como SOLICITUD DEV,
|
||||||
|
// crea la Solicitud de Devolucion con lo no entregado; no genera NC ni transferencias
|
||||||
if(entidad.Proceso.ToUpper() == "ULTIMA MILLA" && entidad.IdEstadoCubico == 23)
|
if(entidad.Proceso.ToUpper() == "ULTIMA MILLA" && entidad.IdEstadoCubico == 23)
|
||||||
{
|
{
|
||||||
if(Proceso.U_ITCA_GNRPROC == "SOLICITUD DEV" && !Reproceso)
|
if(Proceso.U_ITCA_GNRPROC == "SOLICITUD DEV" && !Reproceso)
|
||||||
@@ -769,6 +589,7 @@ namespace Net.Data.WMS.Picking
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// POST /api/Picking/ruta: registra el estado de la hoja de ruta (inicio/fin de ruta) en HANA; no crea documentos en SAP
|
||||||
public async Task<BE_ResultadoTransaccion<object>> CreateHojaRuta(BE_HojaRutaEntities entidad)
|
public async Task<BE_ResultadoTransaccion<object>> CreateHojaRuta(BE_HojaRutaEntities entidad)
|
||||||
{
|
{
|
||||||
_metodoName = regex.Match(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name).Groups[1].Value.ToString();
|
_metodoName = regex.Match(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name).Groups[1].Value.ToString();
|
||||||
@@ -801,11 +622,6 @@ namespace Net.Data.WMS.Picking
|
|||||||
}
|
}
|
||||||
|
|
||||||
HelperProcesoPiciking.Set(entidad.Proceso, entidad.IdEstadoCubico, entidad.DocEntry, HelperProcesoPermitido.ConvertTimeToInteger(entidad.HoraInicio), HelperProcesoPermitido.ConvertTimeToInteger(entidad.FinRuta), entidad.CodMotivo, ListaProceso.FirstOrDefault().Code);
|
HelperProcesoPiciking.Set(entidad.Proceso, entidad.IdEstadoCubico, entidad.DocEntry, HelperProcesoPermitido.ConvertTimeToInteger(entidad.HoraInicio), HelperProcesoPermitido.ConvertTimeToInteger(entidad.FinRuta), entidad.CodMotivo, ListaProceso.FirstOrDefault().Code);
|
||||||
if (entidad.Proceso == "Picking")
|
|
||||||
{
|
|
||||||
//=== IDENTIFICA DESCRIPCION DE ESTADO
|
|
||||||
}
|
|
||||||
|
|
||||||
return resultadoTransaccion.GetInformacionResultado(entidad.DocEntry, entidad.DocEntry.ToString(), "Registrado Correctamente", entidad);
|
return resultadoTransaccion.GetInformacionResultado(entidad.DocEntry, entidad.DocEntry.ToString(), "Registrado Correctamente", entidad);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -815,6 +631,7 @@ namespace Net.Data.WMS.Picking
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// POST /api/Picking/HojaRuta/{docEntry}/Cerrar: cierra el UDO ARD_PHR en SAP (se llama una sola vez al terminar la ruta)
|
||||||
public async Task<BE_ResultadoTransaccion<object>> CerrarHojaRuta(int docEntry)
|
public async Task<BE_ResultadoTransaccion<object>> CerrarHojaRuta(int docEntry)
|
||||||
{
|
{
|
||||||
_metodoName = "CerrarHojaRuta";
|
_metodoName = "CerrarHojaRuta";
|
||||||
|
|||||||
@@ -345,6 +345,8 @@ namespace Net.Data.WMS.Recibo
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
// DEVOLUCION: NC (oCreditNotes) con base en la Solicitud de Devolucion (BaseType 234000031).
|
||||||
|
// Si las lineas de la solicitud vienen de una factura se usan los datos y lotes de la factura; si no, los de la solicitud y el stock de HANA
|
||||||
#region PROCESO DE NOTA DE CREDITO
|
#region PROCESO DE NOTA DE CREDITO
|
||||||
|
|
||||||
if (entidad.IdTipo == EnumRecibo.DEVOLUCION)
|
if (entidad.IdTipo == EnumRecibo.DEVOLUCION)
|
||||||
@@ -379,7 +381,6 @@ namespace Net.Data.WMS.Recibo
|
|||||||
_Invoice = _Invoice_Result.ListData.FirstOrDefault();
|
_Invoice = _Invoice_Result.ListData.FirstOrDefault();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
string SerieOrigen = "";
|
|
||||||
string U_SYP_MDTO = "";
|
string U_SYP_MDTO = "";
|
||||||
string U_SYP_MDCO = "";
|
string U_SYP_MDCO = "";
|
||||||
string U_SYP_MDSO = "";
|
string U_SYP_MDSO = "";
|
||||||
@@ -387,7 +388,6 @@ namespace Net.Data.WMS.Recibo
|
|||||||
|
|
||||||
if (_Invoice == null)
|
if (_Invoice == null)
|
||||||
{
|
{
|
||||||
SerieOrigen = returnRequest.U_SYP_MDSO;
|
|
||||||
U_SYP_MDTO = returnRequest.U_SYP_MDTO;
|
U_SYP_MDTO = returnRequest.U_SYP_MDTO;
|
||||||
U_SYP_MDCO = returnRequest.U_SYP_MDCO;
|
U_SYP_MDCO = returnRequest.U_SYP_MDCO;
|
||||||
U_SYP_MDSO = returnRequest.U_SYP_MDSO;
|
U_SYP_MDSO = returnRequest.U_SYP_MDSO;
|
||||||
@@ -395,7 +395,6 @@ namespace Net.Data.WMS.Recibo
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
SerieOrigen = _Invoice.U_SYP_MDSD;
|
|
||||||
U_SYP_MDTO = _Invoice.U_SYP_MDTD;
|
U_SYP_MDTO = _Invoice.U_SYP_MDTD;
|
||||||
U_SYP_MDCO = _Invoice.U_SYP_MDCD;
|
U_SYP_MDCO = _Invoice.U_SYP_MDCD;
|
||||||
U_SYP_MDSO = _Invoice.U_SYP_MDSD;
|
U_SYP_MDSO = _Invoice.U_SYP_MDSD;
|
||||||
@@ -404,6 +403,7 @@ namespace Net.Data.WMS.Recibo
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
// Serie segun el TIPO de documento origen (03 = boleta), no segun su serie
|
// Serie segun el TIPO de documento origen (03 = boleta), no segun su serie
|
||||||
|
// El correlativo sale de @SYP_NUMDOC (HelperCorrelativo)
|
||||||
string Serie = U_SYP_MDTO == "03" ? "BC10" : "FC10";
|
string Serie = U_SYP_MDTO == "03" ? "BC10" : "FC10";
|
||||||
string Correlativo = HelperCorrelativo.Get(Serie);
|
string Correlativo = HelperCorrelativo.Get(Serie);
|
||||||
creditNotesNotes.DocCurrency = returnRequest.DocCurrency;
|
creditNotesNotes.DocCurrency = returnRequest.DocCurrency;
|
||||||
@@ -452,6 +452,8 @@ namespace Net.Data.WMS.Recibo
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Lotes de cada linea: sin factura relacionada, FIFO por vencimiento del stock de HANA; con factura, los lotes de la factura.
|
||||||
|
// En ambos casos se parte de la lista vacia para que los lotes sumen exactamente la cantidad de la linea
|
||||||
if (_Invoice == null)
|
if (_Invoice == null)
|
||||||
{
|
{
|
||||||
//=== REALIZA DISTRIBUCION DE STOCK POR LOTE
|
//=== REALIZA DISTRIBUCION DE STOCK POR LOTE
|
||||||
@@ -535,100 +537,9 @@ namespace Net.Data.WMS.Recibo
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
List<string> ListaUnicaAlmacen = creditNotesNotes.DocumentLines.Select(x => x.WarehouseCode).Distinct().ToList();
|
// Transferencias de stock automaticas por almacen destino (U_ITC_ALMDES). OJO: en Recibo las lineas de la NC no llenan
|
||||||
List<int> ListDocEntry = new List<int>();
|
// U_ITC_ALMDES, por lo que hoy no se genera ninguna
|
||||||
ListaUnicaAlmacen.ForEach(Almacen =>
|
TransferenciaAutomaticaNC.Crear(creditNotesNotes, (int)result_creditNotesRepository.Data.DocEntry, _clientFactory, _configuration, _creditNotesRepository);
|
||||||
{
|
|
||||||
List<string> ListaUnicaAlmDestino = creditNotesNotes.DocumentLines.Where(Linea => Linea.WarehouseCode == Almacen).ToList().Select(x => x.U_ITC_ALMDES).Distinct().ToList().Where(x => !string.IsNullOrEmpty(x)).ToList();
|
|
||||||
ListaUnicaAlmDestino.ForEach(Destino =>
|
|
||||||
{
|
|
||||||
if (Almacen != Destino)
|
|
||||||
{
|
|
||||||
BE_StockTransferAutomatico Transfer = new BE_StockTransferAutomatico();
|
|
||||||
Transfer.FromWarehouse = Almacen;
|
|
||||||
Transfer.ToWarehouse = Destino;
|
|
||||||
Transfer.DocDate = creditNotesNotes.DocDate;
|
|
||||||
Transfer.TaxDate = creditNotesNotes.DocDate;
|
|
||||||
Transfer.DueDate = creditNotesNotes.DocDueDate;
|
|
||||||
Transfer.U_SYP_MDTS = "TSI";
|
|
||||||
Transfer.U_SYP_MDMT = "24";
|
|
||||||
Transfer.PriceList = -2;
|
|
||||||
var ListaDetalle_PorAlmacen = creditNotesNotes.DocumentLines.Where(x => x.WarehouseCode == Almacen && x.U_ITC_ALMDES == Destino).ToList();
|
|
||||||
var ListaUnicaArticulo = ListaDetalle_PorAlmacen.Select(x => x.ItemCode).Distinct().ToList();
|
|
||||||
|
|
||||||
Transfer.StockTransferLines = new List<BE_StockTransferLine>();
|
|
||||||
Transfer.DocumentReferences = new List<BE_StockTransferDocumentReferences>();
|
|
||||||
|
|
||||||
ListaUnicaArticulo.ForEach(x =>
|
|
||||||
{
|
|
||||||
var ListLote = new List<BE_StockTransferLineBatchNumber>();
|
|
||||||
// Lotes de todas las lineas del articulo (si el mismo lote esta en varias lineas se suma)
|
|
||||||
ListaDetalle_PorAlmacen.Where(t => t.ItemCode == x).SelectMany(t => t.BatchNumbers).GroupBy(r => r.BatchNumber).ToList().ForEach(g =>
|
|
||||||
{
|
|
||||||
ListLote.Add(new BE_StockTransferLineBatchNumber()
|
|
||||||
{
|
|
||||||
BatchNumber = g.Key,
|
|
||||||
Quantity = g.Sum(r => r.Quantity)
|
|
||||||
});
|
|
||||||
});
|
|
||||||
Transfer.StockTransferLines.Add(new BE_StockTransferLine()
|
|
||||||
{
|
|
||||||
ItemCode = x,
|
|
||||||
ItemDescription = ListaDetalle_PorAlmacen.Where(t => t.ItemCode == x).FirstOrDefault().ItemDescription,
|
|
||||||
Quantity = Convert.ToDouble(ListaDetalle_PorAlmacen.Where(t => t.ItemCode == x).Sum(t => t.Quantity)),
|
|
||||||
WarehouseCode = Destino,
|
|
||||||
FromWarehouseCode = Almacen,
|
|
||||||
BatchNumbers = ListLote
|
|
||||||
});
|
|
||||||
|
|
||||||
//=== AGREGA DOCUMENTO REFERENCIA
|
|
||||||
Transfer.DocumentReferences = new List<BE_StockTransferDocumentReferences>
|
|
||||||
{
|
|
||||||
new BE_StockTransferDocumentReferences()
|
|
||||||
{
|
|
||||||
RefDocEntr = (int)result_creditNotesRepository.Data.DocEntry,
|
|
||||||
RefObjType = "rot_SalesCreditNote",
|
|
||||||
IssueDate = DateTime.Now,
|
|
||||||
Remark = "Nota de Credito",
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
//=== CREA INSTANCIA PARA CONECTAR A SAP
|
|
||||||
StockTransferRepository stockTransferRepository = new StockTransferRepository(_clientFactory, _configuration);
|
|
||||||
var ResultTransfer = stockTransferRepository.StockTransfer(Transfer).Result;
|
|
||||||
if (ResultTransfer.StatusCode == -1)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ListDocEntry.Add((int)ResultTransfer.Data.DocEntry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
if (ListDocEntry.Count > 0)
|
|
||||||
{
|
|
||||||
var listaReferencia = new List<BE_CreditNotesDocumentReferences>();
|
|
||||||
ListDocEntry.ForEach(x =>
|
|
||||||
{
|
|
||||||
listaReferencia.Add(new BE_CreditNotesDocumentReferences()
|
|
||||||
{
|
|
||||||
RefDocEntr = x,
|
|
||||||
IssueDate = DateTime.Now,
|
|
||||||
RefObjType = "rot_InventoryTransfer",
|
|
||||||
Remark = "Transferencias Automatica"
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
var credit = new
|
|
||||||
{
|
|
||||||
DocumentReferences = listaReferencia
|
|
||||||
};
|
|
||||||
var Actualizacion = _creditNotesRepository.UpdateCreateCreditNotes(credit, (int)result_creditNotesRepository.Data.DocEntry).Result;
|
|
||||||
}
|
|
||||||
return resultadoTransaccion.GetInformacionResultado((int)result_creditNotesRepository.Data.DocEntry, result_creditNotesRepository.Data.DocNum.ToString(), "Se registro correctamente...!!!", entidad);
|
return resultadoTransaccion.GetInformacionResultado((int)result_creditNotesRepository.Data.DocEntry, result_creditNotesRepository.Data.DocNum.ToString(), "Se registro correctamente...!!!", entidad);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
using Net.Business.Entities.WMS.Transferencia;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
// Devuelve el DocEntry de la entrada elegida, o 0 con el motivo en "error".
|
||||||
|
public static int Elegir(string docNumFactura, List<BE_EntradaImportacionLinea> filas, List<BE_TransferenciaDetalle> detalle, out string error)
|
||||||
|
{
|
||||||
|
error = null;
|
||||||
|
var pedido = (detalle ?? new List<BE_TransferenciaDetalle>())
|
||||||
|
.GroupBy(x => x.CodigoArticulo)
|
||||||
|
.ToDictionary(g => g.Key, g => g.Sum(x => x.CantidadPedida));
|
||||||
|
|
||||||
|
var entradas = filas.GroupBy(x => x.DocEntry).OrderBy(g => g.Key).ToList();
|
||||||
|
var coincidentes = entradas.Where(g => Coincide(g, pedido)).ToList();
|
||||||
|
|
||||||
|
// La mas antigua que aun no tenga transferencia
|
||||||
|
var libre = coincidentes.FirstOrDefault(g => !g.Any(l => l.TransferenciaDocEntry > 0));
|
||||||
|
if (libre != null)
|
||||||
|
{
|
||||||
|
return libre.Key;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (coincidentes.Count > 0)
|
||||||
|
{
|
||||||
|
var detalleYa = coincidentes.Select(g => $"entrada {g.First().DocNum} (DocEntry {g.Key}) ya transferida en la transferencia {g.First(l => l.TransferenciaDocEntry > 0).TransferenciaDocEntry}");
|
||||||
|
error = $"SAP: Todas las entradas de la factura de reserva {docNumFactura} con esos articulos y cantidades ya estan transferidas: {string.Join("; ", detalleYa)}";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var pendientes = entradas.Where(g => !g.Any(l => l.TransferenciaDocEntry > 0)).Select(g => $"{g.First().DocNum}");
|
||||||
|
error = $"SAP: Ninguna entrada de la factura de reserva {docNumFactura} coincide con los articulos y cantidades enviados. " +
|
||||||
|
$"Entradas pendientes de transferir (DocNum): {(pendientes.Any() ? string.Join(", ", pendientes) : "ninguna")}";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool Coincide(IGrouping<int, BE_EntradaImportacionLinea> entrada, Dictionary<string, decimal> pedido)
|
||||||
|
{
|
||||||
|
var enEntrada = entrada.GroupBy(x => x.ItemCode).ToDictionary(g => g.Key, g => g.Sum(x => x.Quantity));
|
||||||
|
if (enEntrada.Count != pedido.Count) return false;
|
||||||
|
return enEntrada.All(kv => pedido.TryGetValue(kv.Key, out decimal cant) && Math.Abs(cant - kv.Value) <= Tolerancia);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ using Net.Business.Entities;
|
|||||||
using Net.Business.Entities.ServicesLayer;
|
using Net.Business.Entities.ServicesLayer;
|
||||||
using Net.Business.Entities.ServicesLayer.Invoices;
|
using Net.Business.Entities.ServicesLayer.Invoices;
|
||||||
using Net.Business.Entities.ServicesLayer.Items;
|
using Net.Business.Entities.ServicesLayer.Items;
|
||||||
|
using Net.Business.Entities.ServicesLayer.PurchaseDeliveryNotes;
|
||||||
using Net.Business.Entities.ServicesLayer.StockTransfer;
|
using Net.Business.Entities.ServicesLayer.StockTransfer;
|
||||||
using Net.Business.Entities.WMS.Transferencia;
|
using Net.Business.Entities.WMS.Transferencia;
|
||||||
using Net.Connection;
|
using Net.Connection;
|
||||||
@@ -110,36 +111,86 @@ namespace Net.Data.WMS.Transferencia
|
|||||||
#endregion
|
#endregion
|
||||||
});
|
});
|
||||||
|
|
||||||
//=== SI EL ALMACEN ES ALM99, FUERZA EL AMARRE CON LA ENTRADA DE MERCANCIA
|
//=== 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")
|
if (entidad.CodigoSubAlmacenOrigen == "ALM99")
|
||||||
{
|
{
|
||||||
StockTransfer.U_SYP_MDMT = "21";
|
StockTransfer.U_SYP_MDMT = "21";
|
||||||
int DocEntry_Entada = entidad.DocEntryEM; //HelperInvoice.Get(entidad.NumOrden);
|
PurchaseDeliveryNotesRepository _purchaseDeliveryNotesRepository = new PurchaseDeliveryNotesRepository(_clientFactory, _configuration);
|
||||||
if(DocEntry_Entada > 0)
|
|
||||||
|
if (entidad.DocEntryEM > 0)
|
||||||
{
|
{
|
||||||
PurchaseDeliveryNotesRepository _purchaseDeliveryNotesRepository = new PurchaseDeliveryNotesRepository(_clientFactory, _configuration);
|
// WMS indica la entrada: se toman sus lotes y se amarra linea por linea (BaseLine del JSON == BaseLine de la entrada)
|
||||||
var _purchaseDeliveryNotesRepository_Response = _purchaseDeliveryNotesRepository.GetPurchaseDeliveryNotes(DocEntry_Entada).Result.ListData.FirstOrDefault();
|
var pdn = _purchaseDeliveryNotesRepository.GetPurchaseDeliveryNotes(entidad.DocEntryEM).Result.ListData?.FirstOrDefault();
|
||||||
|
if (pdn == null)
|
||||||
//=== RESETEA STOCK POR LOTES
|
|
||||||
StockTransfer.StockTransferLines.ForEach(line =>
|
|
||||||
{
|
{
|
||||||
var Lines = _purchaseDeliveryNotesRepository_Response.DocumentLines.FirstOrDefault(line_purchaseDeliveryNotesRepository_Response => line_purchaseDeliveryNotesRepository_Response.BaseLine == line.LineNum);
|
return resultadoTransaccion.GetInformacionResultado($"SAP: La entrada de mercaderia {entidad.DocEntryEM} no se encuentra", entidad);
|
||||||
line.BatchNumbers = new List<BE_StockTransferLineBatchNumber>();
|
}
|
||||||
|
|
||||||
//=== AGREGA LOTES
|
foreach (var line in StockTransfer.StockTransferLines)
|
||||||
Lines.BatchNumbers.ForEach(lotes =>
|
{
|
||||||
|
var Lines = pdn.DocumentLines.FirstOrDefault(l => l.BaseLine == line.LineNum);
|
||||||
|
if (Lines == null)
|
||||||
{
|
{
|
||||||
line.BatchNumbers.Add(new BE_StockTransferLineBatchNumber()
|
return resultadoTransaccion.GetInformacionResultado($"SAP: La linea {line.LineNum} no existe en la entrada de mercaderia {entidad.DocEntryEM}", entidad);
|
||||||
{
|
}
|
||||||
BatchNumber = lotes.BatchNumber,
|
line.BatchNumbers = (Lines.BatchNumbers ?? new List<BE_PurchaseDeliveryNotesBatchNumbers>())
|
||||||
Quantity = lotes.Quantity,
|
.Select(lotes => new BE_StockTransferLineBatchNumber() { BatchNumber = lotes.BatchNumber, Quantity = lotes.Quantity }).ToList();
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
line.BaseLine = Lines.LineNum;
|
line.BaseLine = Lines.LineNum;
|
||||||
line.BaseType = "PurchaseDeliveryNotes";
|
line.BaseType = "PurchaseDeliveryNotes";
|
||||||
line.BaseEntry = DocEntry_Entada;
|
line.BaseEntry = entidad.DocEntryEM;
|
||||||
});
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// WMS envia en NumOrden el DocNum de la Factura de Reserva y en el detalle los articulos/cantidades de UNA de sus
|
||||||
|
// entradas: se busca la entrada con ese mismo juego que aun no tenga transferencia y se transfiere esa.
|
||||||
|
var filas = HelperInvoice.GetEntradasPorFactura(entidad.NumOrden);
|
||||||
|
if (filas.Count > 0)
|
||||||
|
{
|
||||||
|
int docEntryEntrada = EntradaImportacion.Elegir(entidad.NumOrden, filas, entidad.TransferenciaDetalles, out string errorEntrada);
|
||||||
|
if (docEntryEntrada == 0)
|
||||||
|
{
|
||||||
|
return resultadoTransaccion.GetInformacionResultado(errorEntrada, entidad);
|
||||||
|
}
|
||||||
|
|
||||||
|
var pdn = _purchaseDeliveryNotesRepository.GetPurchaseDeliveryNotes(docEntryEntrada).Result.ListData?.FirstOrDefault();
|
||||||
|
if (pdn == null)
|
||||||
|
{
|
||||||
|
return resultadoTransaccion.GetInformacionResultado($"SAP: La entrada de mercaderia {docEntryEntrada} no se puede leer", entidad);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Las lineas de la transferencia salen de la entrada (articulo, cantidad y lotes reales de cada linea)
|
||||||
|
StockTransfer.StockTransferLines = new List<BE_StockTransferLine>();
|
||||||
|
foreach (var l in pdn.DocumentLines)
|
||||||
|
{
|
||||||
|
var lotes = (l.BatchNumbers ?? new List<BE_PurchaseDeliveryNotesBatchNumbers>())
|
||||||
|
.Select(b => new BE_StockTransferLineBatchNumber() { BatchNumber = b.BatchNumber, Quantity = b.Quantity }).ToList();
|
||||||
|
if (lotes.Count > 0 && Math.Abs(lotes.Sum(b => b.Quantity) - l.Quantity) > 0.0001)
|
||||||
|
{
|
||||||
|
return resultadoTransaccion.GetInformacionResultado($"SAP: Los lotes de la linea {l.LineNum} ({l.ItemCode}) de la entrada {docEntryEntrada} no cubren la cantidad {l.Quantity}", entidad);
|
||||||
|
}
|
||||||
|
StockTransfer.StockTransferLines.Add(new BE_StockTransferLine
|
||||||
|
{
|
||||||
|
FromWarehouseCode = entidad.CodigoSubAlmacenOrigen,
|
||||||
|
WarehouseCode = entidad.CodigoSubAlmacenDestino,
|
||||||
|
ItemCode = l.ItemCode,
|
||||||
|
Quantity = l.Quantity,
|
||||||
|
LineNum = l.LineNum,
|
||||||
|
BaseType = "PurchaseDeliveryNotes",
|
||||||
|
BaseEntry = docEntryEntrada,
|
||||||
|
BaseLine = l.LineNum,
|
||||||
|
BatchNumbers = lotes
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Si la factura no tiene entradas (o NumOrden no es una factura de reserva) se mantiene lo de antes:
|
||||||
|
// transferencia con los lotes del stock de HANA, sin amarre.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,10 @@ API de integración WMS ↔ SAP Business One (Service Layer + HANA directo),
|
|||||||
(ruta hardcodeada vía `HintPath` en los `.csproj` de `Net.Data` y
|
(ruta hardcodeada vía `HintPath` en los `.csproj` de `Net.Data` y
|
||||||
`Net.Business.Services` — sin esto no compila ningún proyecto que dependa
|
`Net.Business.Services` — sin esto no compila ningún proyecto que dependa
|
||||||
de HANA).
|
de HANA).
|
||||||
- **Compilar desde macOS/Linux**: el `HintPath` es relativo (6 niveles hacia
|
- **Compilar desde macOS/Linux** (forma simple): pasar la ruta de la DLL con
|
||||||
|
`-p:HanaDll=<ruta>/Sap.Data.Hana.Core.v2.1.dll` a `dotnet build`/`publish`
|
||||||
|
(los `.csproj` usan esa propiedad si está definida). Forma alternativa: el
|
||||||
|
`HintPath` es relativo (6 niveles hacia
|
||||||
arriba + `Program Files\sap\...`). Se puede compilar copiando el repo a
|
arriba + `Program Files\sap\...`). Se puede compilar copiando el repo a
|
||||||
`<raiz>/a/b/c/d/repo/` y poniendo la DLL en
|
`<raiz>/a/b/c/d/repo/` y poniendo la DLL en
|
||||||
`<raiz>/Program Files/sap/hdbclient/dotnetcore/v2.1/` — así el `HintPath`
|
`<raiz>/Program Files/sap/hdbclient/dotnetcore/v2.1/` — así el `HintPath`
|
||||||
@@ -70,6 +73,58 @@ dotnet publish Net.Business.Services/Net.Business.Services.csproj -c Release -r
|
|||||||
`web.config` del código fuente (ese apunta a `bin\Debug\...`, es el de
|
`web.config` del código fuente (ese apunta a `bin\Debug\...`, es el de
|
||||||
desarrollo) a una carpeta de publish.
|
desarrollo) a una carpeta de publish.
|
||||||
|
|
||||||
|
## Versión y script de publish
|
||||||
|
|
||||||
|
- La versión sale de `<Version>`/`<AssemblyVersion>`/`<FileVersion>` en
|
||||||
|
`Net.Business.Services/Net.Business.Services.csproj` (actual: **2.1.9.0** en la rama de limpieza; `main` = 2.1.7.0);
|
||||||
|
súbela en cada publish.
|
||||||
|
- `publicar.ps1` (solo Windows) genera `dist\ITCA_API_SAP_WMS_v<versión>.zip` sin
|
||||||
|
`appsettings.json`. Desde macOS se hace el mismo `dotnet publish` a mano con
|
||||||
|
`-p:HanaDll=...`, se borran `appsettings.json`/`appsettings.Development.json` de
|
||||||
|
la salida y se comprime.
|
||||||
|
- Control de versiones: rama `main` y etiquetas `vX.Y.Z.W` por versión
|
||||||
|
publicada (ver `CLAUDE.md`).
|
||||||
|
|
||||||
|
## Volver a la versión 2.1.7.0 (punto de retorno antes de la limpieza)
|
||||||
|
|
||||||
|
La etiqueta `v2.1.7.0` (commit `d0118d1`) es el último estado antes de la
|
||||||
|
limpieza de código (rama `limpieza-2.1.8.0`, versión 2.1.8.0). Está en el
|
||||||
|
remoto `origin` y en el repositorio local.
|
||||||
|
|
||||||
|
```
|
||||||
|
git fetch --tags # si la etiqueta no está en tu copia local
|
||||||
|
git tag -l "v2.1.*" # ver las etiquetas disponibles
|
||||||
|
git diff v2.1.7.0 --stat # qué cambió respecto a 2.1.7.0
|
||||||
|
git checkout v2.1.7.0 # ver/compilar exactamente esa versión (solo lectura)
|
||||||
|
git switch -c recuperar-2.1.7.0 v2.1.7.0 # trabajar desde ahí en una rama nueva
|
||||||
|
```
|
||||||
|
|
||||||
|
- Para **volver a publicar 2.1.7.0**: `git checkout v2.1.7.0` y generar el
|
||||||
|
publish como en "Versión y script de publish" (`AssemblyVersion` ya vale
|
||||||
|
2.1.7.0 en ese commit). Luego `git switch main` para regresar.
|
||||||
|
- Si la limpieza se une a `main` y hay que **deshacerla**, lo más seguro es
|
||||||
|
`git revert` del merge (no reescribe el historial). Evitar `git reset --hard`
|
||||||
|
y `git push --force` sobre `main`.
|
||||||
|
- La rama `main` no se toca durante la limpieza; el trabajo va en
|
||||||
|
`limpieza-2.1.8.0`.
|
||||||
|
|
||||||
|
## Logs de integración
|
||||||
|
|
||||||
|
- **`B_Log`** (controllers): en cada llamada guarda en
|
||||||
|
`ITC_WMS_SAP.ITCA_LOG_INTEGRACION` el request del WMS (`TRAMA_ORIGEN`) y la
|
||||||
|
respuesta de la API (`TRAMA_RESPUESTA`), con `PROCESO` (Picking, Recibo...),
|
||||||
|
`NRO_ORDEN`, `DOCENTRY`, `DOCNUMSAP` y `DOCENTRYSAP`.
|
||||||
|
- **Log de errores del Service Layer** (desde 2.1.5.0): cuando SAP rechaza un
|
||||||
|
envío con cuerpo JSON (`PostAsync`, `PatchAsyncSBA`, `PatchNoContentAsync`)
|
||||||
|
se guarda en esa misma tabla el JSON enviado y el mensaje de SAP, con
|
||||||
|
`PROCESO = 'ServiceLayer Error'`, `NRO_ORDEN` = ruta del recurso y `DOCENTRY`
|
||||||
|
= `BaseEntry` del documento base. Solo en error; no cambia la respuesta de la
|
||||||
|
API.
|
||||||
|
- Ambos usan el esquema fijo `ITC_WMS_SAP` (columnas `PROCESO`, `NRO_ORDEN` y
|
||||||
|
`DOCENTRY` son `VARCHAR(100)`).
|
||||||
|
- Consulta de ejemplo:
|
||||||
|
`SELECT * FROM "ITC_WMS_SAP"."ITCA_LOG_INTEGRACION" WHERE "PROCESO" = 'ServiceLayer Error' ORDER BY "FECHA" DESC, "HORA" DESC`
|
||||||
|
|
||||||
## Desplegar/actualizar un sitio en IIS
|
## Desplegar/actualizar un sitio en IIS
|
||||||
|
|
||||||
### Si el sitio YA corre desde una carpeta de publish (actualización de versión)
|
### Si el sitio YA corre desde una carpeta de publish (actualización de versión)
|
||||||
@@ -137,13 +192,13 @@ para armar el body del `Login` contra el Service Layer.
|
|||||||
## Riesgos de seguridad heredados (documentados, no corregidos sin coordinar antes)
|
## Riesgos de seguridad heredados (documentados, no corregidos sin coordinar antes)
|
||||||
|
|
||||||
- `appsettings.json` tiene credenciales reales en texto plano (SAP Service
|
- `appsettings.json` tiene credenciales reales en texto plano (SAP Service
|
||||||
Layer, **HANA `B1ADMIN`**, semilla JWT) — no hay `git` en esta carpeta; si alguna vez se versiona o se sube
|
Layer, **HANA `B1ADMIN`**, semilla JWT). Está en `.gitignore` y no se
|
||||||
a un remoto compartido, hay que decidir qué hacer con ese archivo primero.
|
versiona (solo `appsettings.example.json`); no subirlo a ningún repositorio.
|
||||||
- `B1ADMIN` es un usuario administrador de SAP B1; la API solo necesita
|
- `B1ADMIN` es un usuario administrador de SAP B1; la API solo necesita
|
||||||
ejecutar los `ITCA_*` y leer algunas tablas. Conviene un usuario HANA de
|
ejecutar los `ITCA_*` y leer algunas tablas. Conviene un usuario HANA de
|
||||||
permisos mínimos y rotar la contraseña (estuvo en texto plano en el código
|
permisos mínimos y rotar la contraseña (estuvo en texto plano en el código
|
||||||
y en el historial git del repo antiguo archivado).
|
y en el historial git del repo antiguo archivado).
|
||||||
- `ValidarCertificado` en `ConnectionServiceLayer.cs` acepta cualquier
|
- El cliente HTTP `bypass-ssl-validation` (usado por `ConnectionServiceLayer`)
|
||||||
certificado TLS (`return true;` siempre) — bypass de validación de
|
acepta cualquier certificado TLS — bypass de validación de certificado hacia
|
||||||
certificado hacia el Service Layer, igual criterio que
|
el Service Layer, igual criterio que `SERVICE_LAYER_VERIFY_SSL=false` en la
|
||||||
`SERVICE_LAYER_VERIFY_SSL=false` en la reescritura Python.
|
reescritura Python. (Se quitó el método `ValidarCertificado`, que no se usaba.)
|
||||||
@@ -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 = <numorden>`: 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 <ESQUEMA> 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 "<ESQUEMA>"."OPCH" F
|
||||||
|
INNER JOIN "<ESQUEMA>"."PDN1" L ON L."BaseType" = 18 AND L."BaseEntry" = F."DocEntry"
|
||||||
|
INNER JOIN "<ESQUEMA>"."OPDN" E ON E."DocEntry" = L."DocEntry"
|
||||||
|
LEFT JOIN "<ESQUEMA>"."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 `[<BASE>].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.
|
||||||
Reference in new issue
Block a user