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_DTO.Allocate;
|
using WIDESEA_DTO.Basic;
|
using WIDESEA_DTO.Inbound;
|
using WIDESEA_DTO.Outbound;
|
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<InvokeMESService> _logger;
|
private string UserName = "12312";
|
private string Password = "1";
|
|
private readonly IRepository<Dt_FeedbackToMes> _feedbacktomesRepository;
|
private readonly IRepository<Dt_StockInfoDetail> _stockInfoDetailRepository;
|
private readonly IRepository<Dt_StockInfo> _stockInfoRepository;
|
private readonly IRepository<Dt_InboundOrder> _inboundOrderRepository;
|
private readonly IRepository<Dt_InboundOrderDetail> _inboundOrderDetailRepository;
|
private readonly IRepository<Dt_PickingRecord> _pickingRecoreRepository;
|
private readonly IMaterialUnitService _materialUnitService;
|
private readonly IOutboundOrderService _outboundOrderService;
|
private readonly IOutboundOrderDetailService _outboundOrderDetailService;
|
private readonly IOutStockLockInfoService _outStockLockInfoService;
|
private readonly IRepository<Dt_InterfaceLog> _interfacelogRepository;
|
|
// 存储资源ID及其对应的锁对象。使用 ConcurrentDictionary 确保对字典操作本身的线程安全。
|
private static readonly ConcurrentDictionary<string, object> _resourceLocks = new ConcurrentDictionary<string, object>();
|
|
// 全局静态锁:用于保护 _resourceLocks 字典中 GetOrAdd 或 TryRemove 时的竞争
|
private static readonly object _globalLocker = new object();
|
public InvokeMESService(IHttpClientFactory httpClientFactory, ILogger<InvokeMESService> logger, IRepository<Dt_FeedbackToMes> feedbacktomesRepository, IRepository<Dt_StockInfoDetail> stockInfoDetailRepository, IRepository<Dt_StockInfo> stockInfoRepository, IRepository<Dt_InboundOrder> inboundOrderRepository, IOutboundOrderService outboundOrderService, IOutboundOrderDetailService outboundOrderDetailService, IOutStockLockInfoService outStockLockInfoService, IMaterialUnitService materialUnitService, IRepository<Dt_PickingRecord> pickingRecoreRepository, IRepository<Dt_InterfaceLog> interfacelogRepository, IRepository<Dt_InboundOrderDetail> inboundOrderDetailRepository)
|
{
|
_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;
|
}
|
|
/// <summary>
|
/// 入库反馈
|
/// </summary>
|
/// <param name="model"></param>
|
/// <returns></returns>
|
/// <exception cref="HttpRequestException"></exception>
|
public async Task<ResponseModel> 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<ResponseModel>(body);
|
}
|
|
/// <summary>
|
/// 出库反馈
|
/// </summary>
|
/// <param name="model"></param>
|
/// <returns></returns>
|
/// <exception cref="HttpRequestException"></exception>
|
public async Task<ResponseModel> 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<ResponseModel>(body);
|
}
|
|
public async Task<ResponseModel> 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<ResponseModel>(body);
|
}
|
|
public async Task<ResponseModel> 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<ResponseModel>(responseText);
|
}
|
|
public async Task<string> 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;
|
}
|
}
|
|
|
|
/// <summary>
|
///
|
/// </summary>
|
/// <param name="orderNos"></param>
|
/// <param name="inout">入库传1 出库传2</param>
|
/// <returns></returns>
|
public async Task<WebResponseContent> BatchOrderFeedbackToMes(List<string> orderNos, int inout)
|
{
|
try
|
{
|
|
if (inout == 1)
|
{
|
foreach (var orderNo in orderNos)
|
{
|
try
|
{
|
var stockinfos = _stockInfoRepository.Db.Queryable<Dt_StockInfo>("info").Where(info => info.StockStatus == 6)
|
.Where(it => SqlFunc.Subqueryable<Dt_StockInfoDetail>().Where(s => s.StockId == it.Id && s.OrderNo == orderNo).Any())
|
.ToList();
|
var feeds = _feedbacktomesRepository.Db.Queryable<Dt_FeedbackToMes>().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<Dt_InboundOrder>().SetColumns(it => new Dt_InboundOrder { ReturnToMESStatus = 1, Remark = "" })
|
.Where(it => it.InboundOrderNo == orderNo).ExecuteCommand();
|
var inboundOrder = _inboundOrderRepository.Db.Queryable<Dt_InboundOrder>().First(x => x.InboundOrderNo == orderNo);
|
if (inboundOrder != null)
|
{
|
_inboundOrderDetailRepository.Db.Updateable<Dt_InboundOrderDetail>().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<Dt_StockInfoDetail>().Where(x => x.StockId == item.Id).ToList();
|
if (lists.Any())
|
{
|
var inboundOrder = _inboundOrderRepository.Db.Queryable<Dt_InboundOrder>().First(x => x.InboundOrderNo == lists.FirstOrDefault().OrderNo);
|
if (inboundOrder != null)
|
{
|
if (inboundOrder.OrderType == (int)InOrderTypeEnum.AllocatInbound)//调拨入库
|
{
|
var allocate = SqlSugarHelper.DbWMS.Queryable<Dt_AllocateOrder>().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<AllocateDtoDetail>()
|
|
};
|
|
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);
|
if (result != null && result.code == 200)
|
{
|
_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<FeedbackInboundDetailsModel>()
|
|
};
|
|
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);
|
if (result != null && result.code == 200)
|
{
|
_feedbacktomesRepository.Db.Insertable(new Dt_FeedbackToMes { OrderNo = orderNo, PalletCode = item.PalletCode, ReportStatus = 1 }).ExecuteCommand();
|
|
var feedstockinfos = _stockInfoRepository.Db.Queryable<Dt_StockInfo>("info").Where(info => info.StockStatus == 6)
|
.Where(it => SqlFunc.Subqueryable<Dt_StockInfoDetail>().Where(s => s.StockId == it.Id && s.OrderNo == orderNo).Any())
|
.ToList();
|
var feedstomes = _feedbacktomesRepository.Db.Queryable<Dt_FeedbackToMes>().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<Dt_InboundOrder>().SetColumns(it => new Dt_InboundOrder { ReturnToMESStatus = 1, Remark = "" })
|
.Where(it => it.InboundOrderNo == orderNo).ExecuteCommand();
|
var feedinboundOrder = _inboundOrderRepository.Db.Queryable<Dt_InboundOrder>().First(x => x.InboundOrderNo == orderNo);
|
if (feedinboundOrder != null)
|
{
|
_inboundOrderDetailRepository.Db.Updateable<Dt_InboundOrderDetail>().SetColumns(it => new Dt_InboundOrderDetail { ReturnToMESStatus = 1 })
|
.Where(it => it.OrderId == feedinboundOrder.Id).ExecuteCommand();
|
}
|
}
|
return WebResponseContent.Instance.Error("回传成功!");
|
}
|
else
|
{
|
_inboundOrderRepository.Db.Updateable<Dt_InboundOrder>().SetColumns(it => new Dt_InboundOrder { ReturnToMESStatus = 2, Remark = result.message })
|
.Where(it => it.Id == inboundOrder.Id).ExecuteCommand();
|
_inboundOrderDetailRepository.Db.Updateable<Dt_InboundOrderDetail>().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<Dt_OutboundOrder>().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();
|
}
|
|
private async Task<WebResponseContent> 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<Dt_OutboundOrderDetail>()
|
.LeftJoin<Dt_OutboundOrder>((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<Dt_PickingRecord>().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<Dt_PickingRecord> 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<Dt_OutboundOrderDetail> orderDetails, List<Dt_PickingRecord> 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<FeedbackOutboundDetailsModel>()
|
};
|
var detailIds = new List<int>();
|
// 填充明细和条码信息
|
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<WIDESEA_DTO.Outbound.BarcodesModel>()
|
};
|
|
// 填充条码信息(含单位转换)
|
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<Dt_OutboundOrder>()
|
.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<Dt_OutboundOrder>()
|
.SetColumns(x => new Dt_OutboundOrder
|
{
|
ReturnToMESStatus = 2,
|
Remark = messages,
|
})
|
.Where(x => x.OrderNo == orderNo)
|
.ExecuteCommandAsync();
|
// 更新明细为回传失败(ReturnToMESStatus=2)
|
await _outboundOrderDetailService.Db.Updateable<Dt_OutboundOrderDetail>()
|
.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<Dt_InterfaceLog>()
|
.SetColumns(x => x.ReturnToMESStatus == 1)
|
.Where(x => x.OrderNo == orderNo)
|
.ExecuteCommandAsync();
|
|
if (allCompleted)
|
{
|
//MES回传成功:更新明细为回传成功状态
|
await _outboundOrderDetailService.Db.Updateable<Dt_OutboundOrderDetail>()
|
.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<Dt_OutboundOrder>()
|
.SetColumns(x => new Dt_OutboundOrder
|
{
|
ReturnToMESStatus = 1,
|
Remark = "",
|
OrderStatus = newStatus
|
})
|
.Where(x => x.OrderNo == orderNo)
|
.ExecuteCommandAsync();
|
}
|
else
|
{
|
// 二次校验是否所有未回传明细都已完成
|
var dbOrderDetails = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
|
.LeftJoin<Dt_OutboundOrder>((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<Dt_OutboundOrder>()
|
.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<WebResponseContent> 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<Dt_OutboundOrderDetail>()
|
.LeftJoin<Dt_OutboundOrder>((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<Dt_OutboundOrder>()
|
.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<FeedbackOutboundDetailsModel>()
|
};
|
|
// 构建明细数据
|
foreach (var detail in orderDetails)
|
{
|
var detailLocks = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
|
.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<Dt_OutboundOrderDetail>()
|
.SetColumns(x => x.ReturnToMESStatus == 1)
|
.Where(x => x.OrderId == outboundOrder.Id)
|
.ExecuteCommandAsync();
|
|
await _outboundOrderService.Db.Updateable<Dt_OutboundOrder>()
|
.SetColumns(it => new Dt_OutboundOrder { ReturnToMESStatus = 1, Remark = "" })
|
.Where(x => x.OrderNo == orderNo)
|
.ExecuteCommandAsync();
|
|
await _pickingRecoreRepository.Db.Updateable<Dt_PickingRecord>()
|
.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<Dt_OutboundOrderDetail>()
|
.SetColumns(x => x.ReturnToMESStatus == 2)
|
.Where(x => x.OrderId == outboundOrder.Id)
|
.ExecuteCommandAsync();
|
|
await _outboundOrderService.Db.Updateable<Dt_OutboundOrder>()
|
.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;
|
|
/// <summary>
|
/// 生成唯一值(支持高并发)
|
/// </summary>
|
/// <returns>格式:yyyyMMdd + Ticks + 3位计数器(如2025112563867890123001)</returns>
|
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<string, LockMetadata> _resourceLocks = new();
|
// 全局锁(保护锁元数据的创建/删除)
|
private static readonly object _globalLocker = new();
|
// 随机数生成器(用于生成3-5秒随机超时)
|
private static readonly Random _random = new Random();
|
|
/// <summary>
|
/// 尝试锁定资源(带超时自动释放)
|
/// </summary>
|
/// <param name="resourceId">资源ID</param>
|
/// <param name="timeoutSeconds">超时时间(默认3-5秒随机)</param>
|
/// <returns>是否成功获取锁</returns>
|
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;
|
}
|
|
/// <summary>
|
/// 释放资源锁
|
/// </summary>
|
/// <param name="resourceId">资源ID</param>
|
/// <param name="force">是否强制释放(超时自动释放时使用)</param>
|
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);
|
}
|
}
|
});
|
}
|
}
|
|
/// <summary>
|
/// 检查资源是否被锁定
|
/// </summary>
|
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}");
|
}
|
}
|
}
|
}
|
}
|
}
|