using Dm.filter;
using MailKit.Search;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using SqlSugar;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using WIDESEA_Common.LocationEnum;
using WIDESEA_Common.OrderEnum;
using WIDESEA_Common.StockEnum;
using WIDESEA_Common.TaskEnum;
using WIDESEA_Core;
using WIDESEA_Core.BaseRepository;
using WIDESEA_Core.BaseServices;
using WIDESEA_Core.Helper;
using WIDESEA_DTO.Basic;
using WIDESEA_DTO.Outbound;
using WIDESEA_IBasicService;
using WIDESEA_IOutboundService;
using WIDESEA_IStockService;
using WIDESEA_Model.Models;
namespace WIDESEA_OutboundService
{
///
///
///
public class OutboundPickingService : ServiceBase>, IOutboundPickingService
{
private readonly IUnitOfWorkManage _unitOfWorkManage;
public IRepository Repository => BaseDal;
private readonly IStockInfoService _stockInfoService;
private readonly IStockService _stockService;
private readonly IOutStockLockInfoService _outStockLockInfoService;
private readonly IStockInfoDetailService _stockInfoDetailService;
private readonly ILocationInfoService _locationInfoService;
private readonly IOutboundOrderDetailService _outboundOrderDetailService;
private readonly IOutboundOrderService _outboundOrderService;
private readonly ISplitPackageService _splitPackageService;
private readonly IRepository _taskRepository;
private readonly IESSApiService _eSSApiService;
private readonly ILogger _logger;
private Dictionary stations = new Dictionary
{
{"2-1","2-9" },
{"3-1","3-9" },
};
private Dictionary movestations = new Dictionary
{
{"2-1","2-5" },
{"3-1","3-5" },
};
public OutboundPickingService(IRepository BaseDal, IUnitOfWorkManage unitOfWorkManage, IStockInfoService stockInfoService, IStockService stockService, IOutStockLockInfoService outStockLockInfoService, IStockInfoDetailService stockInfoDetailService, ILocationInfoService locationInfoService, IOutboundOrderDetailService outboundOrderDetailService, ISplitPackageService splitPackageService, IOutboundOrderService outboundOrderService, IRepository taskRepository, IESSApiService eSSApiService, ILogger logger) : base(BaseDal)
{
_unitOfWorkManage = unitOfWorkManage;
_stockInfoService = stockInfoService;
_stockService = stockService;
_outStockLockInfoService = outStockLockInfoService;
_stockInfoDetailService = stockInfoDetailService;
_locationInfoService = locationInfoService;
_outboundOrderDetailService = outboundOrderDetailService;
_splitPackageService = splitPackageService;
_outboundOrderService = outboundOrderService;
_taskRepository = taskRepository;
_eSSApiService = eSSApiService;
_logger = logger;
}
#region 查询出库详情列表
public async Task> GetOutStockLockListAsync(string orderNo)
{
var locks = await _outStockLockInfoService.Db.Queryable()
.Where(t => t.OrderNo == orderNo)
.ToListAsync();
return locks.Select(t => new OutStockLockListResp
{
Id = t.Id,
// TaskNum = t.TaskNum,
PalletCode = t.PalletCode,
CurrentBarcode = t.CurrentBarcode,
AssignQuantity = t.AssignQuantity,
PickedQty = t.PickedQty,
Status = t.Status,
// IsSplitted = t.IsSplitted
}).ToList();
}
#endregion
public async Task ValidateBarcode(string barcode)
{
try
{
if (string.IsNullOrEmpty(barcode))
{
return WebResponseContent.Instance.Error("条码不能为空");
}
// 根据条码查询库存明细
var stockDetail = await _stockInfoDetailService.Db.Queryable()
.Includes(x => x.StockInfo)
.Where(x => x.Barcode == barcode)
.FirstAsync();
if (stockDetail == null)
{
return WebResponseContent.Instance.Error("条码不存在");
}
var result = new
{
Barcode = barcode,
MaterielCode = stockDetail.MaterielCode,
BatchNo = stockDetail.BatchNo,
AvailableQuantity = stockDetail.StockQuantity - stockDetail.OutboundQuantity,
LocationCode = stockDetail.StockInfo?.LocationCode,
PalletCode = stockDetail.StockInfo?.PalletCode
};
return WebResponseContent.Instance.OK(null, result);
}
catch (Exception ex)
{
return WebResponseContent.Instance.Error($"条码验证失败: {ex.Message}");
}
}
public async Task ConfirmPicking(string orderNo, string palletCode, string barcode)
{
try
{
_unitOfWorkManage.BeginTran();
var lockInfo = await _outStockLockInfoService.Db.Queryable()
.Where(it => it.OrderNo == orderNo &&
it.Status == (int)OutLockStockStatusEnum.出库中 &&
it.PalletCode == palletCode &&
it.CurrentBarcode == barcode)
.FirstAsync();
if (lockInfo == null)
{
var splitBarcode = await _splitPackageService.Db.Queryable()
.Where(it => it.NewBarcode == barcode && it.Status == 1)
.FirstAsync();
if (splitBarcode != null)
{
// 通过拆包条码记录找到对应的出库锁定记录
lockInfo = await _outStockLockInfoService.Db.Queryable()
.Where(it => it.ParentLockId == splitBarcode.OutStockLockInfoId)
.FirstAsync();
if (lockInfo == null)
throw new Exception($"未找到拆包条码{barcode}对应的出库锁定记录");
}
else
{
throw new Exception($"条码{barcode}不属于托盘{palletCode}或不存在待分拣记录");
}
}
if (lockInfo.PalletCode != palletCode)
throw new Exception($"条码{barcode}不属于托盘{palletCode}");
var stockDetail = await _stockInfoDetailService.Db.Queryable()
.Where(x => x.Barcode == barcode && x.StockId == lockInfo.StockId)
.FirstAsync();
if (stockDetail == null)
return WebResponseContent.Instance.Error("无效的条码或物料编码");
decimal actualQty = lockInfo.AssignQuantity - lockInfo.PickedQty;
// 4. 更新库存
stockDetail.StockQuantity -= actualQty;
stockDetail.OutboundQuantity -= actualQty;
await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
lockInfo.PickedQty += actualQty;
lockInfo.Status = (int)OutLockStockStatusEnum.拣选完成;
await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
var splitBarcodeRecord = await _splitPackageService.Db.Queryable()
.Where(it => it.NewBarcode == barcode)
.FirstAsync();
if (splitBarcodeRecord != null)
{
splitBarcodeRecord.Status = 2;
await _splitPackageService.Db.Updateable(splitBarcodeRecord).ExecuteCommandAsync();
}
await _outboundOrderDetailService.Db.Updateable()
.SetColumns(it => it.PickedQty == it.PickedQty + actualQty)
.Where(it => it.Id == lockInfo.OrderDetailId)
.ExecuteCommandAsync();
await CheckAndUpdateOrderStatus(orderNo);
//查询任务表
var task = _taskRepository.QueryData(x => x.OrderNo == orderNo && x.PalletCode == palletCode).FirstOrDefault();
// 9. 记录拣选历史
var pickingHistory = new Dt_PickingRecord
{
FactoryArea = lockInfo.FactoryArea,
TaskNo = task?.TaskNum ?? 0,
LocationCode = task?.SourceAddress ?? "",
StockId = stockDetail.Id,
OrderNo = orderNo,
OrderDetailId = lockInfo.OrderDetailId,
PalletCode = palletCode,
Barcode = barcode,
MaterielCode = lockInfo.MaterielCode,
PickQuantity = lockInfo.AssignQuantity,
PickTime = DateTime.Now,
Operator = App.User.UserName,
OutStockLockId = lockInfo.Id
};
await Db.Insertable(pickingHistory).ExecuteCommandAsync();
_unitOfWorkManage.CommitTran();
return WebResponseContent.Instance.OK("拣选确认成功");
}
catch (Exception ex)
{
return WebResponseContent.Instance.Error($"拣选确认失败:{ex.Message}");
}
}
// 检查并更新订单状态
private async Task CheckAndUpdateOrderStatus(string orderNo)
{
var orderDetails = await _stockInfoDetailService.Db.Queryable()
.Where(x => x.OrderId == orderNo.ObjToInt())
.ToListAsync();
bool allCompleted = true;
foreach (var detail in orderDetails)
{
if (detail.OverOutQuantity < detail.NeedOutQuantity)
{
allCompleted = false;
break;
}
}
if (allCompleted)
{
await _outboundOrderService.Db.Updateable()
.SetColumns(x => x.OrderStatus == 2) // 已完成
.Where(x => x.OrderNo == orderNo)
.ExecuteCommandAsync();
}
}
///
/// 回库操作
///
//public async Task ReturnRemaining(string orderNo, string palletCode, string reason)
//{
// try
// {
// // 1. 获取所有未分拣的出库锁定记录,包括拆包产生的记录
// var remainingLocks = await _outStockLockInfoService.Db.Queryable()
// .Where(it => it.OrderNo == orderNo && it.Status == 1)
// .ToListAsync();
// if (!remainingLocks.Any())
// {
// return WebResponseContent.Instance.Error("没有需要回库的剩余货物");
// }
// var tasks = new List();
// // 按托盘分组
// var palletGroups = remainingLocks.GroupBy(x => x.PalletCode);
// //查询任务表
// var task = _taskRepository.QueryData(x => x.TaskNum == remainingLocks.First().TaskNum).FirstOrDefault();
// foreach (var group in palletGroups)
// {
// if (group.Key == palletCode)
// {
// var totalReturnQty = group.Sum(x => x.AssignQuantity - x.PickedQty);
// if (totalReturnQty <= 0) continue;
// // 分配新货位
// var newLocation = _locationInfoService.AssignLocation();
// // 更新出库锁定记录状态
// var lockIds = group.Where(x => x.PalletCode == palletCode).Select(x => x.Id).ToList();
// await _outStockLockInfoService.Db.Updateable()
// .SetColumns(it => new Dt_OutStockLockInfo { Status = OutLockStockStatusEnum.回库中.ObjToInt() })
// .Where(it => lockIds.Contains(it.Id))
// .ExecuteCommandAsync();
// // 更新拆包条码记录状态
// var splitBarcodes = await _splitPackageService.Db.Queryable()
// .Where(it => lockIds.Contains(it.OutStockLockInfoId))
// .ToListAsync();
// foreach (var splitBarcode in splitBarcodes)
// {
// splitBarcode.Status = 3;
// await _splitPackageService.Db.Updateable(splitBarcode).ExecuteCommandAsync();
// }
// foreach (var lockInfo in group)
// {
// if (lockInfo.PalletCode == palletCode)
// {
// decimal returnQty = lockInfo.AssignQuantity - lockInfo.PickedQty;
// // 检查库存记录是否存在
// var existingStock = await _stockInfoDetailService.Db.Queryable()
// .Where(it => it.Barcode == lockInfo.CurrentBarcode && it.StockId == lockInfo.StockId)
// .FirstAsync();
// if (existingStock != null)
// {
// // 库存记录存在,恢复锁定数量
// await _stockInfoDetailService.Db.Updateable()
// .SetColumns(it => new Dt_StockInfoDetail
// {
// OutboundQuantity = it.OutboundQuantity - returnQty
// })
// .Where(it => it.Barcode == lockInfo.CurrentBarcode && it.StockId == lockInfo.StockId)
// .ExecuteCommandAsync();
// }
// else
// {
// // 库存记录不存在(可能是拆包产生的新条码),创建新的库存记录
// var newStockDetail = new Dt_StockInfoDetail
// {
// StockId = lockInfo.StockId,
// MaterielCode = lockInfo.MaterielCode,
// OrderNo = lockInfo.OrderNo,
// BatchNo = lockInfo.BatchNo,
// StockQuantity = returnQty, // 实际库存数量
// OutboundQuantity = 0, // 回库后不再锁定
// Barcode = lockInfo.CurrentBarcode,
// InboundOrderRowNo = "0",
// Status = StockStatusEmun.入库确认.ObjToInt(),
// };
// await _stockInfoDetailService.Db.Insertable(newStockDetail).ExecuteCommandAsync();
// }
// }
// }
// Dt_Task newtask = new()
// {
// CurrentAddress = stations[task.TargetAddress],
// Grade = 0,
// PalletCode = palletCode,
// NextAddress = "",
// OrderNo= task.OrderNo,
// Roadway = newLocation.RoadwayNo,
// SourceAddress = stations[task.TargetAddress],
// TargetAddress = newLocation.LocationCode,
// TaskStatus = TaskStatusEnum.New.ObjToInt(),
// TaskType = TaskTypeEnum.InPick.ObjToInt(),
// // TaskNum = BaseDal.GetTaskNum(nameof(SequenceEnum.SeqTaskNum)),
// PalletType = task.PalletType,
// WarehouseId = task.WarehouseId,
// };
// tasks.Add(newtask);
// }
// }
// try
// {
// await _taskRepository.Db.Insertable(tasks).ExecuteCommandAsync();
// //删除 出库的 task
// //给 ess 流动信号 和创建任务
// }
// catch (Exception ex)
// {
// }
// return WebResponseContent.Instance.OK();
// }
// catch (Exception ex)
// {
// return WebResponseContent.Instance.Error($"回库操作失败: {ex.Message}");
// }
//}
public async Task ReturnRemaining(string orderNo, string palletCode, string reason)
{
try
{
// 1. 获取所有未分拣的出库锁定记录,包括拆包产生的记录
var remainingLocks = await _outStockLockInfoService.Db.Queryable()
.Where(it => it.OrderNo == orderNo && it.Status == 1)
.ToListAsync();
var stockinfo = _stockInfoService.Db.Queryable().First(x => x.PalletCode == palletCode);
// 2. 检查托盘上是否有其他非出库货物(库存货物)
var palletStockGoods = await _stockInfoDetailService.Db.Queryable()
.Where(it => it.StockId == stockinfo.Id && it.Status == StockStatusEmun.入库确认.ObjToInt())
.Where(it => it.OutboundQuantity == 0 || it.OutboundQuantity < it.StockQuantity) // 未完全出库的
.ToListAsync();
// 3. 如果没有需要回库的货物(既无未分拣出库货物,也无其他库存货物)
if (!remainingLocks.Any() && !palletStockGoods.Any())
{
return WebResponseContent.Instance.Error("没有需要回库的剩余货物");
}
var tasks = new List();
// 查询任务表
var task = remainingLocks.Any()
? _taskRepository.QueryData(x => x.TaskNum == remainingLocks.First().TaskNum).FirstOrDefault()
: _taskRepository.QueryData(x => x.PalletCode == palletCode).FirstOrDefault();
if (task == null)
{
return WebResponseContent.Instance.Error("未找到对应的任务信息");
}
var firstlocation = _locationInfoService.Db.Queryable().First(x => x.LocationCode == task.SourceAddress);
decimal totalReturnQty = 0;
var hasRemainingLocks = remainingLocks.Any(x => x.PalletCode == palletCode);
// 情况1:处理未分拣的出库锁定记录
if (hasRemainingLocks)
{
var palletLocks = remainingLocks.Where(x => x.PalletCode == palletCode).ToList();
totalReturnQty = palletLocks.Sum(x => x.AssignQuantity - x.PickedQty);
if (totalReturnQty > 0)
{
// 分配新货位
var newLocation = _locationInfoService.AssignLocation(firstlocation.LocationType);
// 更新出库锁定记录状态
var lockIds = palletLocks.Select(x => x.Id).ToList();
await _outStockLockInfoService.Db.Updateable()
.SetColumns(it => new Dt_OutStockLockInfo { Status = OutLockStockStatusEnum.回库中.ObjToInt() })
.Where(it => lockIds.Contains(it.Id))
.ExecuteCommandAsync();
// 更新拆包条码记录状态
var splitBarcodes = await _splitPackageService.Db.Queryable()
.Where(it => lockIds.Contains(it.OutStockLockInfoId))
.ToListAsync();
foreach (var splitBarcode in splitBarcodes)
{
splitBarcode.Status = 3;
await _splitPackageService.Db.Updateable(splitBarcode).ExecuteCommandAsync();
}
// 处理库存记录
foreach (var lockInfo in palletLocks)
{
decimal returnQty = lockInfo.AssignQuantity - lockInfo.PickedQty;
// 检查库存记录是否存在
var existingStock = await _stockInfoDetailService.Db.Queryable()
.Where(it => it.Barcode == lockInfo.CurrentBarcode && it.StockId == lockInfo.StockId)
.FirstAsync();
if (existingStock != null)
{
// 库存记录存在,恢复锁定数量
await _stockInfoDetailService.Db.Updateable()
.SetColumns(it => new Dt_StockInfoDetail
{
OutboundQuantity = it.OutboundQuantity - returnQty
})
.Where(it => it.Barcode == lockInfo.CurrentBarcode && it.StockId == lockInfo.StockId)
.ExecuteCommandAsync();
}
else
{
// 库存记录不存在(可能是拆包产生的新条码),创建新的库存记录
var newStockDetail = new Dt_StockInfoDetail
{
StockId = lockInfo.StockId,
MaterielCode = lockInfo.MaterielCode,
OrderNo = lockInfo.OrderNo,
BatchNo = lockInfo.BatchNo,
StockQuantity = returnQty,
OutboundQuantity = 0,
Barcode = lockInfo.CurrentBarcode,
InboundOrderRowNo = "0",
Status = StockStatusEmun.入库确认.ObjToInt(),
};
await _stockInfoDetailService.Db.Insertable(newStockDetail).ExecuteCommandAsync();
}
}
// 创建回库任务
CreateReturnTask(tasks, task, palletCode, newLocation);
}
}
// 情况2:出库货物已分拣完,但托盘上还有其他库存货物需要回库
if (!hasRemainingLocks && palletStockGoods.Any())
{
// 分配新货位
var newLocation = _locationInfoService.AssignLocation(firstlocation.LocationType);
// 创建回库任务
CreateReturnTask(tasks, task, palletCode, newLocation);
totalReturnQty = palletStockGoods.Sum(x => x.StockQuantity - x.OutboundQuantity);
}
// 保存任务
if (tasks.Any())
{
try
{
await _taskRepository.Db.Insertable(tasks).ExecuteCommandAsync();
var targetAddress = task.TargetAddress;
_taskRepository.DeleteData(task);
// 给 ESS 流动信号和创建任务
try
{
var result = await _eSSApiService.MoveContainerAsync(new WIDESEA_DTO.Basic.MoveContainerRequest
{
slotCode = movestations[targetAddress],
containerCode = palletCode
});
if (result)
{
TaskModel esstask = new TaskModel()
{
taskType = "putaway",
taskGroupCode = "",
groupPriority = 0,
tasks = new List
{
new()
{
taskCode = tasks.First().TaskNum.ToString(),
taskPriority = 0,
taskDescribe = new TaskDescribeType {
containerCode = palletCode,
containerType = "CT_KUBOT_STANDARD",
fromLocationCode = stations.GetValueOrDefault(targetAddress) ?? "",
toStationCode = "",
toLocationCode = tasks.First().TargetAddress,
deadline = 0, storageTag = ""
}
}
}
};
var resulttask = await _eSSApiService.CreateTaskAsync(esstask);
_logger.LogInformation("ReturnRemaining 创建任务返回: " + resulttask);
}
}
catch (Exception ex)
{
_logger.LogInformation("ReturnRemaining 创建任务返回 catch err: " + ex.Message);
}
return WebResponseContent.Instance.OK($"回库操作成功,共回库数量:{totalReturnQty}");
}
catch (Exception ex)
{
return WebResponseContent.Instance.Error($"创建回库任务失败: {ex.Message}");
}
}
return WebResponseContent.Instance.Error("未创建任何回库任务");
}
catch (Exception ex)
{
return WebResponseContent.Instance.Error($"回库操作失败: {ex.Message}");
}
}
///
/// 创建回库任务
///
private void CreateReturnTask(List tasks, Dt_Task originalTask, string palletCode, Dt_LocationInfo newLocation)
{
Dt_Task newTask = new()
{
CurrentAddress = stations[originalTask.TargetAddress],
Grade = 0,
PalletCode = palletCode,
NextAddress = "",
OrderNo = originalTask.OrderNo,
Roadway = newLocation.RoadwayNo,
SourceAddress = stations[originalTask.TargetAddress],
TargetAddress = newLocation.LocationCode,
TaskStatus = TaskStatusEnum.New.ObjToInt(),
TaskType = TaskTypeEnum.InPick.ObjToInt(),
PalletType = originalTask.PalletType,
WarehouseId = originalTask.WarehouseId,
};
tasks.Add(newTask);
}
///
/// 检查托盘是否需要回库的辅助方法
///
public async Task CheckPalletNeedReturn(string orderNo, string palletCode)
{
// 1. 检查是否有未分拣的出库记录
var hasUnpickedLocks = await _outStockLockInfoService.Db.Queryable()
.Where(it => it.OrderNo == orderNo && it.PalletCode == palletCode && it.Status == 1)
.AnyAsync();
if (hasUnpickedLocks)
return true;
// 2. 检查出库是否已完成但托盘还有库存货物
var outboundFinished = !await _outStockLockInfoService.Db.Queryable()
.Where(it => it.PalletCode == palletCode && it.Status == 1)
.AnyAsync();
var stockinfo = _stockInfoService.Db.Queryable().First(x => x.PalletCode == palletCode);
var hasRemainingGoods = await _stockInfoDetailService.Db.Queryable()
.Where(it => it.StockId == stockinfo.Id && it.Status == StockStatusEmun.入库确认.ObjToInt())
.Where(it => it.OutboundQuantity == 0 || it.OutboundQuantity < it.StockQuantity)
.AnyAsync();
return outboundFinished && hasRemainingGoods;
}
// 取消拣选功能
public async Task CancelPicking(string orderNo, string palletCode, string barcode)
{
try
{
_unitOfWorkManage.BeginTran();
// 查找拣选记录
var outStockInfo = await _outStockLockInfoService.Db.Queryable()
.Where(x => x.OrderNo == orderNo &&
x.PalletCode == palletCode &&
x.CurrentBarcode == barcode &&
x.Status == 6)
.FirstAsync();
if (outStockInfo == null)
return WebResponseContent.Instance.Error("未找到已拣选记录");
// 还原出库详情状态
outStockInfo.PickedQty = 0;
outStockInfo.Status = 1;
await _outStockLockInfoService.Db.Updateable(outStockInfo).ExecuteCommandAsync();
var stockDetail = await _stockInfoDetailService.Db.Queryable()
.Where(x => x.Barcode == barcode && x.StockId == outStockInfo.StockId)
.FirstAsync();
stockDetail.StockQuantity += outStockInfo.AssignQuantity;
stockDetail.OutboundQuantity += outStockInfo.AssignQuantity;
await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
// 还原出库单明细
var orderDetail = await _outboundOrderDetailService.Db.Queryable()
.Where(x => x.Id == outStockInfo.OrderDetailId)
.FirstAsync();
orderDetail.OverOutQuantity -= outStockInfo.AssignQuantity;
await _outboundOrderDetailService.Db.Updateable(orderDetail).ExecuteCommandAsync();
// 删除拣选历史
await Db.Deleteable()
.Where(x => x.OutStockLockId == outStockInfo.Id)
.ExecuteCommandAsync();
_unitOfWorkManage.CommitTran();
return WebResponseContent.Instance.OK("取消拣选成功");
}
catch (Exception ex)
{
return WebResponseContent.Instance.Error($"取消拣选失败:{ex.Message}");
}
}
// 获取未拣选列表
public async Task> GetUnpickedList(string orderNo, string palletCode)
{
var list = await _outStockLockInfoService.Db.Queryable()
.Where(x => x.OrderNo == orderNo &&
x.PalletCode == palletCode &&
x.Status == 1)
.ToListAsync();
return list.Where(x => x.RemainQuantity > 0).ToList();
}
// 获取已拣选列表
public async Task> GetPickedList(string orderNo, string palletCode)
{
var list = await _outStockLockInfoService.Db.Queryable()
.Where(x => x.OrderNo == orderNo &&
x.PalletCode == palletCode &&
x.Status == 6)
.ToListAsync();
return list;
}
// 获取拣选汇总
public async Task