2026-10-05 17:53:41 -05:00
using Microsoft.Extensions.Configuration ;
using Microsoft.Extensions.Logging.Abstractions ;
using Net.Business.Entities ;
using Net.Business.Entities.ServicesLayer ;
using Net.Business.Entities.ServicesLayer.CreditNotes ;
using Net.Business.Entities.ServicesLayer.InventoryGenEntries ;
using Net.Business.Entities.ServicesLayer.InventoryGenExits ;
using Net.Business.Entities.ServicesLayer.Invoices ;
using Net.Business.Entities.ServicesLayer.Items ;
using Net.Business.Entities.ServicesLayer.ReturnRequest ;
using Net.Business.Entities.ServicesLayer.StockTransfer ;
using Net.Business.Entities.WMS.HojaRuta ;
using Net.Business.Entities.WMS.Picking ;
using Net.Business.Entities.WMS.ProcesoPicking ;
using Net.Connection ;
using Net.Connection.ServiceLayer ;
using Net.CrossCotting ;
using Net.Data.HelperData.Correlativo ;
using Net.Data.HelperData.HelperProcesoPermitido ;
using Net.Data.HelperData.Stock ;
using Net.Data.ServicesLayer ;
using Net.Data.ServicesLayer.CreditNotes ;
using Net.Data.ServicesLayer.GlobalData ;
using Net.Data.ServicesLayer.InventoryGenExits ;
using Net.Data.ServicesLayer.ReturnRequest ;
using Net.Data.ServicesLayer.StockTransfer ;
using Sap.Data.Hana ;
using System ;
using System.Collections.Generic ;
using System.Linq ;
using System.Net.Http ;
using System.Text.RegularExpressions ;
using System.Threading.Tasks ;
namespace Net.Data.WMS.Picking
{
public class PickingRepository : RepositoryBase < object >, IPickingRepository
{
#region Variables Globales
private string _aplicacionName ;
private string _metodoName ;
private readonly Regex regex = new Regex ( @"<(\w+)>.*" );
private readonly IHttpClientFactory _clientFactory ;
private readonly IConfiguration _configuration ;
#endregion
public PickingRepository ( IConnectionSQL context , IHttpClientFactory clientFactory , IConfiguration configuration ) : base ( context )
{
_clientFactory = clientFactory ;
_configuration = configuration ;
_aplicacionName = GetType (). Name ;
}
2026-10-14 19:31:06 -05:00
// 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
2026-10-05 17:53:41 -05:00
public async Task < BE_ResultadoTransaccion < object >> Create ( BE_PickingRequest entidad )
{
_metodoName = regex . Match ( System . Reflection . MethodBase . GetCurrentMethod (). DeclaringType . Name ). Groups [ 1 ]. Value . ToString ();
ResultadoTransaccion < object > resultadoTransaccion = new ResultadoTransaccion < object >( _metodoName , _aplicacionName );
if ( entidad . DocEntry == 0 )
{
return resultadoTransaccion . GetInformacionResultado ( "El campo docEntry no puede ser cero o vacio" , entidad );
}
return await Task . Run (() =>
{
try
{
2026-10-14 19:31:06 -05:00
// Datos del motivo (@SYP_MOTIVOS): almacen destino, si es reproceso y tipo/descripcion de la NC
2026-10-05 17:53:41 -05:00
var Almacen = HelperProcesoPermitido . GetMotivoAlmacen ( entidad . CodMotivo , out bool Reproceso , out string MotivoNotaCredito , out string DescripcionMotivo );
if ( string . IsNullOrEmpty ( entidad . Proceso ))
{
return resultadoTransaccion . GetInformacionResultado ( "SAP: Se necesita definir el proceso para continuar con el proceso." , entidad );
}
if ( entidad . IdEstadoCubico == 0 )
{
return resultadoTransaccion . GetInformacionResultado ( "SAP: Se necesita indicar el IdEstado Cubico para continuar con el proceso." , entidad );
}
int Existe = HelperProcesoPermitido . Get ( entidad . Proceso , entidad . IdEstadoCubico , "PICKING" , out List < BE_ProcesoPicking > ListaProceso );
var Proceso = ListaProceso . FirstOrDefault ();
if ( Existe == 0 )
{
return resultadoTransaccion . GetInformacionResultado ( "El Proceso y estado no se encuentra configurado para este EndPoint" , entidad );
}
var TotalDetalle = 0 ;
var TotalDetalle_Anulado = 0 ;
var TotalDetalle_Completo = 0 ;
if ( Existe > 1 )
{
TotalDetalle = entidad . Detalle . Count ();
TotalDetalle_Anulado = entidad . Detalle . Where ( x => x . Estado == "A" ). Count ();
TotalDetalle_Completo = entidad . Detalle . Where ( x => x . Estado == "C" ). Count ();
if ( TotalDetalle == TotalDetalle_Anulado )
{
Proceso = ListaProceso . FirstOrDefault ( x => x . Code == "013" );
if ( Reproceso )
{
Proceso = ListaProceso . FirstOrDefault ( x => x . Code == "017" );
}
}
else
{
Proceso = ListaProceso . FirstOrDefault ( x => x . Code == "012" );
}
}
else
{
TotalDetalle = entidad . Detalle . Count ();
TotalDetalle_Anulado = entidad . Detalle . Where ( x => x . Estado == "A" ). Count ();
TotalDetalle_Completo = entidad . Detalle . Where ( x => x . Estado == "C" ). Count ();
}
//=== REALIZA PROCESO COMPLETO DE ACTUALIZACION
HelperProcesoPiciking . Set ( entidad . Proceso , entidad . IdEstadoCubico , entidad . DocEntry , entidad . CodMotivo , Proceso . Code );
2026-10-14 08:47:44 -05:00
// === Solo para el proceso "ULTIMA MILLA": "notificacion" distinta de "cierre"
2026-10-05 17:53:41 -05:00
// (o ausente) es la notificacion preliminar en tiempo real - solo actualiza
// estado en HANA (ya hecho arriba), NUNCA debe crear documentos en SAP.
2026-10-14 08:47:44 -05:00
// DESPACHO, PICKING y demas procesos no dependen de este campo.
if ( string . Equals ( entidad . Proceso , "ULTIMA MILLA" , StringComparison . OrdinalIgnoreCase )
&& ! string . Equals ( entidad . Notificacion , "cierre" , StringComparison . OrdinalIgnoreCase ))
2026-10-05 17:53:41 -05:00
{
return resultadoTransaccion . GetInformacionResultado ( entidad . DocEntry , entidad . NumOrden , "Estado actualizado correctamente" , entidad );
}
2026-10-14 19:31:06 -05:00
// Todas las lineas completas: no hay nada que devolver (el tipo de documento 9 sigue el flujo)
2026-10-05 17:53:41 -05:00
if ( TotalDetalle == TotalDetalle_Completo && entidad . IdTipoDoc != 9 )
{
return resultadoTransaccion . GetInformacionResultado ( entidad . DocEntry , entidad . NumOrden , "Se registro correctamente...!!!" , entidad );
}
#region PROCESO DE NOTA DE CREDITO
2026-10-14 19:31:06 -05:00
// DESPACHO (estado 5 o 6): NC desde la factura; no aplica para IdTipoDoc 9
2026-10-05 17:53:41 -05:00
if ( entidad . Proceso . ToUpper () == "DESPACHO" && ( entidad . IdEstadoCubico == 6 || entidad . IdEstadoCubico == 5 ))
{
if ( entidad . IdTipoDoc != 9 )
{
CreditNotesRepository _creditNotesRepository = new CreditNotesRepository ( _clientFactory , _configuration , context );
BE_CreditNotesNotes creditNotesNotes = new BE_CreditNotesNotes
{
DocObjectCode = "oCreditNotes" ,
DocumentLines = new List < BE_CreditNotesLine >()
};
InvoiceRepository invoicestRepository = new InvoiceRepository ( _clientFactory , _configuration , context );
BE_ResultadoTransaccion < BE_InvoicesConsulta > response_invoicesRepository = invoicestRepository . GetInvoice ( entidad . DocEntry ). Result ;
if ( response_invoicesRepository . DocEntry == - 1 )
{
return resultadoTransaccion . GetInformacionResultado ( "SAP: La Factura no se encuentra" , entidad );
}
BE_InvoicesConsulta _invoices = response_invoicesRepository . ListData . FirstOrDefault ();
if ( _invoices . DocumentStatus != "bost_Open" )
{
var Response_Reopen = invoicestRepository . ReOpen ( _invoices . DocEntry ). Result ;
if ( Response_Reopen . DocEntry == - 1 )
{
2026-10-14 10:15:40 -05:00
return resultadoTransaccion . GetInformacionResultado ( "SAP: La Factura no se puede reabrir. " + Response_Reopen . Message , entidad );
2026-10-05 17:53:41 -05:00
}
}
2026-10-14 19:31:06 -05:00
// 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)
2026-10-05 17:53:41 -05:00
string Serie = _invoices . U_SYP_MDTD == "03" ? "BC10" : "FC10" ;
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 =>
{
var Item = _invoices . DocumentLines . ToList (). FirstOrDefault ( t => t . LineNum == x . Item );
if ( Item != null )
{
if ( Item . Quantity - x . CantidadPedida > 0 )
{
creditNotesNotes . DocumentLines . Add ( new BE_CreditNotesLine ()
{
Currency = _invoices . DocCurrency ,
BatchNumbers = new List < BE_CreditNotesBatchNumbers >(),
ItemCode = Item . ItemCode ,
ItemDescription = Item . ItemDescription ,
PriceAfterVAT = Item . PriceAfterVAT ,
Quantity = Item . Quantity - x . CantidadPedida ,
WarehouseCode = Item . WarehouseCode ,
BaseEntry = entidad . DocEntry ,
BaseLine = x . Item ,
BaseType = 13 ,
LineNum = x . Item ,
U_ITC_ALMDES = (( Almacen == "ALMPRO" ) ? Item . WarehouseCode : Almacen ),
});
}
}
});
// == REALIZA DISTRIBUCION DE STOCK POR LOTE / ALMACEN
2026-10-14 10:29:02 -05:00
// Linea por linea: si la factura tiene una Entrega (BaseType 15) como documento
// origen, los lotes salen de la linea de esa Entrega (BaseLine); si no, de la
// propia linea de la factura (LineNum).
var Entregas = new Dictionary < int , BE_DeliveryNotesConsulta >();
var ErroresLote = new List < string >();
2026-10-05 17:53:41 -05:00
creditNotesNotes . DocumentLines . ForEach ( line =>
{
#region CONSULTA A HANA
//List<BE_ItemStock> ListaStock = HelperStock.Get(line.ItemCode, entidad.CodigoAlmacen);
var ReferenciaFactura = _invoices . DocumentLines . FirstOrDefault ( t => t . LineNum == line . LineNum );
2026-10-14 10:29:02 -05:00
List < BE_InvoicesBatchNumbers > ListaStock ;
if ( ReferenciaFactura . BaseType == 15 && ReferenciaFactura . BaseEntry . HasValue && ReferenciaFactura . BaseLine . HasValue )
{
int DocEntryEntrega = ReferenciaFactura . BaseEntry . Value ;
if (! Entregas . TryGetValue ( DocEntryEntrega , out BE_DeliveryNotesConsulta Entrega ))
{
var response_entrega = invoicestRepository . GetDeliveryNote ( DocEntryEntrega ). Result ;
Entrega = ( response_entrega . DocEntry == - 1 ) ? null : response_entrega . ListData ?. FirstOrDefault ();
Entregas [ DocEntryEntrega ] = Entrega ;
}
if ( Entrega == null )
{
ErroresLote . Add ( $"Linea {line.LineNum} ({line.ItemCode}): no se pudo leer la Entrega {DocEntryEntrega} origen de la factura" );
return ;
}
var LineaEntrega = Entrega . DocumentLines ?. FirstOrDefault ( t => t . LineNum == ReferenciaFactura . BaseLine . Value );
ListaStock = LineaEntrega ?. BatchNumbers ?? new List < BE_InvoicesBatchNumbers >();
}
else
{
ListaStock = ReferenciaFactura . BatchNumbers ?? new List < BE_InvoicesBatchNumbers >();
}
2026-10-05 17:53:41 -05:00
double CantidadPendiente = ( double ) line . Quantity ;
var ListOrdenadaFecha = ListaStock ;
ListOrdenadaFecha . ForEach ( Lote =>
{
if ( CantidadPendiente > 0 )
{
if ( Lote . Quantity >= CantidadPendiente )
{
line . BatchNumbers . Add ( new BE_CreditNotesBatchNumbers () { BatchNumber = Lote . BatchNumber , Quantity = CantidadPendiente });
CantidadPendiente = 0 ;
}
else if ( Lote . Quantity < CantidadPendiente )
{
line . BatchNumbers . Add ( new BE_CreditNotesBatchNumbers () { BatchNumber = Lote . BatchNumber , Quantity = Lote . Quantity });
CantidadPendiente -= Lote . Quantity ;
}
}
});
2026-10-14 10:29:02 -05:00
// Hay lotes de origen pero no alcanzan para la cantidad a devolver
if ( ListaStock . Count > 0 && CantidadPendiente > 0.000001 )
{
ErroresLote . Add ( $"Linea {line.LineNum} ({line.ItemCode}): los lotes de origen no cubren la cantidad {line.Quantity} (faltan {CantidadPendiente})" );
}
2026-10-05 17:53:41 -05:00
#endregion
});
2026-10-14 10:29:02 -05:00
if ( ErroresLote . Count > 0 )
{
return resultadoTransaccion . GetInformacionResultado ( "SAP: No se pudo asignar lotes a la nota de credito. " + string . Join ( " | " , ErroresLote ), entidad );
}
2026-10-05 17:53:41 -05:00
if ( creditNotesNotes . DocumentLines . Count > 0 )
{
var result_creditNotesRepository = _creditNotesRepository . SetCreateCreditNotes ( creditNotesNotes ). Result ;
if ( result_creditNotesRepository . StatusCode == - 1 )
{
return resultadoTransaccion . GetInformacionResultado ( result_creditNotesRepository . Message . ToString (), entidad );
}
else
{
2026-10-14 19:31:06 -05:00
// Por cada almacen de origen y almacen destino (U_ITC_ALMDES) distintos se crea una transferencia de stock automatica
2026-10-05 17:53:41 -05:00
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 >();
Transfer . DocumentReferences = new List < BE_StockTransferDocumentReferences >();
ListaUnicaArticulo . ForEach ( x =>
{
var ListLote = new List < BE_StockTransferLineBatchNumber >();
2026-10-14 17:27:29 -05:00
// 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 =>
2026-10-05 17:53:41 -05:00
{
ListLote . Add ( new BE_StockTransferLineBatchNumber ()
{
2026-10-14 17:27:29 -05:00
BatchNumber = g . Key ,
Quantity = g . Sum ( r => r . Quantity )
2026-10-05 17:53:41 -05:00
});
});
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 ;
2026-10-14 19:31:06 -05:00
// OJO: si la transferencia falla se ignora (la NC ya esta creada) y no se informa al consumidor
2026-10-05 17:53:41 -05:00
if ( ResultTransfer . StatusCode == - 1 )
{
}
else
{
ListDocEntry . Add (( int ) ResultTransfer . Data . DocEntry );
}
}
});
});
2026-10-14 19:31:06 -05:00
// Referencia las transferencias creadas en la NC
2026-10-05 17:53:41 -05:00
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 );
}
}
else
{
return resultadoTransaccion . GetInformacionResultado (( int ) entidad . DocEntry , entidad . NumOrden . ToString (), "Se registro correctamente...!!!" , entidad );
}
}
else
{
//=== ASIGNA PRECIO PROMEDIO
foreach ( var x in entidad . Detalle )
{
var Stock = HelperStock . GetConsolidado ( x . CodigoArticulo , entidad . CodigoAlmacen );
if ( Stock . Count == 0 )
{
return resultadoTransaccion . GetInformacionResultado ( "No existe stock disponible para el articulo " + x . CodigoArticulo , entidad );
}
x . PrecioPromedio = Stock . FirstOrDefault (). AvgPrice ;
};
GlobalDataRepository globalDataRepository = new GlobalDataRepository ( _clientFactory , _configuration , context );
var Data_SolicitudSalida = globalDataRepository . GetSolicitudSalida ( entidad . DocEntry ). Result . ListData . FirstOrDefault ();
InventoryGenExitsRepository inventoryGenExitsRepository = new InventoryGenExitsRepository ( _clientFactory , _configuration , context );
BE_InventoryGenExits _InventoryGenExits = new BE_InventoryGenExits ()
{
DocObjectCode = "oInventoryGenExit" ,
DocDate = DateTime . Now ,
DocDueDate = DateTime . Now ,
PaymentGroupCode = "-2" ,
DocCurrency = "S/" ,
DocRate = 1 ,
DocumentLines = new List < BE_InventoryGenExitsLine >(),
U_SYP_TPOSALME = Data_SolicitudSalida . U_ITC_MDTS ,
U_ITC_WMS_NUMINV = "" ,
U_ARD_PERSOLIC = Data_SolicitudSalida . U_ITC_USRSOL ,
Comments = Data_SolicitudSalida . U_ITC_MDCM
};
int LineNum = 0 ;
entidad . Detalle . ForEach ( detalle =>
{
_InventoryGenExits . DocumentLines . Add ( new BE_InventoryGenExitsLine ()
{
Currency = "S/" ,
ItemCode = detalle . CodigoArticulo ,
ItemDescription = Data_SolicitudSalida . ITCA_SOLSALMERDETCollection . FirstOrDefault ( item_s => item_s . LineId == detalle . Item ). U_ITC_ItemName ,
Quantity = ( double ) detalle . CantidadPedida ,
LineNum = LineNum ,
PriceAfterVAT = detalle . PrecioPromedio ,
LineTotal = detalle . PrecioPromedio * ( double ) detalle . CantidadPedida ,
WarehouseCode = entidad . CodigoAlmacen ,
AccountCode = Data_SolicitudSalida . ITCA_SOLSALMERDETCollection . FirstOrDefault ( item_s => item_s . LineId == detalle . Item ). U_ITC_AccCode ,
BatchNumbers = new List < BE_InventoryGenExitsBatchNumbers >(),
CostingCode = Data_SolicitudSalida . ITCA_SOLSALMERDETCollection . FirstOrDefault ( item_s => item_s . LineId == detalle . Item ). U_ITC_CCOST ,
});
LineNum ++;
});
_InventoryGenExits . DocumentLines . ForEach ( line =>
{
#region CONSULTA A HANA
List < BE_ItemStock > ListaStock = HelperStock . Get ( line . ItemCode , entidad . CodigoAlmacen );
var CantidadPendiente = line . Quantity ;
var ListOrdenadaFecha = ListaStock . OrderBy ( x => x . ExpDate ). ToList ();
var cantidadStock = ListaStock . Sum ( x => x . OnHandQty );
if ( cantidadStock < line . Quantity )
{
throw new Exception ( $"El producto {line.ItemCode} no cuenta con stock suficiente" );
}
else
{
ListOrdenadaFecha . ForEach ( Lote =>
{
if ( CantidadPendiente > 0 )
{
if ( Lote . OnHandQty >= CantidadPendiente )
{
line . BatchNumbers . Add ( new BE_InventoryGenExitsBatchNumbers () { BatchNumber = Lote . DistNumber , Quantity = CantidadPendiente });
CantidadPendiente = 0 ;
}
else if ( Lote . OnHandQty < CantidadPendiente )
{
line . BatchNumbers . Add ( new BE_InventoryGenExitsBatchNumbers () { BatchNumber = Lote . DistNumber , Quantity = Lote . OnHandQty });
CantidadPendiente -= Lote . OnHandQty ;
}
}
});
}
#endregion
});
var Result = inventoryGenExitsRepository . SetCreateInventoryGenExits ( _InventoryGenExits , false ). Result ;
if ( Result . StatusCode == - 1 )
{
return resultadoTransaccion . GetInformacionResultado ( Result . Message . ToString (), entidad );
}
return resultadoTransaccion . GetInformacionResultado (( int ) entidad . DocEntry , entidad . NumOrden . ToString (), "Se registro correctamente...!!!" , entidad );
}
}
2026-10-14 19:31:06 -05:00
// PICKING (estado 13): NC con un lote nuevo (fecha y hora) por linea, sin usar los lotes de la factura; solo lineas con diferencia
2026-10-05 17:53:41 -05:00
if ( entidad . Proceso . ToUpper () == "PICKING" && entidad . IdEstadoCubico == 13 )
{
CreditNotesRepository _creditNotesRepository = new CreditNotesRepository ( _clientFactory , _configuration , context );
BE_CreditNotesNotes creditNotesNotes = new BE_CreditNotesNotes
{
DocObjectCode = "oCreditNotes" ,
DocumentLines = new List < BE_CreditNotesLine >()
};
InvoiceRepository invoicestRepository = new InvoiceRepository ( _clientFactory , _configuration , context );
BE_ResultadoTransaccion < BE_InvoicesConsulta > response_invoicesRepository = invoicestRepository . GetInvoice ( entidad . DocEntry ). Result ;
if ( response_invoicesRepository . DocEntry == - 1 )
{
return resultadoTransaccion . GetInformacionResultado ( "SAP: La Factura no se encuentra" , entidad );
}
BE_InvoicesConsulta _invoices = response_invoicesRepository . ListData . FirstOrDefault ();
if ( _invoices . DocumentStatus != "bost_Open" )
{
var Response_Reopen = invoicestRepository . ReOpen ( _invoices . DocEntry ). Result ;
if ( Response_Reopen . DocEntry == - 1 )
{
2026-10-14 10:15:40 -05:00
return resultadoTransaccion . GetInformacionResultado ( "SAP: La Factura no se puede reabrir. " + Response_Reopen . Message , entidad );
2026-10-05 17:53:41 -05:00
}
}
2026-10-14 19:31:06 -05:00
// Serie segun el tipo de documento de la factura (03 = boleta -> BC10, otro -> FC10)
2026-10-05 17:53:41 -05:00
string Serie = _invoices . U_SYP_MDTD == "03" ? "BC10" : "FC10" ;
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 =>
{
var Item = _invoices . DocumentLines . ToList (). FirstOrDefault ( t => t . LineNum == x . Item );
2026-10-14 17:27:29 -05:00
// Solo lineas con diferencia a devolver (igual que DESPACHO): una linea completa daria cantidad 0
if ( Item != null && Item . Quantity - x . CantidadPedida > 0 )
2026-10-05 17:53:41 -05:00
{
creditNotesNotes . DocumentLines . Add ( new BE_CreditNotesLine ()
{
Currency = _invoices . DocCurrency ,
BatchNumbers = new List < BE_CreditNotesBatchNumbers >(),
ItemCode = Item . ItemCode ,
ItemDescription = Item . ItemDescription ,
PriceAfterVAT = Item . PriceAfterVAT ,
Quantity = Item . Quantity - x . CantidadPedida ,
WarehouseCode = Item . WarehouseCode ,
BaseEntry = entidad . DocEntry ,
BaseLine = x . Item ,
BaseType = 13 ,
LineNum = x . Item ,
U_ITC_ALMDES = (( Almacen == "ALMPRO" ) ? Item . WarehouseCode : Almacen ),
});
}
});
creditNotesNotes . DocumentLines . ForEach ( line =>
{
line . BatchNumbers . Add ( new BE_CreditNotesBatchNumbers () { BatchNumber = DateTime . Now . ToString ( "yyyyMMddhhmmss" ), Quantity = ( double ) line . Quantity });
});
if ( creditNotesNotes . DocumentLines . Count > 0 )
{
var result_creditNotesRepository = _creditNotesRepository . SetCreateCreditNotes ( creditNotesNotes ). Result ;
if ( result_creditNotesRepository . StatusCode == - 1 )
{
return resultadoTransaccion . GetInformacionResultado ( result_creditNotesRepository . Message . ToString (), entidad );
}
else
{
2026-10-14 19:31:06 -05:00
// Transferencias automaticas por almacen origen/destino (igual que en DESPACHO)
2026-10-05 17:53:41 -05:00
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 . 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 >();
2026-10-14 17:27:29 -05:00
// 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 =>
2026-10-05 17:53:41 -05:00
{
ListLote . Add ( new BE_StockTransferLineBatchNumber ()
{
2026-10-14 17:27:29 -05:00
BatchNumber = g . Key ,
Quantity = g . Sum ( r => r . Quantity )
2026-10-05 17:53:41 -05:00
});
});
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 ;
2026-10-14 19:31:06 -05:00
// OJO: si la transferencia falla se ignora (la NC ya esta creada)
2026-10-05 17:53:41 -05:00
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 );
}
}
else
{
return resultadoTransaccion . GetInformacionResultado (( int ) entidad . DocEntry , entidad . NumOrden . ToString (), "Se registro correctamente...!!!" , entidad );
}
}
2026-10-14 19:31:06 -05:00
// 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
2026-10-05 17:53:41 -05:00
if ( entidad . Proceso . ToUpper () == "ULTIMA MILLA" && entidad . IdEstadoCubico == 23 )
{
if ( Proceso . U_ITCA_GNRPROC == "SOLICITUD DEV" && ! Reproceso )
{
if ( string . IsNullOrEmpty ( Almacen ))
{
return resultadoTransaccion . GetInformacionResultado ( "El motivo no tiene asignado un almacen de destino" , entidad );
}
ReturnRequestRepository returnRequestRepository = new ReturnRequestRepository ( _clientFactory , _configuration );
InvoiceRepository invoicestRepository = new InvoiceRepository ( _clientFactory , _configuration , context );
BE_ResultadoTransaccion < BE_InvoicesConsulta > response_invoicesRepository = invoicestRepository . GetInvoice ( entidad . DocEntry ). Result ;
if ( response_invoicesRepository . DocEntry == - 1 )
{
return resultadoTransaccion . GetInformacionResultado ( "SAP: La Factura no se encuentra" , entidad );
}
BE_InvoicesConsulta invoices = response_invoicesRepository . ListData . FirstOrDefault ();
BE_ReturnRequest _ReturnRequest = new BE_ReturnRequest ()
{
CardCode = invoices . CardCode ,
CardName = invoices . CardName ,
DocCurrency = invoices . DocCurrency ,
DocDate = DateTime . Now ,
DocDueDate = DateTime . Now ,
DocObjectCode = "oReturnRequest" ,
PaymentGroupCode = invoices . PaymentGroupCode ,
U_SYP_MOTNC = DescripcionMotivo ,
U_SYP_TPONC = MotivoNotaCredito ,
U_SYP_MDTO = invoices . U_SYP_MDTD ,
U_SYP_MDCO = invoices . U_SYP_MDCD ,
U_SYP_MDSO = invoices . U_SYP_MDSD ,
U_SYP_FECHAREF = invoices . DocDate ,
DocumentLines = new List < BE_ReturnRequestLine >()
};
var ListaDetalle = entidad . Detalle . Where ( x => x . Estado != "C" ). ToList ();
ListaDetalle . ForEach ( detalle =>
{
var Detalle_Factura = invoices . DocumentLines . FirstOrDefault ( x => x . LineNum == detalle . Item );
_ReturnRequest . DocumentLines . Add ( new BE_ReturnRequestLine ()
{
LineNum = detalle . Item ,
BaseEntry = entidad . DocEntry ,
BaseLine = Detalle_Factura . LineNum ,
BatchNumbers = new List < BE_ReturnRequestBatchNumbers >(),
Currency = invoices . DocCurrency ,
ItemCode = Detalle_Factura . ItemCode ,
ItemDescription = Detalle_Factura . ItemDescription ,
PriceAfterVAT = Detalle_Factura . PriceAfterVAT ,
Quantity = ( Detalle_Factura . Quantity - detalle . CantidadPedida ),
WarehouseCode = Detalle_Factura . WarehouseCode ,
BaseType = 13 ,
U_ITC_ALMDES = (( Almacen == "ALMPRO" ) ? Detalle_Factura . WarehouseCode : Almacen ),
});
});
var _returnRequestRepository_Result = returnRequestRepository . SetCreateReturnRequest ( _ReturnRequest ). Result ;
if ( _returnRequestRepository_Result . StatusCode == - 1 )
{
return resultadoTransaccion . GetInformacionResultado ( _returnRequestRepository_Result . Message . ToString (), entidad );
}
return resultadoTransaccion . GetInformacionResultado (( int ) _returnRequestRepository_Result . Data . DocEntry , _returnRequestRepository_Result . Data . DocNum . ToString (), "Se registro correctamente...!!!" , entidad );
}
}
#endregion
return resultadoTransaccion . GetInformacionResultado ( entidad . DocEntry , entidad . NumOrden , "Registrado Correctamente" , entidad );
}
catch ( Exception ex )
{
return resultadoTransaccion . GetInformacionResultado ( ex . Message . ToString (), entidad );
}
});
}
2026-10-14 19:31:06 -05:00
// POST /api/Picking/ruta: registra el estado de la hoja de ruta (inicio/fin de ruta) en HANA; no crea documentos en SAP
2026-10-05 17:53:41 -05:00
public async Task < BE_ResultadoTransaccion < object >> CreateHojaRuta ( BE_HojaRutaEntities entidad )
{
_metodoName = regex . Match ( System . Reflection . MethodBase . GetCurrentMethod (). DeclaringType . Name ). Groups [ 1 ]. Value . ToString ();
ResultadoTransaccion < object > resultadoTransaccion = new ResultadoTransaccion < object >( _metodoName , _aplicacionName );
if ( entidad . DocEntry == 0 )
{
return resultadoTransaccion . GetInformacionResultado ( "El campo docEntry no puede ser cero o vacio" , entidad );
}
return await Task . Run (() =>
{
try
{
if ( string . IsNullOrEmpty ( entidad . Proceso ))
{
return resultadoTransaccion . GetInformacionResultado ( "SAP: Se necesita definir el proceso para continuar con el proceso." , entidad );
}
if ( entidad . IdEstadoCubico == 0 )
{
return resultadoTransaccion . GetInformacionResultado ( "SAP: Se necesita indicar el IdEstado Cubico para continuar con el proceso." , entidad );
}
int Existe = HelperProcesoPermitido . Get ( entidad . Proceso , entidad . IdEstadoCubico , "HOJARUTA" , out List < BE_ProcesoPicking > ListaProceso );
if ( Existe == 0 )
{
return resultadoTransaccion . GetInformacionResultado ( "El Proceso y estado no se encuentra configurado para este EndPoint" , entidad );
}
HelperProcesoPiciking . Set ( entidad . Proceso , entidad . IdEstadoCubico , entidad . DocEntry , HelperProcesoPermitido . ConvertTimeToInteger ( entidad . HoraInicio ), HelperProcesoPermitido . ConvertTimeToInteger ( entidad . FinRuta ), entidad . CodMotivo , ListaProceso . FirstOrDefault (). Code );
return resultadoTransaccion . GetInformacionResultado ( entidad . DocEntry , entidad . DocEntry . ToString (), "Registrado Correctamente" , entidad );
}
catch ( Exception ex )
{
return resultadoTransaccion . GetInformacionResultado ( ex . Message . ToString (), entidad );
}
});
}
2026-10-14 19:31:06 -05:00
// POST /api/Picking/HojaRuta/{docEntry}/Cerrar: cierra el UDO ARD_PHR en SAP (se llama una sola vez al terminar la ruta)
2026-10-05 17:53:41 -05:00
public async Task < BE_ResultadoTransaccion < object >> CerrarHojaRuta ( int docEntry )
{
_metodoName = "CerrarHojaRuta" ;
ResultadoTransaccion < object > resultadoTransaccion = new ResultadoTransaccion < object >( _metodoName , _aplicacionName );
try
{
// Hoja de Ruta es un UDO en SAP (ARD_PHR) - mismo patron que
// SolicitudTransferencia/{docEntry}/cerrar (POST {recurso}({docEntry})/Close).
var connectServiceLayer = new ConnectionServiceLayer ( _configuration , _clientFactory );
await connectServiceLayer . PostActionAsync ( $"ARD_PHR({docEntry})/Close" );
return resultadoTransaccion . GetInformacionResultado ( docEntry , docEntry . ToString (), "Cerrado Correctamente" , ( object ) null );
}
catch ( Exception ex )
{
return resultadoTransaccion . GetInformacionResultado ( ex . Message . ToString (), ( object ) null );
}
}
}
}