using Dm.filter; using MailKit.Search; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using Org.BouncyCastle.Asn1.Ocsp; using SqlSugar; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; using System.Reflection.Metadata; using System.Security.Policy; using System.Text; using System.Threading.Tasks; using WIDESEA_Common.OrderEnum; using WIDESEA_Common.StockEnum; using WIDESEA_Core; using WIDESEA_Core.BaseRepository; using WIDESEA_Core.Helper; using WIDESEA_Core.Util; using WIDESEA_DTO.Allocate; using WIDESEA_DTO.Basic; using WIDESEA_DTO.Inbound; using WIDESEA_DTO.Outbound; using WIDESEA_DTO.ReturnMES; using WIDESEA_IBasicService; using WIDESEA_IOutboundService; using WIDESEA_Model.Models; using WIDESEA_Model.Models.Outbound; namespace WIDESEA_BasicService { public class InvokeMESService : IInvokeMESService { private readonly IHttpClientFactory _httpClientFactory; private readonly ILogger _logger; private string UserName = "12312"; private string Password = "1"; private readonly IRepository _feedbacktomesRepository; private readonly IRepository _stockInfoDetailRepository; private readonly IRepository _stockInfoRepository; private readonly IRepository _inboundOrderRepository; private readonly IRepository _inboundOrderDetailRepository; private readonly IRepository _pickingRecoreRepository; private readonly IMaterialUnitService _materialUnitService; private readonly IOutboundOrderService _outboundOrderService; private readonly IOutboundOrderDetailService _outboundOrderDetailService; private readonly IOutStockLockInfoService _outStockLockInfoService; private readonly IRepository _interfacelogRepository; private readonly HttpClientHelper _httpClientHelper; private readonly IUnitOfWorkManage _unitOfWorkManage; // 存储资源ID及其对应的锁对象。使用 ConcurrentDictionary 确保对字典操作本身的线程安全。 private static readonly ConcurrentDictionary _resourceLocks = new ConcurrentDictionary(); // 全局静态锁:用于保护 _resourceLocks 字典中 GetOrAdd 或 TryRemove 时的竞争 private static readonly object _globalLocker = new object(); public InvokeMESService(IHttpClientFactory httpClientFactory, ILogger logger, IRepository feedbacktomesRepository, IRepository stockInfoDetailRepository, IRepository stockInfoRepository, IRepository inboundOrderRepository, IOutboundOrderService outboundOrderService, IOutboundOrderDetailService outboundOrderDetailService, IOutStockLockInfoService outStockLockInfoService, IMaterialUnitService materialUnitService, IRepository pickingRecoreRepository, IRepository interfacelogRepository, IRepository inboundOrderDetailRepository, HttpClientHelper httpClientHelper, IUnitOfWorkManage unitOfWorkManage) { _httpClientFactory = httpClientFactory; _logger = logger; _feedbacktomesRepository = feedbacktomesRepository; _stockInfoDetailRepository = stockInfoDetailRepository; _stockInfoRepository = stockInfoRepository; _inboundOrderRepository = inboundOrderRepository; _outboundOrderService = outboundOrderService; _outboundOrderDetailService = outboundOrderDetailService; _outStockLockInfoService = outStockLockInfoService; _materialUnitService = materialUnitService; _pickingRecoreRepository = pickingRecoreRepository; _interfacelogRepository = interfacelogRepository; _inboundOrderDetailRepository = inboundOrderDetailRepository; _httpClientHelper = httpClientHelper; _unitOfWorkManage = unitOfWorkManage; } /// /// 入库反馈 /// /// /// /// public async Task FeedbackInbound(FeedbackInboundRequestModel model) { string json = JsonConvert.SerializeObject(model, new JsonSerializerSettings { ContractResolver = new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver() }); var content = new StringContent(json, Encoding.UTF8, "application/json"); var _client = _httpClientFactory.CreateClient("MESUrl"); _client.DefaultRequestHeaders.Clear(); _client.DefaultRequestHeaders.Add("Accept", "application/json"); _logger.LogInformation("InvokeMESService FeedbackInbound : " + json); var response = await _client.PostAsync("AldMaterialWarehousing/MaterialWarehousing", content); string body = await response.Content.ReadAsStringAsync(); _logger.LogInformation("InvokeMESService FeedbackInbound body: " + body); if (!response.IsSuccessStatusCode) { throw new HttpRequestException(body); } return JsonConvert.DeserializeObject(body); } /// /// 出库反馈 /// /// /// /// public async Task FeedbackOutbound(FeedbackOutboundRequestModel model) { string json = JsonConvert.SerializeObject(model, new JsonSerializerSettings { ContractResolver = new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver() }); var content = new StringContent(json, Encoding.UTF8, "application/json"); var _client = _httpClientFactory.CreateClient("MESUrl"); _client.DefaultRequestHeaders.Clear(); _client.DefaultRequestHeaders.Add("Accept", "application/json"); _logger.LogInformation("InvokeMESService FeedbackOutbound : " + model.orderNo + " , " + json); var response = await _client.PostAsync("AldMaterialOutbound/MaterialOutbound", content); string body = await response.Content.ReadAsStringAsync(); if (!response.IsSuccessStatusCode) { throw new HttpRequestException(body); } _logger.LogInformation("InvokeMESService FeedbackOutbound body: " + body); return JsonConvert.DeserializeObject(body); } public async Task FeedbackAllocate(AllocateDto model) { _logger.LogInformation($"InvokeMESService FeedbackAllocate 序列化前: {JsonConvert.SerializeObject(model)}"); string json = JsonConvert.SerializeObject(model, new JsonSerializerSettings { ContractResolver = new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver(), NullValueHandling = NullValueHandling.Include }); var content = new StringContent(json, Encoding.UTF8, "application/json"); var _client = _httpClientFactory.CreateClient("MESUrl"); _client.DefaultRequestHeaders.Clear(); _client.DefaultRequestHeaders.Add("Accept", "application/json"); _logger.LogInformation("InvokeMESService FeedbackAllocate : " + json); var response = await _client.PostAsync("AldAllocationOperation/AllocationOperation", content); string body = await response.Content.ReadAsStringAsync(); _logger.LogInformation("InvokeMESService FeedbackAllocate body: " + body); if (!response.IsSuccessStatusCode) { throw new HttpRequestException(body); } return JsonConvert.DeserializeObject(body); } public async Task NewMaterielToMes(MaterielToMesDTO model) { string json = JsonConvert.SerializeObject(model, new JsonSerializerSettings { ContractResolver = new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver() }); //string userDataEncoded = Uri.EscapeDataString(json); ////string baseUrl = "http://mestest.ald.com//OrBitWCFServiceR15/orbitwebapi.ashx?"; //string userTicket = await GetToken(UserName, Password); //string api = "WMS_BarcodeInformation"; var client = _httpClientFactory.CreateClient("MESUrl"); // 拼接 URL 参数 // string url = $"{client.BaseAddress}UserTicket={userTicket}&API={api}&UserData={userDataEncoded}"; client.DefaultRequestHeaders.Clear(); client.DefaultRequestHeaders.Add("Accept", "application/json"); var content = new StringContent(json, Encoding.UTF8, "application/json"); _logger.LogInformation("InvokeMESService NewMaterielToMes : " + json); using var response = await client.PostAsync("AldBarcodeInformation/BarcodeInformation", content); var responseText = await response.Content.ReadAsStringAsync(); _logger.LogInformation("InvokeMESService NewMaterielToMes body: " + responseText); if (!response.IsSuccessStatusCode) { throw new HttpRequestException(responseText); } return JsonConvert.DeserializeObject(responseText); } public async Task GetToken(String username, string password) { var clientHandler = new HttpClientHandler { AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate, }; //var client = new HttpClient(clientHandler); var client = _httpClientFactory.CreateClient("MESUrl"); client.DefaultRequestHeaders.Clear(); var request = new HttpRequestMessage { Method = HttpMethod.Post, RequestUri = new Uri($"{client.BaseAddress}UserName={username}&UserPassword={password}"), Headers ={ { "Accept", "*/*" }, { "User-Agent", "PostmanRuntime-ApipostRuntime/1.1.0" }, { "Connection", "keep-alive" }, }, Content = new MultipartFormDataContent { }, }; using (var response = await client.SendAsync(request)) { response.EnsureSuccessStatusCode(); var body = await response.Content.ReadAsStringAsync(); if (!response.IsSuccessStatusCode) { throw new HttpRequestException(body); } return body; } } /// /// /// /// /// 入库传1 出库传2 /// public async Task BatchOrderFeedbackToMes(List orderNos, int inout) { try { if (inout == 1) { foreach (var orderNo in orderNos) { try { var stockinfos = _stockInfoRepository.Db.Queryable("info").Where(info => info.StockStatus == 6) .Where(it => SqlFunc.Subqueryable().Where(s => s.StockId == it.Id && s.OrderNo == orderNo).Any()) .ToList(); var feeds = _feedbacktomesRepository.Db.Queryable().Where(x => x.OrderNo == orderNo && x.ReportStatus == 1).Select(o => o.PalletCode).ToList(); var unreports = stockinfos.Where(x => !feeds.Contains(x.PalletCode)).ToList(); if (unreports != null && !unreports.Any()) { _inboundOrderRepository.Db.Updateable().SetColumns(it => new Dt_InboundOrder { ReturnToMESStatus = 1, Remark = "" }) .Where(it => it.InboundOrderNo == orderNo).ExecuteCommand(); var inboundOrder = _inboundOrderRepository.Db.Queryable().First(x => x.InboundOrderNo == orderNo); if (inboundOrder != null) { _inboundOrderDetailRepository.Db.Updateable().SetColumns(it => new Dt_InboundOrderDetail { ReturnToMESStatus = 1 }) .Where(it => it.OrderId == inboundOrder.Id).ExecuteCommand(); } return WebResponseContent.Instance.Error("没有需要回传的数据"); } foreach (var item in unreports) { var lists = _stockInfoDetailRepository.Db.Queryable().Where(x => x.StockId == item.Id).ToList(); if (lists.Any()) { var inboundOrder = _inboundOrderRepository.Db.Queryable().First(x => x.InboundOrderNo == lists.FirstOrDefault().OrderNo); if (inboundOrder != null) { if (inboundOrder.OrderType == (int)InOrderTypeEnum.AllocatInbound)//调拨入库 { var allocate = SqlSugarHelper.DbWMS.Queryable().Where(x => x.OrderNo == inboundOrder.InboundOrderNo).First(); var allocatefeedmodel = new AllocateDto { ReqCode = Guid.NewGuid().ToString(), ReqTime = DateTime.Now.ToString(), BusinessType = "3", FactoryArea = inboundOrder.FactoryArea, OperationType = 1, Operator = inboundOrder.Operator, OrderNo = inboundOrder.UpperOrderNo, fromWarehouse = allocate?.FromWarehouse ?? "", toWarehouse = allocate?.ToWarehouse ?? "", Details = new List() }; var groupedData = lists.GroupBy(item => new { item.MaterielCode, item.InboundOrderRowNo, item.BarcodeUnit, item.WarehouseCode }) .Select(group => new AllocateDtoDetail { MaterialCode = group.Key.MaterielCode, LineNo = group.Key.InboundOrderRowNo, WarehouseCode = group.Key.WarehouseCode, Qty = group.Sum(x => x.BarcodeQty), Unit = group.Key.BarcodeUnit, Barcodes = group.Select(row => new BarcodeInfo { Barcode = row.Barcode, Qty = row.BarcodeQty, BatchNo = row.BatchNo, SupplyCode = row.SupplyCode, Unit = row.BarcodeUnit }).ToList() }).ToList(); allocatefeedmodel.Details = groupedData; //var result = await FeedbackAllocate(allocatefeedmodel); var result = responseModel(inboundOrder, 3, null, allocatefeedmodel); //if (response != null && response.code == 200) if (result != null && result.IsSuccess) { _feedbacktomesRepository.Db.Insertable(new Dt_FeedbackToMes { OrderNo = orderNo, PalletCode = item.PalletCode, ReportStatus = 1 }).ExecuteCommand(); } } else { var feedmodel = new FeedbackInboundRequestModel { reqCode = Guid.NewGuid().ToString(), reqTime = DateTime.Now.ToString(), business_type = inboundOrder.BusinessType, factoryArea = inboundOrder.FactoryArea, operationType = 1, Operator = inboundOrder.Operator, orderNo = inboundOrder.UpperOrderNo, status = inboundOrder.OrderStatus, details = new List() }; var groupedData = lists.GroupBy(item => new { item.MaterielCode, item.SupplyCode, item.BatchNo, item.InboundOrderRowNo, item.BarcodeUnit, item.WarehouseCode }) .Select(group => new FeedbackInboundDetailsModel { materialCode = group.Key.MaterielCode, supplyCode = group.Key.SupplyCode, batchNo = group.Key.BatchNo, lineNo = group.Key.InboundOrderRowNo, qty = group.Sum(x => x.BarcodeQty), // warehouseCode = group.Key.WarehouseCode=="0"?"1072": group.Key.WarehouseCode, warehouseCode = group.Key.WarehouseCode, unit = group.Key.BarcodeUnit, barcodes = group.Select(row => new FeedbackBarcodesModel { barcode = row.Barcode, qty = row.BarcodeQty }).ToList() }).ToList(); feedmodel.details = groupedData; //var result = await FeedbackInbound(feedmodel); var response = responseModel(inboundOrder, 2, feedmodel); //if (result != null && result.code == 200) if (response != null && response.IsSuccess) { _feedbacktomesRepository.Db.Insertable(new Dt_FeedbackToMes { OrderNo = orderNo, PalletCode = item.PalletCode, ReportStatus = 1 }).ExecuteCommand(); var feedstockinfos = _stockInfoRepository.Db.Queryable("info").Where(info => info.StockStatus == 6) .Where(it => SqlFunc.Subqueryable().Where(s => s.StockId == it.Id && s.OrderNo == orderNo).Any()) .ToList(); var feedstomes = _feedbacktomesRepository.Db.Queryable().Where(x => x.OrderNo == orderNo && x.ReportStatus == 1).Select(o => o.PalletCode).ToList(); var feedunreports = feedstockinfos.Where(x => !feedstomes.Contains(x.PalletCode)).ToList(); if (feedunreports != null && !feedunreports.Any()) { _inboundOrderRepository.Db.Updateable().SetColumns(it => new Dt_InboundOrder { ReturnToMESStatus = 1, Remark = "" }) .Where(it => it.InboundOrderNo == orderNo).ExecuteCommand(); var feedinboundOrder = _inboundOrderRepository.Db.Queryable().First(x => x.InboundOrderNo == orderNo); if (feedinboundOrder != null) { _inboundOrderDetailRepository.Db.Updateable().SetColumns(it => new Dt_InboundOrderDetail { ReturnToMESStatus = 1 }) .Where(it => it.OrderId == feedinboundOrder.Id).ExecuteCommand(); } } return WebResponseContent.Instance.Error("回传成功!"); } else { _inboundOrderRepository.Db.Updateable().SetColumns(it => new Dt_InboundOrder { ReturnToMESStatus = 2 }) .Where(it => it.Id == inboundOrder.Id).ExecuteCommand(); _inboundOrderDetailRepository.Db.Updateable().SetColumns(it => new Dt_InboundOrderDetail { ReturnToMESStatus = 2 }) .Where(it => it.OrderId == inboundOrder.Id).ExecuteCommand(); return WebResponseContent.Instance.Error("回传异常!"); } } } } } } catch (Exception ex) { _logger.LogInformation("InvokeMESService BatchOrderFeedbackToMes 回写MES失败: " + ex.Message); return WebResponseContent.Instance.Error(ex.Message); } } } else if (inout == 2) { foreach (var orderNo in orderNos) { var outboundOrder = await _outboundOrderService.Db.Queryable().FirstAsync(x => x.OrderNo == orderNo); if (outboundOrder != null && outboundOrder.IsBatch == 0) { var result = await HandleOutboundOrderToMESCompletion(outboundOrder, orderNo); return result; } else if (outboundOrder != null && outboundOrder.IsBatch == 1) { var result = await HandleOutboundOrderBatchToMESCompletion(outboundOrder, orderNo); return result; } } } //} //else //{ // // 抢锁失败:说明有另一个线程(WCS回调或人工操作)正在处理 // return WebResponseContent.Instance.Error("WMS正在处理此回传任务,请勿重复操作。"); //} } catch (Exception ex) { _logger.LogInformation("InvokeMESService BatchOrderFeedbackToMes : " + ex.Message); } finally { // 2. 【释放内存锁】无论成功失败,必须释放 // MemoryLockManager.ReleaseLock(orderNos[0]); } return WebResponseContent.Instance.OK(); } public HttpResponseResult responseModel(Dt_InboundOrder order, int InterfaceType, FeedbackInboundRequestModel model = null, AllocateDto allocateDto = null) { HttpResponseResult httpResponseResult = new HttpResponseResult(); string reqCode = Guid.NewGuid().ToString(); string reqTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); string requestData = string.Empty; string apiUrl = string.Empty; if (model != null) { apiUrl = AppSettings.GetValue("AldMaterialWarehousing"); httpResponseResult = _httpClientHelper.Post(apiUrl, model.Serialize()); requestData = model.Serialize(); } else { apiUrl = AppSettings.GetValue("AldAllocationOperation"); httpResponseResult = _httpClientHelper.Post(apiUrl, allocateDto.Serialize()); requestData = allocateDto.Serialize(); } httpResponseResult.ApiUrl = apiUrl; bool isSuccess = httpResponseResult.IsSuccess && httpResponseResult.Data.Code == "200"; string message = "成功"; if (!isSuccess) { if (!httpResponseResult.IsSuccess) { message = $"MES接口返回错误,HTTP代码:{httpResponseResult.StatusCode},信息:{httpResponseResult.ErrorMessage}"; } else if (httpResponseResult?.Data?.Code != "200") { message = $"调用MES接口失败,代码:{httpResponseResult?.Data?.Code},信息:{httpResponseResult?.Data?.Message}"; } } Dt_MesReturnRecord mesReturnRecord = new Dt_MesReturnRecord() { ApiUrl = httpResponseResult.ApiUrl, InterfaceType = InterfaceType, OrderId = order.Id, OrderNo = order.InboundOrderNo, RequestCode = reqCode, RequestData = requestData, FailureReason = message, LastReturnTime = DateTime.Now, HttpStatusCode = httpResponseResult.StatusCode.ObjToInt(), ResponseData = httpResponseResult.Content, ReturnType = 0, ReturnCount = 1, ReturnStatus = httpResponseResult.IsSuccess ? 1 : 2, SuccessTime = httpResponseResult.IsSuccess ? DateTime.Now : null }; _unitOfWorkManage.Db.Insertable(mesReturnRecord).ExecuteCommand(); return httpResponseResult; } private async Task HandleOutboundOrderBatchToMESCompletion(Dt_OutboundOrder outboundOrder, string orderNo) { // 定义默认返回(成功态) WebResponseContent response = WebResponseContent.Instance.OK("回传MES处理完成"); //0 = 未回传,1 = 已回传成功,2 = 回传失败 try { // 校验:已回传直接返回错误 //if (outboundOrder.ReturnToMESStatus == 1) //{ // return WebResponseContent.Instance.Error("该单已经回传!"); //} // 查询订单明细(仅查询未回传成功的) var orderDetails = await _outboundOrderDetailService.Db.Queryable() .LeftJoin((o, item) => o.OrderId == item.Id) .Where((o, item) => item.OrderNo == orderNo && item.ReturnToMESStatus != 1) .Select((o, item) => o) .ToListAsync(); if (!orderDetails.Any()) { return WebResponseContent.Instance.Error("暂无需要处理的订单明细"); } var pickingRecords = await _pickingRecoreRepository.Db.Queryable().Where(x => x.OrderNo == orderNo && x.ReturnToMESStatus != 1 && !x.IsCancelled).ToListAsync(); if (!pickingRecords.Any()) return WebResponseContent.Instance.Error("没有需要回传的分拣记录"); var groups = pickingRecords.GroupBy(x => x.FeedBackMesDocumentNo).ToList(); foreach (var group in groups) { List records = group.ToList(); // 该分组下的所有记录 if (string.IsNullOrEmpty(group.Key)) { var emptydocumentNo = UniqueValueGenerator.Generate(); records.ForEach(x => { x.FeedBackMesDocumentNo = emptydocumentNo; }); var result = await _pickingRecoreRepository.Db.Updateable(records).ExecuteCommandAsync(); var interfacelog = new Dt_InterfaceLog { Content = JsonConvert.SerializeObject(records), DocumentNo = emptydocumentNo, OrderNo = orderNo, OrderType = "2", }; _interfacelogRepository.AddData(interfacelog); if (result > 0) { (bool _flowControl, WebResponseContent _value) = await FeedBackBatchToMes(outboundOrder, orderNo, orderDetails, pickingRecords, emptydocumentNo); return _value; } } else { var ilog = _interfacelogRepository.QueryFirst(x => x.DocumentNo == group.Key); if (ilog == null) { var interfacelog = new Dt_InterfaceLog { Content = JsonConvert.SerializeObject(records), DocumentNo = group.Key, OrderNo = orderNo, OrderType = "2", }; _interfacelogRepository.AddData(interfacelog); } (bool _flowControl, WebResponseContent _value) = await FeedBackBatchToMes(outboundOrder, orderNo, orderDetails, pickingRecords, group.Key); return _value; } } //var documentNo = UniqueValueGenerator.Generate(); //(bool flowControl, WebResponseContent value) = await FeedBackBatchToMes(outboundOrder, orderNo, orderDetails, pickingRecords, documentNo); //if (!flowControl) //{ // return value; //} // 回传成功的最终返回 response = WebResponseContent.Instance.OK($"回传MES成功,单据号:{orderNo}"); } catch (Exception ex) { // 全局异常捕获:记录详细日志 + 返回错误 string errorMsg = $"处理出库单回传MES时发生异常 - OrderNo: {orderNo}, Error: {ex.Message}, StackTrace: {ex.StackTrace}"; _logger.LogError(ex, errorMsg); // 记录带异常堆栈的日志 // 异常返回(给前端的友好提示,隐藏堆栈信息) response = WebResponseContent.Instance.Error("处理回传MES时发生异常,请联系管理员"); } return response; } private async Task<(bool flowControl, WebResponseContent value)> FeedBackBatchToMes(Dt_OutboundOrder outboundOrder, string orderNo, List orderDetails, List pickingRecords, string documentNo) { var feedModel = new FeedbackOutboundRequestModel { reqCode = Guid.NewGuid().ToString(), reqTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), business_type = outboundOrder.BusinessType, factoryArea = outboundOrder.FactoryArea, operationType = 1, Operator = outboundOrder.Operator != "" ? outboundOrder.Operator : App.User.UserName, orderNo = outboundOrder.UpperOrderNo, documentsNO = documentNo, status = outboundOrder.OrderStatus, details = new List() }; var detailIds = new List(); // 填充明细和条码信息 foreach (var detail in orderDetails) { // 查询该明细对应的锁定条码记录 var detailPicks = pickingRecords.Where(x => x.OrderNo == orderNo && detail.Id == x.OrderDetailId).ToList(); if (!detailPicks.Any()) { continue; } var detailModel = new FeedbackOutboundDetailsModel { materialCode = detail.MaterielCode, lineNo = detail.lineNo, warehouseCode = detail.WarehouseCode, qty = detail.BarcodeQty, currentDeliveryQty = 0, unit = detail.BarcodeUnit, barcodes = new List() }; // 填充条码信息(含单位转换) foreach (var item in detailPicks) { if (item.PickQuantity <= 0) { continue; } var barModel = new WIDESEA_DTO.Outbound.BarcodesModel { barcode = item.Barcode, supplyCode = item.SupplyCode, batchNo = item.BatchNo, unit = item.BarcodeUnit, qty = item.PickQuantity }; // 单位不一致时转换 if (detail.BarcodeUnit != detail.Unit) { var convertResult = await _materialUnitService.ConvertAsync( item.MaterielCode, item.PickQuantity, detail.Unit, detail.BarcodeUnit); barModel.unit = convertResult.Unit; barModel.qty = convertResult.Quantity; } else { barModel.qty = item.PickQuantity; } detailModel.currentDeliveryQty += barModel.qty; detailModel.barcodes.Add(barModel); } detailIds.Add(detail.Id); feedModel.details.Add(detailModel); } feedModel.details = feedModel.details.GroupBy(item => new { item.materialCode, item.lineNo, item.warehouseCode, item.unit, item.qty }).Select(group => new FeedbackOutboundDetailsModel { materialCode = group.Key.materialCode, lineNo = group.Key.lineNo, warehouseCode = group.Key.warehouseCode, qty = group.Key.qty, currentDeliveryQty = group.Sum(x => x.currentDeliveryQty), unit = group.Key.unit, barcodes = group.SelectMany(x => x.barcodes.GroupBy(o => new { o.barcode, o.supplyCode, o.batchNo, o.unit }).Select(row => new WIDESEA_DTO.Outbound.BarcodesModel { barcode = row.Key.barcode, supplyCode = row.Key.supplyCode, batchNo = row.Key.batchNo, unit = row.Key.unit, qty = row.Sum(y => y.qty) })).ToList() }).ToList(); var allCompleted = true; // 筛选待回传的明细(ReturnToMESStatus=0) var pendingDetails = orderDetails.Where(x => x.ReturnToMESStatus == 0).ToList(); foreach (var detail in pendingDetails) { if (detail.OverOutQuantity < detail.NeedOutQuantity) { allCompleted = false; } } // 存在回传失败的明细(ReturnToMESStatus=2),标记未完成 if (orderDetails.Any(x => x.ReturnToMESStatus == 2)) { allCompleted = false; } // 更新订单状态 int newStatus = allCompleted ? (int)OutOrderStatusEnum.出库完成 : (int)OutOrderStatusEnum.出库中; if (allCompleted && outboundOrder.OrderStatus != newStatus) { int updateCount = await _outboundOrderService.Db.Updateable() .SetColumns(x => x.OrderStatus == newStatus) .Where(x => x.OrderNo == orderNo) .ExecuteCommandAsync(); if (updateCount <= 0) { _logger.LogWarning($"更新出库单状态失败 - OrderNo: {orderNo}, 目标状态: {newStatus}"); } } // 调用MES回传接口 var mesResult = await FeedbackOutbound(feedModel); if (mesResult == null || mesResult.code != 200) { var messages = mesResult?.message ?? ""; await _outboundOrderService.Db.Updateable() .SetColumns(x => new Dt_OutboundOrder { ReturnToMESStatus = 2, Remark = messages, }) .Where(x => x.OrderNo == orderNo) .ExecuteCommandAsync(); // 更新明细为回传失败(ReturnToMESStatus=2) await _outboundOrderDetailService.Db.Updateable() .SetColumns(it => new Dt_OutboundOrderDetail { ReturnToMESStatus = 2, documentsNO = documentNo, }) .Where(x => detailIds.Contains(x.Id)) .ExecuteCommandAsync(); return (flowControl: false, value: WebResponseContent.Instance.Error($"回传MES失败")); } var updates = pickingRecords.Where(x => detailIds.Contains(x.OrderDetailId)).ToList(); updates.ForEach(x => { x.ReturnToMESStatus = 1; }); await _pickingRecoreRepository.Db.Updateable(updates).ExecuteCommandAsync(); await _interfacelogRepository.Db.Updateable() .SetColumns(x => x.ReturnToMESStatus == 1) .Where(x => x.OrderNo == orderNo) .ExecuteCommandAsync(); if (allCompleted) { //MES回传成功:更新明细为回传成功状态 await _outboundOrderDetailService.Db.Updateable() .SetColumns(it => new Dt_OutboundOrderDetail { ReturnToMESStatus = 1, documentsNO = documentNo, }) .Where(x => detailIds.Contains(x.Id)) .ExecuteCommandAsync(); } // 校验是否所有明细都完成,更新订单最终状态 if (allCompleted && newStatus == (int)OutOrderStatusEnum.出库完成) { await _outboundOrderService.Db.Updateable() .SetColumns(x => new Dt_OutboundOrder { ReturnToMESStatus = 1, Remark = "", OrderStatus = newStatus }) .Where(x => x.OrderNo == orderNo) .ExecuteCommandAsync(); } else { // 二次校验是否所有未回传明细都已完成 var dbOrderDetails = await _outboundOrderDetailService.Db.Queryable() .LeftJoin((o, item) => o.OrderId == item.Id) .Where((o, item) => item.OrderNo == orderNo && item.ReturnToMESStatus != 1) .Select((o, item) => o) .ToListAsync(); var secAllCompleted = true; foreach (var detail in dbOrderDetails.Where(x => x.ReturnToMESStatus == 0).ToList()) { if (detail.OverOutQuantity < detail.NeedOutQuantity) { secAllCompleted = false; break; } } if (secAllCompleted) { await _outboundOrderService.Db.Updateable() .SetColumns(it => new Dt_OutboundOrder { ReturnToMESStatus = 1, Remark = "", OrderStatus = OutOrderStatusEnum.出库完成.ObjToInt(), }) .Where(x => x.OrderNo == orderNo) .ExecuteCommandAsync(); } } return (flowControl: true, value: WebResponseContent.Instance.OK($"回传MES成功,单据号:{orderNo}")); } private async Task HandleOutboundOrderToMESCompletion(Dt_OutboundOrder outboundOrder, string orderNo) { // 前置参数校验:空值直接返回错误 if (outboundOrder == null) { return WebResponseContent.Instance.Error("出库单实体为空,无法处理回传MES"); } if (string.IsNullOrWhiteSpace(orderNo)) { return WebResponseContent.Instance.Error("订单号为空,无法处理回传MES"); } try { if (outboundOrder.ReturnToMESStatus == 1) { return WebResponseContent.Instance.Error($"OrderNo: {orderNo}, 该单已经回传!"); } var orderDetails = await _outboundOrderDetailService.Db.Queryable() .LeftJoin((o, item) => o.OrderId == item.Id) .Where((o, item) => item.OrderNo == orderNo) .Select((o, item) => o) .ToListAsync(); // 无明细场景返回警告 if (!orderDetails.Any()) { return WebResponseContent.Instance.Error($"OrderNo: {orderNo} 未查询到订单明细"); } // 判断是否所有明细完成出库 bool allCompleted = true; foreach (var detail in orderDetails) { if (detail.OverOutQuantity < detail.NeedOutQuantity) { allCompleted = false; break; } } // 更新订单状态(修正语法错误:== → =) int newStatus = allCompleted ? (int)OutOrderStatusEnum.出库完成 : (int)OutOrderStatusEnum.出库中; if (outboundOrder.OrderStatus != newStatus) { await _outboundOrderService.Db.Updateable() .SetColumns(x => x.OrderStatus == newStatus) // 关键修正:赋值而非判断 .Where(x => x.OrderNo == orderNo) .ExecuteCommandAsync(); } // 仅分拣完成时向MES反馈 if (allCompleted && newStatus == (int)OutOrderStatusEnum.出库完成) { var feedmodel = new FeedbackOutboundRequestModel { reqCode = Guid.NewGuid().ToString(), reqTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), business_type = outboundOrder.BusinessType, factoryArea = outboundOrder.FactoryArea, operationType = 1, Operator = outboundOrder.Operator, orderNo = outboundOrder.UpperOrderNo, documentsNO = outboundOrder.OrderNo, status = outboundOrder.OrderStatus, details = new List() }; // 构建明细数据 foreach (var detail in orderDetails) { var detailLocks = await _outStockLockInfoService.Db.Queryable() .Where(x => x.OrderNo == orderNo && x.OrderDetailId == detail.Id && (x.Status == (int)OutLockStockStatusEnum.拣选完成 || x.Status == (int)OutLockStockStatusEnum.已回库)) .ToListAsync(); var groupdata = detailLocks.GroupBy(item => new { item.MaterielCode, item.lineNo, item.BarcodeUnit, item.WarehouseCode }) .Select(group => new FeedbackOutboundDetailsModel { materialCode = group.Key.MaterielCode, lineNo = group.Key.lineNo, warehouseCode = group.Key.WarehouseCode, qty = group.Sum(x => x.PickedQty), currentDeliveryQty = group.Sum(x => x.PickedQty), unit = group.Key.BarcodeUnit, barcodes = group.Select(lockInfo => new WIDESEA_DTO.Outbound.BarcodesModel { barcode = lockInfo.CurrentBarcode, supplyCode = lockInfo.SupplyCode, batchNo = lockInfo.BatchNo, unit = lockInfo.BarcodeUnit, qty = lockInfo.PickedQty }).ToList() }).ToList(); feedmodel.details.AddRange(groupdata); } // 调用MES接口 var result = await FeedbackOutbound(feedmodel); if (result == null) { return WebResponseContent.Instance.Error($"OrderNo: {orderNo} MES回传接口返回空"); } if (result.code == 200) { // 回传成功:更新回传状态 await _outboundOrderDetailService.Db.Updateable() .SetColumns(x => x.ReturnToMESStatus == 1) .Where(x => x.OrderId == outboundOrder.Id) .ExecuteCommandAsync(); await _outboundOrderService.Db.Updateable() .SetColumns(it => new Dt_OutboundOrder { ReturnToMESStatus = 1, Remark = "" }) .Where(x => x.OrderNo == orderNo) .ExecuteCommandAsync(); await _pickingRecoreRepository.Db.Updateable() .SetColumns(x => x.ReturnToMESStatus == 1) .Where(x => x.OrderNo == orderNo) .ExecuteCommandAsync(); return WebResponseContent.Instance.OK("回传MES成功"); } else { var errorMsg = $"OrderNo: {orderNo} 回传MES失败,错误码:{result.code},错误信息:{result.message ?? "无"}"; _logger.LogError(errorMsg); await _outboundOrderDetailService.Db.Updateable() .SetColumns(x => x.ReturnToMESStatus == 2) .Where(x => x.OrderId == outboundOrder.Id) .ExecuteCommandAsync(); await _outboundOrderService.Db.Updateable() .SetColumns(it => new Dt_OutboundOrder { ReturnToMESStatus = 2, Remark = result.message }) .Where(x => x.OrderNo == orderNo) .ExecuteCommandAsync(); return WebResponseContent.Instance.Error(errorMsg); } } return WebResponseContent.Instance.OK("订单状态已更新,未完全完成分拣无需回传MES"); } catch (Exception ex) { // 全局异常捕获:返回错误+记录详细日志 var errorMsg = $"OrderNo: {orderNo} 处理回传MES时发生异常:{ex.Message}"; _logger.LogError(ex, errorMsg); // 记录堆栈信息便于排查 return WebResponseContent.Instance.Error("处理回传MES时发生异常,请联系管理员"); } } } public static class UniqueValueGenerator { // 原子计数器(线程安全,每次递增1,避免同一Ticks重复) private static long _counter = 0; /// /// 生成唯一值(支持高并发) /// /// 格式:yyyyMMdd + Ticks + 3位计数器(如2025112563867890123001) public static string Generate() { var now = DateTime.Now; string datePart = now.ToString("MMdd"); long ticksPart = now.Ticks; // 拼接:计数器补0为3位(避免位数不一致) return $"{datePart}{ticksPart}"; } public static string GenerateCount() { var now = DateTime.Now; string datePart = now.ToString("yyyyMMddHHmmss"); // 原子递增计数器(取模1000,确保计数器仅3位,控制长度) long counterPart = Interlocked.Increment(ref _counter) % 1000; // 拼接:计数器补0为3位(避免位数不一致) return $"{datePart}{counterPart:D3}"; } } public static class MemoryLockManager { // 存储资源锁的元数据(锁对象、持有线程、占用时间、超时时间) private class LockMetadata { public object LockObject { get; } = new object(); public int HoldingThreadId { get; set; } = -1; // 持有锁的线程ID public DateTime AcquireTime { get; set; } // 获取锁的时间 public TimeSpan Timeout { get; set; } // 锁超时时间 public bool IsReleased { get; set; } // 是否已释放 } // 资源ID -> 锁元数据 private static readonly ConcurrentDictionary _resourceLocks = new(); // 全局锁(保护锁元数据的创建/删除) private static readonly object _globalLocker = new(); // 随机数生成器(用于生成3-5秒随机超时) private static readonly Random _random = new Random(); /// /// 尝试锁定资源(带超时自动释放) /// /// 资源ID /// 超时时间(默认3-5秒随机) /// 是否成功获取锁 public static bool TryAcquireLock(string resourceId, int? timeoutSeconds = null) { if (string.IsNullOrEmpty(resourceId)) throw new ArgumentNullException(nameof(resourceId)); // 确定超时时间(3-5秒随机) var timeout = TimeSpan.FromSeconds(timeoutSeconds ?? _random.Next(3, 6)); var currentThreadId = Thread.CurrentThread.ManagedThreadId; LockMetadata lockMeta = null; lock (_globalLocker) { // 获取或创建锁元数据 lockMeta = _resourceLocks.GetOrAdd(resourceId, key => new LockMetadata()); // 防止重复获取(当前线程已持有锁) if (lockMeta.HoldingThreadId == currentThreadId && !lockMeta.IsReleased) return true; // 线程可重入 } // 尝试获取锁(非阻塞) if (!Monitor.TryEnter(lockMeta.LockObject)) return false; // 标记锁持有状态 lockMeta.HoldingThreadId = currentThreadId; lockMeta.AcquireTime = DateTime.Now; lockMeta.Timeout = timeout; lockMeta.IsReleased = false; // 启动超时自动释放任务 _ = Task.Delay(timeout).ContinueWith(_ => { try { ReleaseLock(resourceId, force: true); } catch (Exception ex) { // 记录超时释放异常 Console.WriteLine($"资源[{resourceId}]超时自动释放失败:{ex.Message}"); } }); return true; } /// /// 释放资源锁 /// /// 资源ID /// 是否强制释放(超时自动释放时使用) public static void ReleaseLock(string resourceId, bool force = false) { if (string.IsNullOrEmpty(resourceId)) throw new ArgumentNullException(nameof(resourceId)); if (!_resourceLocks.TryGetValue(resourceId, out var lockMeta)) return; var currentThreadId = Thread.CurrentThread.ManagedThreadId; // 校验释放合法性:仅持有锁的线程或强制释放可执行 if (!force && lockMeta.HoldingThreadId != currentThreadId) { // 非持有线程尝试释放,抛出异常或返回(根据业务选择) throw new InvalidOperationException($"线程[{currentThreadId}]无权释放资源[{resourceId}]的锁(当前持有线程:{lockMeta.HoldingThreadId})"); } // 双重校验锁状态 lock (_globalLocker) { if (lockMeta.IsReleased) return; // 确保锁被当前线程持有(强制释放除外) if (Monitor.IsEntered(lockMeta.LockObject)) { try { Monitor.Exit(lockMeta.LockObject); } catch (SynchronizationLockException) { // 已被释放,忽略 return; } } // 标记锁已释放 lockMeta.IsReleased = true; lockMeta.HoldingThreadId = -1; // 延迟清理锁元数据(避免并发创建) // 等待1秒后清理,防止刚释放就有新线程抢锁导致重复创建 _ = Task.Delay(1000).ContinueWith(_ => { lock (_globalLocker) { // 仅当锁未被重新持有且已释放时清理 if (_resourceLocks.TryGetValue(resourceId, out var meta) && meta.IsReleased && meta.HoldingThreadId == -1) { _resourceLocks.TryRemove(resourceId, out var _resid); } } }); } } /// /// 检查资源是否被锁定 /// public static bool IsLocked(string resourceId) { if (!_resourceLocks.TryGetValue(resourceId, out var meta)) return false; return !meta.IsReleased && meta.HoldingThreadId != -1; } public static void TestUsed() { string orderNo = "testt"; bool lockAcquired = false; try { // 尝试获取锁(自动3-5秒超时) lockAcquired = MemoryLockManager.TryAcquireLock(orderNo); if (lockAcquired) { // 执行业务逻辑(如处理订单) Console.WriteLine($"线程[{Thread.CurrentThread.ManagedThreadId}]获取锁:{orderNo}"); // 模拟业务耗时(测试超时释放) // Thread.Sleep(6000); } else { Console.WriteLine($"资源[{orderNo}]被占用,获取锁失败"); } } catch (Exception ex) { Console.WriteLine($"业务处理异常:{ex.Message}"); } finally { // 释放锁(仅当成功获取时) if (lockAcquired) { try { MemoryLockManager.ReleaseLock(orderNo); Console.WriteLine($"线程[{Thread.CurrentThread.ManagedThreadId}]释放锁:{orderNo}"); } catch (Exception ex) { Console.WriteLine($"释放锁失败:{ex.Message}"); } } } } } }