using MailKit.Search;
|
using Microsoft.Extensions.Logging;
|
using Newtonsoft.Json;
|
using OfficeOpenXml.FormulaParsing.Excel.Functions.RefAndLookup;
|
using SqlSugar;
|
using System;
|
using System.Collections;
|
using System.Collections.Concurrent;
|
using System.Collections.Generic;
|
using System.Linq;
|
using System.Text;
|
using System.Threading.Tasks;
|
using System.Transactions;
|
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.CodeConfigEnum;
|
using WIDESEA_Core.DB;
|
using WIDESEA_Core.Helper;
|
using WIDESEA_Core.Seed;
|
using WIDESEA_DTO.Basic;
|
using WIDESEA_DTO.Stock;
|
using WIDESEA_IBasicService;
|
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
|
{
|
/// <summary>
|
/// 空托盘出库任务
|
/// </summary>
|
/// <param name="inTask"></param>
|
/// <returns></returns>
|
public async Task<WebResponseContent> PalletOutboundTask(int num, int locationType)
|
{
|
WebResponseContent content = new WebResponseContent();
|
try
|
{
|
Dictionary<string, SqlSugar.OrderByType> orderByDict = new Dictionary<string, SqlSugar.OrderByType>()
|
{
|
{ nameof(Dt_LocationInfo.Layer), SqlSugar.OrderByType.Asc },
|
{ nameof(Dt_LocationInfo.Row), SqlSugar.OrderByType.Asc },
|
{ nameof(Dt_LocationInfo.Column), SqlSugar.OrderByType.Asc },
|
{ nameof(Dt_LocationInfo.Depth), SqlSugar.OrderByType.Desc },
|
};
|
|
var query = _stockRepository.Db.Queryable<Dt_StockInfo>()
|
.Where(x => x.PalletType == PalletTypeEnum.Empty.ObjToInt()
|
&& x.StockStatus == StockStatusEmun.入库完成.ObjToInt())
|
.WhereIF(locationType != 0, x => x.LocationType == locationType)
|
.LeftJoin<Dt_LocationInfo>((s, l) => s.LocationCode == l.LocationCode);
|
|
if (query.Count() == 0)
|
{
|
return WebResponseContent.Instance.Error("未找到空托盘库存");
|
}
|
|
bool isFirstOrder = true;
|
foreach (var item in orderByDict)
|
{
|
string fieldName = item.Key.Equals("Column", StringComparison.OrdinalIgnoreCase)
|
? $"l.[{item.Key}]"
|
: $"l.{item.Key}";
|
|
string sortSql = $"{fieldName} {(item.Value == SqlSugar.OrderByType.Asc ? "ASC" : "DESC")}";
|
|
if (isFirstOrder)
|
{
|
query = query.OrderBy(sortSql);
|
isFirstOrder = false;
|
}
|
else
|
{
|
query = query.OrderBy(sortSql);
|
}
|
}
|
|
var stockInfos = await query.Take(num).ToListAsync();
|
|
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();
|
|
}
|
return content.OK("空托出库成功!");
|
}
|
catch (Exception ex)
|
{
|
return WebResponseContent.Instance.Error(ex.Message);
|
}
|
}
|
|
|
|
|
/// <summary>
|
/// 出库任务数据处理
|
/// </summary>
|
/// <param name="orderDetailId"></param>
|
/// <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[] keys, string outStation)
|
{
|
List<Dt_Task> tasks = new List<Dt_Task>();
|
List<Dt_OutboundOrderDetail> 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<Dt_StockInfo>? stockInfos = null;
|
List<Dt_OutboundOrderDetail>? orderDetails = null;
|
List<Dt_OutStockLockInfo>? outStockLockInfos = null;
|
List<Dt_LocationInfo>? locationInfos = null;
|
|
CleanupPreviousInvalidLocks(outboundOrderDetails);
|
// 开启事务,使用数据库行级锁
|
using (var transaction = _outboundOrderDetailService.Db.Ado.UseTran())
|
{
|
try
|
{
|
// 使用悲观锁锁定订单明细
|
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) WHERE Id = @Id",
|
new { Id = key });
|
|
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);
|
}
|
}
|
/// <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>
|
/// 生成出库任务后数据更新到数据库
|
/// </summary>
|
/// <param name="tasks"></param>
|
/// <param name="stockInfos"></param>
|
/// <param name="outboundOrderDetails"></param>
|
/// <param name="outStockLockInfos"></param>
|
/// <param name="locationInfos"></param>
|
/// <returns></returns>
|
public async Task<WebResponseContent> GenerateOutboundTaskDataUpdateAsync(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
|
{
|
_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();
|
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)
|
{
|
_unitOfWorkManage.RollbackTran();
|
return WebResponseContent.Instance.Error(ex.Message);
|
}
|
}
|
|
/// <summary>
|
/// 库存数据转出库任务
|
/// </summary>
|
/// <param name="stockInfos"></param>
|
/// <returns></returns>
|
public List<Dt_Task> GetTasks(List<Dt_StockInfo> stockInfos, TaskTypeEnum taskType, string outStation)
|
{
|
List<Dt_Task> tasks = new List<Dt_Task>();
|
List<Dt_LocationInfo> 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;
|
}
|
|
|
|
#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>
|
/// <param name="keys">出库单明细主键</param>
|
/// <returns></returns>
|
public async Task<WebResponseContent> GenerateOutboundTasksAsync(int[] keys, string outStation)
|
{
|
try
|
{
|
List<Dt_Task> tasks = new List<Dt_Task>();
|
List<StockSelectViewDTO> stockSelectViews = new List<StockSelectViewDTO>();
|
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 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<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)
|
{
|
_unitOfWorkManage.RollbackTran();
|
return WebResponseContent.Instance.Error(ex.Message);
|
}
|
}
|
|
/// <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 = SqlSugarHelper.DbWMS.Queryable<Dt_OutboundOrder>().Includes(x => x.Details).Where(x => x.Details.Any(o => o.Id == orderDetailId)).First();
|
|
if (outboundOrder == null)
|
{
|
return WebResponseContent.Instance.Error("找不到出库单据");
|
}
|
if (outboundOrder.Details == null || !outboundOrder.Details.Any())
|
{
|
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 async Task<WebResponseContent> GenerateOutboundTask(int orderDetailId, List<StockSelectViewDTO> stockSelectViews, string station = null)
|
{
|
try
|
{
|
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 = 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>
|
/// <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)
|
{
|
List<Dt_Task> tasks = new List<Dt_Task>();
|
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<Dt_StockInfo>? stockInfos = null;
|
Dt_OutboundOrderDetail? orderDetail = null;
|
List<Dt_OutStockLockInfo>? outStockLockInfos = null;
|
List<Dt_LocationInfo>? locationInfos = null;
|
if (outboundOrderDetail.OrderDetailStatus == OrderDetailStatusEnum.New.ObjToInt() ||
|
outboundOrderDetail.OrderDetailStatus == OrderDetailStatusEnum.Outbound.ObjToInt())
|
{
|
(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);
|
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<Dt_OutStockLockInfo> stockLockInfos = _outStockLockInfoService.GetByOrderDetailId(outboundOrderDetail.OrderId, OutLockStockStatusEnum.已分配);
|
if (stockLockInfos != null && stockLockInfos.Count > 0)
|
{
|
List<Dt_StockInfo> stocks = _stockService.StockInfoService.GetStockInfosByPalletCodes(stockLockInfos.Select(x => x.PalletCode).Distinct().ToList());
|
tasks = GetTasks(stocks, TaskTypeEnum.Outbound,station);
|
}
|
}
|
|
return (tasks, stockInfos, orderDetail == null ? null : new List<Dt_OutboundOrderDetail> { orderDetail }, outStockLockInfos, locationInfos);
|
}
|
|
/// <summary>
|
/// 生成出库任务后数据更新到数据库
|
/// </summary>
|
/// <param name="tasks"></param>
|
/// <param name="stockInfos"></param>
|
/// <param name="outboundOrderDetails"></param>
|
/// <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)
|
{
|
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();
|
return WebResponseContent.Instance.OK();
|
//PushTasksToWCS(tasks);
|
//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)
|
{
|
_unitOfWorkManage.RollbackTran();
|
return WebResponseContent.Instance.Error(ex.Message);
|
}
|
|
}
|
|
|
#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")
|
.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
|
|
/// <summary>
|
/// 选定库存生成盘点单出库
|
/// </summary>
|
/// <param name="id"></param>
|
/// <returns></returns>
|
public async Task<WebResponseContent> TakeOutbound(List<StockViewDTO> stockViews, string outStation)
|
{
|
WebResponseContent content = new WebResponseContent();
|
try
|
{
|
var allFactoryAreas = stockViews.SelectMany(sv => sv.Details)
|
.Select(x => x.FactoryArea)
|
.GroupBy(x => x)
|
.ToList();
|
if (allFactoryAreas.Count >= 2)
|
{
|
return content.Error($"请选择同一厂区区域的库存进行盘点,当前涉及{allFactoryAreas.Count}个不同的厂区");
|
}
|
List<int> ids = stockViews.Select(x => x.StockId).ToList();
|
//获取库存
|
List<Dt_StockInfo> stockInfos = _stockRepository.Db.Queryable<Dt_StockInfo>().Where(x => ids.Contains(x.Id)).Includes(x => x.Details).ToList();
|
if (stockInfos.Count != stockViews.Count)
|
{
|
StockViewDTO? stockViewDTO = stockViews.FirstOrDefault(x => !stockInfos.Select(x => x.PalletCode).ToList().Contains(x.PalletCode));
|
return content.Error($"未找到{stockViewDTO?.PalletCode}库存");
|
}
|
//获取货位
|
List<string> locStrs = stockInfos.Select(x => x.LocationCode).ToList();
|
List<Dt_LocationInfo> locationInfos =_locationInfoService.Db.Queryable<Dt_LocationInfo>().Where(x => locStrs.Contains(x.LocationCode)).ToList();
|
if (stockInfos.Count != locationInfos.Count)
|
{
|
string? locStr = locStrs.FirstOrDefault(x => !locationInfos.Select(x => x.LocationCode).ToList().Contains(x));
|
return content.Error($"未找到{locStr}货位数据");
|
}
|
Dt_TakeStockOrder takeStockOrder = new Dt_TakeStockOrder()
|
{
|
WarehouseId = stockInfos.FirstOrDefault().WarehouseId,
|
TakeStockStatus = TakeStockStatusEnum.盘点中.ObjToInt(),
|
OrderNo = CreateCodeByRule(nameof(RuleCodeEnum.PDCodeRule)),
|
AllPalletCode = string.Join(",", stockInfos.Select(item => item.PalletCode).Where(palletCode => !string.IsNullOrEmpty(palletCode))),
|
Remark = outStation
|
|
};
|
foreach (var item in stockInfos)
|
{
|
if (item.Details.Count <= 0)
|
{
|
return content.Error($"未找到{item.PalletCode}库存明细数据");
|
}
|
Dt_LocationInfo? locationInfo = locationInfos.FirstOrDefault(x => x.LocationCode == item.LocationCode);
|
if (locationInfo == null || (locationInfo.EnableStatus == EnableStatusEnum.Disable.ObjToInt() || locationInfo.EnableStatus != EnableStatusEnum.Normal.ObjToInt()) || locationInfo.LocationStatus != LocationStatusEnum.InStock.ObjToInt() || item.StockStatus != StockStatusEmun.入库完成.ObjToInt())
|
{
|
return content.Error($"{item.PalletCode}货位或库存状态不满足出库条件");
|
}
|
}
|
List<Dt_Task> tasks = GetTasks(stockInfos, TaskTypeEnum.OutInventory,outStation);
|
if (tasks == null || tasks.Count <= 0)
|
{
|
return content.Error($"生成任务失败");
|
}
|
stockInfos.ForEach(x =>
|
{
|
x.StockStatus = StockStatusEmun.出库锁定.ObjToInt();
|
});
|
tasks.ForEach(x =>
|
{
|
x.OrderNo = takeStockOrder.OrderNo;
|
});
|
locationInfos.ForEach(x =>
|
{
|
x.LocationStatus = LocationStatusEnum.Lock.ObjToInt();
|
});
|
_unitOfWorkManage.BeginTran();
|
//更新库存状态
|
_stockRepository.UpdateData(stockInfos);
|
_takeStockOrder.AddData(takeStockOrder);
|
//新建任务
|
BaseDal.AddData(tasks);
|
_locationInfoService.UpdateData(locationInfos);
|
_unitOfWorkManage.CommitTran();
|
content.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("下发机器人任务失败!");
|
//}
|
//content.OK();
|
}
|
catch (Exception ex)
|
{
|
_unitOfWorkManage.RollbackTran();
|
return await Task.FromResult(WebResponseContent.Instance.Error(ex.Message));
|
}
|
return content;
|
}
|
/// <summary>
|
/// 单据生成方法
|
/// </summary>
|
static object lock_code = new object();
|
public string CreateCodeByRule(string ruleCode)
|
{
|
lock (lock_code)
|
{
|
|
string code = string.Empty;
|
DateTime dateTime = DateTime.Now;
|
DateTime now = DateTime.Now;
|
try
|
{
|
if (string.IsNullOrEmpty(ruleCode))
|
throw new ArgumentNullException(nameof(ruleCode));
|
SqlSugarClient sugarClient = new SqlSugarClient(new ConnectionConfig
|
{
|
IsAutoCloseConnection = true,
|
DbType = DbType.SqlServer,
|
ConnectionString = DBContext.ConnectionString
|
});
|
Dt_CodeRuleConfig codeRuleConfig = sugarClient.Queryable<Dt_CodeRuleConfig>().Where(x => x.RuleCode == ruleCode).First();
|
if (codeRuleConfig == null)
|
throw new ArgumentNullException(nameof(codeRuleConfig));
|
if (codeRuleConfig.ModifyDate != null)
|
{
|
dateTime = Convert.ToDateTime(codeRuleConfig.ModifyDate);
|
}
|
else
|
{
|
dateTime = Convert.ToDateTime(codeRuleConfig.CreateDate);
|
}
|
|
if (now.Year == dateTime.Year && now.Month == dateTime.Month && now.Day == dateTime.Day)
|
{
|
now = dateTime;
|
codeRuleConfig.CurrentVal = Convert.ToInt32(codeRuleConfig.CurrentVal) + 1;
|
}
|
else
|
{
|
codeRuleConfig.CurrentVal = 1;
|
}
|
codeRuleConfig.ModifyDate = DateTime.Now;
|
code = codeRuleConfig.StartStr + codeRuleConfig.Format;
|
code = code.Replace($"[{CodeFormatTypeEnum.YYYY}]", now.Year.ToString().PadLeft(4, '0'));
|
code = code.Replace($"[{CodeFormatTypeEnum.MM}]", now.Month.ToString().PadLeft(2, '0'));
|
code = code.Replace($"[{CodeFormatTypeEnum.DD}]", now.Day.ToString().PadLeft(2, '0'));
|
code = code.Replace($"[{CodeFormatTypeEnum.ST}]", codeRuleConfig.StartStr?.ToString() ?? "");
|
code = code.Replace($"[{CodeFormatTypeEnum.NUM}]", codeRuleConfig.CurrentVal.ToString().PadLeft(codeRuleConfig.Length, '0'));
|
Dictionary<string, object> keyValuePairs = new Dictionary<string, object>() { { nameof(codeRuleConfig.CurrentVal), codeRuleConfig.CurrentVal }, { nameof(codeRuleConfig.Id), codeRuleConfig.Id }, { nameof(codeRuleConfig.ModifyDate), DateTime.Now } };
|
sugarClient.Updateable(keyValuePairs).AS(MainDb.CodeRuleConfig).WhereColumns(nameof(codeRuleConfig.Id)).ExecuteCommand();
|
sugarClient.Updateable(codeRuleConfig);
|
|
}
|
catch (Exception ex)
|
{
|
|
}
|
return code;
|
}
|
}
|
|
/// <summary>
|
/// 选定库存同区域移库
|
/// </summary>
|
/// <param name="id"></param>
|
/// <returns></returns>
|
public async Task<WebResponseContent> AreaOutbound(List<StockViewDTO> stockViews)
|
{
|
WebResponseContent content = new WebResponseContent();
|
try
|
{
|
List<int> ids = stockViews.Select(x => x.StockId).ToList();
|
//获取库存
|
List<Dt_StockInfo> stockInfos = _stockRepository.Db.Queryable<Dt_StockInfo>().Where(x => ids.Contains(x.Id)).Includes(x => x.Details).ToList();
|
if (stockInfos.Count != stockViews.Count)
|
{
|
StockViewDTO? stockViewDTO = stockViews.FirstOrDefault(x => !stockInfos.Select(x => x.PalletCode).ToList().Contains(x.PalletCode));
|
return content.Error($"未找到{stockViewDTO?.PalletCode}库存");
|
}
|
//获取货位
|
List<string> locStrs = stockInfos.Select(x => x.LocationCode).ToList();
|
List<Dt_LocationInfo> locationInfos = _locationInfoService.Db.Queryable<Dt_LocationInfo>().Where(x => locStrs.Contains(x.LocationCode)).ToList();
|
if (stockInfos.Count != locationInfos.Count)
|
{
|
string? locStr = locStrs.FirstOrDefault(x => !locationInfos.Select(x => x.LocationCode).ToList().Contains(x));
|
return content.Error($"未找到{locStr}货位数据");
|
}
|
|
foreach (var item in stockInfos)
|
{
|
Dt_LocationInfo? locationInfo = locationInfos.FirstOrDefault(x => x.LocationCode == item.LocationCode);
|
if (locationInfo == null || (locationInfo.EnableStatus == EnableStatusEnum.Disable.ObjToInt() || locationInfo.EnableStatus != EnableStatusEnum.Normal.ObjToInt()) || item.StockStatus != StockStatusEmun.入库完成.ObjToInt())
|
{
|
return content.Error($"{item.PalletCode}货位或库存状态不满足出库条件");
|
}
|
}
|
List<Dt_Task> tasks = AreaGetTasks(stockInfos, TaskTypeEnum.AreaRelocation);
|
if (tasks == null || tasks.Count <= 0)
|
{
|
return content.Error($"生成任务失败");
|
}
|
stockInfos.ForEach(x =>
|
{
|
x.StockStatus = StockStatusEmun.出库锁定.ObjToInt();
|
});
|
tasks.ForEach(x =>
|
{
|
x.OrderNo = "无单据移库";
|
});
|
locationInfos.ForEach(x =>
|
{
|
x.LocationStatus = LocationStatusEnum.Lock.ObjToInt();
|
});
|
_unitOfWorkManage.BeginTran();
|
//更新库存状态
|
_stockRepository.UpdateData(stockInfos);
|
//新建任务
|
BaseDal.AddData(tasks);
|
_locationInfoService.UpdateData(locationInfos);
|
_unitOfWorkManage.CommitTran();
|
content.OK();
|
}
|
catch (Exception ex)
|
{
|
_unitOfWorkManage.RollbackTran();
|
return await Task.FromResult(WebResponseContent.Instance.Error(ex.Message));
|
}
|
return content;
|
}
|
|
public List<Dt_Task> AreaGetTasks(List<Dt_StockInfo> stockInfos, TaskTypeEnum taskType)
|
{
|
// 使用 TransactionScope 包裹整个操作,确保所有数据库操作在同一事务中
|
using (var scope = new TransactionScope(TransactionScopeOption.Required,
|
new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted }))
|
{
|
try
|
{
|
List<Dt_Task> tasks = new List<Dt_Task>();
|
List<Dt_LocationInfo> 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);
|
var newLocation = _locationInfoService.AssignLocation(stockInfo.LocationType);
|
|
if (newLocation == null)
|
{
|
throw new Exception($"在{stockInfo.PalletCode}时没有空闲库位可进行同区域移库,请检查或减少移库料箱");
|
}
|
|
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 = newLocation.LocationCode,
|
TaskStatus = TaskStatusEnum.New.ObjToInt(),
|
TaskType = taskType.ObjToInt(),
|
PalletType = stockInfo.PalletType,
|
WarehouseId = stockInfo.WarehouseId,
|
};
|
tasks.Add(task);
|
}
|
|
newLocation.LocationStatus = LocationStatusEnum.Lock.ObjToInt();
|
_locationInfoService.UpdateData(newLocation);
|
}
|
}
|
|
// 提交事务
|
scope.Complete();
|
return tasks;
|
}
|
catch (Exception ex)
|
{
|
// TransactionScope 会自动回滚事务,无需手动 Rollback
|
throw new Exception(ex.Message);
|
}
|
}
|
}
|
}
|
|
}
|