using Microsoft.Extensions.Logging;
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;
using WIDESEA_Common.OtherEnum;
using WIDESEA_Common.StockEnum;
using WIDESEA_Common.TaskEnum;
using WIDESEA_Core;
using WIDESEA_Core.Helper;
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
{
public partial class TaskService
{
///
/// 空托盘出库任务
///
///
///
public async Task PalletOutboundTask(int num, int locationType)
{
WebResponseContent content = new WebResponseContent();
try
{
var stockInfos = _stockRepository.Db.Queryable().Where(x => x.PalletType == PalletTypeEnum.Empty.ObjToInt() && x.StockStatus == StockStatusEmun.入库完成.ObjToInt()).WhereIF(locationType != 0, x => x.LocationType == locationType).Take(num).ToList();
if (stockInfos.Count() == 0)
{
return WebResponseContent.Instance.Error("未找到空托盘库存");
}
foreach (var stockInfo in stockInfos)
{
Dt_LocationInfo locationInfo = _locationInfoService.Repository.QueryFirst(x => x.LocationCode == stockInfo.LocationCode);
if (locationInfo == null)
{
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
{
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)
{
return WebResponseContent.Instance.Error(ex.Message);
}
}
///
/// 出库任务数据处理
///
///
///
///
///
public (List, List?, List?, List?, List?) OutboundTaskDataHandle(int[] keys, string outStation)
{
List tasks = new List();
List outboundOrderDetails = _outboundOrderDetailService.Repository.QueryData(x => keys.Contains(x.Id));
if (outboundOrderDetails == null || outboundOrderDetails.Count == 0)
{
throw new Exception("未找到出库单明细信息");
}
//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("所选出库单明细存在已完成状态,无法重新分配");
}
List? stockInfos = null;
List? orderDetails = null;
List? outStockLockInfos = null;
List? locationInfos = null;
CleanupPreviousInvalidLocks(outboundOrderDetails);
// 开启事务,使用数据库行级锁
using (var transaction = _outboundOrderDetailService.Db.Ado.UseTran())
{
try
{
// 使用悲观锁锁定订单明细
var lockedOrderDetails = new List();
foreach (var key in keys)
{
var detail = _outboundOrderDetailService.Db.Ado.SqlQuerySingle(
"SELECT * FROM Dt_OutboundOrderDetail WITH (UPDLOCK, ROWLOCK) WHERE Id = @Id",
new { Id = key });
if (detail != null)
{
lockedOrderDetails.Add(detail);
}
}
if (!lockedOrderDetails.Any())
{
throw new Exception("未找到出库单明细信息");
}
(List, List, List, List) 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);
}
}
///
/// 清理之前的无效锁定记录
///
private void CleanupPreviousInvalidLocks(List orderDetails)
{
var orderIds = orderDetails.Select(x => x.OrderId).Distinct().ToList();
var orderNos = _outboundOrderService.Db.Queryable()
.Where(x => orderIds.Contains(x.Id))
.Select(x => x.OrderNo)
.ToList();
// 清理状态为"已释放"或"回库中"的旧锁定记录
foreach (var orderNo in orderNos)
{
_outStockLockInfoService.Db.Updateable()
.SetColumns(x => new Dt_OutStockLockInfo
{
Status = (int)OutLockStockStatusEnum.已释放
})
.Where(x => x.OrderNo == orderNo &&
(x.Status == (int)OutLockStockStatusEnum.回库中 ||
x.Status == (int)OutLockStockStatusEnum.已释放))
.ExecuteCommand();
}
}
///
/// 生成出库任务后数据更新到数据库
///
///
///
///
///
///
///
public async Task GenerateOutboundTaskDataUpdateAsync(List tasks, List? stockInfos = null, List? outboundOrderDetails = null, List? outStockLockInfos = null, List? locationInfos = null)
{
try
{
_unitOfWorkManage.BeginTran();
BaseDal.AddData(tasks);
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 =>
{
x.OrderDetailStatus = OrderDetailStatusEnum.Outbound.ObjToInt();
});
Dt_OutboundOrder outboundOrder = _outboundOrderService.Repository.QueryFirst(x => x.Id == outboundOrderDetails.FirstOrDefault().OrderId);
if (outboundOrder.OrderStatus != OutOrderStatusEnum.出库中.ObjToInt())
{
_outboundOrderService.Repository.UpdateData(outboundOrder);
}
else
{
outboundOrder.OrderStatus = OutOrderStatusEnum.出库中.ObjToInt();
}
outboundOrder.Operator = App.User.UserName;
_outboundOrderService.Repository.UpdateData(outboundOrder);
WebResponseContent content = _outboundOrderDetailService.LockOutboundStockDataUpdate(stockInfos, outboundOrderDetails, outStockLockInfos, locationInfos, tasks: tasks);
if (!content.Status)
{
_unitOfWorkManage.RollbackTran();
return content;
}
}
else if (outboundOrderDetails != null && outboundOrderDetails.Count > 0)
{
outboundOrderDetails.ForEach(x =>
{
x.OrderDetailStatus = OrderDetailStatusEnum.Outbound.ObjToInt();
});
Dt_OutboundOrder outboundOrder = _outboundOrderService.Repository.QueryFirst(x => x.Id == outboundOrderDetails.FirstOrDefault().OrderId);
if (outboundOrder.OrderStatus != OutOrderStatusEnum.出库中.ObjToInt())
{
_outboundOrderService.Repository.UpdateData(outboundOrder);
}
else
{
outboundOrder.OrderStatus = OutOrderStatusEnum.出库中.ObjToInt();
}
outboundOrder.Operator = App.User.UserName;
_outboundOrderService.Repository.UpdateData(outboundOrder);
_outboundOrderDetailService.Repository.UpdateData(outboundOrderDetails);
}
_unitOfWorkManage.CommitTran();
TaskModel esstask = new TaskModel()
{
taskType = "carry",
taskGroupCode = "",
groupPriority = 0,
tasks = new List()
};
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)
{
_unitOfWorkManage.RollbackTran();
return WebResponseContent.Instance.Error(ex.Message);
}
}
///
/// 库存数据转出库任务
///
///
///
public List GetTasks(List stockInfos, TaskTypeEnum taskType, string outStation)
{
List tasks = new List();
List locationInfos = _locationInfoService.Repository.QueryData(x => stockInfos.Select(x => x.LocationCode).Contains(x.LocationCode));
for (int i = 0; i < stockInfos.Count; i++)
{
Dt_StockInfo stockInfo = stockInfos[i];
if (stockInfo != null)
{
Dt_LocationInfo? locationInfo = locationInfos.FirstOrDefault(x => x.LocationCode == stockInfo.LocationCode);
if (!tasks.Exists(x => x.PalletCode == stockInfo.PalletCode))
{
Dt_Task task = new()
{
CurrentAddress = stockInfo.LocationCode,
Grade = 0,
PalletCode = stockInfo.PalletCode,
NextAddress = "",
Roadway = locationInfo.RoadwayNo,
SourceAddress = stockInfo.LocationCode,
TargetAddress = outStation,
TaskStatus = TaskStatusEnum.New.ObjToInt(),
TaskType = taskType.ObjToInt(),
// TaskNum = BaseDal.GetTaskNum(nameof(SequenceEnum.SeqTaskNum)),
PalletType = stockInfo.PalletType,
WarehouseId = stockInfo.WarehouseId,
};
//if (taskType != TaskTypeEnum.OutEmpty)
//{
// task.MaterielCode = stockInfo.Details?.Where(x => x.StockId == stockInfo.Id).FirstOrDefault()?.MaterielCode;
// task.Quantity = (float)stockInfo.Details?.Where(x => x.StockId == stockInfo.Id).Sum(x => x.StockQuantity);
// task.BatchNo = stockInfo.Details?.Where(x => x.StockId == stockInfo.Id).FirstOrDefault()?.BatchNo;
//}
tasks.Add(task);
}
}
}
return tasks;
}
public List GetTasks(List stockInfos, TaskTypeEnum taskType)
{
List tasks = new List();
List locationInfos = _locationInfoService.Repository.QueryData(x => stockInfos.Select(x => x.LocationCode).Contains(x.LocationCode));
for (int i = 0; i < stockInfos.Count; i++)
{
Dt_StockInfo stockInfo = stockInfos[i];
if (stockInfo != null)
{
Dt_LocationInfo? locationInfo = locationInfos.FirstOrDefault(x => x.LocationCode == stockInfo.LocationCode);
if (!tasks.Exists(x => x.PalletCode == stockInfo.PalletCode))
{
Dt_Task task = new()
{
CurrentAddress = stockInfo.LocationCode,
Grade = 0,
PalletCode = stockInfo.PalletCode,
NextAddress = "",
Roadway = locationInfo.RoadwayNo,
SourceAddress = stockInfo.LocationCode,
TargetAddress = "",
TaskStatus = TaskStatusEnum.New.ObjToInt(),
TaskType = taskType.ObjToInt(),
//TaskNum = BaseDal.GetTaskNum(nameof(SequenceEnum.SeqTaskNum)),
PalletType = stockInfo.PalletType,
WarehouseId = stockInfo.WarehouseId,
};
//if (taskType != TaskTypeEnum.OutEmpty)
//{
// task.MaterielCode = stockInfo.Details?.Where(x => x.StockId == stockInfo.Id).FirstOrDefault()?.MaterielCode;
// task.Quantity = (float)stockInfo.Details?.Where(x => x.StockId == stockInfo.Id).Sum(x => x.StockQuantity);
// task.BatchNo = stockInfo.Details?.Where(x => x.StockId == stockInfo.Id).FirstOrDefault()?.BatchNo;
//}
//if (stockInfo.StockLength > 0)
//{
// task.TaskLength = stockInfo.StockLength;
//}
tasks.Add(task);
}
}
}
return tasks;
}
#region 内存锁管理器
private static readonly ConcurrentDictionary _normalmaterialLocks =
new ConcurrentDictionary();
private static readonly ConcurrentDictionary _normallockLastUsed =
new ConcurrentDictionary();
private static readonly object _normalcleanupLock = new object();
private static DateTime _normallastCleanupTime = DateTime.MinValue;
///
/// 获取物料级内存锁
///
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;
}
///
/// 更新内存锁最后使用时间
///
private void UpdateNormalMaterialLockUsedTime(string materialCode, string batchNo, string supplyCode)
{
string lockKey = $"MaterialLock_{materialCode}_{batchNo}_{supplyCode}";
_normallockLastUsed[lockKey] = DateTime.Now;
}
#endregion
///
/// 生成出库任务
///
/// 出库单明细主键
///
public async Task GenerateOutboundTasksAsync(int[] keys, string outStation)
{
try
{
List tasks = new List();
List stockSelectViews = new List();
List stockInfos = new List();
List outboundOrderDetails = new List();
List outStockLockInfos = new List();
List locationInfos = new List();
// 先获取所有订单明细,确定需要锁定的物料
var orderDetails = _outboundOrderDetailService.Repository.QueryData(x => keys.Contains(x.Id));
if (orderDetails == null || orderDetails.Count == 0)
{
return WebResponseContent.Instance.Error("未找到出库单明细信息");
}
// 获取所有需要锁定的物料分组
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();
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, List?, List?, List?, List?) 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)
{
_unitOfWorkManage.RollbackTran();
return WebResponseContent.Instance.Error(ex.Message);
}
}
///
/// 智仓调智仓
///
///
///
///
///
public async Task GenerateAllocatOutboundTask(int orderDetailId, List stockSelectViews, string station = null)
{
try
{
var allocorder = _allocateOrderRepository.Db.Queryable().Includes(x => x.Details).Where(x => x.Details.Any(o => o.Id == orderDetailId)).First();
//var allocorder = _allocateOrderDetailRepository.Db.Queryable()
// .LeftJoin((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().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, List?, List?, List?, List?) 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);
}
}
///
/// 生成出库任务
///
///
///
///
public async Task GenerateOutboundTask(int orderDetailId, List stockSelectViews, string station = null)
{
try
{
var orderNo = _reCheckOrderRepository.Db.Queryable().First(x => x.Id == orderDetailId)?.OrderNo;
var outboundOrder = _outboundOrderService.Db.Queryable().Includes(x => x.Details).First(x => x.UpperOrderNo == orderNo);
if (outboundOrder == null)
{
return WebResponseContent.Instance.Error("找不到单据");
}
(List, List?, List?, List?, List?) 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);
}
}
///
/// 出库任务数据处理
///
///
///
///
///
public (List, List?, List?, List?, List?) OutboundTaskDataHandle(int orderDetailId, List stockSelectViews, string station = null)
{
List tasks = new List();
Dt_OutboundOrderDetail outboundOrderDetail = _outboundOrderDetailService.Repository.QueryFirst(x => x.Id == orderDetailId);
if (outboundOrderDetail == null)
{
throw new Exception("未找到出库单明细信息");
}
//if (stockSelectViews.Sum(x => x.UseableQuantity) > outboundOrderDetail.OrderQuantity - outboundOrderDetail.LockQuantity)
//{
// throw new Exception("选择数量超出单据数量");
//}
List? stockInfos = null;
Dt_OutboundOrderDetail? orderDetail = null;
List? outStockLockInfos = null;
List? locationInfos = null;
if (outboundOrderDetail.OrderDetailStatus == OrderDetailStatusEnum.New.ObjToInt())
{
(List, Dt_OutboundOrderDetail, List, List) result = _outboundOrderDetailService.AssignStockOutbound(outboundOrderDetail, stockSelectViews);
if (result.Item1 != null && result.Item1.Count > 0)
{
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,
_ => TaskTypeEnum.Outbound
};
tasks = GetTasks(result.Item1, typeEnum, station);
result.Item2.OrderDetailStatus = OrderDetailStatusEnum.Outbound.ObjToInt();
result.Item3.ForEach(x =>
{
x.Status = OutLockStockStatusEnum.出库中.ObjToInt();
});
stockInfos = result.Item1;
orderDetail = result.Item2;
outStockLockInfos = result.Item3;
locationInfos = result.Item4;
}
else
{
throw new Exception("无库存");
}
}
else
{
List stockLockInfos = _outStockLockInfoService.GetByOrderDetailId(outboundOrderDetail.OrderId, OutLockStockStatusEnum.已分配);
if (stockLockInfos != null && stockLockInfos.Count > 0)
{
List stocks = _stockService.StockInfoService.GetStockInfosByPalletCodes(stockLockInfos.Select(x => x.PalletCode).Distinct().ToList());
tasks = GetTasks(stocks, TaskTypeEnum.Outbound);
}
}
return (tasks, stockInfos, orderDetail == null ? null : new List { orderDetail }, outStockLockInfos, locationInfos);
}
///
/// 生成出库任务后数据更新到数据库
///
///
///
///
///
///
///
public async Task GenerateOutboundTaskDataUpdate(List tasks, List? stockInfos = null, List? outboundOrderDetails = null, List? outStockLockInfos = null, List? locationInfos = null)
{
try
{
_unitOfWorkManage.BeginTran();
BaseDal.AddData(tasks);
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 =>
{
x.OrderDetailStatus = OrderDetailStatusEnum.Outbound.ObjToInt();
});
Dt_OutboundOrder outboundOrder = _outboundOrderService.Repository.QueryFirst(x => x.Id == outboundOrderDetails.FirstOrDefault().OrderId);
if (outboundOrder.OrderStatus != OutOrderStatusEnum.出库中.ObjToInt())
{
_outboundOrderService.Repository.UpdateData(outboundOrder);
}
WebResponseContent content = _outboundOrderDetailService.LockOutboundStockDataUpdate(stockInfos, outboundOrderDetails, outStockLockInfos, locationInfos, tasks: tasks);
if (!content.Status)
{
_unitOfWorkManage.RollbackTran();
return content;
}
}
else if (outboundOrderDetails != null && outboundOrderDetails.Count > 0)
{
outboundOrderDetails.ForEach(x =>
{
x.OrderDetailStatus = OrderDetailStatusEnum.Outbound.ObjToInt();
});
Dt_OutboundOrder outboundOrder = _outboundOrderService.Repository.QueryFirst(x => x.Id == outboundOrderDetails.FirstOrDefault().OrderId);
if (outboundOrder.OrderStatus != OutOrderStatusEnum.出库中.ObjToInt())
{
_outboundOrderService.Repository.UpdateData(outboundOrder);
}
_outboundOrderDetailService.Repository.UpdateData(outboundOrderDetails);
}
_unitOfWorkManage.CommitTran();
//PushTasksToWCS(tasks);
TaskModel esstask = new TaskModel()
{
taskType = "carry",
taskGroupCode = "",
groupPriority = 0,
tasks = new List()
};
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)
{
_unitOfWorkManage.RollbackTran();
return WebResponseContent.Instance.Error(ex.Message);
}
}
#region 分批分配库存
#region 内存锁管理器
private static readonly ConcurrentDictionary _materialLocks =
new ConcurrentDictionary();
private static readonly ConcurrentDictionary _lockLastUsed =
new ConcurrentDictionary();
private static readonly object _cleanupLock = new object();
private static DateTime _lastCleanupTime = DateTime.MinValue;
///
/// 获取物料级内存锁
///
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;
}
///
/// 释放内存锁并更新最后使用时间
///
private void UpdateMaterialLockUsedTime(string materialCode, string batchNo, string supplyCode)
{
string lockKey = $"MaterialLock_{materialCode}_{batchNo}_{supplyCode}";
_lockLastUsed[lockKey] = DateTime.Now;
}
#endregion
///
/// 分批分配库存
///
public async Task GenerateOutboundBatchTasksAsync(int orderDetailId, decimal batchQuantity, string outStation)
{
try
{
// 先获取订单明细信息,确定物料
var orderDetail = await _outboundOrderDetailService.Db.Queryable()
.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 tasks = new List();
List stockInfos = new List();
List outboundOrderDetails = new List();
List outStockLockInfos = new List();
List locationInfos = new List();
(List, List?, List?, List?, List?) 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}");
}
}
///
/// 分批分配库存数据处理
///
public async Task<(List, List?, List?, List?, List?)>
BatchAllocateStockDataHandle(int orderDetailId, decimal batchQuantity, string outStation)
{
List tasks = new List();
// 获取订单明细
var outboundOrderDetail = await _outboundOrderDetailService.Db.Queryable().With("UPDLOCK, ROWLOCK")
.FirstAsync(x => x.Id == orderDetailId);
if (outboundOrderDetail == null)
{
throw new Exception("未找到出库单明细信息");
}
var outboundOrder = await _outboundOrderService.Db.Queryable().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? stockInfos = null;
List? orderDetails = null;
List? outStockLockInfos = null;
List? locationInfos = null;
// 生成批次号
string batchNo = await GenerateBatchNo();
// 分配库存
(List, List, List, List) 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);
}
///
/// 更新订单明细状态
///
private void UpdateOrderDetailStatus(List 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 GenerateBatchNo()
{
var batchNo = UniqueValueGenerator.Generate();
return $"Out{batchNo} ";
}
private async Task 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
}
}