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(PickingConfirmRequest request) { try { _unitOfWorkManage.BeginTran(); // 1. 验证条码有效性 var stockDetail = await _stockInfoDetailService.Db.Queryable() .Where(x => x.Barcode == request.Barcode && x.MaterielCode == request.MaterielCode) .FirstAsync(); if (stockDetail == null) return WebResponseContent.Instance.Error("无效的条码或物料编码"); // 2. 检查库存可用数量 decimal availableQuantity = stockDetail.StockQuantity - stockDetail.OutboundQuantity; if (request.PickQuantity > availableQuantity) return WebResponseContent.Instance.Error($"拣选数量超过可用库存,可用数量:{availableQuantity}"); // 3. 查找相关的出库锁定信息(支持拆包后的新条码) var lockInfo = await FindLockInfoByBarcode(request.OrderDetailId, request.Barcode, request.MaterielCode); if (lockInfo == null) return WebResponseContent.Instance.Error("未找到相关的出库锁定信息"); // 4. 检查锁定数量 decimal remainingLockQuantity = lockInfo.AssignQuantity - lockInfo.PickedQty; if (request.PickQuantity > remainingLockQuantity) return WebResponseContent.Instance.Error($"拣选数量超过锁定数量,剩余可拣选:{remainingLockQuantity}"); // 5. 更新锁定信息的已拣选数量 lockInfo.PickedQty += request.PickQuantity; await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync(); // 6. 更新库存出库数量 - 实际减少库存 stockDetail.OutboundQuantity += request.PickQuantity; await _stockInfoService.Db.Updateable(stockDetail).ExecuteCommandAsync(); // 7. 更新出库单明细 var orderDetail = await _outboundOrderDetailService.Db.Queryable() .Where(x => x.Id == request.OrderDetailId) .FirstAsync(); orderDetail.OverOutQuantity += request.PickQuantity; orderDetail.LockQuantity -= request.PickQuantity; // 检查是否完成出库 if (Math.Abs(orderDetail.OverOutQuantity - orderDetail.OrderQuantity) < 0.001m) { orderDetail.OrderDetailStatus = (int)OrderDetailStatusEnum.Over; orderDetail.LockQuantity = 0; // 更新相关的锁定信息状态为已出库 var relatedLockInfos = await _outStockLockInfoService.Db.Queryable() .Where(x => x.OrderDetailId == request.OrderDetailId && x.Status == (int)OutLockStockStatusEnum.出库中) .ToListAsync(); foreach (var relatedLock in relatedLockInfos) { relatedLock.Status = (int)OutLockStockStatusEnum.已出库; } await _outStockLockInfoService.Db.Updateable(relatedLockInfos).ExecuteCommandAsync(); } await _outboundOrderDetailService.Db.Updateable(orderDetail).ExecuteCommandAsync(); // 8. 记录拣选历史 var pickHistory = new Dt_PickingRecord { OrderDetailId = request.OrderDetailId, Barcode = request.Barcode, PickQuantity = request.PickQuantity, PickTime = DateTime.Now, LocationCode = request.LocationCode, StockId = stockDetail.StockId }; await Db.Insertable(pickHistory).ExecuteCommandAsync(); _unitOfWorkManage.CommitTran(); return WebResponseContent.Instance.OK("拣选确认成功"); } catch (Exception ex) { _unitOfWorkManage.RollbackTran(); 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(); // 给 ESS 流动信号和创建任务 try { var result = await _eSSApiService.MoveContainerAsync(new WIDESEA_DTO.Basic.MoveContainerRequest { slotCode = movestations[task.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(task.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(); //// 还原库存出库数量 //await _stockInfoDetailService.Db.Updateable() // .SetColumns(x => x.OutboundQuantity == x.OutboundQuantity - outStockInfo.AssignQuantity) // .Where(x => x.Barcode == barcode) // .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}"); } } /// /// 根据条码查找锁定信息 /// private async Task FindLockInfoByBarcode(int orderDetailId, string barcode, string materielCode) { return await _outStockLockInfoService.Db.Queryable() .Where(x => x.OrderDetailId == orderDetailId && x.MaterielCode == materielCode && x.CurrentBarcode == barcode && x.Status == (int)OutLockStockStatusEnum.出库中 && x.AssignQuantity > x.PickedQty) .FirstAsync(); } // 获取未拣选列表 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 GetPickingSummary(ConfirmPickingDto dto) { var picked = await _outStockLockInfoService.Db.Queryable() .WhereIF(!string.IsNullOrEmpty(dto.OrderNo), x => x.OrderNo == dto.OrderNo) .WhereIF(!string.IsNullOrEmpty(dto.PalletCode), x => x.PalletCode == dto.PalletCode) .Where(x => x.Status == 6) .GroupBy(x => new { x.PalletCode, x.MaterielCode }) .Select(x => new SummaryPickingDto { PalletCode = x.PalletCode, MaterielCode = x.MaterielCode, pickedCount = SqlFunc.AggregateCount(x.Id) }).FirstAsync(); if (picked == null) { picked = new SummaryPickingDto { pickedCount = 0 }; } var summary = await _outStockLockInfoService.Db.Queryable() .WhereIF(!string.IsNullOrEmpty(dto.OrderNo), x => x.OrderNo == dto.OrderNo) .WhereIF(!string.IsNullOrEmpty(dto.PalletCode), x => x.PalletCode == dto.PalletCode) .Where(x => x.Status == 1) .GroupBy(x => new { x.PalletCode, x.MaterielCode }) .Select(x => new SummaryPickingDto { PalletCode = x.PalletCode, MaterielCode = x.MaterielCode, UnpickedCount = SqlFunc.AggregateCount(x.Id), UnpickedQuantity = SqlFunc.AggregateSum(x.AssignQuantity) - SqlFunc.AggregateSum(x.PickedQty), }).FirstAsync(); if (summary == null) { summary = new SummaryPickingDto { pickedCount = 0 }; } summary.pickedCount = picked.pickedCount; return summary; } /// /// 获取拣选历史 /// public async Task> GetPickingHistory(int orderId) { // 通过出库单ID查询相关的拣选历史 // 注意:Dt_PickingRecord 中没有直接存储OrderId,需要通过出库单明细关联 var detailIds = await _outboundOrderDetailService.Db.Queryable() .Where(d => d.OrderId == orderId) .Select(d => d.Id) .ToListAsync(); return await Db.Queryable() .Where(p => detailIds.Contains(p.OrderDetailId)) .OrderByDescending(p => p.PickTime) .ToListAsync(); } public async Task GetPalletPickingSummary(string orderNo, string palletCode) { var summary = await _outStockLockInfoService.Db.Queryable() .Where(x => x.OrderNo == orderNo && x.PalletCode == palletCode) .GroupBy(x => new { x.PalletCode, x.Status }) .Select(x => new { PalletCode = x.PalletCode, Status = x.Status, TotalAssignQty = SqlFunc.AggregateSum(x.AssignQuantity), TotalPickedQty = SqlFunc.AggregateSum(x.PickedQty) }) .ToListAsync(); // return summary; } /// /// 撤销拣选 /// public async Task CancelPicking(CancelPickingRequest request) { // 实现撤销拣选的逻辑,需要: // 1. 恢复库存出库数量 // 2. 恢复锁定信息的已拣选数量 // 3. 恢复出库单明细的已出数量和锁定数量 // 4. 删除或标记拣选历史记录 // 注意:这里需要事务处理 try { _unitOfWorkManage.BeginTran(); var pickHistory = await Db.Queryable() .Where(x => x.Id == request.PickingHistoryId) .FirstAsync(); if (pickHistory == null) return WebResponseContent.Instance.Error("未找到拣选记录"); // 恢复库存 var stockDetail = await _stockInfoService.Db.Queryable() .Where(x => x.Barcode == pickHistory.Barcode && x.StockId == pickHistory.StockId) .FirstAsync(); if (stockDetail != null) { stockDetail.OutboundQuantity -= pickHistory.PickQuantity; await _stockInfoService.Db.Updateable(stockDetail).ExecuteCommandAsync(); } // 恢复锁定信息 var lockInfo = await _outStockLockInfoService.Db.Queryable() .Where(x => x.OrderDetailId == pickHistory.OrderDetailId && x.StockId == pickHistory.StockId) .FirstAsync(); lockInfo.PickedQty -= pickHistory.PickQuantity; await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync(); // 恢复出库单明细 var orderDetail = await _outboundOrderDetailService.Db.Queryable() .Where(x => x.Id == pickHistory.OrderDetailId) .FirstAsync(); orderDetail.OverOutQuantity -= pickHistory.PickQuantity; orderDetail.LockQuantity += pickHistory.PickQuantity; if (orderDetail.OverOutQuantity < orderDetail.OrderQuantity) { orderDetail.OrderDetailStatus = orderDetail.LockQuantity > 0 ? (int)OrderDetailStatusEnum.Outbound : (int)OrderDetailStatusEnum.AssignOverPartial; } await _outboundOrderDetailService.Db.Updateable(orderDetail).ExecuteCommandAsync(); // 删除拣选历史记录 await Db.Deleteable().Where(x => x.Id == request.PickingHistoryId).ExecuteCommandAsync(); _unitOfWorkManage.CommitTran(); return WebResponseContent.Instance.OK("撤销成功"); } catch (Exception ex) { _unitOfWorkManage.RollbackTran(); return WebResponseContent.Instance.Error($"撤销失败: {ex.Message}"); } } /// /// 获取托盘的出库状态信息 /// public async Task GetPalletOutboundStatus(string palletCode) { // 获取托盘的锁定信息 var lockInfos = await _outStockLockInfoService.Db.Queryable() .Where(x => x.PalletCode == palletCode) .ToListAsync(); // 获取托盘库存信息 var stockInfo = await _stockInfoService.Db.Queryable() .Includes(x => x.Details) .Where(x => x.PalletCode == palletCode) .FirstAsync(); if (stockInfo == null) return WebResponseContent.Instance.Error("未找到托盘信息"); // 计算各种数量 var totalStockQuantity = stockInfo.Details.Sum(x => x.StockQuantity); var totalOutboundQuantity = stockInfo.Details.Sum(x => x.OutboundQuantity); var totalLockedQuantity = lockInfos.Where(x => x.Status == (int)OutLockStockStatusEnum.出库中) .Sum(x => x.AssignQuantity - x.PickedQty); var totalPickedQuantity = lockInfos.Sum(x => x.PickedQty); var result = new { PalletCode = palletCode, LocationCode = stockInfo.LocationCode, StockStatus = stockInfo.StockStatus, TotalStockQuantity = totalStockQuantity, TotalOutboundQuantity = totalOutboundQuantity, TotalLockedQuantity = totalLockedQuantity, TotalPickedQuantity = totalPickedQuantity, AvailableQuantity = totalStockQuantity - totalOutboundQuantity, LockInfos = lockInfos.Select(x => new { x.Id, x.MaterielCode, x.OrderDetailId, x.AssignQuantity, x.PickedQty, x.Status, x.CurrentBarcode, x.IsSplitted }).ToList(), StockDetails = stockInfo.Details.Select(x => new { x.Barcode, x.MaterielCode, StockQuantity = x.StockQuantity, OutboundQuantity = x.OutboundQuantity, AvailableQuantity = x.StockQuantity - x.OutboundQuantity }).ToList() }; return WebResponseContent.Instance.OK(null, result); } /// /// 直接出库 - 整个托盘出库,清空库存 /// public async Task DirectOutbound(DirectOutboundRequest request) { try { _unitOfWorkManage.BeginTran(); var stockInfo = await _stockInfoService.Db.Queryable() .Includes(x => x.Details) .Where(x => x.PalletCode == request.PalletCode).FirstAsync(); if (stockInfo == null) return WebResponseContent.Instance.Error("未找到托盘库存信息"); var lockInfos = await _outStockLockInfoService.Db.Queryable() .Where(x => x.OrderNo == request.OrderNo && x.PalletCode == request.PalletCode) .ToListAsync(); foreach (var lockInfo in lockInfos) { if (lockInfo.Status == (int)OutLockStockStatusEnum.出库中) { lockInfo.PickedQty = lockInfo.AssignQuantity; } lockInfo.Status = (int)OutLockStockStatusEnum.已出库; await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync(); var orderDetail = await _outboundOrderDetailService.Db.Queryable() .Where(x => x.Id == lockInfo.OrderDetailId) .FirstAsync(); if (orderDetail != null) { orderDetail.OverOutQuantity += lockInfo.PickedQty; orderDetail.LockQuantity -= lockInfo.PickedQty; orderDetail.OrderDetailStatus = (int)OrderDetailStatusEnum.Over; orderDetail.LockQuantity = 0; await _outboundOrderDetailService.Db.Updateable(orderDetail).ExecuteCommandAsync(); } } var groupDetails = lockInfos.GroupBy(x => x.OrderDetailId).Select(x => new { OrderDetailId = x.Key, TotalQuantity = x.Sum(o => o.PickedQty) }).ToList(); foreach (var item in groupDetails) { var orderDetail = await _outboundOrderDetailService.Db.Queryable().Where(x => x.Id == item.OrderDetailId).FirstAsync(); if (orderDetail != null) { orderDetail.OverOutQuantity = item.TotalQuantity; orderDetail.LockQuantity = 0; orderDetail.OrderDetailStatus = (int)OrderDetailStatusEnum.Over; await _outboundOrderDetailService.Db.Updateable(orderDetail).ExecuteCommandAsync(); } } await CheckAndUpdateOrderStatus(request.OrderNo); var lockInfoIds = lockInfos.Select(x => x.Id).ToList(); var splitRecords = await _splitPackageService.Db.Queryable() .Where(x => lockInfoIds.Contains(x.OutStockLockInfoId) && x.Status == (int)SplitPackageStatusEnum.已拆包) .ToListAsync(); foreach (var record in splitRecords) { record.Status = (int)SplitPackageStatusEnum.已拣选; await _splitPackageService.Db.Updateable(record).ExecuteCommandAsync(); } var location = await _locationInfoService.Db.Queryable() .Where(x => x.LocationCode == stockInfo.LocationCode) .FirstAsync(); if (location != null) { location.LocationStatus = (int)LocationStatusEnum.Free; await _locationInfoService.Db.Updateable(location).ExecuteCommandAsync(); } foreach (var detail in stockInfo.Details) { await _stockInfoDetailService.Db.Deleteable(detail).ExecuteCommandAsync(); } await _stockInfoService.Db.Deleteable(stockInfo).ExecuteCommandAsync(); _unitOfWorkManage.CommitTran(); return WebResponseContent.Instance.OK("直接出库成功"); } catch (Exception ex) { _unitOfWorkManage.RollbackTran(); return WebResponseContent.Instance.Error($"直接出库失败: {ex.Message}"); } } } }