ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_BasicService/InvokeMESService.cs
@@ -5,6 +5,7 @@ using Org.BouncyCastle.Asn1.Ocsp; using SqlSugar; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Net; @@ -27,7 +28,7 @@ using WIDESEA_Model.Models; namespace WIDESEA_BasicService { { public class InvokeMESService : IInvokeMESService { private readonly IHttpClientFactory _httpClientFactory; @@ -44,6 +45,12 @@ private readonly IOutboundOrderService _outboundOrderService; private readonly IOutboundOrderDetailService _outboundOrderDetailService; private readonly IOutStockLockInfoService _outStockLockInfoService; // åå¨èµæº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) { _httpClientFactory = httpClientFactory; @@ -105,7 +112,7 @@ _client.DefaultRequestHeaders.Clear(); _client.DefaultRequestHeaders.Add("Accept", "application/json"); _logger.LogInformation("InvokeMESService FeedbackOutbound : "+ model.orderNo +" , " + json); _logger.LogInformation("InvokeMESService FeedbackOutbound : " + model.orderNo + " , " + json); var response = await _client.PostAsync("AldMaterialOutbound/MaterialOutbound", content); string body = await response.Content.ReadAsStringAsync(); @@ -224,7 +231,8 @@ } } /// <summary> /// /// </summary> @@ -233,141 +241,159 @@ /// <returns></returns> public async Task<WebResponseContent> BatchOrderFeedbackToMes(List<string> orderNos, int inout) { if (inout == 1) // 1. ãå å鿢å ã if (MemoryLockManager.TryAcquireLock(orderNos[0])) { foreach (var orderNo in orderNos) try { try if (inout == 1) { 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()) foreach (var orderNo in orderNos) { 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()) try { var inboundOrder = _inboundOrderRepository.Db.Queryable<Dt_InboundOrder>().First(x => x.InboundOrderNo == lists.FirstOrDefault().OrderNo); if (inboundOrder != null) 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()) { if (inboundOrder.OrderType == (int)InOrderTypeEnum.AllocatInbound)//è°æ¨å ¥åº 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 allocate = SqlSugarHelper.DbWMS.Queryable<Dt_AllocateOrder>().Where(x => x.OrderNo == inboundOrder.InboundOrderNo).First(); var allocatefeedmodel = new AllocateDto var inboundOrder = _inboundOrderRepository.Db.Queryable<Dt_InboundOrder>().First(x => x.InboundOrderNo == lists.FirstOrDefault().OrderNo); if (inboundOrder != null) { ReqCode = Guid.NewGuid().ToString(), ReqTime = DateTime.Now.ToString(), BusinessType = "2", FactoryArea = inboundOrder.FactoryArea, OperationType = 1, Operator = inboundOrder.Operator, OrderNo = inboundOrder.UpperOrderNo, fromWarehouse = allocate?.FromWarehouse ?? "", toWarehouse = allocate?.ToWarehouse ?? "", Details = new List<AllocateDtoDetail>() 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 = "2", 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 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 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 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(); } } } } } } catch (Exception ex) { _logger.LogInformation("InvokeMESService BatchOrderFeedbackToMes ååMES失败: " + ex.Message); return WebResponseContent.Instance.Error(ex.Message); } } } catch (Exception ex) else if (inout == 2) { _logger.LogInformation("InvokeMESService BatchOrderFeedbackToMes ååMES失败: " + ex.Message); return WebResponseContent.Instance.Error(ex.Message); } 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; } } } } finally { // 2. ãéæ¾å åéãæ è®ºæå失败ï¼å¿ 须鿾 MemoryLockManager.ReleaseLock(orderNos[0]); } } else if (inout == 2) else { 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; } } // æ¢é失败ï¼è¯´ææå¦ä¸ä¸ªçº¿ç¨ï¼WCSåè°æäººå·¥æä½ï¼æ£å¨å¤ç return WebResponseContent.Instance.OK("WMSæ£å¨å¤çæ¤åä¼ ä»»å¡ï¼è¯·å¿é夿ä½ã"); } return WebResponseContent.Instance.OK(); } @@ -414,7 +440,7 @@ business_type = outboundOrder.BusinessType, factoryArea = outboundOrder.FactoryArea, operationType = 1, Operator = outboundOrder.Operator!=""? outboundOrder.Operator:App.User.UserName, Operator = outboundOrder.Operator != "" ? outboundOrder.Operator : App.User.UserName, orderNo = outboundOrder.UpperOrderNo, documentsNO = documentNo, status = outboundOrder.OrderStatus, @@ -469,7 +495,7 @@ else { barModel.qty = item.PickQuantity; } } detailModel.currentDeliveryQty += barModel.qty; detailModel.barcodes.Add(barModel); } @@ -477,7 +503,7 @@ 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 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, @@ -777,7 +803,7 @@ return WebResponseContent.Instance.Error("å¤çåä¼ MESæ¶åçå¼å¸¸ï¼è¯·è系管çå"); } } } } public static class UniqueValueGenerator { @@ -796,10 +822,10 @@ // æ¼æ¥ï¼è®¡æ°å¨è¡¥0为3ä½ï¼é¿å 使°ä¸ä¸è´ï¼ return $"{datePart}{ticksPart}"; } public static string GenerateCount() { var now = DateTime.Now; var now = DateTime.Now; string datePart = now.ToString("yyyyMMddHHmmss"); @@ -810,4 +836,59 @@ return $"{datePart}{counterPart:D3}"; } } public static class MemoryLockManager { // åå¨èµæºIDåå ¶å¯¹åºçé对象ãä½¿ç¨ ConcurrentDictionary ç¡®ä¿å¯¹åå ¸æä½æ¬èº«ç线ç¨å®å ¨ã private static readonly ConcurrentDictionary<string, object> _resourceLocks = new ConcurrentDictionary<string, object>(); // å ¨å±éæéï¼ç¨äºä¿æ¤ _resourceLocks åå ¸ä¸ GetOrAdd æ TryRemove æ¶çç«äº private static readonly object _globalLocker = new object(); /// <summary> /// å°è¯éå®ä¸ä¸ªèµæºIDã /// </summary> /// <param name="resourceId">è¦éå®çèµæºIDï¼ä¾å¦ InboundRecord IDï¼</param> /// <returns>æ¯å¦æåè·åé</returns> public static bool TryAcquireLock(string resourceId) { object lockObject = null; // æ ¸å¿æè·¯ï¼ä¸ºæ¯ä¸ªèµæºå建ä¸ä¸ªå¯ä¸çé对象 lock (_globalLocker) { // å¦æèµæºIDä¸å¨åå ¸ä¸ï¼åæ·»å ä¸ä¸ªæ°çé对象 // å¦åï¼ä½¿ç¨å·²åå¨çé对象 lockObject = _resourceLocks.GetOrAdd(resourceId, new object()); } // å°è¯è·åèµæºç¹å®çé // ä½¿ç¨ Monitor.TryEnter é¿å é»å¡ï¼å¹¶å®ç°éé»å¡çæ¢é return Monitor.TryEnter(lockObject); } /// <summary> /// éæ¾èµæºIDçéå®ã /// </summary> /// <param name="resourceId">è¦éæ¾çèµæºID</param> public static void ReleaseLock(string resourceId) { if (_resourceLocks.TryGetValue(resourceId, out object lockObject)) { // ç¡®ä¿éæ¾çæ¯å½åçº¿ç¨ææçé if (Monitor.IsEntered(lockObject)) { Monitor.Exit(lockObject); // éæ¾éåï¼å°è¯ä»åå ¸ä¸ç§»é¤è¿ä¸ªéå¯¹è±¡ï¼æ¸ çå åã // å¿ é¡»å¨ Monitor.Exit ä¹åæ§è¡ã lock (_globalLocker) { _resourceLocks.TryRemove(resourceId, out _); } } } } } } ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundOrderDetailService.cs
@@ -636,24 +636,25 @@ if (userSelection == null) continue; // 计ç®è¯¥æçå®é å¯ç¨æ°é var availableQuantity = CalculateAvailableQuantity(stock, outboundOrderDetail.MaterielCode, outboundOrderDetail.BatchNo, outboundOrderDetail.SupplyCode); var availableQuantity = CalculateAvailableQuantityByBarcode(stock, outboundOrderDetail.MaterielCode, outboundOrderDetail.BatchNo, outboundOrderDetail.SupplyCode, userSelection.Barcode); // ç¡®å®åé æ°éï¼åç¨æ·éæ©æ°éãå¯ç¨æ°éåå©ä½éæ±çæå°å¼ var assignQuantity = Math.Min(Math.Min(userSelection.UseableQuantity, availableQuantity),remainingNeedQuantity); var assignQuantity = Math.Min(Math.Min(userSelection.UseableQuantity, availableQuantity), remainingNeedQuantity); if (assignQuantity <= 0) continue; // æ§è¡åé var (actualAssigned, barcode) = AssignStockQuantity(stock, outboundOrderDetail, assignQuantity); // æ§è¡åé ï¼ä½¿ç¨ç¨æ·éæ©çæ¡ç var actualAssigned = AssignStockQuantity(stock, outboundOrderDetail, assignQuantity, userSelection.Barcode); if (actualAssigned > 0) { outStocks.Add(stock); totalAssignedFromUserSelection += actualAssigned; remainingNeedQuantity -= actualAssigned; // å建éå®è®°å½ var lockInfo = CreateOutStockLockInfo(outboundOrder, outboundOrderDetail, stock, actualAssigned, barcode); // å建éå®è®°å½ï¼ä½¿ç¨ç¨æ·éæ©çæ¡ç var lockInfo = CreateOutStockLockInfo(outboundOrder, outboundOrderDetail, stock, actualAssigned, userSelection.Barcode); outStockLockInfos.Add(lockInfo); } } @@ -721,7 +722,25 @@ return (outStocks, outboundOrderDetail, outStockLockInfos, locationInfos); } private decimal CalculateAvailableQuantityByBarcode(Dt_StockInfo stock, string materielCode, string batchNo, string supplyCode, string barcode) { var query = stock.Details.AsQueryable() .Where(d => d.MaterielCode == materielCode && (d.StockQuantity - d.OutboundQuantity) > 0 && d.Barcode == barcode); if (!string.IsNullOrEmpty(batchNo)) { query = query.Where(x => x.BatchNo == batchNo); } if (!string.IsNullOrEmpty(supplyCode)) { query = query.Where(d => d.SupplyCode == supplyCode); } return query.Sum(d => d.StockQuantity - d.OutboundQuantity); } // è¾ å©æ¹æ³ private decimal CalculateAvailableQuantity(Dt_StockInfo stock, string materielCode, string batchNo, string supplyCode) { @@ -732,6 +751,41 @@ .ToList(); return relevantDetails.Sum(d => d.StockQuantity - d.OutboundQuantity); } private decimal AssignStockQuantity(Dt_StockInfo stock, Dt_OutboundOrderDetail detail, decimal assignQuantity, string barcode) { decimal remainingAssign = assignQuantity; // æå è¿å åºåé æå®æ¡ç çåºåæç» var query = stock.Details.AsQueryable() .Where(d => d.MaterielCode == detail.MaterielCode && (d.StockQuantity - d.OutboundQuantity) > 0 && d.Barcode == barcode); // åªåé æå®æ¡ç if (!string.IsNullOrEmpty(detail.BatchNo)) { query = query.Where(x => x.BatchNo == detail.BatchNo); } // 妿åºåºåæä¾åºåè¦æ±ï¼æä¾åºåè¿æ»¤ if (!string.IsNullOrEmpty(detail.SupplyCode)) { query = query.Where(d => d.SupplyCode == detail.SupplyCode); } var sortedDetails = query.ToList().OrderBy(d => d.CreateDate); foreach (var stockDetail in sortedDetails) { if (remainingAssign <= 0) break; var available = stockDetail.StockQuantity - stockDetail.OutboundQuantity; var assign = Math.Min(available, remainingAssign); stockDetail.OutboundQuantity += assign; remainingAssign -= assign; } return assignQuantity - remainingAssign; // è¿åå®é åé æ°é } private (decimal assignedQuantity, string barcode) AssignStockQuantity(Dt_StockInfo stock, Dt_OutboundOrderDetail detail, decimal assignQuantity) @@ -843,8 +897,8 @@ return (false, $"æç[{selection.PalletCode}]ä¸åå¨"); } var available = CalculateAvailableQuantity(stock, outboundOrderDetail.MaterielCode, outboundOrderDetail.BatchNo, outboundOrderDetail.SupplyCode); var available = CalculateAvailableQuantityByBarcode(stock, outboundOrderDetail.MaterielCode, outboundOrderDetail.BatchNo, outboundOrderDetail.SupplyCode,selection.Barcode); if (available <= 0) { ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_TaskInfoService/TaskService_Outbound.cs
@@ -2,6 +2,7 @@ using Newtonsoft.Json; using OfficeOpenXml.FormulaParsing.Excel.Functions.RefAndLookup; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Text; @@ -148,42 +149,73 @@ List<Dt_LocationInfo>? locationInfos = null; CleanupPreviousInvalidLocks(outboundOrderDetails); (List<Dt_StockInfo>, List<Dt_OutboundOrderDetail>, List<Dt_OutStockLockInfo>, List<Dt_LocationInfo>) result = _outboundOrderDetailService.AssignStockOutbound(outboundOrderDetails); if (result.Item1 != null && result.Item1.Count > 0) // å¼å¯äºå¡ï¼ä½¿ç¨æ°æ®åºè¡çº§é using (var transaction = _outboundOrderDetailService.Db.Ado.UseTran()) { Dt_OutboundOrder outboundOrder = _outboundOrderService.Repository.QueryFirst(x => x.Id == outboundOrderDetails.FirstOrDefault().OrderId); TaskTypeEnum typeEnum = outboundOrder.OrderType switch try { (int)OutOrderTypeEnum.Issue => TaskTypeEnum.Outbound, (int)OutOrderTypeEnum.Allocate => TaskTypeEnum.OutAllocate, (int)OutOrderTypeEnum.Quality => TaskTypeEnum.OutQuality, _ => TaskTypeEnum.Outbound }; tasks = GetTasks(result.Item1, typeEnum, outStation); tasks.ForEach(x => { x.OrderNo = outboundOrder.OrderNo; }); result.Item2.ForEach(x => { x.OrderDetailStatus = OrderDetailStatusEnum.Outbound.ObjToInt(); }); result.Item3.ForEach(x => { x.Status = OutLockStockStatusEnum.åºåºä¸.ObjToInt(); }); // ä½¿ç¨æ²è§ééå®è®¢åæç» var lockedOrderDetails = new List<Dt_OutboundOrderDetail>(); foreach (var key in keys) { var detail = _outboundOrderDetailService.Db.Ado.SqlQuerySingle<Dt_OutboundOrderDetail>( "SELECT * FROM Dt_OutboundOrderDetail WITH (UPDLOCK, ROWLOCK) WHERE Id = @Id", new { Id = key }); stockInfos = result.Item1; orderDetails = result.Item2; outStockLockInfos = result.Item3; locationInfos = result.Item4; if (detail != null) { lockedOrderDetails.Add(detail); } } if (!lockedOrderDetails.Any()) { throw new Exception("æªæ¾å°åºåºåæç»ä¿¡æ¯"); } (List<Dt_StockInfo>, List<Dt_OutboundOrderDetail>, List<Dt_OutStockLockInfo>, List<Dt_LocationInfo>) result = _outboundOrderDetailService.AssignStockOutbound(outboundOrderDetails); if (result.Item1 != null && result.Item1.Count > 0) { Dt_OutboundOrder outboundOrder = _outboundOrderService.Repository.QueryFirst(x => x.Id == outboundOrderDetails.FirstOrDefault().OrderId); TaskTypeEnum typeEnum = outboundOrder.OrderType switch { (int)OutOrderTypeEnum.Issue => TaskTypeEnum.Outbound, (int)OutOrderTypeEnum.Allocate => TaskTypeEnum.OutAllocate, (int)OutOrderTypeEnum.Quality => TaskTypeEnum.OutQuality, _ => TaskTypeEnum.Outbound }; tasks = GetTasks(result.Item1, typeEnum, outStation); tasks.ForEach(x => { x.OrderNo = outboundOrder.OrderNo; }); result.Item2.ForEach(x => { x.OrderDetailStatus = OrderDetailStatusEnum.Outbound.ObjToInt(); }); result.Item3.ForEach(x => { x.Status = OutLockStockStatusEnum.åºåºä¸.ObjToInt(); }); stockInfos = result.Item1; orderDetails = result.Item2; outStockLockInfos = result.Item3; locationInfos = result.Item4; transaction.CommitTran(); } else { transaction.RollbackTran(); throw new Exception("æ åºå"); } } catch (Exception) { transaction.RollbackTran(); throw; } return (tasks, stockInfos, orderDetails, outStockLockInfos, locationInfos); } else { throw new Exception("æ åºå"); } return (tasks, stockInfos, orderDetails, outStockLockInfos, locationInfos); } /// <summary> /// æ¸ çä¹åçæ æéå®è®°å½ @@ -417,6 +449,65 @@ return tasks; } #region å åé管çå¨ private static readonly ConcurrentDictionary<string, SemaphoreSlim> _normalmaterialLocks = new ConcurrentDictionary<string, SemaphoreSlim>(); private static readonly ConcurrentDictionary<string, DateTime> _normallockLastUsed = new ConcurrentDictionary<string, DateTime>(); private static readonly object _normalcleanupLock = new object(); private static DateTime _normallastCleanupTime = DateTime.MinValue; /// <summary> /// è·åç©æçº§å åé /// </summary> private SemaphoreSlim GetNormalMaterialSemaphore(string materialCode, string batchNo, string supplyCode) { // å建éé®ï¼ç©æ+æ¹æ¬¡+ä¾åºå string lockKey = $"MaterialLock_{materialCode}_{batchNo}_{supplyCode}"; // æ¸ çé¿æ¶é´ä¸ç¨çéï¼æ¯å°æ¶æ¸ ç䏿¬¡ï¼ var now = DateTime.Now; if ((now - _normallastCleanupTime).TotalHours >= 1) { lock (_normalcleanupLock) { if ((now - _normallastCleanupTime).TotalHours >= 1) { var keysToRemove = _normallockLastUsed .Where(kvp => (now - kvp.Value).TotalHours > 2) .Select(kvp => kvp.Key) .ToList(); foreach (var key in keysToRemove) { if (_normalmaterialLocks.TryRemove(key, out var _semaphore)) { _semaphore.Dispose(); } _normallockLastUsed.TryRemove(key, out _); } _normallastCleanupTime = now; } } } // è·åæå建信å·é var semaphore = _normalmaterialLocks.GetOrAdd(lockKey, _ => new SemaphoreSlim(1, 1)); _normallockLastUsed[lockKey] = now; return semaphore; } /// <summary> /// æ´æ°å åéæåä½¿ç¨æ¶é´ /// </summary> private void UpdateNormalMaterialLockUsedTime(string materialCode, string batchNo, string supplyCode) { string lockKey = $"MaterialLock_{materialCode}_{batchNo}_{supplyCode}"; _normallockLastUsed[lockKey] = DateTime.Now; } #endregion /// <summary> /// çæåºåºä»»å¡ /// </summary> @@ -432,31 +523,90 @@ List<Dt_OutboundOrderDetail> outboundOrderDetails = new List<Dt_OutboundOrderDetail>(); List<Dt_OutStockLockInfo> outStockLockInfos = new List<Dt_OutStockLockInfo>(); List<Dt_LocationInfo> locationInfos = new List<Dt_LocationInfo>(); (List<Dt_Task>, List<Dt_StockInfo>?, List<Dt_OutboundOrderDetail>?, List<Dt_OutStockLockInfo>?, List<Dt_LocationInfo>?) result = OutboundTaskDataHandle(keys, outStation); if (result.Item2 != null && result.Item2.Count > 0) // å è·åææè®¢åæç»ï¼ç¡®å®éè¦éå®çç©æ var orderDetails = _outboundOrderDetailService.Repository.QueryData(x => keys.Contains(x.Id)); if (orderDetails == null || orderDetails.Count == 0) { stockInfos.AddRange(result.Item2); } if (result.Item3 != null && result.Item3.Count > 0) { outboundOrderDetails.AddRange(result.Item3); } if (result.Item4 != null && result.Item4.Count > 0) { outStockLockInfos.AddRange(result.Item4); } if (result.Item5 != null && result.Item5.Count > 0) { locationInfos.AddRange(result.Item5); } if (result.Item1 != null && result.Item1.Count > 0) { tasks.AddRange(result.Item1); return WebResponseContent.Instance.Error("æªæ¾å°åºåºåæç»ä¿¡æ¯"); } WebResponseContent content = await GenerateOutboundTaskDataUpdateAsync(tasks, stockInfos, outboundOrderDetails, outStockLockInfos, locationInfos); return content; // è·åææéè¦éå®çç©æåç» var materialGroups = orderDetails .GroupBy(x => new { x.MaterielCode, x.BatchNo, x.SupplyCode }) .Select(g => new { g.Key.MaterielCode, g.Key.BatchNo, g.Key.SupplyCode, Count = g.Count() }) .ToList(); // æé¡ºåºè·åææç©æçå åéï¼æç©æä»£ç æåºä»¥é¿å æ»éï¼ var semaphores = new List<SemaphoreSlim>(); var acquiredLocks = new List<(string MaterialCode, string BatchNo, string SupplyCode)>(); try { foreach (var group in materialGroups.OrderBy(g => g.MaterielCode).ThenBy(g => g.BatchNo)) { var semaphore = GetMaterialSemaphore(group.MaterielCode, group.BatchNo, group.SupplyCode); // çå¾ è·åéï¼æå¤çå¾ 30ç§ bool lockAcquired = await semaphore.WaitAsync(TimeSpan.FromSeconds(30)); if (!lockAcquired) { // 妿è·åé失败ï¼éæ¾å·²è·åçææé foreach (var acquiredSemaphore in semaphores) { acquiredSemaphore.Release(); } return WebResponseContent.Instance.Error($"ç©æ[{group.MaterielCode}]æ¹æ¬¡[{group.BatchNo}]åé ç¹å¿ï¼è¯·ç¨åéè¯"); } semaphores.Add(semaphore); acquiredLocks.Add((group.MaterielCode, group.BatchNo, group.SupplyCode)); } (List<Dt_Task>, List<Dt_StockInfo>?, List<Dt_OutboundOrderDetail>?, List<Dt_OutStockLockInfo>?, List<Dt_LocationInfo>?) result = OutboundTaskDataHandle(keys, outStation); if (result.Item2 != null && result.Item2.Count > 0) { stockInfos.AddRange(result.Item2); } if (result.Item3 != null && result.Item3.Count > 0) { outboundOrderDetails.AddRange(result.Item3); } if (result.Item4 != null && result.Item4.Count > 0) { outStockLockInfos.AddRange(result.Item4); } if (result.Item5 != null && result.Item5.Count > 0) { locationInfos.AddRange(result.Item5); } if (result.Item1 != null && result.Item1.Count > 0) { tasks.AddRange(result.Item1); } WebResponseContent content = await GenerateOutboundTaskDataUpdateAsync(tasks, stockInfos, outboundOrderDetails, outStockLockInfos, locationInfos); return content; } finally { // éæ¾ææå åéå¹¶æ´æ°ä½¿ç¨æ¶é´ foreach (var semaphore in semaphores) { semaphore.Release(); } foreach (var lockInfo in acquiredLocks) { UpdateMaterialLockUsedTime(lockInfo.MaterialCode, lockInfo.BatchNo, lockInfo.SupplyCode); } } } catch (Exception ex) { @@ -491,17 +641,17 @@ { return WebResponseContent.Instance.Error("æ¾ä¸å°åºåºåæ®"); } var orderdetail = outboundOrder.Details.Where(outItem => allocorder.Details .Any(allocItem => allocItem.MaterielCode == outItem.MaterielCode && allocItem.LineNo == outItem.lineNo && allocItem.BarcodeQty==outItem.BarcodeQty && allocItem.WarehouseCode==outItem.WarehouseCode && allocItem.BarcodeUnit==outItem.BarcodeUnit) ) .First(); var orderdetail = outboundOrder.Details.Where(outItem => allocorder.Details.Any(allocItem => allocItem.MaterielCode == outItem.MaterielCode && allocItem.LineNo == outItem.lineNo && allocItem.BarcodeQty == outItem.BarcodeQty && allocItem.WarehouseCode == outItem.WarehouseCode && allocItem.BarcodeUnit == outItem.BarcodeUnit)).First(); if (orderdetail == null) { return WebResponseContent.Instance.Error("æ¾ä¸å°åºåºæç»åæ®"); } (List<Dt_Task>, List<Dt_StockInfo>?, List<Dt_OutboundOrderDetail>?, List<Dt_OutStockLockInfo>?, List<Dt_LocationInfo>?) result = OutboundTaskDataHandle(outboundOrder.Details.First().Id, stockSelectViews,station); (List<Dt_Task>, List<Dt_StockInfo>?, List<Dt_OutboundOrderDetail>?, List<Dt_OutStockLockInfo>?, List<Dt_LocationInfo>?) result = OutboundTaskDataHandle(outboundOrder.Details.First().Id, stockSelectViews, station); WebResponseContent content =await GenerateOutboundTaskDataUpdate(result.Item1, result.Item2, result.Item3, result.Item4, result.Item5); WebResponseContent content = await GenerateOutboundTaskDataUpdate(result.Item1, result.Item2, result.Item3, result.Item4, result.Item5); return content; } @@ -516,7 +666,7 @@ /// <param name="orderDetailId"></param> /// <param name="stockSelectViews"></param> /// <returns></returns> public async Task<WebResponseContent> GenerateOutboundTask(int orderDetailId, List<StockSelectViewDTO> stockSelectViews,string station=null) public async Task<WebResponseContent> GenerateOutboundTask(int orderDetailId, List<StockSelectViewDTO> stockSelectViews, string station = null) { try { @@ -526,9 +676,9 @@ { return WebResponseContent.Instance.Error("æ¾ä¸å°åæ®"); } (List<Dt_Task>, List<Dt_StockInfo>?, List<Dt_OutboundOrderDetail>?, List<Dt_OutStockLockInfo>?, List<Dt_LocationInfo>?) result = OutboundTaskDataHandle(outboundOrder.Details.First().Id, stockSelectViews,station); (List<Dt_Task>, List<Dt_StockInfo>?, List<Dt_OutboundOrderDetail>?, List<Dt_OutStockLockInfo>?, List<Dt_LocationInfo>?) result = OutboundTaskDataHandle(outboundOrder.Details.First().Id, stockSelectViews, station); WebResponseContent content =await GenerateOutboundTaskDataUpdate(result.Item1, result.Item2, result.Item3, result.Item4, result.Item5); WebResponseContent content = await GenerateOutboundTaskDataUpdate(result.Item1, result.Item2, result.Item3, result.Item4, result.Item5); return content; } @@ -545,7 +695,7 @@ /// <param name="stockSelectViews"></param> /// <returns></returns> /// <exception cref="Exception"></exception> public (List<Dt_Task>, List<Dt_StockInfo>?, List<Dt_OutboundOrderDetail>?, List<Dt_OutStockLockInfo>?, List<Dt_LocationInfo>?) OutboundTaskDataHandle(int orderDetailId, List<StockSelectViewDTO> stockSelectViews,string station=null) public (List<Dt_Task>, List<Dt_StockInfo>?, List<Dt_OutboundOrderDetail>?, List<Dt_OutStockLockInfo>?, List<Dt_LocationInfo>?) OutboundTaskDataHandle(int orderDetailId, List<StockSelectViewDTO> stockSelectViews, string station = null) { List<Dt_Task> tasks = new List<Dt_Task>(); Dt_OutboundOrderDetail outboundOrderDetail = _outboundOrderDetailService.Repository.QueryFirst(x => x.Id == orderDetailId); @@ -568,7 +718,7 @@ (List<Dt_StockInfo>, Dt_OutboundOrderDetail, List<Dt_OutStockLockInfo>, List<Dt_LocationInfo>) result = _outboundOrderDetailService.AssignStockOutbound(outboundOrderDetail, stockSelectViews); if (result.Item1 != null && result.Item1.Count > 0) { Dt_OutboundOrder outboundOrder = _outboundOrderService.Repository.QueryFirst(x => x.Id == outboundOrderDetail.OrderId); Dt_OutboundOrder outboundOrder = _outboundOrderService.Repository.QueryFirst(x => x.Id == outboundOrderDetail.OrderId); TaskTypeEnum typeEnum = outboundOrder.OrderType switch { (int)OutOrderTypeEnum.Issue => TaskTypeEnum.Outbound, @@ -576,7 +726,7 @@ (int)OutOrderTypeEnum.Quality => TaskTypeEnum.OutQuality, _ => TaskTypeEnum.Outbound }; tasks = GetTasks(result.Item1, typeEnum,station); tasks = GetTasks(result.Item1, typeEnum, station); result.Item2.OrderDetailStatus = OrderDetailStatusEnum.Outbound.ObjToInt(); result.Item3.ForEach(x => { @@ -615,7 +765,7 @@ /// <param name="outStockLockInfos"></param> /// <param name="locationInfos"></param> /// <returns></returns> public async Task< WebResponseContent> GenerateOutboundTaskDataUpdate(List<Dt_Task> tasks, List<Dt_StockInfo>? stockInfos = null, List<Dt_OutboundOrderDetail>? outboundOrderDetails = null, List<Dt_OutStockLockInfo>? outStockLockInfos = null, List<Dt_LocationInfo>? locationInfos = null) public async Task<WebResponseContent> GenerateOutboundTaskDataUpdate(List<Dt_Task> tasks, List<Dt_StockInfo>? stockInfos = null, List<Dt_OutboundOrderDetail>? outboundOrderDetails = null, List<Dt_OutStockLockInfo>? outStockLockInfos = null, List<Dt_LocationInfo>? locationInfos = null) { try { @@ -699,7 +849,7 @@ { return WebResponseContent.Instance.Error("ä¸åæºå¨äººä»»å¡å¤±è´¥ï¼"); } } catch (Exception ex) { @@ -711,7 +861,65 @@ #region åæ¹åé åºå #region å åé管çå¨ private static readonly ConcurrentDictionary<string, SemaphoreSlim> _materialLocks = new ConcurrentDictionary<string, SemaphoreSlim>(); private static readonly ConcurrentDictionary<string, DateTime> _lockLastUsed = new ConcurrentDictionary<string, DateTime>(); private static readonly object _cleanupLock = new object(); private static DateTime _lastCleanupTime = DateTime.MinValue; /// <summary> /// è·åç©æçº§å åé /// </summary> private SemaphoreSlim GetMaterialSemaphore(string materialCode, string batchNo, string supplyCode) { // å建éé®ï¼ç©æ+æ¹æ¬¡+ä¾åºå string lockKey = $"MaterialLock_{materialCode}_{batchNo}_{supplyCode}"; // æ¸ çé¿æ¶é´ä¸ç¨çéï¼æ¯å°æ¶æ¸ ç䏿¬¡ï¼ var now = DateTime.Now; if ((now - _lastCleanupTime).TotalHours >= 1) { lock (_cleanupLock) { if ((now - _lastCleanupTime).TotalHours >= 1) { var keysToRemove = _lockLastUsed .Where(kvp => (now - kvp.Value).TotalHours > 2) .Select(kvp => kvp.Key) .ToList(); foreach (var key in keysToRemove) { if (_materialLocks.TryRemove(key, out var _semaphore)) { _semaphore.Dispose(); } _lockLastUsed.TryRemove(key, out _); } _lastCleanupTime = now; } } } // è·åæå建信å·é var semaphore = _materialLocks.GetOrAdd(lockKey, _ => new SemaphoreSlim(1, 1)); _lockLastUsed[lockKey] = now; return semaphore; } /// <summary> /// éæ¾å åéå¹¶æ´æ°æåä½¿ç¨æ¶é´ /// </summary> private void UpdateMaterialLockUsedTime(string materialCode, string batchNo, string supplyCode) { string lockKey = $"MaterialLock_{materialCode}_{batchNo}_{supplyCode}"; _lockLastUsed[lockKey] = DateTime.Now; } #endregion /// <summary> /// åæ¹åé åºå /// </summary> @@ -719,37 +927,62 @@ { try { List<Dt_Task> tasks = new List<Dt_Task>(); List<Dt_StockInfo> stockInfos = new List<Dt_StockInfo>(); List<Dt_OutboundOrderDetail> outboundOrderDetails = new List<Dt_OutboundOrderDetail>(); List<Dt_OutStockLockInfo> outStockLockInfos = new List<Dt_OutStockLockInfo>(); List<Dt_LocationInfo> locationInfos = new List<Dt_LocationInfo>(); // å è·å订åæç»ä¿¡æ¯ï¼ç¡®å®ç©æ var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>() .FirstAsync(x => x.Id == orderDetailId); (List<Dt_Task>, List<Dt_StockInfo>?, List<Dt_OutboundOrderDetail>?, List<Dt_OutStockLockInfo>?, List<Dt_LocationInfo>?) result = await BatchAllocateStockDataHandle(orderDetailId, batchQuantity, outStation); if (orderDetail == null) return WebResponseContent.Instance.Error("æªæ¾å°è®¢åæç»ä¿¡æ¯"); if (result.Item2 != null && result.Item2.Count > 0) { stockInfos.AddRange(result.Item2); } if (result.Item3 != null && result.Item3.Count > 0) { outboundOrderDetails.AddRange(result.Item3); } if (result.Item4 != null && result.Item4.Count > 0) { outStockLockInfos.AddRange(result.Item4); } if (result.Item5 != null && result.Item5.Count > 0) { locationInfos.AddRange(result.Item5); } if (result.Item1 != null && result.Item1.Count > 0) { tasks.AddRange(result.Item1); } // è·åç©æçº§å åé var semaphore = GetMaterialSemaphore(orderDetailId + orderDetail.MaterielCode, orderDetail.BatchNo, orderDetail.SupplyCode); WebResponseContent content = await GenerateOutboundTaskDataUpdateAsync(tasks, stockInfos, outboundOrderDetails, outStockLockInfos, locationInfos); return content; // çå¾ è·åå åéï¼æå¤çå¾ 30ç§ bool memoryLockAcquired = await semaphore.WaitAsync(TimeSpan.FromSeconds(30)); if (!memoryLockAcquired) return WebResponseContent.Instance.Error("ç³»ç»ç¹å¿ï¼è¯·ç¨åéè¯"); try { List<Dt_Task> tasks = new List<Dt_Task>(); List<Dt_StockInfo> stockInfos = new List<Dt_StockInfo>(); List<Dt_OutboundOrderDetail> outboundOrderDetails = new List<Dt_OutboundOrderDetail>(); List<Dt_OutStockLockInfo> outStockLockInfos = new List<Dt_OutStockLockInfo>(); List<Dt_LocationInfo> locationInfos = new List<Dt_LocationInfo>(); (List<Dt_Task>, List<Dt_StockInfo>?, List<Dt_OutboundOrderDetail>?, List<Dt_OutStockLockInfo>?, List<Dt_LocationInfo>?) result = await BatchAllocateStockDataHandle(orderDetailId, batchQuantity, outStation); if (result.Item2 != null && result.Item2.Count > 0) { stockInfos.AddRange(result.Item2); } if (result.Item3 != null && result.Item3.Count > 0) { outboundOrderDetails.AddRange(result.Item3); } if (result.Item4 != null && result.Item4.Count > 0) { outStockLockInfos.AddRange(result.Item4); } if (result.Item5 != null && result.Item5.Count > 0) { locationInfos.AddRange(result.Item5); } if (result.Item1 != null && result.Item1.Count > 0) { tasks.AddRange(result.Item1); } WebResponseContent content = await GenerateOutboundTaskDataUpdateAsync(tasks, stockInfos, outboundOrderDetails, outStockLockInfos, locationInfos); return content; } finally { // éæ¾å åé semaphore.Release(); UpdateMaterialLockUsedTime(orderDetail.MaterielCode, orderDetail.BatchNo, orderDetail.SupplyCode); } } catch (Exception ex) { @@ -768,7 +1001,7 @@ List<Dt_Task> tasks = new List<Dt_Task>(); // è·å订åæç» var outboundOrderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>() var outboundOrderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>().With("UPDLOCK, ROWLOCK") .FirstAsync(x => x.Id == orderDetailId); if (outboundOrderDetail == null)