Compare commits
13
Commits
31b0929342
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
44efabb44c | ||
|
|
3c104cca45 | ||
|
|
215aff2c64 | ||
|
|
18e01c2774 | ||
|
|
d7676dee41 | ||
|
|
e664799de7 | ||
|
|
a65cd4d90a | ||
|
|
2fb7509fc2 | ||
|
|
20f3a59d35 | ||
|
|
48167d9138 | ||
|
|
3111bb3842 | ||
|
|
d53d5d07c5 | ||
|
|
baaf85554a |
No files matched your search
@@ -10,12 +10,18 @@ comprimido (versión antigua)**.
|
|||||||
|
|
||||||
## Versión y control de versiones
|
## Versión y control de versiones
|
||||||
|
|
||||||
- **Versión actual: 2.1.8.0** (rama `limpieza-2.1.8.0`; `main` = 2.1.7.0) (`Net.Business.Services.csproj`: `Version`,
|
- **Versión actual: 2.1.15.0** (rama única `main`; la rama `limpieza-2.1.8.0` se unió a `main` el 2026-10-20 por avance directo) (`Net.Business.Services.csproj`: `Version`,
|
||||||
`AssemblyVersion`, `FileVersion`). Cada versión tiene su commit; al publicar
|
`AssemblyVersion`, `FileVersion`). Cada versión tiene su commit; al publicar
|
||||||
se sube la versión (ver `publicar.ps1`).
|
se sube la versión (ver `publicar.ps1`, única forma de generar el zip; subir las TRES propiedades, el script falla si no coinciden).
|
||||||
|
- **Semver** (desde la 2.1.15): `MAJOR.MINOR.PATCH`; el cuarto número del `csproj` (.NET lo pide) va siempre en `0`.
|
||||||
|
`MAJOR` = cambia el contrato de la API y el WMS debe adaptarse (campo obligatorio nuevo, ruta distinta);
|
||||||
|
`MINOR` = funcionalidad nueva compatible (endpoint o campo opcional nuevo, aviso en `Message`);
|
||||||
|
`PATCH` = corrección o limpieza sin cambio de contrato. Etiquetas de git nuevas sin el cuarto número (`v2.1.15`;
|
||||||
|
las anteriores, como `v2.1.7.0`, no se tocan). Una versión de prueba para QA puede ir en `Version` como
|
||||||
|
`2.2.0-rc.1`, pero `AssemblyVersion`/`FileVersion` solo aceptan dígitos (`2.2.0.0`).
|
||||||
- **Git**: rama `main`, remoto `origin` =
|
- **Git**: rama `main`, remoto `origin` =
|
||||||
`http://100.68.242.27:30000/george/itca_api_sap_wms.git` (HTTP sin cifrar,
|
`https://unasgeorge.vaquita-hadar.ts.net/george/itca_api_sap_wms.git` (cambiado desde
|
||||||
red interna; confirmar que el repositorio sea **privado**). Etiqueta
|
`http://100.68.242.27:30000/...`; confirmar que el repositorio sea **privado**). Etiqueta
|
||||||
`v2.1.7.0` = último estado antes de la limpieza de código.
|
`v2.1.7.0` = último estado antes de la limpieza de código.
|
||||||
- `appsettings.json` y `appsettings.*.json` están en `.gitignore` (solo se
|
- `appsettings.json` y `appsettings.*.json` están en `.gitignore` (solo se
|
||||||
versiona `appsettings.example.json`); las credenciales no están en el
|
versiona `appsettings.example.json`); las credenciales no están en el
|
||||||
@@ -59,7 +65,7 @@ transferencias siempre, sin depender de `Notificacion`. Detalle y tabla en
|
|||||||
- **v2.1.5.0 — log del JSON enviado al Service Layer cuando SAP lo rechaza**
|
- **v2.1.5.0 — log del JSON enviado al Service Layer cuando SAP lo rechaza**
|
||||||
(cualquier documento, no solo la NC): `ConnectionServiceLayer` llama a
|
(cualquier documento, no solo la NC): `ConnectionServiceLayer` llama a
|
||||||
`ErrorLogger` (asignado en `Startup` a `Net.Data/HelperData/Log/HelperLogServiceLayer.AddError`)
|
`ErrorLogger` (asignado en `Startup` a `Net.Data/HelperData/Log/HelperLogServiceLayer.AddError`)
|
||||||
en los fallos de `PostAsync`, `PatchAsyncSBA` y `PatchNoContentAsync`. Guarda en
|
en los fallos de `PostAsync` y `PatchNoContentAsync` (hasta la 2.1.13.0 también `PatchAsyncSBA`). Guarda en
|
||||||
`ITC_WMS_SAP.ITCA_LOG_INTEGRACION`: `ORIGEN = "ServiceLayer"`,
|
`ITC_WMS_SAP.ITCA_LOG_INTEGRACION`: `ORIGEN = "ServiceLayer"`,
|
||||||
`PROCESO = "ServiceLayer Error"`, `NRO_ORDEN` = ruta del recurso (p. ej.
|
`PROCESO = "ServiceLayer Error"`, `NRO_ORDEN` = ruta del recurso (p. ej.
|
||||||
`CreditNotes`), `DOCENTRY` = `BaseEntry` de la primera línea del JSON (factura o
|
`CreditNotes`), `DOCENTRY` = `BaseEntry` de la primera línea del JSON (factura o
|
||||||
@@ -106,6 +112,58 @@ transferencias siempre, sin depender de `Notificacion`. Detalle y tabla en
|
|||||||
mantiene su bug del 204 (sin marcar éxito; el único llamador ignora el resultado).
|
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)
|
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`.
|
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.
|
||||||
|
- **v2.1.10.0 — solo versión** (sin cambios de comportamiento): `Version`, `AssemblyVersion` y
|
||||||
|
`FileVersion` del `csproj` quedan iguales (2.1.10.0). Swagger muestra `AssemblyVersion`
|
||||||
|
(`Startup.cs`: `OpenApiInfo.Version`); si Swagger muestra otro número el servidor corre
|
||||||
|
binarios viejos (pool sin reiniciar, carpeta equivocada o sitio desde `bin\Debug`).
|
||||||
|
- **v2.1.11.0 — tabla de log configurable y lotes de la NC de Recibo**: (1) la tabla de log sale de
|
||||||
|
`Hana:logTable` en `appsettings.json` (`LocalData.LogTable`; por defecto `ITCA_LOG_INTEGRACION` para
|
||||||
|
QA/Desarrollo, producción usa `ITCA_LOG_INTEGRACION_PROD`); el esquema sigue fijo en `ITC_WMS_SAP`.
|
||||||
|
Aplica a `B_Log` y `HelperLogServiceLayer`. (2) Recibo/Devolución: si el reparto de lotes (stock HANA o
|
||||||
|
factura) no cubre la cantidad de la línea, el resto va a un lote nuevo por fecha/hora (antes SAP
|
||||||
|
respondía "No es posible añadir una línea sin la selección completa de los números de serie/lote");
|
||||||
|
una línea sin equivalente en la factura ya no revienta con referencia nula. Sin probar contra SAP.
|
||||||
|
- **v2.1.12.0 — más limpieza** (sin cambios de comportamiento): se quitó `PurchaseOrdersRepository.SetCreatePurchaseInvoices`
|
||||||
|
(duplicado sin llamadores; queda el de `PurchaseInvoicesRepository`), el código comentado de `HRRepository`
|
||||||
|
(rama "Privado"), `Program.cs` (`CreateHostBuilder` viejo), `ConnectionServiceLayer` (líneas de header/éxito y un
|
||||||
|
`if` de `NoContent` vacío), `CreditNotesRepository`, `ReciboRepository`, `InventarioRepository`, `ConnectionSQL`, `DBParameter` y
|
||||||
|
`BE_BusinessPartners` (ya no queda código comentado). Sigue pendiente `PatchAsyncSBA` y la redundancia de serie/cabecera de la NC.
|
||||||
|
- **v2.1.13.0 — cabecera única de la NC** (sin cambios de comportamiento): la serie (`BC10` si el tipo del
|
||||||
|
documento origen es `03`, si no `FC10`), el correlativo (`HelperCorrelativo`) y los ~15 campos de la cabecera de
|
||||||
|
la NC estaban copiados en Picking DESPACHO, Picking PICKING (estado 13) y Recibo Devolución; ahora están en
|
||||||
|
`Net.Data/ServicesLayer/CreditNotes/CabeceraNotaCredito.Completar` (con comentarios de la regla de la 2.1.7.0).
|
||||||
|
Cada llamador pasa los datos de su origen (factura o, en Recibo sin factura, la solicitud de devolución).
|
||||||
|
- **v2.1.14.0 — se quitó `PatchAsyncSBA`**: su único llamador (`CreditNotesRepository.UpdateCreateCreditNotes`, que agrega
|
||||||
|
a la NC las referencias de las transferencias automáticas) usa ahora `PatchNoContentAsync`. En el Service Layer un PATCH
|
||||||
|
exitoso es un `204` sin cuerpo; `PatchAsyncSBA` devolvía `null` en ese caso (y en cualquier error de red) y el llamador
|
||||||
|
lo tomaba como fallo. Ahora el éxito es éxito y un rechazo de SAP lanza su mensaje (se captura y devuelve como error;
|
||||||
|
`Crear` sigue ignorando el resultado). Sigue sin reintentar el 401 (hace login antes de cada llamada). Los docs de
|
||||||
|
`docs/PORTE_FUNCIONALIDAD_NUEVA.md` y `docs/SOLICITUD_TRANSFERENCIA.md` mencionan `PatchAsyncSBA` como historia del porte.
|
||||||
|
- **v2.1.15.0 — se avisa al consumidor si falla la transferencia automática**: `TransferenciaAutomaticaNC.Crear` devuelve un
|
||||||
|
texto de advertencia (vacío si todo salió bien) cuando SAP rechaza una transferencia o no se puede agregar su referencia a
|
||||||
|
la NC. Los 3 llamadores (Picking DESPACHO y PICKING, Recibo Devolución) lo agregan al `Message` de la respuesta
|
||||||
|
(`"Se registro correctamente...!!! ADVERTENCIA: la nota de credito se creo, pero no se creo la transferencia A -> B: <SAP>."`).
|
||||||
|
La respuesta sigue siendo 200 con el DocEntry/DocNum de la NC (la NC existe en SAP; responder 500 haría que el WMS
|
||||||
|
reintente y duplique la NC). El consumidor debe leer el `Message`. El detalle del rechazo también queda en `ITCA_LOG_INTEGRACION`
|
||||||
|
(`ServiceLayer Error`). Sin probar contra SAP.
|
||||||
- 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.
|
||||||
|
|
||||||
@@ -158,8 +216,6 @@ 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`
|
|
||||||
sin marcar éxito.
|
|
||||||
- El cliente HTTP `bypass-ssl-validation` acepta cualquier certificado TLS hacia
|
- El cliente HTTP `bypass-ssl-validation` acepta cualquier certificado TLS hacia
|
||||||
el Service Layer (se quitó el método `ValidarCertificado`, que no se usaba).
|
el Service Layer (se quitó el método `ValidarCertificado`, que no se usaba).
|
||||||
- `appsettings.json` (fuera de git, en `.gitignore`) contiene credenciales
|
- `appsettings.json` (fuera de git, en `.gitignore`) contiene credenciales
|
||||||
@@ -170,8 +226,6 @@ Picking, columna `HORA` en `B_Log`. Lo que **no** se trajo: `Chatbots` y
|
|||||||
`ITC_WMS_SAP` (`ITCA_LOG_INTEGRACION`), no en `LocalData.Schema`; si la tabla
|
`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í
|
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.
|
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
|
||||||
|
|
||||||
|
|||||||
@@ -108,7 +108,6 @@ namespace Net.Business.Entities.ServicesLayer
|
|||||||
[DBParameter(SqlDbType.Int, 0, ActionType.Everything)]
|
[DBParameter(SqlDbType.Int, 0, ActionType.Everything)]
|
||||||
public int U_BPP_BPTD { get; set; }
|
public int U_BPP_BPTD { get; set; }
|
||||||
public int PriceListNum { get; set; }
|
public int PriceListNum { get; set; }
|
||||||
//U_BPP_BPTD = 6,
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,7 +7,7 @@ namespace Net.Business.Services.Log
|
|||||||
{
|
{
|
||||||
public static string Add(string Origen, byte[] Trama_Origen, byte[] Trama_Respuesta, string Proceso, string Nro_Orden, string DocEntry, string DocNumSAP, string DocEntrySAP)
|
public static string Add(string Origen, byte[] Trama_Origen, byte[] Trama_Respuesta, string Proceso, string Nro_Orden, string DocEntry, string DocNumSAP, string DocEntrySAP)
|
||||||
{
|
{
|
||||||
string DB = "ITCA_LOG_INTEGRACION";
|
string DB = Net.Data.LocalData.LogTable;
|
||||||
string Code = System.Guid.NewGuid().ToString();
|
string Code = System.Guid.NewGuid().ToString();
|
||||||
string Fecha = DateTime.Now.ToString("yyyy-MM-dd");
|
string Fecha = DateTime.Now.ToString("yyyy-MM-dd");
|
||||||
HanaConnection hana = new HanaConnection(Net.Data.LocalData.ConnectionString);
|
HanaConnection hana = new HanaConnection(Net.Data.LocalData.ConnectionString);
|
||||||
|
|||||||
@@ -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.8</Version>
|
<Version>2.1.15.0</Version>
|
||||||
<AssemblyVersion>2.1.8.0</AssemblyVersion>
|
<AssemblyVersion>2.1.15.0</AssemblyVersion>
|
||||||
<FileVersion>2.1.8.0</FileVersion>
|
<FileVersion>2.1.15.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
|
||||||
|
|||||||
@@ -23,25 +23,5 @@ namespace Net.Business.Services
|
|||||||
{
|
{
|
||||||
webBuilder.UseStartup<Startup>();
|
webBuilder.UseStartup<Startup>();
|
||||||
});
|
});
|
||||||
|
|
||||||
//public static IHostBuilder CreateHostBuilder(string[] args) =>
|
|
||||||
// Host.CreateDefaultBuilder(args)
|
|
||||||
// .ConfigureWebHostDefaults(webBuilder =>
|
|
||||||
// {
|
|
||||||
// webBuilder.
|
|
||||||
// UseStartup<Startup>()
|
|
||||||
// .UseContentRoot(Directory.GetCurrentDirectory())
|
|
||||||
// .UseKestrel(options =>
|
|
||||||
// {
|
|
||||||
// options.Limits.MaxRequestBodySize = long.MaxValue;
|
|
||||||
// })
|
|
||||||
// .UseIISIntegration();
|
|
||||||
// //.UseKestrel(options =>
|
|
||||||
// //{
|
|
||||||
// // options.Limits.MaxRequestBodySize = long.MaxValue;
|
|
||||||
// //})
|
|
||||||
|
|
||||||
// });
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -40,7 +40,8 @@
|
|||||||
"Hana": {
|
"Hana": {
|
||||||
"server": "",
|
"server": "",
|
||||||
"user": "",
|
"user": "",
|
||||||
"password": ""
|
"password": "",
|
||||||
|
"logTable": "ITCA_LOG_INTEGRACION"
|
||||||
},
|
},
|
||||||
"EmailSenderOptions": {
|
"EmailSenderOptions": {
|
||||||
"Port": "",
|
"Port": "",
|
||||||
|
|||||||
@@ -23,14 +23,6 @@ namespace Net.Connection.Attributes
|
|||||||
{
|
{
|
||||||
DBParameterMethod(dbType, 0, ParameterDirection.Input, "", actionType, Key);
|
DBParameterMethod(dbType, 0, ParameterDirection.Input, "", actionType, Key);
|
||||||
}
|
}
|
||||||
//public DBParameter(SqlDbType dbType, ActionType actionType)
|
|
||||||
//{
|
|
||||||
// DBParameterMethod(dbType, 0, ParameterDirection.Input, "", actionType, false);
|
|
||||||
//}
|
|
||||||
//public DBParameter(SqlDbType dbType, ActionType actionType, ParameterDirection direction)
|
|
||||||
//{
|
|
||||||
// DBParameterMethod(dbType, 0, direction, "", actionType, false);
|
|
||||||
//}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Asignacion del Tipo de Dato
|
/// Asignacion del Tipo de Dato
|
||||||
@@ -42,14 +34,6 @@ namespace Net.Connection.Attributes
|
|||||||
{
|
{
|
||||||
DBParameterMethod(dbType, size, ParameterDirection.Input, "", actionType, false);
|
DBParameterMethod(dbType, size, ParameterDirection.Input, "", actionType, false);
|
||||||
}
|
}
|
||||||
//public DBParameter(SqlDbType dbType, int size, ParameterDirection direction, ActionType actionType)
|
|
||||||
//{
|
|
||||||
// DBParameterMethod(dbType, size, direction, "", actionType, false);
|
|
||||||
//}
|
|
||||||
//public DBParameter(SqlDbType dbType, int size, ParameterDirection direction, string name, ActionType actionType)
|
|
||||||
//{
|
|
||||||
// DBParameterMethod(dbType, size, direction, name, actionType, false);
|
|
||||||
//}
|
|
||||||
|
|
||||||
private void DBParameterMethod(SqlDbType dbType, int size, ParameterDirection direction, string name, ActionType actionType, Boolean Key)
|
private void DBParameterMethod(SqlDbType dbType, int size, ParameterDirection direction, string name, ActionType actionType, Boolean Key)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -172,7 +172,6 @@ namespace Net.Connection.ServiceLayer
|
|||||||
|
|
||||||
// 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)
|
||||||
//request_.Headers = new AuthenticationHeaderValue("B1SESSION", _responseLoginServiceLayer.SessionId);
|
|
||||||
request_.Headers.Add("Cookie", "B1SESSION=" + _responseLoginServiceLayer.SessionId);
|
request_.Headers.Add("Cookie", "B1SESSION=" + _responseLoginServiceLayer.SessionId);
|
||||||
/////////////////////////////////////////
|
/////////////////////////////////////////
|
||||||
// CONSTRUIMOS LA RESPUESTA (RESPONSE) //
|
// CONSTRUIMOS LA RESPUESTA (RESPONSE) //
|
||||||
@@ -198,10 +197,6 @@ namespace Net.Connection.ServiceLayer
|
|||||||
{
|
{
|
||||||
List<T> lista = new List<T>();
|
List<T> lista = new List<T>();
|
||||||
dynamic responseBody = JsonConvert.DeserializeObject<T>(responseText_);
|
dynamic responseBody = JsonConvert.DeserializeObject<T>(responseText_);
|
||||||
//responseBody.Exito = true;
|
|
||||||
//var estado = (int)response_.StatusCode;
|
|
||||||
//responseBody.CodigoHttp = estado.ToString();
|
|
||||||
//responseBody.Mensaje = "Datos Correctos del SAP";
|
|
||||||
lista.Add(responseBody);
|
lista.Add(responseBody);
|
||||||
return lista;
|
return lista;
|
||||||
}
|
}
|
||||||
@@ -271,8 +266,6 @@ namespace Net.Connection.ServiceLayer
|
|||||||
|
|
||||||
// DEFINIMOS EL Accept, EN ESTE CASO ES "application/json"
|
// DEFINIMOS EL Accept, EN ESTE CASO ES "application/json"
|
||||||
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("charset=utf-8"));
|
|
||||||
if (conCuerpo)
|
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");
|
||||||
|
|
||||||
@@ -373,138 +366,13 @@ namespace Net.Connection.ServiceLayer
|
|||||||
// PASAMOS LA EXCEPCIÓN A LA APP.
|
// PASAMOS LA EXCEPCIÓN A LA APP.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// PATCH con cuerpo JSON. OJO: con un 204 real devuelve null sin marcar exito (bug conocido); para PATCH sin respuesta usar PatchNoContentAsync
|
|
||||||
public async Task<T> PatchAsyncSBA<T>(string Query, object obj)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// CONSTRUIMOS LA URL DE LA ACCIÓN
|
|
||||||
var urlBuilder_ = new StringBuilder();
|
|
||||||
urlBuilder_.Append(_url != null ? _url.TrimEnd('/') : "")
|
|
||||||
.Append("/" + Query);
|
|
||||||
var url_ = urlBuilder_.ToString();
|
|
||||||
|
|
||||||
// RECUPERAMOS EL HttpClient
|
|
||||||
var client_ = _httpClient.CreateClient("bypass-ssl-validation");
|
|
||||||
|
|
||||||
int NumError = 0;
|
|
||||||
|
|
||||||
band:
|
|
||||||
|
|
||||||
using (var request_ = new HttpRequestMessage())
|
|
||||||
{
|
|
||||||
///////////////////////////////////////
|
|
||||||
// CONSTRUIMOS LA PETICIÓN (REQUEST) //
|
|
||||||
///////////////////////////////////////
|
|
||||||
// DEFINIMOS EL MÉTODO HTTP GET
|
|
||||||
request_.Method = new HttpMethod("PATCH");
|
|
||||||
|
|
||||||
// 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();
|
|
||||||
LogErrorEnvio(Query, obj, responseBody.Mensaje);
|
|
||||||
return responseBody;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (_responseLoginServiceLayer.error.code.ToString() == "301")
|
|
||||||
{
|
|
||||||
await Login();
|
|
||||||
goto band;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
// CUALQUIER OTRA RESPUESTA ...
|
|
||||||
if (response_.StatusCode != System.Net.HttpStatusCode.OK && 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();
|
|
||||||
LogErrorEnvio(Query, obj, responseBody.Mensaje);
|
|
||||||
return responseBody;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response_.StatusCode == System.Net.HttpStatusCode.NoContent)
|
|
||||||
{
|
|
||||||
//dynamic responseBody = JsonConvert.DeserializeObject<T>(responseText_);
|
|
||||||
////responseBody.Mensaje = "OK";
|
|
||||||
//return responseBody;
|
|
||||||
}
|
|
||||||
|
|
||||||
// RETORNAMOS EL OBJETO POR DEFECTO ESPERADO
|
|
||||||
return default(T);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
return default(T);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// === NUEVO (SolicitudTransferencia / Picking HojaRuta Cerrar) ===
|
// === NUEVO (SolicitudTransferencia / Picking HojaRuta Cerrar) ===
|
||||||
//
|
//
|
||||||
// PatchAsyncSBA<T>/PostAsync<T> estan pensados para respuestas con body
|
// PatchNoContentAsync/PostActionAsync no devuelven un T deserializado, solo exito (sin excepcion)
|
||||||
// (200/201) que se deserializan a T y se les "inyecta" Exito/Mensaje/CodigoHttp
|
// o fallo (excepcion con el mensaje real de SAP). En el Service Layer un PATCH exitoso es un 204 sin cuerpo,
|
||||||
// via dynamic - no sirven para una respuesta sin body como el 204 real que
|
// asi que no hay nada que deserializar. (v2.1.14.0: se quito PatchAsyncSBA, que con el 204 devolvia null
|
||||||
// devuelve SAP en un PATCH exitoso o en una accion /Close, y PatchAsyncSBA
|
// sin marcar exito y con un error de red devolvia lo mismo.)
|
||||||
// tiene ademas un bug conocido ahi (ver docs/PORTE_FUNCIONALIDAD_NUEVA.md,
|
|
||||||
// hallazgo #2): con un 204 real cae a "return default(T)" sin marcar exito.
|
|
||||||
// Por eso estos dos metodos nuevos son deliberadamente simples: no devuelven
|
|
||||||
// un T deserializado, solo exito (sin excepcion) o fallo (excepcion con el
|
|
||||||
// mensaje real de SAP).
|
|
||||||
|
|
||||||
public async Task PatchNoContentAsync(string Query, object obj, Dictionary<string, string> extraHeaders = null)
|
public async Task PatchNoContentAsync(string Query, object obj, Dictionary<string, string> extraHeaders = null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -188,63 +188,6 @@ namespace Net.Connection
|
|||||||
return queryResult;
|
return queryResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
//private static SqlParameterCollection CreateParametros(string procedureName, string conexion)
|
|
||||||
//{
|
|
||||||
// SqlCommand cmd = new SqlCommand(procedureName.Trim());
|
|
||||||
// try
|
|
||||||
// {
|
|
||||||
// cmd.CommandType = CommandType.StoredProcedure;
|
|
||||||
// cmd.Connection = new SqlConnection(conexion);
|
|
||||||
// if (cmd.Connection.State == ConnectionState.Closed)
|
|
||||||
// {
|
|
||||||
// cmd.Connection.Open();
|
|
||||||
// }
|
|
||||||
// SqlCommandBuilder.DeriveParameters(cmd);
|
|
||||||
// for (int i = 0; i < cmd.Parameters.Count; i++)
|
|
||||||
// {
|
|
||||||
// string Nombre = cmd.Parameters[i].ParameterName;
|
|
||||||
// if (Nombre.Substring(0, 1) == "@")
|
|
||||||
// {
|
|
||||||
// Nombre = Nombre.Substring(1);
|
|
||||||
// }
|
|
||||||
// cmd.Parameters[i].ParameterName = Nombre.ToUpper();
|
|
||||||
// }
|
|
||||||
// return cmd.Parameters;
|
|
||||||
// }
|
|
||||||
// catch (Exception ex)
|
|
||||||
// {
|
|
||||||
// throw ex;
|
|
||||||
// }
|
|
||||||
// finally
|
|
||||||
// {
|
|
||||||
// cmd.Connection.Dispose();
|
|
||||||
// cmd.Dispose();
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
|
|
||||||
//private static IList<DbParametro> GetParametersSqlQuery(Object parameters)
|
|
||||||
//{
|
|
||||||
// IList<DbParametro> listaParametro = new List<DbParametro>();
|
|
||||||
|
|
||||||
// foreach (PropertyInfo prop in parameters.GetType().GetProperties())
|
|
||||||
// {
|
|
||||||
// dynamic valor = prop.GetValue(parameters, null);
|
|
||||||
// Type type = Nullable.GetUnderlyingType(prop.PropertyType);
|
|
||||||
|
|
||||||
// if (valor != null)
|
|
||||||
// {
|
|
||||||
// type = (type == null) ? prop.PropertyType : type;
|
|
||||||
|
|
||||||
// if (type != typeof(System.Collections.Generic.ICollection<>))
|
|
||||||
// {
|
|
||||||
// listaParametro.Add(new DbParametro() { nombre = prop.Name, valor = valor });
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return listaParametro;
|
|
||||||
//}
|
|
||||||
|
|
||||||
private static IList<DbParametro> GetParametersSqlQueryAnotation<T>(T parameters, ActionType actionType)
|
private static IList<DbParametro> GetParametersSqlQueryAnotation<T>(T parameters, ActionType actionType)
|
||||||
{
|
{
|
||||||
IList<DbParametro> listaParametro = new List<DbParametro>();
|
IList<DbParametro> listaParametro = new List<DbParametro>();
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -27,7 +27,7 @@ namespace Net.Data.HelperData.Log
|
|||||||
{
|
{
|
||||||
hana.Open();
|
hana.Open();
|
||||||
HanaCommand hanaCommand = hana.CreateCommand();
|
HanaCommand hanaCommand = hana.CreateCommand();
|
||||||
hanaCommand.CommandText = "INSERT INTO \"ITC_WMS_SAP\".\"ITCA_LOG_INTEGRACION\" (\"CODIGO\", \"ORIGEN\", \"TRAMA_ORIGEN\", \"TRAMA_RESPUESTA\", \"PROCESO\", \"FECHA\", \"NRO_ORDEN\", \"DOCENTRY\", \"DOCNUMSAP\", \"DOCENTRYSAP\", \"HORA\") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
|
hanaCommand.CommandText = $"INSERT INTO \"ITC_WMS_SAP\".\"{LocalData.LogTable}\" (\"CODIGO\", \"ORIGEN\", \"TRAMA_ORIGEN\", \"TRAMA_RESPUESTA\", \"PROCESO\", \"FECHA\", \"NRO_ORDEN\", \"DOCENTRY\", \"DOCNUMSAP\", \"DOCENTRYSAP\", \"HORA\") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
|
||||||
hanaCommand.Parameters.Add("CODIGO", HanaDbType.VarChar).Value = Guid.NewGuid().ToString();
|
hanaCommand.Parameters.Add("CODIGO", HanaDbType.VarChar).Value = Guid.NewGuid().ToString();
|
||||||
hanaCommand.Parameters.Add("ORIGEN", HanaDbType.VarChar).Value = "ServiceLayer";
|
hanaCommand.Parameters.Add("ORIGEN", HanaDbType.VarChar).Value = "ServiceLayer";
|
||||||
hanaCommand.Parameters.Add("TRAMA_ORIGEN", HanaDbType.Blob).Value = trama;
|
hanaCommand.Parameters.Add("TRAMA_ORIGEN", HanaDbType.Blob).Value = trama;
|
||||||
|
|||||||
@@ -10,6 +10,10 @@ namespace Net.Data
|
|||||||
// Esquema HANA: por defecto la compañia del Service Layer (SapServiceLayer:company)
|
// Esquema HANA: por defecto la compañia del Service Layer (SapServiceLayer:company)
|
||||||
public static string Schema { get; private set; }
|
public static string Schema { get; private set; }
|
||||||
|
|
||||||
|
// Tabla de log de integracion (esquema fijo ITC_WMS_SAP). QA/Desarrollo: ITCA_LOG_INTEGRACION;
|
||||||
|
// produccion: ITCA_LOG_INTEGRACION_PROD (Hana:logTable en appsettings.json)
|
||||||
|
public static string LogTable { get; private set; } = "ITCA_LOG_INTEGRACION";
|
||||||
|
|
||||||
// Unico punto de lectura de las credenciales HANA (seccion "Hana" de appsettings.json)
|
// Unico punto de lectura de las credenciales HANA (seccion "Hana" de appsettings.json)
|
||||||
public static void Configure(IConfiguration configuration)
|
public static void Configure(IConfiguration configuration)
|
||||||
{
|
{
|
||||||
@@ -32,6 +36,9 @@ namespace Net.Data
|
|||||||
}
|
}
|
||||||
|
|
||||||
Schema = schema;
|
Schema = schema;
|
||||||
|
|
||||||
|
var logTable = configuration["Hana:logTable"];
|
||||||
|
if (!string.IsNullOrWhiteSpace(logTable)) LogTable = logTable.Trim();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
using Net.Business.Entities.ServicesLayer.CreditNotes;
|
||||||
|
using Net.Data.HelperData.Correlativo;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Net.Data.ServicesLayer.CreditNotes
|
||||||
|
{
|
||||||
|
// Cabecera de la nota de credito (Picking DESPACHO, Picking PICKING estado 13 y Recibo Devolucion).
|
||||||
|
// Antes la serie, el correlativo y estos campos estaban copiados 3 veces.
|
||||||
|
public static class CabeceraNotaCredito
|
||||||
|
{
|
||||||
|
// Completa la cabecera de la NC con los datos del documento origen (la factura o, en Recibo sin factura,
|
||||||
|
// la solicitud de devolucion). Las lineas las arma cada llamador.
|
||||||
|
// tipoOrigen/serieOrigen/correlativoOrigen: U_SYP_MDTD / U_SYP_MDSD / U_SYP_MDCD del documento origen.
|
||||||
|
public static void Completar(BE_CreditNotesNotes nc, string docCurrency, string cardCode, string cardName, string paymentGroupCode,
|
||||||
|
string tipoOrigen, string serieOrigen, string correlativoOrigen, DateTime fechaOrigen, string motivo, string tipoMotivo)
|
||||||
|
{
|
||||||
|
// v2.1.7.0: la serie sale del TIPO del documento origen (03 = boleta -> BC10, otro -> FC10), no de su serie.
|
||||||
|
// En Recibo se comparaba la serie origen (p. ej. B005) con "03" y siempre salia FC10.
|
||||||
|
// Pendiente: validar con contabilidad la serie de boletas.
|
||||||
|
string serie = SerieNotaCredito(tipoOrigen);
|
||||||
|
|
||||||
|
// El correlativo sale de @SYP_NUMDOC (HelperCorrelativo)
|
||||||
|
string correlativo = HelperCorrelativo.Get(serie);
|
||||||
|
|
||||||
|
nc.DocCurrency = docCurrency;
|
||||||
|
nc.CardCode = cardCode;
|
||||||
|
nc.CardName = cardName;
|
||||||
|
nc.PaymentGroupCode = paymentGroupCode;
|
||||||
|
nc.DocDate = DateTime.Now;
|
||||||
|
nc.DocDueDate = DateTime.Now;
|
||||||
|
|
||||||
|
// Documento origen (el que se devuelve)
|
||||||
|
nc.U_SYP_MDTO = tipoOrigen;
|
||||||
|
nc.U_SYP_MDCO = correlativoOrigen;
|
||||||
|
nc.U_SYP_MDSO = serieOrigen;
|
||||||
|
nc.U_SYP_FECHAREF = fechaOrigen.ToString("yyyy-MM-dd");
|
||||||
|
|
||||||
|
nc.U_SYP_MOTNC = motivo;
|
||||||
|
nc.U_SYP_TPONC = tipoMotivo;
|
||||||
|
|
||||||
|
// La NC misma: tipo 07, con la serie y el correlativo elegidos arriba
|
||||||
|
nc.U_SYP_MDTD = "07";
|
||||||
|
nc.U_SYP_MDSD = serie;
|
||||||
|
nc.U_SYP_MDCD = correlativo;
|
||||||
|
nc.NumAtCard = "07-" + serie + "-" + correlativo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string SerieNotaCredito(string tipoOrigen)
|
||||||
|
{
|
||||||
|
return tipoOrigen == "03" ? "BC10" : "FC10";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -46,7 +46,6 @@ namespace Net.Data.ServicesLayer.CreditNotes
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
//var cadena = "Drafts";
|
|
||||||
var cadena = "CreditNotes";
|
var cadena = "CreditNotes";
|
||||||
var data = await _connectServiceLayer.PostAsync<BE_BaseResponseSap>(cadena, entidad);
|
var data = await _connectServiceLayer.PostAsync<BE_BaseResponseSap>(cadena, entidad);
|
||||||
|
|
||||||
@@ -73,15 +72,11 @@ namespace Net.Data.ServicesLayer.CreditNotes
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
//var cadena = "Drafts";
|
|
||||||
var cadena = $"CreditNotes({DocEntry})";
|
var cadena = $"CreditNotes({DocEntry})";
|
||||||
var data = await _connectServiceLayer.PatchAsyncSBA<BE_BaseResponseSap>(cadena, entidad);
|
// PATCH exitoso = 204 sin cuerpo; si SAP rechaza lanza excepcion con su mensaje (y deja el JSON en el log)
|
||||||
if (!data.Exito)
|
await _connectServiceLayer.PatchNoContentAsync(cadena, entidad);
|
||||||
{
|
|
||||||
return resultadoTransaccion.GetInformacionResultado(-1, -1, data.Mensaje);
|
|
||||||
}
|
|
||||||
|
|
||||||
return resultadoTransaccion.GetInformacionResultado(0, "", "Se Listo correctamento...!!!", data);
|
return resultadoTransaccion.GetInformacionResultado(0, "", "Se Listo correctamento...!!!", new BE_BaseResponseSap { Exito = true });
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -75,28 +75,6 @@ namespace Net.Data.ServicesLayer.HR
|
|||||||
U_ARD_PHR_CNTDOC = entidad.Detalle.Count()
|
U_ARD_PHR_CNTDOC = entidad.Detalle.Count()
|
||||||
};
|
};
|
||||||
|
|
||||||
//if(entidad.ModalidadTransporte == "Privado")
|
|
||||||
//{
|
|
||||||
// HojaRuta = new HojaRutaSAP()
|
|
||||||
// {
|
|
||||||
// ARD_PCK_HJRT_DETCollection = new List<HojaRutaDetailsEntities>(),
|
|
||||||
// U_ARD_PHR_PLACA = entidad.PlacaVehiculo,
|
|
||||||
// U_ARD_PHR_TERRI = entidad.Territorio,
|
|
||||||
// U_ARD_PHR_ZONDIS = entidad.Zona,
|
|
||||||
// U_ARD_PHR_FECCRE = entidad.FechaDespacho,
|
|
||||||
// U_ARD_PHR_MODTRANSP = entidad.ModalidadTransporte,
|
|
||||||
// U_ARD_PHR_NOMDESP = entidad.Despachador,
|
|
||||||
// U_ARD_PHR_NOMAGENTRANS = "ARDICORP S.A.C.",
|
|
||||||
// U_ARD_PHR_CODAGENTRANS = "P20136836545",
|
|
||||||
// U_ARD_PHR_RUCAGENTRANS = "20136836545",
|
|
||||||
// U_ARD_PHR_ESTDESP = "Embarcado"
|
|
||||||
// };
|
|
||||||
//}
|
|
||||||
//else
|
|
||||||
//{
|
|
||||||
|
|
||||||
//}
|
|
||||||
|
|
||||||
HojaRuta.ARD_PCK_HJRT_DETCollection = detalle;
|
HojaRuta.ARD_PCK_HJRT_DETCollection = detalle;
|
||||||
|
|
||||||
var cadena = "ARD_PHR";
|
var cadena = "ARD_PHR";
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using Net.Business.Entities;
|
using Net.Business.Entities;
|
||||||
using Net.Business.Entities.ServicesLayer;
|
using Net.Business.Entities.ServicesLayer;
|
||||||
using Net.Business.Entities.ServicesLayer.PurchaseInvoices;
|
|
||||||
using Net.Business.Entities.ServicesLayer.PurchaseOrders;
|
using Net.Business.Entities.ServicesLayer.PurchaseOrders;
|
||||||
using Net.Connection;
|
using Net.Connection;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@@ -9,7 +8,6 @@ namespace Net.Data.ServicesLayer
|
|||||||
{
|
{
|
||||||
public interface IPurchaseOrdersRepository
|
public interface IPurchaseOrdersRepository
|
||||||
{
|
{
|
||||||
Task<BE_ResultadoTransaccion<BE_BaseResponseSap>> SetCreatePurchaseInvoices(BE_PurchaseInvoices entidad);
|
|
||||||
Task<BE_ResultadoTransaccion<BE_PurchaseOrders>> GetPurchaseInvoices(int DocEntry);
|
Task<BE_ResultadoTransaccion<BE_PurchaseOrders>> GetPurchaseInvoices(int DocEntry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Net.Business.Entities;
|
using Net.Business.Entities;
|
||||||
using Net.Business.Entities.ServicesLayer;
|
using Net.Business.Entities.ServicesLayer;
|
||||||
using Net.Business.Entities.ServicesLayer.PurchaseInvoices;
|
|
||||||
using Net.Business.Entities.ServicesLayer.PurchaseOrders;
|
using Net.Business.Entities.ServicesLayer.PurchaseOrders;
|
||||||
using Net.Connection.ServiceLayer;
|
using Net.Connection.ServiceLayer;
|
||||||
using Net.CrossCotting;
|
using Net.CrossCotting;
|
||||||
@@ -32,31 +31,6 @@ namespace Net.Data.ServicesLayer
|
|||||||
_connectServiceLayer = new ConnectionServiceLayer(configuration, clientFactory);
|
_connectServiceLayer = new ConnectionServiceLayer(configuration, clientFactory);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<BE_ResultadoTransaccion<BE_BaseResponseSap>> SetCreatePurchaseInvoices(BE_PurchaseInvoices entidad)
|
|
||||||
{
|
|
||||||
_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 = "PurchaseInvoices";
|
|
||||||
var data = await _connectServiceLayer.PostAsync<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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public async Task<BE_ResultadoTransaccion<BE_PurchaseOrders>> GetPurchaseInvoices(int DocEntry)
|
public async Task<BE_ResultadoTransaccion<BE_PurchaseOrders>> GetPurchaseInvoices(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();
|
||||||
|
|||||||
@@ -16,11 +16,14 @@ namespace Net.Data.ServicesLayer.StockTransfer
|
|||||||
// Por cada almacen de origen (WarehouseCode) y almacen destino (U_ITC_ALMDES) distintos de las lineas de la NC
|
// 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.
|
// 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.
|
// 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.
|
// La NC ya esta creada, asi que un fallo aqui NO la deshace ni responde error (el consumidor reintentaria y
|
||||||
public static void Crear(BE_CreditNotesNotes creditNotesNotes, int docEntryNC, IHttpClientFactory clientFactory, IConfiguration configuration, CreditNotesRepository creditNotesRepository)
|
// duplicaria la NC). Devuelve el texto de advertencia para el mensaje de la respuesta ("" si todo salio bien):
|
||||||
|
// transferencias que SAP rechazo y/o la referencia que no se pudo agregar a la NC.
|
||||||
|
public static string Crear(BE_CreditNotesNotes creditNotesNotes, int docEntryNC, IHttpClientFactory clientFactory, IConfiguration configuration, CreditNotesRepository creditNotesRepository)
|
||||||
{
|
{
|
||||||
List<string> ListaUnicaAlmacen = creditNotesNotes.DocumentLines.Select(x => x.WarehouseCode).Distinct().ToList();
|
List<string> ListaUnicaAlmacen = creditNotesNotes.DocumentLines.Select(x => x.WarehouseCode).Distinct().ToList();
|
||||||
List<int> ListDocEntry = new List<int>();
|
List<int> ListDocEntry = new List<int>();
|
||||||
|
List<string> Advertencias = new List<string>();
|
||||||
ListaUnicaAlmacen.ForEach(Almacen =>
|
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();
|
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();
|
||||||
@@ -83,6 +86,10 @@ namespace Net.Data.ServicesLayer.StockTransfer
|
|||||||
{
|
{
|
||||||
ListDocEntry.Add((int)ResultTransfer.Data.DocEntry);
|
ListDocEntry.Add((int)ResultTransfer.Data.DocEntry);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Advertencias.Add($"no se creo la transferencia {Almacen} -> {Destino}: {ResultTransfer.Message}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -108,7 +115,15 @@ namespace Net.Data.ServicesLayer.StockTransfer
|
|||||||
};
|
};
|
||||||
|
|
||||||
var Actualizacion = creditNotesRepository.UpdateCreateCreditNotes(credit, docEntryNC).Result;
|
var Actualizacion = creditNotesRepository.UpdateCreateCreditNotes(credit, docEntryNC).Result;
|
||||||
|
if (Actualizacion.StatusCode == -1)
|
||||||
|
{
|
||||||
|
Advertencias.Add($"se crearon las transferencias {string.Join(", ", ListDocEntry)} pero no se pudo agregar su referencia a la NC: {Actualizacion.Message}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return Advertencias.Count == 0
|
||||||
|
? ""
|
||||||
|
: " ADVERTENCIA: la nota de credito se creo, pero " + string.Join("; ", Advertencias) + ".";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -497,7 +497,6 @@ namespace Net.Data.WMS.Inventario
|
|||||||
};
|
};
|
||||||
|
|
||||||
return resultadoTransaccion.GetInformacionResultado(DocEntry, DocNum.ToString(), "Registrado Correctamente", entidad);
|
return resultadoTransaccion.GetInformacionResultado(DocEntry, DocNum.ToString(), "Registrado Correctamente", entidad);
|
||||||
// return resultadoTransaccion.GetInformacionResultado((int)inventoryGenEntries_result.Data.DocEntry, inventoryGenEntries_result.Data.DocNum.ToString(), "Registrado Correctamente", entidad);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//=== SALIDA DE MERCANCIA
|
//=== SALIDA DE MERCANCIA
|
||||||
@@ -586,7 +585,6 @@ namespace Net.Data.WMS.Inventario
|
|||||||
};
|
};
|
||||||
|
|
||||||
return resultadoTransaccion.GetInformacionResultado(DocEntry, DocNum.ToString(), "Registrado Correctamente", entidad);
|
return resultadoTransaccion.GetInformacionResultado(DocEntry, DocNum.ToString(), "Registrado Correctamente", entidad);
|
||||||
//return resultadoTransaccion.GetInformacionResultado((int)inventoryGenExits_result.Data.DocEntry, inventoryGenExits_result.Data.DocNum.ToString(), "Registrado Correctamente", entidad);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return resultadoTransaccion.GetInformacionResultado(0, "", "Registrado Correctamente", entidad);
|
return resultadoTransaccion.GetInformacionResultado(0, "", "Registrado Correctamente", entidad);
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ using Net.Business.Entities.WMS.ProcesoPicking;
|
|||||||
using Net.Connection;
|
using Net.Connection;
|
||||||
using Net.Connection.ServiceLayer;
|
using Net.Connection.ServiceLayer;
|
||||||
using Net.CrossCotting;
|
using Net.CrossCotting;
|
||||||
using Net.Data.HelperData.Correlativo;
|
|
||||||
using Net.Data.HelperData.HelperProcesoPermitido;
|
using Net.Data.HelperData.HelperProcesoPermitido;
|
||||||
using Net.Data.HelperData.Stock;
|
using Net.Data.HelperData.Stock;
|
||||||
using Net.Data.ServicesLayer;
|
using Net.Data.ServicesLayer;
|
||||||
@@ -171,26 +170,9 @@ 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.
|
// Cabecera de la NC (serie BC10/FC10 por tipo de la factura, correlativo, documento origen)
|
||||||
// El correlativo sale de @SYP_NUMDOC (HelperCorrelativo)
|
CabeceraNotaCredito.Completar(creditNotesNotes, _invoices.DocCurrency, _invoices.CardCode, _invoices.CardName, _invoices.PaymentGroupCode,
|
||||||
string Serie = _invoices.U_SYP_MDTD == "03" ? "BC10" : "FC10";
|
_invoices.U_SYP_MDTD, _invoices.U_SYP_MDSD, _invoices.U_SYP_MDCD, _invoices.DocDate, DescripcionMotivo, MotivoNotaCredito);
|
||||||
string Correlativo = HelperCorrelativo.Get(Serie);
|
|
||||||
creditNotesNotes.DocCurrency = _invoices.DocCurrency;
|
|
||||||
creditNotesNotes.CardCode = _invoices.CardCode;
|
|
||||||
creditNotesNotes.CardName = _invoices.CardName;
|
|
||||||
creditNotesNotes.PaymentGroupCode = _invoices.PaymentGroupCode;
|
|
||||||
creditNotesNotes.DocDate = DateTime.Now;
|
|
||||||
creditNotesNotes.DocDueDate = DateTime.Now;
|
|
||||||
creditNotesNotes.U_SYP_MDTO = _invoices.U_SYP_MDTD;
|
|
||||||
creditNotesNotes.U_SYP_MDCO = _invoices.U_SYP_MDCD;
|
|
||||||
creditNotesNotes.U_SYP_MDSO = _invoices.U_SYP_MDSD;
|
|
||||||
creditNotesNotes.U_SYP_MOTNC = DescripcionMotivo;
|
|
||||||
creditNotesNotes.U_SYP_TPONC = MotivoNotaCredito;
|
|
||||||
creditNotesNotes.U_SYP_MDCD = Correlativo;
|
|
||||||
creditNotesNotes.U_SYP_MDSD = Serie;
|
|
||||||
creditNotesNotes.U_SYP_MDTD = "07";
|
|
||||||
creditNotesNotes.U_SYP_FECHAREF = _invoices.DocDate.ToString("yyyy-MM-dd");
|
|
||||||
creditNotesNotes.NumAtCard = ("07-" + Serie + "-" + Correlativo);
|
|
||||||
|
|
||||||
entidad.Detalle.ForEach(x =>
|
entidad.Detalle.ForEach(x =>
|
||||||
{
|
{
|
||||||
@@ -300,8 +282,8 @@ namespace Net.Data.WMS.Picking
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Transferencias de stock automaticas por almacen destino (U_ITC_ALMDES) y su referencia en la NC
|
// Transferencias de stock automaticas por almacen destino (U_ITC_ALMDES) y su referencia en la NC
|
||||||
TransferenciaAutomaticaNC.Crear(creditNotesNotes, (int)result_creditNotesRepository.Data.DocEntry, _clientFactory, _configuration, _creditNotesRepository);
|
var advertenciaTransferencias = TransferenciaAutomaticaNC.Crear(creditNotesNotes, (int)result_creditNotesRepository.Data.DocEntry, _clientFactory, _configuration, _creditNotesRepository);
|
||||||
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...!!!" + advertenciaTransferencias, entidad);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -437,26 +419,9 @@ namespace Net.Data.WMS.Picking
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Serie segun el tipo de documento de la factura (03 = boleta -> BC10, otro -> FC10)
|
// Cabecera de la NC (serie BC10/FC10 por tipo de la factura, correlativo, documento origen)
|
||||||
string Serie = _invoices.U_SYP_MDTD == "03" ? "BC10" : "FC10";
|
CabeceraNotaCredito.Completar(creditNotesNotes, _invoices.DocCurrency, _invoices.CardCode, _invoices.CardName, _invoices.PaymentGroupCode,
|
||||||
string Correlativo = HelperCorrelativo.Get(Serie);
|
_invoices.U_SYP_MDTD, _invoices.U_SYP_MDSD, _invoices.U_SYP_MDCD, _invoices.DocDate, DescripcionMotivo, MotivoNotaCredito);
|
||||||
|
|
||||||
creditNotesNotes.DocCurrency = _invoices.DocCurrency;
|
|
||||||
creditNotesNotes.CardCode = _invoices.CardCode;
|
|
||||||
creditNotesNotes.CardName = _invoices.CardName;
|
|
||||||
creditNotesNotes.PaymentGroupCode = _invoices.PaymentGroupCode;
|
|
||||||
creditNotesNotes.DocDate = DateTime.Now;
|
|
||||||
creditNotesNotes.DocDueDate = DateTime.Now;
|
|
||||||
creditNotesNotes.U_SYP_MDTO = _invoices.U_SYP_MDTD;
|
|
||||||
creditNotesNotes.U_SYP_MDCO = _invoices.U_SYP_MDCD;
|
|
||||||
creditNotesNotes.U_SYP_MDSO = _invoices.U_SYP_MDSD;
|
|
||||||
creditNotesNotes.U_SYP_MOTNC = DescripcionMotivo;
|
|
||||||
creditNotesNotes.U_SYP_TPONC = MotivoNotaCredito;
|
|
||||||
creditNotesNotes.U_SYP_MDCD = Correlativo;
|
|
||||||
creditNotesNotes.U_SYP_MDSD = Serie;
|
|
||||||
creditNotesNotes.U_SYP_MDTD = "07";
|
|
||||||
creditNotesNotes.U_SYP_FECHAREF = _invoices.DocDate.ToString("yyyy-MM-dd");
|
|
||||||
creditNotesNotes.NumAtCard = ("07-" + Serie+ "-" + Correlativo);
|
|
||||||
|
|
||||||
entidad.Detalle.ForEach(x =>
|
entidad.Detalle.ForEach(x =>
|
||||||
{
|
{
|
||||||
@@ -499,8 +464,8 @@ namespace Net.Data.WMS.Picking
|
|||||||
{
|
{
|
||||||
|
|
||||||
// Transferencias de stock automaticas por almacen destino (U_ITC_ALMDES) y su referencia en la NC
|
// Transferencias de stock automaticas por almacen destino (U_ITC_ALMDES) y su referencia en la NC
|
||||||
TransferenciaAutomaticaNC.Crear(creditNotesNotes, (int)result_creditNotesRepository.Data.DocEntry, _clientFactory, _configuration, _creditNotesRepository);
|
var advertenciaTransferencias = TransferenciaAutomaticaNC.Crear(creditNotesNotes, (int)result_creditNotesRepository.Data.DocEntry, _clientFactory, _configuration, _creditNotesRepository);
|
||||||
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...!!!" + advertenciaTransferencias, entidad);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ using Net.Business.Entities.ServicesLayer.StockTransfer;
|
|||||||
using Net.Business.Entities.WMS.Recibo;
|
using Net.Business.Entities.WMS.Recibo;
|
||||||
using Net.Connection;
|
using Net.Connection;
|
||||||
using Net.CrossCotting;
|
using Net.CrossCotting;
|
||||||
using Net.Data.HelperData.Correlativo;
|
|
||||||
using Net.Data.HelperData.Stock;
|
using Net.Data.HelperData.Stock;
|
||||||
using Net.Data.ServicesLayer;
|
using Net.Data.ServicesLayer;
|
||||||
using Net.Data.ServicesLayer.CreditNotes;
|
using Net.Data.ServicesLayer.CreditNotes;
|
||||||
@@ -402,27 +401,10 @@ namespace Net.Data.WMS.Recibo
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
// Serie segun el TIPO de documento origen (03 = boleta), no segun su serie
|
// Cabecera de la NC: serie BC10/FC10 por el TIPO del documento origen (v2.1.7.0), correlativo y documento origen.
|
||||||
// El correlativo sale de @SYP_NUMDOC (HelperCorrelativo)
|
// El origen es la factura de la solicitud de devolucion o, si no hay, la propia solicitud
|
||||||
string Serie = U_SYP_MDTO == "03" ? "BC10" : "FC10";
|
CabeceraNotaCredito.Completar(creditNotesNotes, returnRequest.DocCurrency, returnRequest.CardCode, returnRequest.CardName, returnRequest.PaymentGroupCode,
|
||||||
string Correlativo = HelperCorrelativo.Get(Serie);
|
U_SYP_MDTO, U_SYP_MDSO, U_SYP_MDCO, DocDate, returnRequest.U_SYP_MOTNC, returnRequest.U_SYP_TPONC);
|
||||||
creditNotesNotes.DocCurrency = returnRequest.DocCurrency;
|
|
||||||
creditNotesNotes.CardCode = returnRequest.CardCode;
|
|
||||||
creditNotesNotes.CardName = returnRequest.CardName;
|
|
||||||
creditNotesNotes.PaymentGroupCode = returnRequest.PaymentGroupCode;
|
|
||||||
creditNotesNotes.DocDate = DateTime.Now;
|
|
||||||
creditNotesNotes.DocDueDate = DateTime.Now;
|
|
||||||
//creditNotesNotes.NumAtCard = entidad.DocImportacion;
|
|
||||||
creditNotesNotes.NumAtCard = ("07-" + Serie +"-" + Correlativo);
|
|
||||||
creditNotesNotes.U_SYP_MDTO = U_SYP_MDTO;
|
|
||||||
creditNotesNotes.U_SYP_MDCO = U_SYP_MDCO;
|
|
||||||
creditNotesNotes.U_SYP_MDSO = U_SYP_MDSO;
|
|
||||||
creditNotesNotes.U_SYP_MOTNC = returnRequest.U_SYP_MOTNC;
|
|
||||||
creditNotesNotes.U_SYP_TPONC = returnRequest.U_SYP_TPONC;
|
|
||||||
creditNotesNotes.U_SYP_MDCD = Correlativo;
|
|
||||||
creditNotesNotes.U_SYP_MDSD = Serie;
|
|
||||||
creditNotesNotes.U_SYP_MDTD = "07";
|
|
||||||
creditNotesNotes.U_SYP_FECHAREF = DocDate.ToString("yyyy-MM-dd");
|
|
||||||
entidad.Detalle.ForEach(x =>
|
entidad.Detalle.ForEach(x =>
|
||||||
{
|
{
|
||||||
var Item = returnRequest.DocumentLines.ToList().FirstOrDefault(t => t.LineNum == x.Item);
|
var Item = returnRequest.DocumentLines.ToList().FirstOrDefault(t => t.LineNum == x.Item);
|
||||||
@@ -486,6 +468,11 @@ namespace Net.Data.WMS.Recibo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
// Stock de HANA menor a la cantidad: el resto va a un lote nuevo para que los lotes sumen la cantidad de la linea
|
||||||
|
if (CantidadPendiente > 0)
|
||||||
|
{
|
||||||
|
line.BatchNumbers.Add(new BE_CreditNotesBatchNumbers() { BatchNumber = DateTime.Now.ToString("yyyyMMddhhmmss"), Quantity = (double)CantidadPendiente });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -501,7 +488,7 @@ namespace Net.Data.WMS.Recibo
|
|||||||
{
|
{
|
||||||
line.BatchNumbers.Clear();
|
line.BatchNumbers.Clear();
|
||||||
var ReferenciaFactura = returnRequest.DocumentLines.FirstOrDefault(t => t.LineNum == line.LineNum);
|
var ReferenciaFactura = returnRequest.DocumentLines.FirstOrDefault(t => t.LineNum == line.LineNum);
|
||||||
var ListaStock = _Invoice.DocumentLines.FirstOrDefault(InvoiceLine => InvoiceLine.LineNum == ReferenciaFactura.BaseLine).BatchNumbers;
|
var ListaStock = _Invoice.DocumentLines.FirstOrDefault(InvoiceLine => InvoiceLine.LineNum == ReferenciaFactura.BaseLine)?.BatchNumbers ?? new List<BE_InvoicesBatchNumbers>();
|
||||||
if (ListaStock.Count > 0)
|
if (ListaStock.Count > 0)
|
||||||
{
|
{
|
||||||
var CantidadPendiente = line.Quantity;
|
var CantidadPendiente = line.Quantity;
|
||||||
@@ -522,6 +509,11 @@ namespace Net.Data.WMS.Recibo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
// Lotes de la factura menores a la cantidad: el resto va a un lote nuevo para que los lotes sumen la cantidad de la linea
|
||||||
|
if (CantidadPendiente > 0)
|
||||||
|
{
|
||||||
|
line.BatchNumbers.Add(new BE_CreditNotesBatchNumbers() { BatchNumber = DateTime.Now.ToString("yyyyMMddhhmmss"), Quantity = (double)CantidadPendiente });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -539,11 +531,10 @@ namespace Net.Data.WMS.Recibo
|
|||||||
{
|
{
|
||||||
// Transferencias de stock automaticas por almacen destino (U_ITC_ALMDES). OJO: en Recibo las lineas de la NC no llenan
|
// Transferencias de stock automaticas por almacen destino (U_ITC_ALMDES). OJO: en Recibo las lineas de la NC no llenan
|
||||||
// U_ITC_ALMDES, por lo que hoy no se genera ninguna
|
// U_ITC_ALMDES, por lo que hoy no se genera ninguna
|
||||||
TransferenciaAutomaticaNC.Crear(creditNotesNotes, (int)result_creditNotesRepository.Data.DocEntry, _clientFactory, _configuration, _creditNotesRepository);
|
var advertenciaTransferencias = TransferenciaAutomaticaNC.Crear(creditNotesNotes, (int)result_creditNotesRepository.Data.DocEntry, _clientFactory, _configuration, _creditNotesRepository);
|
||||||
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...!!!" + advertenciaTransferencias, entidad);
|
||||||
}
|
}
|
||||||
|
|
||||||
//return resultadoTransaccion.GetInformacionResultado((int)result_creditNotesRepository.Data.DocEntry, result_creditNotesRepository.Data.DocNum.ToString(), "Se registro correctamente...!!!", entidad);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|||||||
@@ -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.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -76,12 +76,15 @@ dotnet publish Net.Business.Services/Net.Business.Services.csproj -c Release -r
|
|||||||
## Versión y script de publish
|
## Versión y script de publish
|
||||||
|
|
||||||
- La versión sale de `<Version>`/`<AssemblyVersion>`/`<FileVersion>` en
|
- La versión sale de `<Version>`/`<AssemblyVersion>`/`<FileVersion>` en
|
||||||
`Net.Business.Services/Net.Business.Services.csproj` (actual: **2.1.8.0** en la rama de limpieza; `main` = 2.1.7.0);
|
`Net.Business.Services/Net.Business.Services.csproj` (actual: **2.1.15.0** en `main`);
|
||||||
súbela en cada publish.
|
súbela en cada publish. Se usa **semver** (`MAJOR.MINOR.PATCH`, cuarto número del
|
||||||
- `publicar.ps1` (solo Windows) genera `dist\ITCA_API_SAP_WMS_v<versión>.zip` sin
|
`csproj` siempre `0`): `MAJOR` cambia el contrato de la API, `MINOR` agrega funcionalidad
|
||||||
`appsettings.json`. Desde macOS se hace el mismo `dotnet publish` a mano con
|
compatible, `PATCH` corrige o limpia sin cambio de contrato (detalle en `CLAUDE.md`).
|
||||||
`-p:HanaDll=...`, se borran `appsettings.json`/`appsettings.Development.json` de
|
- **Única forma de generar el zip**: `publicar.ps1` (Windows: `.\publicar.ps1`; macOS/Linux con PowerShell 7:
|
||||||
la salida y se comprime.
|
`pwsh ./publicar.ps1`). Genera `dist/ITCA_API_SAP_WMS_v<MAJOR.MINOR.PATCH>.zip` sin `appsettings.json` ni
|
||||||
|
`appsettings.Development.json`; `-Destino <carpeta>` además copia el zip ahí (p. ej. `-Destino ~/Desktop`).
|
||||||
|
Verifica que `Version`, `AssemblyVersion` y `FileVersion` del `csproj` coincidan. En macOS/Linux define antes
|
||||||
|
`export HanaDll=<ruta>/Sap.Data.Hana.Core.v2.1.dll`. No hacer el `dotnet publish` a mano.
|
||||||
- Control de versiones: rama `main` y etiquetas `vX.Y.Z.W` por versión
|
- Control de versiones: rama `main` y etiquetas `vX.Y.Z.W` por versión
|
||||||
publicada (ver `CLAUDE.md`).
|
publicada (ver `CLAUDE.md`).
|
||||||
|
|
||||||
@@ -102,11 +105,11 @@ git switch -c recuperar-2.1.7.0 v2.1.7.0 # trabajar desde ahí en una rama nue
|
|||||||
- Para **volver a publicar 2.1.7.0**: `git checkout v2.1.7.0` y generar el
|
- 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
|
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.
|
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
|
- Para **deshacer** la limpieza ya unida a `main`: como fue avance directo (no hay commit de merge), usar
|
||||||
`git revert` del merge (no reescribe el historial). Evitar `git reset --hard`
|
`git revert --no-commit v2.1.7.0..HEAD` y confirmar (no reescribe el historial). Evitar `git reset --hard`
|
||||||
y `git push --force` sobre `main`.
|
y `git push --force` sobre `main`.
|
||||||
- La rama `main` no se toca durante la limpieza; el trabajo va en
|
- La limpieza se unió a `main` el 2026-10-20 (avance directo, sin commit de merge), por
|
||||||
`limpieza-2.1.8.0`.
|
lo que ya no hay una rama aparte; la etiqueta `v2.1.7.0` sigue siendo el punto de retorno.
|
||||||
|
|
||||||
## Logs de integración
|
## Logs de integración
|
||||||
|
|
||||||
@@ -115,12 +118,14 @@ git switch -c recuperar-2.1.7.0 v2.1.7.0 # trabajar desde ahí en una rama nue
|
|||||||
respuesta de la API (`TRAMA_RESPUESTA`), con `PROCESO` (Picking, Recibo...),
|
respuesta de la API (`TRAMA_RESPUESTA`), con `PROCESO` (Picking, Recibo...),
|
||||||
`NRO_ORDEN`, `DOCENTRY`, `DOCNUMSAP` y `DOCENTRYSAP`.
|
`NRO_ORDEN`, `DOCENTRY`, `DOCNUMSAP` y `DOCENTRYSAP`.
|
||||||
- **Log de errores del Service Layer** (desde 2.1.5.0): cuando SAP rechaza un
|
- **Log de errores del Service Layer** (desde 2.1.5.0): cuando SAP rechaza un
|
||||||
envío con cuerpo JSON (`PostAsync`, `PatchAsyncSBA`, `PatchNoContentAsync`)
|
envío con cuerpo JSON (`PostAsync`, `PatchNoContentAsync`)
|
||||||
se guarda en esa misma tabla el JSON enviado y el mensaje de SAP, con
|
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`
|
`PROCESO = 'ServiceLayer Error'`, `NRO_ORDEN` = ruta del recurso y `DOCENTRY`
|
||||||
= `BaseEntry` del documento base. Solo en error; no cambia la respuesta de la
|
= `BaseEntry` del documento base. Solo en error; no cambia la respuesta de la
|
||||||
API.
|
API.
|
||||||
- Ambos usan el esquema fijo `ITC_WMS_SAP` (columnas `PROCESO`, `NRO_ORDEN` y
|
- Ambos usan el esquema fijo `ITC_WMS_SAP` y la tabla de `Hana:logTable`
|
||||||
|
(por defecto `ITCA_LOG_INTEGRACION` para QA/Desarrollo; en **producción**
|
||||||
|
poner `ITCA_LOG_INTEGRACION_PROD`) (columnas `PROCESO`, `NRO_ORDEN` y
|
||||||
`DOCENTRY` son `VARCHAR(100)`).
|
`DOCENTRY` son `VARCHAR(100)`).
|
||||||
- Consulta de ejemplo:
|
- Consulta de ejemplo:
|
||||||
`SELECT * FROM "ITC_WMS_SAP"."ITCA_LOG_INTEGRACION" WHERE "PROCESO" = 'ServiceLayer Error' ORDER BY "FECHA" DESC, "HORA" DESC`
|
`SELECT * FROM "ITC_WMS_SAP"."ITCA_LOG_INTEGRACION" WHERE "PROCESO" = 'ServiceLayer Error' ORDER BY "FECHA" DESC, "HORA" DESC`
|
||||||
|
|||||||
@@ -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.
|
||||||
+25
-11
@@ -1,21 +1,35 @@
|
|||||||
# Genera el publish para IIS y lo comprime como dist\ITCA_API_SAP_WMS_v<version>.zip
|
# UNICA forma de generar el zip del publish para IIS (Windows y macOS/Linux con PowerShell 7: pwsh ./publicar.ps1).
|
||||||
# La versión sale de <AssemblyVersion> en Net.Business.Services.csproj.
|
# Genera dist/ITCA_API_SAP_WMS_v<MAJOR.MINOR.PATCH>.zip a partir de <AssemblyVersion> de Net.Business.Services.csproj.
|
||||||
# El zip NO incluye appsettings.json ni appsettings.Development.json (no pisar los del servidor).
|
# El zip NO incluye appsettings.json ni appsettings.Development.json (no pisar los del servidor).
|
||||||
# Requiere Windows (el driver Sap.Data.Hana se referencia desde C:\Program Files\sap\hdbclient).
|
# -Destino <carpeta>: ademas copia el zip a esa carpeta (p. ej. el escritorio).
|
||||||
|
# Driver Sap.Data.Hana: en Windows sale de C:\Program Files\sap\hdbclient; en macOS/Linux definir antes la variable de entorno
|
||||||
|
# HanaDll con la ruta de Sap.Data.Hana.Core.v2.1.dll (MSBuild la toma como propiedad, ver README).
|
||||||
|
param([string]$Destino)
|
||||||
$ErrorActionPreference = 'Stop'
|
$ErrorActionPreference = 'Stop'
|
||||||
Set-Location $PSScriptRoot
|
Set-Location $PSScriptRoot
|
||||||
|
|
||||||
$csproj = 'Net.Business.Services\Net.Business.Services.csproj'
|
$csproj = 'Net.Business.Services/Net.Business.Services.csproj'
|
||||||
$version = ([xml](Get-Content $csproj)).Project.PropertyGroup.AssemblyVersion | Where-Object { $_ } | Select-Object -First 1
|
$props = ([xml](Get-Content $csproj)).Project.PropertyGroup
|
||||||
if (-not $version) { throw "No se encontró AssemblyVersion en $csproj" }
|
$version = $props.AssemblyVersion | Where-Object { $_ } | Select-Object -First 1
|
||||||
|
if (-not $version) { throw "No se encontro AssemblyVersion en $csproj" }
|
||||||
|
# Version, AssemblyVersion y FileVersion deben ser iguales (Swagger muestra AssemblyVersion; se desalinearon en la 2.1.12-2.1.15)
|
||||||
|
$otras = @($props.Version, $props.FileVersion) | Where-Object { $_ }
|
||||||
|
if ($otras | Where-Object { $_ -ne $version }) { throw "Version/AssemblyVersion/FileVersion del csproj no coinciden (AssemblyVersion=$version)" }
|
||||||
|
# Semver: el nombre lleva 3 numeros (2.1.15.0 -> 2.1.15); el cuarto del csproj siempre es 0
|
||||||
|
$version = ($version -replace '\.0$', '')
|
||||||
|
|
||||||
$out = "dist\ITCA_API_SAP_WMS_v$version"
|
$out = "dist/ITCA_API_SAP_WMS_v$version"
|
||||||
$zip = "dist\ITCA_API_SAP_WMS_v$version.zip"
|
$zip = "dist/ITCA_API_SAP_WMS_v$version.zip"
|
||||||
Remove-Item $out, $zip -Recurse -Force -ErrorAction SilentlyContinue
|
Remove-Item $out, $zip -Recurse -Force -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
dotnet publish $csproj -c Release -r win-x64 --self-contained false -o $out
|
dotnet publish $csproj -c Release -r win-x64 --self-contained false -o $out
|
||||||
if ($LASTEXITCODE -ne 0) { throw 'dotnet publish falló' }
|
if ($LASTEXITCODE -ne 0) { throw 'dotnet publish fallo' }
|
||||||
|
|
||||||
Remove-Item "$out\appsettings.json", "$out\appsettings.Development.json" -ErrorAction SilentlyContinue
|
Remove-Item "$out/appsettings.json", "$out/appsettings.Development.json" -ErrorAction SilentlyContinue
|
||||||
Compress-Archive -Path "$out\*" -DestinationPath $zip
|
Compress-Archive -Path "$out/*" -DestinationPath $zip
|
||||||
Write-Host "Generado: $zip"
|
Write-Host "Generado: $zip"
|
||||||
|
|
||||||
|
if ($Destino) {
|
||||||
|
Copy-Item $zip $Destino -Force
|
||||||
|
Write-Host "Copiado a: $Destino"
|
||||||
|
}
|
||||||
Reference in new issue
Block a user