From db215dc033392d843cec846f0a8f9dbff2578c6a Mon Sep 17 00:00:00 2001
From: pan <antony1029@163.com>
Date: 星期四, 04 十二月 2025 12:57:53 +0800
Subject: [PATCH] 提交
---
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_TaskInfoService/TaskService_Outbound.cs | 835 +++++++++++++++++++++++++++++++++++++++++++++++++----------
1 files changed, 691 insertions(+), 144 deletions(-)
diff --git "a/\351\241\271\347\233\256\344\273\243\347\240\201/WMS\346\227\240\344\273\223\345\202\250\347\211\210/WIDESEA_WMSServer/WIDESEA_TaskInfoService/TaskService_Outbound.cs" "b/\351\241\271\347\233\256\344\273\243\347\240\201/WMS\346\227\240\344\273\223\345\202\250\347\211\210/WIDESEA_WMSServer/WIDESEA_TaskInfoService/TaskService_Outbound.cs"
index 8b0eb09..5cf304e 100644
--- "a/\351\241\271\347\233\256\344\273\243\347\240\201/WMS\346\227\240\344\273\223\345\202\250\347\211\210/WIDESEA_WMSServer/WIDESEA_TaskInfoService/TaskService_Outbound.cs"
+++ "b/\351\241\271\347\233\256\344\273\243\347\240\201/WMS\346\227\240\344\273\223\345\202\250\347\211\210/WIDESEA_WMSServer/WIDESEA_TaskInfoService/TaskService_Outbound.cs"
@@ -2,10 +2,12 @@
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;
using System.Threading.Tasks;
+using WIDESEA_BasicService;
using WIDESEA_Common.CommonEnum;
using WIDESEA_Common.LocationEnum;
using WIDESEA_Common.OrderEnum;
@@ -17,6 +19,9 @@
using WIDESEA_DTO.Basic;
using WIDESEA_DTO.Stock;
using WIDESEA_Model.Models;
+using WIDESEA_Model.Models.Basic;
+using WIDESEA_Model.Models.Check;
+using WIDESEA_Model.Models.Outbound;
namespace WIDESEA_TaskInfoService
{
@@ -27,95 +32,83 @@
/// </summary>
/// <param name="inTask"></param>
/// <returns></returns>
- public async Task<WebResponseContent> PalletOutboundTask(string endStation, string palletCode = "")
+ public async Task<WebResponseContent> PalletOutboundTask(int num, int locationType)
{
+ WebResponseContent content = new WebResponseContent();
try
{
- Dt_StockInfo stockInfo;
- if (string.IsNullOrEmpty(palletCode))
- {
- stockInfo = _stockRepository.Db.Queryable<Dt_StockInfo>().Where(x => x.PalletType == PalletTypeEnum.Empty.ObjToInt() && x.StockStatus == StockStatusEmun.鍏ュ簱瀹屾垚.ObjToInt() && !string.IsNullOrWhiteSpace(x.LocationCode)).First();
- }
- else
- {
- stockInfo = _stockRepository.Db.Queryable<Dt_StockInfo>().Where(x => x.PalletType == PalletTypeEnum.Empty.ObjToInt() && x.PalletCode == palletCode && x.StockStatus == StockStatusEmun.鍏ュ簱瀹屾垚.ObjToInt()).First();
- }
+ var stockInfos = _stockRepository.Db.Queryable<Dt_StockInfo>().Where(x => x.PalletType == PalletTypeEnum.Empty.ObjToInt() && x.StockStatus == StockStatusEmun.鍏ュ簱瀹屾垚.ObjToInt()).WhereIF(locationType != 0, x => x.LocationType == locationType).Take(num).ToList();
- if (stockInfo == null)
+ if (stockInfos.Count() == 0)
{
return WebResponseContent.Instance.Error("鏈壘鍒扮┖鎵樼洏搴撳瓨");
}
- Dt_LocationInfo locationInfo = _locationInfoService.Repository.QueryFirst(x => x.LocationCode == stockInfo.LocationCode);
- if (locationInfo == null)
+ foreach (var stockInfo in stockInfos)
{
- return WebResponseContent.Instance.Error("鏈壘鍒扮┖鎵樼洏搴撳瓨瀵瑰簲鐨勮揣浣嶄俊鎭�");
- }
-
- Dt_Task task = new Dt_Task()
- {
- CurrentAddress = stockInfo.LocationCode,
- Grade = 0,
- NextAddress = endStation,
- PalletCode = stockInfo.PalletCode,
- Roadway = locationInfo.RoadwayNo,
- SourceAddress = stockInfo.LocationCode,
- TargetAddress = endStation,
- TaskStatus = TaskStatusEnum.New.ObjToInt(),
- TaskType = TaskTypeEnum.OutEmpty.ObjToInt(),
- WarehouseId = stockInfo.WarehouseId,
- PalletType = stockInfo.PalletType
-
- };
- int beforeStatus = locationInfo.LocationStatus;
- _unitOfWorkManage.BeginTran();
- stockInfo.StockStatus = StockStatusEmun.鍑哄簱閿佸畾.ObjToInt();
- locationInfo.LocationStatus = LocationStatusEnum.Lock.ObjToInt();
-
- int taskId = BaseDal.AddData(task);
- task.TaskId = taskId;
-
- _stockService.StockInfoService.UpdateData(stockInfo);
-
- _locationInfoService.UpdateData(locationInfo);
-
- _recordService.LocationStatusChangeRecordSetvice.AddLocationStatusChangeRecord(locationInfo, beforeStatus, StockChangeType.Outbound.ObjToInt(), "", task.TaskNum);
-
- _unitOfWorkManage.CommitTran();
-
- TaskModel esstask = new TaskModel()
- {
- taskType = "carry",
- taskGroupCode = "",
- groupPriority = 0,
- tasks = new List<TasksType>
+ Dt_LocationInfo locationInfo = _locationInfoService.Repository.QueryFirst(x => x.LocationCode == stockInfo.LocationCode);
+ if (locationInfo == null)
{
- new()
- {
- taskCode=task.TaskNum.ToString(),
- taskPriority=0,
- taskDescribe=new TaskDescribeType{
- containerCode=stockInfo.PalletCode,
- containerType= "CT_KUBOT_STANDARD",
- fromLocationCode=stockInfo.LocationCode??"",
- toStationCode="",
- toLocationCode=endStation,
- deadline=0,storageTag=""
- }
- }
+ return WebResponseContent.Instance.Error("鏈壘鍒扮┖鎵樼洏搴撳瓨瀵瑰簲鐨勮揣浣嶄俊鎭�");
}
- };
- var result = await _eSSApiService.CreateTaskAsync(esstask);
- _logger.LogInformation("鍒涘缓浠诲姟PalletOutboundTask 杩斿洖: " + result);
- if (result)
- {
- return WebResponseContent.Instance.OK(200);
- }
- else
- {
- return WebResponseContent.Instance.Error("涓嬪彂鏈哄櫒浜轰换鍔″け璐ワ紒");
- }
+ Dt_Task task = new Dt_Task()
+ {
+ CurrentAddress = stockInfo.LocationCode,
+ Grade = 0,
+ NextAddress = "1-2",
+ PalletCode = stockInfo.PalletCode,
+ Roadway = locationInfo.RoadwayNo,
+ SourceAddress = stockInfo.LocationCode,
+ TargetAddress = "1-2",
+ TaskStatus = TaskStatusEnum.New.ObjToInt(),
+ TaskType = TaskTypeEnum.OutEmpty.ObjToInt(),
+ WarehouseId = stockInfo.WarehouseId,
+ PalletType = stockInfo.PalletType
+ };
+ int beforeStatus = locationInfo.LocationStatus;
+ _unitOfWorkManage.BeginTran();
+ stockInfo.StockStatus = StockStatusEmun.鍑哄簱閿佸畾.ObjToInt();
+ locationInfo.LocationStatus = LocationStatusEnum.Lock.ObjToInt();
+
+ int taskId = BaseDal.AddData(task);
+ task.TaskId = taskId;
+
+ _stockService.StockInfoService.UpdateData(stockInfo);
+
+ _locationInfoService.UpdateData(locationInfo);
+
+ _recordService.LocationStatusChangeRecordSetvice.AddLocationStatusChangeRecord(locationInfo, beforeStatus, StockChangeType.Outbound.ObjToInt(), "", task.TaskNum);
+
+ _unitOfWorkManage.CommitTran();
+
+ TaskModel esstask = new TaskModel()
+ {
+ taskType = "carry",
+ taskGroupCode = "",
+ groupPriority = 0,
+ tasks = new List<TasksType>
+ {
+ new()
+ {
+ taskCode=task.TaskNum.ToString(),
+ taskPriority=0,
+ taskDescribe=new TaskDescribeType{
+ containerCode=stockInfo.PalletCode,
+ containerType= "CT_KUBOT_STANDARD",
+ fromLocationCode=stockInfo.LocationCode??"",
+ toStationCode="",
+ toLocationCode="1-2",
+ deadline=0,storageTag=""
+ }
+ }
+ }
+ };
+ var result = await _eSSApiService.CreateTaskAsync(esstask);
+
+ _logger.LogInformation("鍒涘缓浠诲姟PalletOutboundTask 杩斿洖: " + result);
+ }
+ return content.OK("绌烘墭鍑哄簱鎴愬姛!");
}
catch (Exception ex)
{
@@ -140,52 +133,115 @@
{
throw new Exception("鏈壘鍒板嚭搴撳崟鏄庣粏淇℃伅");
}
- if (outboundOrderDetails.FirstOrDefault(x => x.OrderDetailStatus > OrderDetailStatusEnum.New.ObjToInt() && x.OrderDetailStatus != OrderDetailStatusEnum.AssignOverPartial.ObjToInt()) != null)
+ //if (outboundOrderDetails.FirstOrDefault(x => x.OrderDetailStatus > OrderDetailStatusEnum.New.ObjToInt() && x.OrderDetailStatus != OrderDetailStatusEnum.AssignOverPartial.ObjToInt()) != null)
+ //{
+ // throw new Exception("鎵�閫夊嚭搴撳崟鏄庣粏瀛樺湪鍑哄簱涓垨宸插畬鎴�");
+ //}
+
+ if (outboundOrderDetails.FirstOrDefault(x => x.OrderDetailStatus > OrderDetailStatusEnum.Outbound.ObjToInt() && x.OrderDetailStatus != OrderDetailStatusEnum.AssignOverPartial.ObjToInt()) != null)
{
- throw new Exception("鎵�閫夊嚭搴撳崟鏄庣粏瀛樺湪鍑哄簱涓垨宸插畬鎴�");
+ throw new Exception("鎵�閫夊嚭搴撳崟鏄庣粏瀛樺湪宸插畬鎴愮姸鎬侊紝鏃犳硶閲嶆柊鍒嗛厤");
}
+
List<Dt_StockInfo>? stockInfos = null;
List<Dt_OutboundOrderDetail>? orderDetails = null;
List<Dt_OutStockLockInfo>? outStockLockInfos = null;
List<Dt_LocationInfo>? locationInfos = null;
- (List<Dt_StockInfo>, List<Dt_OutboundOrderDetail>, List<Dt_OutStockLockInfo>, List<Dt_LocationInfo>) result = _outboundOrderDetailService.AssignStockOutbound(outboundOrderDetails);
- if (result.Item1 != null && result.Item1.Count > 0)
+ CleanupPreviousInvalidLocks(outboundOrderDetails);
+ // 寮�鍚簨鍔★紝浣跨敤鏁版嵁搴撹绾ч攣
+ 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>
+ /// 娓呯悊涔嬪墠鐨勬棤鏁堥攣瀹氳褰�
+ /// </summary>
+ private void CleanupPreviousInvalidLocks(List<Dt_OutboundOrderDetail> orderDetails)
+ {
+ var orderIds = orderDetails.Select(x => x.OrderId).Distinct().ToList();
+ var orderNos = _outboundOrderService.Db.Queryable<Dt_OutboundOrder>()
+ .Where(x => orderIds.Contains(x.Id))
+ .Select(x => x.OrderNo)
+ .ToList();
+ // 娓呯悊鐘舵�佷负"宸查噴鏀�"鎴�"鍥炲簱涓�"鐨勬棫閿佸畾璁板綍
+ foreach (var orderNo in orderNos)
+ {
+ _outStockLockInfoService.Db.Updateable<Dt_OutStockLockInfo>()
+ .SetColumns(x => new Dt_OutStockLockInfo
+ {
+ Status = (int)OutLockStockStatusEnum.宸查噴鏀�
+ })
+ .Where(x => x.OrderNo == orderNo &&
+ (x.Status == (int)OutLockStockStatusEnum.鍥炲簱涓� ||
+ x.Status == (int)OutLockStockStatusEnum.宸查噴鏀�))
+ .ExecuteCommand();
+ }
+ }
/// <summary>
/// 鐢熸垚鍑哄簱浠诲姟鍚庢暟鎹洿鏂板埌鏁版嵁搴�
@@ -206,7 +262,7 @@
if (stockInfos != null && stockInfos.Count > 0 && outboundOrderDetails != null && outboundOrderDetails.Count > 0 && outStockLockInfos != null && outStockLockInfos.Count > 0 && locationInfos != null && locationInfos.Count > 0)
{
stockInfos.ForEach(x =>
- {
+ {
x.StockStatus = StockStatusEmun.鍑哄簱閿佸畾.ObjToInt();
});
outboundOrderDetails.ForEach(x =>
@@ -218,8 +274,11 @@
{
_outboundOrderService.Repository.UpdateData(outboundOrder);
}
+ else
+ {
+ outboundOrder.OrderStatus = OutOrderStatusEnum.鍑哄簱涓�.ObjToInt();
+ }
outboundOrder.Operator = App.User.UserName;
- outboundOrder.OrderStatus = OutOrderStatusEnum.鍑哄簱涓�.ObjToInt();
_outboundOrderService.Repository.UpdateData(outboundOrder);
WebResponseContent content = _outboundOrderDetailService.LockOutboundStockDataUpdate(stockInfos, outboundOrderDetails, outStockLockInfos, locationInfos, tasks: tasks);
@@ -240,8 +299,11 @@
{
_outboundOrderService.Repository.UpdateData(outboundOrder);
}
+ else
+ {
+ outboundOrder.OrderStatus = OutOrderStatusEnum.鍑哄簱涓�.ObjToInt();
+ }
outboundOrder.Operator = App.User.UserName;
- outboundOrder.OrderStatus = OutOrderStatusEnum.鍑哄簱涓�.ObjToInt();
_outboundOrderService.Repository.UpdateData(outboundOrder);
_outboundOrderDetailService.Repository.UpdateData(outboundOrderDetails);
}
@@ -387,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>
@@ -402,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)
{
@@ -435,20 +615,70 @@
}
}
+ /// <summary>
+ /// 鏅轰粨璋冩櫤浠�
+ /// </summary>
+ /// <param name="orderDetailId"></param>
+ /// <param name="stockSelectViews"></param>
+ /// <param name="station"></param>
+ /// <returns></returns>
+ public async Task<WebResponseContent> GenerateAllocatOutboundTask(int orderDetailId, List<StockSelectViewDTO> stockSelectViews, string station = null)
+ {
+ try
+ {
+ var allocorder = _allocateOrderRepository.Db.Queryable<Dt_AllocateOrder>().Includes(x => x.Details).Where(x => x.Details.Any(o => o.Id == orderDetailId)).First();
+ //var allocorder = _allocateOrderDetailRepository.Db.Queryable<Dt_AllocateOrderDetail>()
+ // .LeftJoin<Dt_AllocateOrder>((detail, order) => detail.OrderId == order.Id)
+ // .Where((detail, order) => order.Id == orderDetailId)
+ // .Select((detail, order) => order)
+ // .First();
+ if (allocorder == null)
+ {
+ return WebResponseContent.Instance.Error("鎵句笉鍒板崟鎹�");
+ }
+ var outboundOrder = _outboundOrderService.Db.Queryable<Dt_OutboundOrder>().Includes(x => x.Details).First(x => x.OrderNo == allocorder.OrderNo);
+ if (outboundOrder == null)
+ {
+ 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();
+ 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);
+
+ WebResponseContent content = await GenerateOutboundTaskDataUpdate(result.Item1, result.Item2, result.Item3, result.Item4, result.Item5);
+
+ return content;
+ }
+ catch (Exception ex)
+ {
+ return WebResponseContent.Instance.Error(ex.Message);
+ }
+ }
/// <summary>
/// 鐢熸垚鍑哄簱浠诲姟
/// </summary>
/// <param name="orderDetailId"></param>
/// <param name="stockSelectViews"></param>
/// <returns></returns>
- public WebResponseContent GenerateOutboundTask(int orderDetailId, List<StockSelectViewDTO> stockSelectViews)
+ public async Task<WebResponseContent> GenerateOutboundTask(int orderDetailId, List<StockSelectViewDTO> stockSelectViews, string station = null)
{
try
{
- (List<Dt_Task>, List<Dt_StockInfo>?, List<Dt_OutboundOrderDetail>?, List<Dt_OutStockLockInfo>?, List<Dt_LocationInfo>?) result = OutboundTaskDataHandle(orderDetailId, stockSelectViews);
+ var orderNo = _reCheckOrderRepository.Db.Queryable<Dt_ReCheckOrder>().First(x => x.Id == orderDetailId)?.OrderNo;
+ var outboundOrder = _outboundOrderService.Db.Queryable<Dt_OutboundOrder>().Includes(x => x.Details).First(x => x.UpperOrderNo == orderNo);
+ if (outboundOrder == 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);
- WebResponseContent content = 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;
}
@@ -465,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)
+ 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);
@@ -488,15 +718,15 @@
(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,
(int)OutOrderTypeEnum.Allocate => TaskTypeEnum.OutAllocate,
(int)OutOrderTypeEnum.Quality => TaskTypeEnum.OutQuality,
- _ => new TaskTypeEnum()
+ _ => TaskTypeEnum.Outbound
};
- tasks = GetTasks(result.Item1, typeEnum);
+ tasks = GetTasks(result.Item1, typeEnum, station);
result.Item2.OrderDetailStatus = OrderDetailStatusEnum.Outbound.ObjToInt();
result.Item3.ForEach(x =>
{
@@ -535,7 +765,7 @@
/// <param name="outStockLockInfos"></param>
/// <param name="locationInfos"></param>
/// <returns></returns>
- public 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
{
@@ -580,7 +810,46 @@
}
_unitOfWorkManage.CommitTran();
//PushTasksToWCS(tasks);
- return WebResponseContent.Instance.OK();
+ TaskModel esstask = new TaskModel()
+ {
+ taskType = "carry",
+ taskGroupCode = "",
+ groupPriority = 0,
+ tasks = new List<TasksType>()
+ };
+
+ foreach (var task in tasks)
+ {
+ esstask.
+ tasks.Add(new TasksType
+ {
+ taskCode = task.TaskNum.ToString(),
+ taskPriority = 0,
+ taskDescribe = new TaskDescribeType
+ {
+ containerCode = task.PalletCode,
+ containerType = "CT_KUBOT_STANDARD",
+ fromLocationCode = task.SourceAddress ?? "",
+ toStationCode = "",
+ toLocationCode = task.TargetAddress,
+ deadline = 0,
+ storageTag = ""
+ }
+ }
+ );
+ }
+ var result = await _eSSApiService.CreateTaskAsync(esstask);
+
+ _logger.LogInformation("鍒涘缓浠诲姟PalletOutboundTask 杩斿洖: " + result);
+ if (result)
+ {
+ return WebResponseContent.Instance.OK();
+ }
+ else
+ {
+ return WebResponseContent.Instance.Error("涓嬪彂鏈哄櫒浜轰换鍔″け璐ワ紒");
+ }
+
}
catch (Exception ex)
{
@@ -590,5 +859,283 @@
}
+
+ #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>
+ public async Task<WebResponseContent> GenerateOutboundBatchTasksAsync(int orderDetailId, decimal batchQuantity, string outStation)
+ {
+ try
+ {
+ // 鍏堣幏鍙栬鍗曟槑缁嗕俊鎭紝纭畾鐗╂枡
+ var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
+ .FirstAsync(x => x.Id == orderDetailId);
+
+ if (orderDetail == null)
+ return WebResponseContent.Instance.Error("鏈壘鍒拌鍗曟槑缁嗕俊鎭�");
+
+ // 鑾峰彇鐗╂枡绾у唴瀛橀攣
+ var semaphore = GetMaterialSemaphore(orderDetailId + orderDetail.MaterielCode, orderDetail.BatchNo, orderDetail.SupplyCode);
+
+ // 绛夊緟鑾峰彇鍐呭瓨閿侊紝鏈�澶氱瓑寰�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)
+ {
+ _unitOfWorkManage.RollbackTran();
+ _logger.LogError($"鍒嗘壒鍒嗛厤搴撳瓨澶辫触 - OrderDetailId: {orderDetailId}, Quantity: {batchQuantity}, Error: {ex.Message}");
+ return WebResponseContent.Instance.Error($"鍒嗘壒鍒嗛厤澶辫触锛歿ex.Message}");
+ }
+ }
+
+ /// <summary>
+ /// 鍒嗘壒鍒嗛厤搴撳瓨鏁版嵁澶勭悊
+ /// </summary>
+ public async Task<(List<Dt_Task>, List<Dt_StockInfo>?, List<Dt_OutboundOrderDetail>?, List<Dt_OutStockLockInfo>?, List<Dt_LocationInfo>?)>
+ BatchAllocateStockDataHandle(int orderDetailId, decimal batchQuantity, string outStation)
+ {
+ List<Dt_Task> tasks = new List<Dt_Task>();
+
+ // 鑾峰彇璁㈠崟鏄庣粏
+ var outboundOrderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>().With("UPDLOCK, ROWLOCK")
+ .FirstAsync(x => x.Id == orderDetailId);
+
+ if (outboundOrderDetail == null)
+ {
+ throw new Exception("鏈壘鍒板嚭搴撳崟鏄庣粏淇℃伅");
+ }
+ var outboundOrder = await _outboundOrderService.Db.Queryable<Dt_OutboundOrder>().FirstAsync(x => x.Id == outboundOrderDetail.OrderId);
+ if (outboundOrder == null)
+ {
+ throw new Exception("鏈壘鍒板嚭搴撳崟淇℃伅");
+ }
+ // 楠岃瘉璁㈠崟鏄庣粏鐘舵��
+ if (outboundOrderDetail.OrderDetailStatus > OrderDetailStatusEnum.Outbound.ObjToInt() &&
+ outboundOrderDetail.OrderDetailStatus != OrderDetailStatusEnum.AssignOverPartial.ObjToInt())
+ {
+ throw new Exception("鎵�閫夊嚭搴撳崟鏄庣粏瀛樺湪鍑哄簱涓垨宸插畬鎴�");
+ }
+
+ // 楠岃瘉鍒嗛厤鏁伴噺
+ decimal allocatedQty = outboundOrderDetail.AllocatedQuantity;
+ decimal overOutQty = outboundOrderDetail.OverOutQuantity;
+ decimal needOutQty = outboundOrderDetail.NeedOutQuantity;
+ decimal availableQty = needOutQty - allocatedQty - overOutQty;
+
+ if (availableQty <= 0)
+ throw new Exception("鏃犲彲鍒嗛厤鏁伴噺");
+
+ if (batchQuantity > availableQty)
+ throw new Exception($"鍒嗛厤鏁伴噺涓嶈兘瓒呰繃鍙垎閰嶆暟閲弡availableQty}");
+
+ List<Dt_StockInfo>? stockInfos = null;
+ List<Dt_OutboundOrderDetail>? orderDetails = null;
+ List<Dt_OutStockLockInfo>? outStockLockInfos = null;
+ List<Dt_LocationInfo>? locationInfos = null;
+
+ // 鐢熸垚鎵规鍙�
+ string batchNo = await GenerateBatchNo();
+
+ // 鍒嗛厤搴撳瓨
+ (List<Dt_StockInfo>, List<Dt_OutboundOrderDetail>, List<Dt_OutStockLockInfo>, List<Dt_LocationInfo>) allocateResult =
+ await _outboundOrderDetailService.AssignStockForBatch(outboundOrderDetail, batchQuantity, batchNo);
+
+ if (allocateResult.Item1 != null && allocateResult.Item1.Count > 0)
+ {
+ // 鍒涘缓鍒嗘壒璁板綍
+ await CreateBatchRecord(outboundOrder.OrderNo, orderDetailId, batchQuantity, batchNo);
+
+ TaskTypeEnum typeEnum = outboundOrder.OrderType switch
+ {
+ (int)OutOrderTypeEnum.Issue => TaskTypeEnum.Outbound,
+ (int)OutOrderTypeEnum.Allocate => TaskTypeEnum.OutAllocate,
+ (int)OutOrderTypeEnum.Quality => TaskTypeEnum.OutQuality,
+ _ => TaskTypeEnum.Outbound
+ };
+
+ tasks = GetTasks(allocateResult.Item1, typeEnum, outStation);
+ tasks.ForEach(x =>
+ {
+ x.OrderNo = outboundOrder.OrderNo;
+ });
+
+ allocateResult.Item2.ForEach(x =>
+ {
+ x.OrderDetailStatus = OrderDetailStatusEnum.Outbound.ObjToInt();
+ });
+
+ allocateResult.Item3.ForEach(x =>
+ {
+ x.Status = OutLockStockStatusEnum.鍑哄簱涓�.ObjToInt();
+ });
+
+ stockInfos = allocateResult.Item1;
+ orderDetails = allocateResult.Item2;
+ outStockLockInfos = allocateResult.Item3;
+ locationInfos = allocateResult.Item4;
+ }
+ else
+ {
+ throw new Exception("鏃犲簱瀛�");
+ }
+
+ return (tasks, stockInfos, orderDetails, outStockLockInfos, locationInfos);
+ }
+
+
+
+
+
+ /// <summary>
+ /// 鏇存柊璁㈠崟鏄庣粏鐘舵��
+ /// </summary>
+ private void UpdateOrderDetailStatus(List<Dt_OutboundOrderDetail> details, decimal allocatedQuantity, decimal needQuantity)
+ {
+ foreach (var detail in details)
+ {
+ // 鏍规嵁鍒嗛厤鎯呭喌鏇存柊鐘舵��
+ if (allocatedQuantity >= needQuantity)
+ {
+ detail.OrderDetailStatus = OrderDetailStatusEnum.Outbound.ObjToInt();
+ }
+ else
+ {
+ detail.OrderDetailStatus = OrderDetailStatusEnum.AssignOverPartial.ObjToInt();
+ }
+ }
+ }
+
+
+
+ private async Task<string> GenerateBatchNo()
+ {
+ var batchNo = UniqueValueGenerator.Generate();
+
+ return $"Out{batchNo} ";
+ }
+
+ private async Task<Dt_OutboundBatch> CreateBatchRecord(string orderNo, int orderDetailId, decimal batchQuantity, string batchNo)
+ {
+ var batchRecord = new Dt_OutboundBatch
+ {
+ BatchNo = batchNo,
+ OrderNo = orderNo,
+ OrderDetailId = orderDetailId,
+ BatchQuantity = batchQuantity,
+ BatchStatus = (int)BatchStatusEnum.鍒嗛厤涓�,
+ Operator = App.User.UserName
+ };
+
+ await _OutboundBatchRepository.Db.Insertable(batchRecord).ExecuteCommandAsync();
+ return batchRecord;
+ }
+
+ #endregion
+
+
}
}
--
Gitblit v1.9.3