using Dm.filter; using MailKit.Search; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using SqlSugar; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Text.Json; using System.Threading.Tasks; using WIDESEA_Common.CommonEnum; 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.Enums; using WIDESEA_Core.Helper; using WIDESEA_Core.Utilities; using WIDESEA_DTO.Allocate; using WIDESEA_DTO.Basic; using WIDESEA_DTO.Inbound; using WIDESEA_DTO.Outbound; using WIDESEA_IAllocateService; using WIDESEA_IBasicService; using WIDESEA_ICheckService; using WIDESEA_IInboundService; using WIDESEA_IOutboundService; using WIDESEA_IStockService; using WIDESEA_Model.Models; using WIDESEA_Model.Models.Basic; using WIDESEA_Model.Models.Check; 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 IInvokeMESService _invokeMESService; private readonly IDailySequenceService _dailySequenceService; private readonly IAllocateService _allocateService; private readonly IRepository _inboundOrderRepository; private readonly IInboundOrderDetailService _inboundOrderDetailService; private readonly IRepository _warehouseAreaRepository; private readonly IReCheckOrderService _reCheckOrderService; 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, IInvokeMESService invokeMESService, IDailySequenceService dailySequenceService, IAllocateService allocateService, IRepository inboundOrderRepository, IInboundOrderDetailService inboundOrderDetailService, IRepository warehouseAreaRepository, IReCheckOrderService reCheckOrderService) : 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; _invokeMESService = invokeMESService; _dailySequenceService = dailySequenceService; _allocateService = allocateService; _inboundOrderRepository = inboundOrderRepository; _inboundOrderDetailService = inboundOrderDetailService; _warehouseAreaRepository = warehouseAreaRepository; _reCheckOrderService = reCheckOrderService; } #region 查询方法 // 获取未拣选列表 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; } #endregion #region 核心业务流程 /// /// 拣选 /// /// /// /// /// public async Task ConfirmPicking(string orderNo, string palletCode, string barcode) { try { _unitOfWorkManage.BeginTran(); var validationResult = await ValidatePickingRequest(orderNo, palletCode, barcode); if (!validationResult.IsValid) return WebResponseContent.Instance.Error(validationResult.ErrorMessage); var (lockInfo, orderDetail, stockDetail) = validationResult.Data; // 计算实际拣选数量 var quantityResult = await CalculateActualPickingQuantity(lockInfo, orderDetail, stockDetail); if (!quantityResult.IsValid) return WebResponseContent.Instance.Error(quantityResult.ErrorMessage); var (actualQty, adjustedReason) = quantityResult.Data; var overPickingValidation = await ValidateOverPicking(orderDetail.Id, actualQty); if (!overPickingValidation.IsValid) { return WebResponseContent.Instance.Error(overPickingValidation.ErrorMessage); } // 执行分拣逻辑(只处理库存和锁定,不处理订单) var pickingResult = await ExecutePickingLogic(lockInfo, orderDetail, stockDetail, orderNo, palletCode, barcode, actualQty); // 统一更新订单数据(所有分支都在这里更新) await UpdateOrderRelatedData(orderDetail.Id, pickingResult.ActualPickedQty, orderNo); // 记录操作历史 await RecordPickingHistory(pickingResult, orderNo, palletCode); _unitOfWorkManage.CommitTran(); return CreatePickingResponse(pickingResult, adjustedReason); } catch (Exception ex) { _unitOfWorkManage.RollbackTran(); _logger.LogError($"ConfirmPicking失败 - OrderNo: {orderNo}, PalletCode: {palletCode}, Barcode: {barcode}, Error: {ex.Message}"); return WebResponseContent.Instance.Error($"拣选确认失败:{ex.Message}"); } } /// /// 取消拣选 /// /// /// /// /// public async Task CancelPicking(string orderNo, string palletCode, string barcode) { try { if (await IsPalletReturned(palletCode)) { return WebResponseContent.Instance.Error($"托盘{palletCode}已经回库,不能取消分拣"); } _unitOfWorkManage.BeginTran(); // 前置验证 var validationResult = await ValidateCancelRequest(orderNo, palletCode, barcode); if (!validationResult.IsValid) return WebResponseContent.Instance.Error(validationResult.ErrorMessage); var (pickingRecord, lockInfo, orderDetail) = validationResult.Data; //执行取消逻辑 await ExecuteCancelLogic(lockInfo, pickingRecord, orderDetail, orderNo); _unitOfWorkManage.CommitTran(); return WebResponseContent.Instance.OK($"取消分拣成功"); } catch (Exception ex) { _unitOfWorkManage.RollbackTran(); _logger.LogError($"CancelPicking失败 - OrderNo: {orderNo}, PalletCode: {palletCode}, Barcode: {barcode}, Error: {ex.Message}"); return WebResponseContent.Instance.Error($"取消分拣失败:{ex.Message}"); } } /// /// 回库 /// /// /// /// /// public async Task ReturnRemaining(string orderNo, string palletCode, string reason) { try { _unitOfWorkManage.BeginTran(); if (string.IsNullOrEmpty(orderNo) || string.IsNullOrEmpty(palletCode)) return WebResponseContent.Instance.Error("订单号和托盘码不能为空"); // 获取库存和任务信息 var stockInfo = await _stockInfoService.Db.Queryable().FirstAsync(x => x.PalletCode == palletCode); if (stockInfo == null) return WebResponseContent.Instance.Error($"未找到托盘 {palletCode} 对应的库存信息"); var task = await GetCurrentTask(orderNo, palletCode); if (task == null) return WebResponseContent.Instance.Error("未找到对应的任务信息"); //分析需要回库的货物 //var returnAnalysis = await AnalyzeReturnItems(orderNo, palletCode, stockInfo.Id); //if (!returnAnalysis.HasItemsToReturn) // return await HandleNoReturnItems(orderNo, palletCode, task); var statusAnalysis = await AnalyzePalletStatus(orderNo, palletCode, stockInfo.Id); if (!statusAnalysis.HasItemsToReturn) return await HandleNoReturnItems(orderNo, palletCode, task, stockInfo.Id); // 4. 检查是否有进行中的任务 if (statusAnalysis.HasActiveTasks) { return WebResponseContent.Instance.Error($"托盘 {palletCode} 有进行中的任务,不能执行回库操作"); } //执行回库操作 await ExecuteReturnOperations(orderNo, palletCode, stockInfo, task, statusAnalysis); await ReleaseAllLocksForReallocation(orderNo, palletCode, statusAnalysis); _unitOfWorkManage.CommitTran(); // 创建回库任务 await CreateReturnTaskAndHandleESS(orderNo, palletCode, task, TaskTypeEnum.InPick, task.PalletType); // 更新订单状态(不触发MES回传) await UpdateOrderStatusForReturn(orderNo); return WebResponseContent.Instance.OK($"回库操作成功"); //return WebResponseContent.Instance.OK($"回库操作成功,共回库数量:{statusAnalysis.TotalReturnQty}"); } catch (Exception ex) { _unitOfWorkManage.RollbackTran(); _logger.LogError($"ReturnRemaining失败 - OrderNo: {orderNo}, PalletCode: {palletCode}, Error: {ex.Message}"); return WebResponseContent.Instance.Error($"回库操作失败: {ex.Message}"); } } /// /// 空托盘取走接口(带订单号) /// 验证托盘是否真的为空,清理数据,更新订单状态,创建取托盘任务 /// public async Task RemoveEmptyPallet(string orderNo, string palletCode) { try { _unitOfWorkManage.BeginTran(); if (string.IsNullOrEmpty(orderNo) || string.IsNullOrEmpty(palletCode)) return WebResponseContent.Instance.Error("订单号和托盘码不能为空"); // 检查订单是否存在 var order = await _outboundOrderService.Db.Queryable() .Where(x => x.OrderNo == orderNo) .FirstAsync(); if (order == null) return WebResponseContent.Instance.Error($"未找到订单 {orderNo}"); //检查托盘是否存在且属于该订单 var stockInfo = await _stockInfoService.Db.Queryable() .Where(x => x.PalletCode == palletCode) .FirstAsync(); if (stockInfo == null) return WebResponseContent.Instance.Error($"未找到托盘 {palletCode} 对应的库存信息"); var statusAnalysis = await AnalyzePalletStatus(orderNo, palletCode, stockInfo.Id); if (!statusAnalysis.CanRemove) { if (!statusAnalysis.IsEmptyPallet) { return WebResponseContent.Instance.Error($"托盘 {palletCode} 上还有货物,不能取走"); } if (statusAnalysis.HasActiveTasks) { return WebResponseContent.Instance.Error($"托盘 {palletCode} 还有进行中的任务,不能取走"); } } // 清理零库存数据 await CleanupZeroStockData(stockInfo.Id); // 删除或取消相关任务 await HandleTaskCleanup(orderNo, palletCode); // 更新订单相关数据 await UpdateOrderData(orderNo, palletCode); _unitOfWorkManage.CommitTran(); _logger.LogInformation($"空托盘取走操作成功 - 订单: {orderNo}, 托盘: {palletCode}, 操作人: {App.User.UserName}"); return WebResponseContent.Instance.OK("空托盘取走操作成功"); } catch (Exception ex) { _unitOfWorkManage.RollbackTran(); _logger.LogError($"RemoveEmptyPallet失败 - OrderNo: {orderNo}, PalletCode: {palletCode}, Error: {ex.Message}"); return WebResponseContent.Instance.Error($"空托盘取走失败: {ex.Message}"); } } #endregion #region 分拣确认私有方法 private async Task> ValidatePickingRequest(string orderNo, string palletCode, string barcode) { // 1. 基础参数验证 if (string.IsNullOrEmpty(orderNo) || string.IsNullOrEmpty(palletCode) || string.IsNullOrEmpty(barcode)) return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail)>.Error("订单号、托盘码和条码不能为空"); // 2. 查找有效的锁定信息 var lockInfo = await FindValidLockInfo(orderNo, palletCode, barcode); if (lockInfo == null) return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail)>.Error($"未找到有效的锁定信息"); // 3. 检查订单状态 var order = await _outboundOrderService.Db.Queryable() .Where(x => x.OrderNo == orderNo) .FirstAsync(); if (order?.OrderStatus == (int)OutOrderStatusEnum.出库完成) return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail)>.Error($"订单{orderNo}已完成,不能继续分拣"); // 4. 获取订单明细 var orderDetail = await _outboundOrderDetailService.Db.Queryable() .FirstAsync(x => x.Id == lockInfo.OrderDetailId); if (orderDetail == null) return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail)>.Error($"未找到订单明细"); // 5. 检查订单明细数量 if (orderDetail.OverOutQuantity >= orderDetail.NeedOutQuantity) return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail)>.Error($"订单明细需求数量已满足"); // 6. 获取库存明细 var stockDetail = await _stockInfoDetailService.Db.Queryable() .Where(x => x.Barcode == barcode && x.StockId == lockInfo.StockId && x.Status != StockStatusEmun.入库确认.ObjToInt()) .FirstAsync(); if (stockDetail == null) return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail)>.Error($"无效的条码或物料编码"); // 7. 检查库存状态和数量 if (stockDetail.StockQuantity <= 0) return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail)>.Error($"条码{barcode}库存不足"); if (stockDetail.Status != StockStatusEmun.出库锁定.ObjToInt()) return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail)>.Error($"条码{barcode}状态不正确,无法分拣"); // 8. 检查是否重复分拣 var existingPicking = await Db.Queryable() .Where(x => x.Barcode == barcode && x.OrderNo == orderNo && x.PalletCode == palletCode && x.OutStockLockId == lockInfo.Id) .FirstAsync(); if (existingPicking != null) return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail)>.Error($"条码{barcode}已经分拣过"); return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail)>.Success((lockInfo, orderDetail, stockDetail)); } private async Task FindValidLockInfo(string orderNo, string palletCode, string barcode) { // 优先查找精确匹配的记录 var lockInfo = await _outStockLockInfoService.Db.Queryable() .Where(it => it.OrderNo == orderNo && it.Status == (int)OutLockStockStatusEnum.出库中 && it.PalletCode == palletCode && it.CurrentBarcode == barcode && it.AssignQuantity > it.PickedQty).FirstAsync(); if (lockInfo == null) { // 查找同一订单下的记录 lockInfo = await _outStockLockInfoService.Db.Queryable() .Where(it => it.OrderNo == orderNo && it.CurrentBarcode == barcode && it.Status == (int)OutLockStockStatusEnum.出库中 && it.AssignQuantity > it.PickedQty).FirstAsync(); if (lockInfo == null) { // 检查是否已经完成分拣 var completedLockInfo = await _outStockLockInfoService.Db.Queryable() .Where(it => it.CurrentBarcode == barcode && (it.Status == (int)OutLockStockStatusEnum.拣选完成 || it.PickedQty >= it.AssignQuantity)).FirstAsync(); if (completedLockInfo != null) throw new Exception($"条码{barcode}已经完成分拣,不能重复分拣"); else return null; } } return lockInfo; } private async Task> CalculateActualPickingQuantity( Dt_OutStockLockInfo lockInfo, Dt_OutboundOrderDetail orderDetail, Dt_StockInfoDetail stockDetail) { decimal plannedQty = lockInfo.AssignQuantity - lockInfo.PickedQty; decimal remainingOrderQty = orderDetail.NeedOutQuantity - orderDetail.OverOutQuantity; decimal stockQuantity = stockDetail.StockQuantity; if (plannedQty <= 0) { return ValidationResult<(decimal, string)>.Error($"计划拣选数量必须大于0,当前: {plannedQty}"); } if (remainingOrderQty <= 0) { return ValidationResult<(decimal, string)>.Error($"订单剩余需求数量必须大于0,当前: {remainingOrderQty}"); } if (stockQuantity <= 0) { return ValidationResult<(decimal, string)>.Error($"库存数量必须大于0,当前: {stockQuantity}"); } // 三重检查:取最小值 decimal actualQty = plannedQty; string adjustedReason = null; if (plannedQty > remainingOrderQty) { actualQty = remainingOrderQty; adjustedReason = $"订单数量限制:从{plannedQty}调整为{actualQty}"; } if (actualQty > stockQuantity) { actualQty = stockQuantity; adjustedReason = adjustedReason != null ? $"{adjustedReason},库存数量限制:进一步调整为{actualQty}" : $"库存数量限制:从{plannedQty}调整为{actualQty}"; } if (actualQty <= 0) { return ValidationResult<(decimal, string)>.Error($"无法分拣:计算后的实际数量为{actualQty}"); } decimal projectedOverOut = orderDetail.OverOutQuantity + actualQty; if (projectedOverOut > orderDetail.NeedOutQuantity) { // 如果会超拣,调整为刚好满足需求的数量 actualQty = orderDetail.NeedOutQuantity - orderDetail.OverOutQuantity; adjustedReason = adjustedReason != null ? $"{adjustedReason},防超拣限制:最终调整为{actualQty}" : $"防超拣限制:从{plannedQty}调整为{actualQty}"; } if (adjustedReason != null) { _logger.LogWarning($"分拣数量调整:{adjustedReason},订单{orderDetail.NeedOutQuantity},已出库{orderDetail.OverOutQuantity},库存{stockQuantity}"); } return ValidationResult<(decimal, string)>.Success((actualQty, adjustedReason)); } /// /// 专门验证是否会发生超拣 /// private async Task> ValidateOverPicking(int orderDetailId, decimal pickingQty) { var orderDetail = await _outboundOrderDetailService.Db.Queryable() .FirstAsync(x => x.Id == orderDetailId); if (orderDetail == null) return ValidationResult.Error("未找到订单明细"); decimal projectedOverOut = orderDetail.OverOutQuantity + pickingQty; if (projectedOverOut > orderDetail.NeedOutQuantity) { return ValidationResult.Error( $"分拣后将导致超拣:当前已出库{orderDetail.OverOutQuantity},本次分拣{pickingQty},合计{projectedOverOut},超过需求{orderDetail.NeedOutQuantity}"); } return ValidationResult.Success(true); } private async Task ExecutePickingLogic( Dt_OutStockLockInfo lockInfo, Dt_OutboundOrderDetail orderDetail, Dt_StockInfoDetail stockDetail, string orderNo, string palletCode, string barcode, decimal actualQty) { decimal stockQuantity = stockDetail.StockQuantity; var result = new PickingResult { FinalLockInfo = lockInfo, FinalBarcode = barcode, FinalStockId = stockDetail.Id, ActualPickedQty = actualQty }; if (actualQty < stockQuantity) { await HandleSplitPacking(lockInfo, stockDetail, actualQty, stockQuantity, result); // 拆包场景返回实际拣选数量 result.ActualPickedQty = actualQty; } else if (actualQty == stockQuantity) { await HandleFullPicking(lockInfo, stockDetail, actualQty, result); // 整包拣选返回实际拣选数量 result.ActualPickedQty = actualQty; } else { await HandlePartialPicking(lockInfo, stockDetail, actualQty, stockQuantity, result); // 部分拣选返回调整后的数量 result.ActualPickedQty = result.ActualPickedQty; // 已经在方法内调整 } return result; } private async Task HandleSplitPacking(Dt_OutStockLockInfo lockInfo, Dt_StockInfoDetail stockDetail, decimal actualQty, decimal stockQuantity, PickingResult result) { decimal remainingStockQty = stockQuantity - actualQty; // 更新原条码库存 stockDetail.StockQuantity = remainingStockQty; stockDetail.OutboundQuantity = remainingStockQty; await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync(); // 生成新条码 string newBarcode = await GenerateNewBarcode(); // 创建新锁定信息 var newLockInfo = await CreateSplitLockInfo(lockInfo, actualQty, newBarcode); // 记录拆包历史 await RecordSplitHistory(lockInfo, stockDetail, actualQty, remainingStockQty, newBarcode); // 更新原锁定信息 lockInfo.AssignQuantity = remainingStockQty; lockInfo.PickedQty = 0; lockInfo.Operator = App.User.UserName; await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync(); // 设置结果 result.FinalLockInfo = newLockInfo; result.FinalBarcode = newBarcode; result.SplitResults.AddRange(CreateSplitResults(lockInfo, actualQty, remainingStockQty, newBarcode, stockDetail.Barcode)); _logger.LogInformation($"拆包分拣完成 - OrderDetailId: {lockInfo.OrderDetailId}, 分拣数量: {actualQty}"); } private async Task HandleFullPicking(Dt_OutStockLockInfo lockInfo, Dt_StockInfoDetail stockDetail, decimal actualQty, PickingResult result) { // 1. 更新库存 stockDetail.StockQuantity = 0; stockDetail.OutboundQuantity = 0; stockDetail.Status = StockStatusEmun.出库完成.ObjToInt(); await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync(); // 2. 更新锁定信息 lockInfo.PickedQty += actualQty; lockInfo.Status = (int)OutLockStockStatusEnum.拣选完成; lockInfo.Operator = App.User.UserName; await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync(); } private async Task HandlePartialPicking(Dt_OutStockLockInfo lockInfo, Dt_StockInfoDetail stockDetail, decimal actualQty, decimal stockQuantity, PickingResult result) { decimal stockOutQty = stockQuantity; decimal remainingAssignQty = actualQty - stockQuantity; // 1. 更新库存 stockDetail.StockQuantity = 0; stockDetail.OutboundQuantity = 0; stockDetail.Status = StockStatusEmun.出库完成.ObjToInt(); await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync(); // 2. 更新锁定信息 lockInfo.PickedQty += stockOutQty; lockInfo.AssignQuantity = remainingAssignQty; lockInfo.Operator = App.User.UserName; await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync(); // 3. 更新拆包记录状态 await UpdateSplitRecordsStatus(stockDetail.Barcode); result.ActualPickedQty = stockOutQty; } private async Task UpdateOrderRelatedData(int orderDetailId, decimal pickedQty, string orderNo) { // 获取最新的订单明细数据 var currentOrderDetail = await _outboundOrderDetailService.Db.Queryable() .FirstAsync(x => x.Id == orderDetailId); decimal newOverOutQuantity = currentOrderDetail.OverOutQuantity + pickedQty; decimal newPickedQty = currentOrderDetail.PickedQty + pickedQty; if (newOverOutQuantity > currentOrderDetail.NeedOutQuantity) { _logger.LogError($"防超拣检查失败 - OrderDetailId: {orderDetailId}, 已出库: {newOverOutQuantity}, 需求: {currentOrderDetail.NeedOutQuantity}, 本次分拣: {pickedQty}"); decimal adjustedQty = currentOrderDetail.NeedOutQuantity - currentOrderDetail.OverOutQuantity; if (adjustedQty > 0) { _logger.LogWarning($"自动调整分拣数量防止超拣:从{pickedQty}调整为{adjustedQty}"); newOverOutQuantity = currentOrderDetail.NeedOutQuantity; newPickedQty = currentOrderDetail.PickedQty + adjustedQty; pickedQty = adjustedQty; // 更新实际拣选数量 } else { throw new Exception($"分拣后将导致已出库数量({newOverOutQuantity})超过订单需求数量({currentOrderDetail.NeedOutQuantity}),且无法自动调整"); } } // 更新订单明细数量和状态 await _outboundOrderDetailService.Db.Updateable() .SetColumns(it => new Dt_OutboundOrderDetail { PickedQty = newPickedQty, OverOutQuantity = newOverOutQuantity, OrderDetailStatus = newOverOutQuantity >= currentOrderDetail.NeedOutQuantity ? OrderDetailStatusEnum.Over.ObjToInt() : OrderDetailStatusEnum.Outbound.ObjToInt() }) .Where(it => it.Id == orderDetailId) .ExecuteCommandAsync(); // 更新锁定数量 await UpdateOrderDetailLockQuantity(orderDetailId); // 检查并更新订单状态 await CheckAndUpdateOrderStatus(orderNo); } private async Task RecordPickingHistory(PickingResult result, string orderNo, string palletCode) { var task = await _taskRepository.Db.Queryable() .Where(x => x.OrderNo == orderNo && x.PalletCode == palletCode) .FirstAsync(); if (result.FinalLockInfo.Id <= 0) { throw new Exception($"锁定信息ID无效: {result.FinalLockInfo.Id},无法记录拣选历史"); } var pickingHistory = new Dt_PickingRecord { FactoryArea = result.FinalLockInfo.FactoryArea, TaskNo = task?.TaskNum ?? 0, LocationCode = task?.SourceAddress ?? "", StockId = result.FinalStockId, OrderNo = orderNo, OrderDetailId = result.FinalLockInfo.OrderDetailId, PalletCode = palletCode, Barcode = result.FinalBarcode, MaterielCode = result.FinalLockInfo.MaterielCode, PickQuantity = result.ActualPickedQty, PickTime = DateTime.Now, Operator = App.User.UserName, OutStockLockId = result.FinalLockInfo.Id, BarcodeUnit = result.FinalLockInfo.BarcodeUnit, BarcodeQty = result.FinalLockInfo.BarcodeQty, BatchNo = result.FinalLockInfo.BatchNo, lineNo = result.FinalLockInfo.lineNo, SupplyCode = result.FinalLockInfo.SupplyCode, WarehouseCode = result.FinalLockInfo.WarehouseCode, }; await Db.Insertable(pickingHistory).ExecuteCommandAsync(); } #endregion #region 取消分拣私有方法 private async Task> ValidateDataConsistencyBeforeCancel(CancelPickingContext context) { try { // 验证订单明细数据 var currentOrderDetail = await _outboundOrderDetailService.Db.Queryable() .FirstAsync(x => x.Id == context.OrderDetail.Id); if (currentOrderDetail.OverOutQuantity < context.PickingRecord.PickQuantity) return ValidationResult.Error($"订单明细已出库数量({currentOrderDetail.OverOutQuantity})小于取消数量({context.PickingRecord.PickQuantity})"); if (currentOrderDetail.PickedQty < context.PickingRecord.PickQuantity) return ValidationResult.Error($"订单明细已拣选数量({currentOrderDetail.PickedQty})小于取消数量({context.PickingRecord.PickQuantity})"); // 验证锁定信息数据 var currentLockInfo = await _outStockLockInfoService.Db.Queryable() .FirstAsync(x => x.Id == context.LockInfo.Id); if (currentLockInfo.PickedQty < context.PickingRecord.PickQuantity) return ValidationResult.Error($"锁定信息已拣选数量({currentLockInfo.PickedQty})小于取消数量({context.PickingRecord.PickQuantity})"); ////// 验证库存数据 ////var currentStockDetail = await _stockInfoDetailService.Db.Queryable() //// .FirstAsync(x => x.Barcode == context.PickingRecord.Barcode && x.StockId == context.PickingRecord.StockId); ////if (currentStockDetail == null) //// return ValidationResult.Error($"未找到对应的库存明细记录"); ////if (currentStockDetail.Status == StockStatusEmun.入库确认.ObjToInt() || //// currentStockDetail.Status == StockStatusEmun.入库完成.ObjToInt()) //// return ValidationResult.Error($"条码{context.PickingRecord.Barcode}已经回库,无法取消分拣"); // 验证状态流转的合法性 if (!await CanCancelPicking(currentLockInfo, null)) return ValidationResult.Error($"当前状态不允许取消分拣"); return ValidationResult.Success(true); } catch (Exception ex) { _logger.LogError($"取消分拣数据一致性验证失败: {ex.Message}"); return ValidationResult.Error($"数据验证失败: {ex.Message}"); } } private async Task CanCancelPicking(Dt_OutStockLockInfo lockInfo, Dt_StockInfoDetail stockDetail) { // 锁定信息状态检查 if (lockInfo.Status != (int)OutLockStockStatusEnum.拣选完成) return false; ////// 库存状态检查 ////if (stockDetail.Status == StockStatusEmun.出库完成.ObjToInt()) //// return false; // 如果是拆包记录,还需要检查父锁定信息状态 if (lockInfo.IsSplitted == 1 && lockInfo.ParentLockId.HasValue) { var parentLock = await _outStockLockInfoService.Db.Queryable() .FirstAsync(x => x.Id == lockInfo.ParentLockId.Value); if (parentLock == null || parentLock.Status == (int)OutLockStockStatusEnum.回库中) return false; } return true; } private async Task> ValidateCancelRequest(string orderNo, string palletCode, string barcode) { // 基础参数验证 if (string.IsNullOrEmpty(orderNo) || string.IsNullOrEmpty(palletCode) || string.IsNullOrEmpty(barcode)) return ValidationResult<(Dt_PickingRecord, Dt_OutStockLockInfo, Dt_OutboundOrderDetail)>.Error("订单号、托盘码和条码不能为空"); // 查找拣选记录 var pickingRecord = await Db.Queryable() .Where(it => it.OrderNo == orderNo && it.PalletCode == palletCode && it.Barcode == barcode) .OrderByDescending(it => it.PickTime) .FirstAsync(); if (pickingRecord == null) return ValidationResult<(Dt_PickingRecord, Dt_OutStockLockInfo, Dt_OutboundOrderDetail)>.Error("未找到对应的拣选记录"); if (pickingRecord.PickQuantity <= 0) { return ValidationResult<(Dt_PickingRecord, Dt_OutStockLockInfo, Dt_OutboundOrderDetail)>.Error($"拣选记录数量无效: {pickingRecord.PickQuantity}"); } // 查找锁定信息 var lockInfo = await _outStockLockInfoService.Db.Queryable() .Where(it => it.Id == pickingRecord.OutStockLockId) .FirstAsync(); if (lockInfo == null) return ValidationResult<(Dt_PickingRecord, Dt_OutStockLockInfo, Dt_OutboundOrderDetail)>.Error("未找到对应的出库锁定信息"); if (lockInfo.PickedQty < pickingRecord.PickQuantity) { return ValidationResult<(Dt_PickingRecord, Dt_OutStockLockInfo, Dt_OutboundOrderDetail)>.Error( $"取消数量({pickingRecord.PickQuantity})超过锁定信息的已拣选数量({lockInfo.PickedQty})"); } // 检查状态是否允许取消 if (lockInfo.Status != (int)OutLockStockStatusEnum.拣选完成) return ValidationResult<(Dt_PickingRecord, Dt_OutStockLockInfo, Dt_OutboundOrderDetail)>.Error("当前状态不允许取消分拣"); var order = await _outboundOrderService.Db.Queryable().FirstAsync(x => x.OrderNo == orderNo); if (order?.OrderStatus == (int)OutOrderStatusEnum.出库完成) return ValidationResult<(Dt_PickingRecord, Dt_OutStockLockInfo, Dt_OutboundOrderDetail)>.Error("订单已出库完成,不允许取消分拣"); var orderDetail = await _outboundOrderDetailService.Db.Queryable().FirstAsync(x => x.Id == pickingRecord.OrderDetailId); if (orderDetail == null) return ValidationResult<(Dt_PickingRecord, Dt_OutStockLockInfo, Dt_OutboundOrderDetail)>.Error($"未找到订单明细,ID: {pickingRecord.OrderDetailId}"); // 检查订单明细的已拣选数量是否足够取消 if (orderDetail.PickedQty < pickingRecord.PickQuantity) { return ValidationResult<(Dt_PickingRecord, Dt_OutStockLockInfo, Dt_OutboundOrderDetail)>.Error($"取消数量({pickingRecord.PickQuantity})超过订单明细的已拣选数量({orderDetail.PickedQty})"); } // 检查订单明细的已出库数量是否足够取消 if (orderDetail.OverOutQuantity < pickingRecord.PickQuantity) { return ValidationResult<(Dt_PickingRecord, Dt_OutStockLockInfo, Dt_OutboundOrderDetail)>.Error($"取消数量({pickingRecord.PickQuantity})超过订单明细的已出库数量({orderDetail.OverOutQuantity})"); } var stockDetail = await _stockInfoDetailService.Db.Queryable().FirstAsync(it => it.Barcode == barcode && it.StockId == pickingRecord.StockId); if (stockDetail != null) { // 检查库存状态 - 如果状态是入库确认或入库完成,说明已经回库 if (stockDetail.Status == StockStatusEmun.入库确认.ObjToInt() || stockDetail.Status == StockStatusEmun.入库完成.ObjToInt()) { return ValidationResult<(Dt_PickingRecord, Dt_OutStockLockInfo, Dt_OutboundOrderDetail)>.Error($"条码{barcode}已经回库,不能取消分拣"); } } return ValidationResult<(Dt_PickingRecord, Dt_OutStockLockInfo, Dt_OutboundOrderDetail)>.Success((pickingRecord, lockInfo, orderDetail)); } /// /// 检查条码是否已经回库 /// private async Task IsBarcodeReturned(string barcode, int stockId) { var stockDetail = await _stockInfoDetailService.Db.Queryable() .Where(it => it.Barcode == barcode && it.StockId == stockId) .FirstAsync(); if (stockDetail == null) return false; // 如果状态是入库确认或入库完成,说明已经回库 return stockDetail.Status == StockStatusEmun.入库确认.ObjToInt() || stockDetail.Status == StockStatusEmun.入库完成.ObjToInt(); } /// /// 检查锁定信息对应的条码是否已经回库 /// private async Task IsLockInfoReturned(Dt_OutStockLockInfo lockInfo) { var stockDetail = await _stockInfoDetailService.Db.Queryable() .Where(it => it.Barcode == lockInfo.CurrentBarcode && it.StockId == lockInfo.StockId) .FirstAsync(); if (stockDetail == null) return false; return stockDetail.Status == StockStatusEmun.入库确认.ObjToInt() || stockDetail.Status == StockStatusEmun.入库完成.ObjToInt(); } private async Task ExecuteCancelLogic(Dt_OutStockLockInfo lockInfo, Dt_PickingRecord pickingRecord, Dt_OutboundOrderDetail orderDetail, string orderNo) { decimal cancelQty = pickingRecord.PickQuantity; // 数据一致性验证 var context = new CancelPickingContext { LockInfo = lockInfo, PickingRecord = pickingRecord, OrderDetail = orderDetail, OrderNo = orderNo, CancelQuantity = cancelQty }; var validationResult = await ValidateDataConsistencyBeforeCancel(context); if (!validationResult.IsValid) throw new Exception(validationResult.ErrorMessage); // 处理不同类型的取消 if (lockInfo.IsSplitted == 1 && lockInfo.ParentLockId.HasValue) { await HandleSplitBarcodeCancel(lockInfo, pickingRecord, cancelQty); } else { await HandleNormalBarcodeCancel(lockInfo, pickingRecord, cancelQty); } // 更新订单明细 await UpdateOrderDetailOnCancel(pickingRecord.OrderDetailId, cancelQty); // 删除拣选记录 var deleteResult = await Db.Deleteable() .Where(x => x.Id == pickingRecord.Id) .ExecuteCommandAsync(); if (deleteResult <= 0) throw new Exception("删除拣选记录失败"); _logger.LogInformation($"删除拣选记录 - 记录ID: {pickingRecord.Id}, 条码: {pickingRecord.Barcode}"); // 重新检查订单状态 await UpdateOrderStatusForReturn(orderNo); } private async Task HandleSplitBarcodeCancel(Dt_OutStockLockInfo lockInfo, Dt_PickingRecord pickingRecord, decimal cancelQty) { // 查找父锁定信息 var parentLockInfo = await _outStockLockInfoService.Db.Queryable() .Where(x => x.Id == lockInfo.ParentLockId.Value) .FirstAsync(); if (parentLockInfo == null) throw new Exception("未找到父锁定信息,无法取消拆包分拣"); // 检查父条码和拆包条码的状态 if (await IsLockInfoReturned(parentLockInfo)) throw new Exception($"父条码{parentLockInfo.CurrentBarcode}已经回库,无法取消拆包分拣"); if (await IsLockInfoReturned(lockInfo)) throw new Exception($"拆包条码{lockInfo.CurrentBarcode}已经回库,无法取消拆包分拣"); // 恢复父锁定信息的分配数量 parentLockInfo.AssignQuantity += cancelQty; parentLockInfo.Status = (int)OutLockStockStatusEnum.出库中; // 恢复为出库中状态 await _outStockLockInfoService.Db.Updateable(parentLockInfo).ExecuteCommandAsync(); // 恢复父条码库存 var parentStockDetail = await _stockInfoDetailService.Db.Queryable() .Where(x => x.Barcode == parentLockInfo.CurrentBarcode && x.StockId == parentLockInfo.StockId) .FirstAsync(); if (parentStockDetail != null) { parentStockDetail.StockQuantity += cancelQty; parentStockDetail.OutboundQuantity = parentStockDetail.StockQuantity; parentStockDetail.Status = StockStatusEmun.出库锁定.ObjToInt(); await _stockInfoDetailService.Db.Updateable(parentStockDetail).ExecuteCommandAsync(); _logger.LogInformation($"恢复父条码库存 - 条码: {parentStockDetail.Barcode}, 恢复数量: {cancelQty}, 新库存: {parentStockDetail.StockQuantity}"); } // 处理拆包产生的新条码库存 var splitStockDetail = await _stockInfoDetailService.Db.Queryable() .Where(x => x.Barcode == lockInfo.CurrentBarcode && x.StockId == lockInfo.StockId) .FirstAsync(); if (splitStockDetail != null) { // 删除拆包产生的新条码库存记录 await _stockInfoDetailService.Db.Deleteable(splitStockDetail).ExecuteCommandAsync(); _logger.LogInformation($"删除拆包新条码库存 - 条码: {splitStockDetail.Barcode}"); } // 更新拆包记录状态 var updateCount = await _splitPackageService.Db.Updateable() .SetColumns(x => new Dt_SplitPackageRecord { Status = (int)SplitPackageStatusEnum.已撤销, IsReverted = true, Operator = App.User.UserName, RevertTime = DateTime.Now }) .Where(x => x.NewBarcode == lockInfo.CurrentBarcode && !x.IsReverted) .ExecuteCommandAsync(); _logger.LogInformation($"更新拆包记录状态 - 更新记录数: {updateCount}"); // 删除拆包产生的锁定信息 await _outStockLockInfoService.Db.Deleteable() .Where(x => x.Id == lockInfo.Id) .ExecuteCommandAsync(); _logger.LogInformation($"删除拆包锁定信息 - 锁定ID: {lockInfo.Id}, 条码: {lockInfo.CurrentBarcode}"); } private async Task HandleNormalBarcodeCancel(Dt_OutStockLockInfo lockInfo, Dt_PickingRecord pickingRecord, decimal cancelQty) { if (await IsLockInfoReturned(lockInfo)) throw new Exception($"条码{lockInfo.CurrentBarcode}已经回库,无法取消分拣"); // 恢复锁定信息 lockInfo.PickedQty -= cancelQty; if (lockInfo.PickedQty < 0) lockInfo.PickedQty = 0; // 只有当拣选数量完全取消时才恢复状态 if (lockInfo.PickedQty == 0) { lockInfo.Status = (int)OutLockStockStatusEnum.出库中; } lockInfo.Operator = App.User.UserName; await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync(); _logger.LogInformation($"恢复锁定信息 - 锁定ID: {lockInfo.Id}, 扣减拣选数量: {cancelQty}, 新已拣选数量: {lockInfo.PickedQty}"); // 恢复库存 var stockDetail = await _stockInfoDetailService.Db.Queryable() .Where(x => x.Barcode == pickingRecord.Barcode && x.StockId == pickingRecord.StockId) .FirstAsync(); if (stockDetail != null) { stockDetail.StockQuantity += cancelQty; stockDetail.OutboundQuantity = stockDetail.StockQuantity; // 恢复库存状态 if (stockDetail.Status == StockStatusEmun.出库完成.ObjToInt()) { stockDetail.Status = StockStatusEmun.出库锁定.ObjToInt(); } await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync(); _logger.LogInformation($"恢复库存 - 条码: {stockDetail.Barcode}, 恢复数量: {cancelQty}, " + $"新库存: {stockDetail.StockQuantity}, 新状态: {stockDetail.Status}"); } else { _logger.LogWarning($"未找到库存记录 - 条码: {pickingRecord.Barcode}, 库存ID: {pickingRecord.StockId}"); } } private async Task UpdateOrderDetailOnCancel(int orderDetailId, decimal cancelQty) { // 获取最新的订单明细数据(带锁) var currentOrderDetail = await _outboundOrderDetailService.Db.Queryable() .With(SqlWith.RowLock) .FirstAsync(x => x.Id == orderDetailId); decimal newOverOutQuantity = currentOrderDetail.OverOutQuantity - cancelQty; decimal newPickedQty = currentOrderDetail.PickedQty - cancelQty; // 严格检查取消后数量不会为负数 if (newOverOutQuantity < 0) throw new Exception($"取消分拣将导致已出库数量({newOverOutQuantity})为负数"); if (newPickedQty < 0) throw new Exception($"取消分拣将导致已拣选数量({newPickedQty})为负数"); // 确定新的状态 int newStatus; if (newOverOutQuantity >= currentOrderDetail.NeedOutQuantity) { newStatus = OrderDetailStatusEnum.Over.ObjToInt(); } else if (newOverOutQuantity > 0) { newStatus = OrderDetailStatusEnum.Outbound.ObjToInt(); } else { newStatus = OrderDetailStatusEnum.New.ObjToInt(); } // 更新订单明细 var updateResult = await _outboundOrderDetailService.Db.Updateable() .SetColumns(it => new Dt_OutboundOrderDetail { PickedQty = newPickedQty, OverOutQuantity = newOverOutQuantity, OrderDetailStatus = newStatus }) .Where(it => it.Id == orderDetailId) .ExecuteCommandAsync(); if (updateResult <= 0) throw new Exception("更新订单明细失败"); // 更新锁定数量 await UpdateOrderDetailLockQuantity(orderDetailId); _logger.LogInformation($"更新订单明细 - OrderDetailId: {orderDetailId}, " + $"扣减已出库: {cancelQty}, 新已出库: {newOverOutQuantity}, " + $"扣减已拣选: {cancelQty}, 新已拣选: {newPickedQty}, " + $"新状态: {newStatus}"); } #endregion #region 回库操作私有方法 private async Task GetStockInfo(string palletCode) { return await _stockInfoService.Db.Queryable() .FirstAsync(x => x.PalletCode == palletCode); } /// /// 检查整个托盘是否已经回库 /// private async Task IsPalletReturned(string palletCode) { var stockInfo = await _stockInfoService.Db.Queryable() .Where(x => x.PalletCode == palletCode) .FirstAsync(); if (stockInfo == null) return false; // 如果托盘状态是入库确认或入库完成,说明已经回库 return stockInfo.StockStatus == StockStatusEmun.入库确认.ObjToInt() || stockInfo.StockStatus == StockStatusEmun.入库完成.ObjToInt(); } private async Task GetCurrentTask(string orderNo, string palletCode) { // 先尝试通过订单号和托盘号查找任务 var task = await _taskRepository.Db.Queryable() .Where(x => x.OrderNo == orderNo && x.PalletCode == palletCode) .FirstAsync(); if (task == null) { // 如果找不到,再通过托盘号查找 task = await _taskRepository.Db.Queryable() .Where(x => x.PalletCode == palletCode) .FirstAsync(); } return task; } private async Task CalculateSplitReturnQuantity(List splitRecords, int stockId) { decimal totalQty = 0; var processedBarcodes = new HashSet(); foreach (var splitRecord in splitRecords) { if (splitRecord.Status != (int)SplitPackageStatusEnum.已撤销) continue; // 检查原条码 if (!processedBarcodes.Contains(splitRecord.OriginalBarcode)) { var originalStock = await _stockInfoDetailService.Db.Queryable() .Where(it => it.Barcode == splitRecord.OriginalBarcode && it.StockId == stockId && it.Status != StockStatusEmun.出库完成.ObjToInt()) .FirstAsync(); if (originalStock != null && originalStock.StockQuantity > 0) { totalQty += originalStock.StockQuantity; processedBarcodes.Add(splitRecord.OriginalBarcode); } } // 检查新条码 if (!processedBarcodes.Contains(splitRecord.NewBarcode)) { var newStock = await _stockInfoDetailService.Db.Queryable() .Where(it => it.Barcode == splitRecord.NewBarcode && it.StockId == stockId && it.Status != StockStatusEmun.出库完成.ObjToInt()) .FirstAsync(); if (newStock != null && newStock.StockQuantity > 0) { totalQty += newStock.StockQuantity; processedBarcodes.Add(splitRecord.NewBarcode); } } } return totalQty; } private async Task HandleNoReturnItems(string orderNo, string palletCode, Dt_Task originalTask, int stockInfoId) { // 检查是否所有货物都已拣选完成 //var allPicked = await _outStockLockInfoService.Db.Queryable() // .Where(it => it.OrderNo == orderNo && it.PalletCode == palletCode) // .AnyAsync(it => it.Status == (int)OutLockStockStatusEnum.拣选完成); //if (allPicked) //{ // // 删除原始出库任务 组空盘 空盘回库 // //await _taskRepository.Db.Deleteable(originalTask).ExecuteCommandAsync(); // return WebResponseContent.Instance.OK("所有货物已拣选完成,托盘为空"); //} //else //{ // // 删除原始出库任务 // //await _taskRepository.Db.Deleteable(originalTask).ExecuteCommandAsync(); // return WebResponseContent.Instance.Error("没有需要回库的剩余货物"); //} try { var locationtype = 0; var stockInfo = await _stockInfoService.Db.Queryable() .Where(x => x.PalletCode == palletCode) .FirstAsync(); if (stockInfo == null) { var firstLocation = await _locationInfoService.Db.Queryable().FirstAsync(x => x.LocationCode == originalTask.SourceAddress); locationtype = firstLocation?.LocationType ?? 1; } else { locationtype = stockInfo.LocationType; _stockInfoService.DeleteData(stockInfo); } var targetAddress = originalTask.TargetAddress; await CleanupZeroStockData(stockInfoId); var emptystockInfo = new Dt_StockInfo() { PalletType = PalletTypeEnum.Empty.ObjToInt(), StockStatus = StockStatusEmun.组盘暂存.ObjToInt(), PalletCode = palletCode, LocationType = locationtype }; emptystockInfo.Details = new List(); _stockInfoService.AddMaterielGroup(emptystockInfo); //空托盘如何处理 还有一个出库任务要处理。 originalTask.PalletType = PalletTypeEnum.Empty.ObjToInt(); await CreateReturnTaskAndHandleESS(orderNo, palletCode, originalTask, TaskTypeEnum.InEmpty, PalletTypeEnum.Empty.ObjToInt()); } catch (Exception ex) { _logger.LogError($" HandleNoReturnItems 失败: {ex.Message}"); return WebResponseContent.Instance.Error($" 回库空托盘失败!"); } return WebResponseContent.Instance.OK("空托盘回库任务创建成功"); } private async Task ExecuteReturnOperations(string orderNo, string palletCode, Dt_StockInfo stockInfo, Dt_Task task, PalletStatusAnalysis analysis) { // 情况1:处理未分拣的出库锁定记录 if (analysis.HasRemainingLocks) { await HandleRemainingLocksReturn(analysis.RemainingLocks, stockInfo.Id); // await UpdateOrderDetailsOnReturn(analysis.RemainingLocks); } // 处理托盘上其他库存货物 if (analysis.HasPalletStockGoods) { var validStockGoods = analysis.PalletStockGoods .Where(x => x.Status != StockStatusEmun.出库完成.ObjToInt()) .ToList(); if (validStockGoods.Any()) { await HandlePalletStockGoodsReturn(analysis.PalletStockGoods); } else { _logger.LogInformation("没有有效的库存货物需要回库"); } } // 处理拆包记录 if (analysis.HasSplitRecords) { var validSplitRecords = analysis.SplitRecords .Where(x => x.Status != (int)SplitPackageStatusEnum.已拣选) .ToList(); if (validSplitRecords.Any()) { await HandleSplitRecordsReturn(analysis.SplitRecords, orderNo, palletCode); } else { _logger.LogInformation("没有有效的拆包记录需要处理"); } } // 更新库存主表状态 await UpdateStockInfoStatus(stockInfo); } /// /// 完全释放锁定,允许重新分配库存 /// private async Task ReleaseAllLocksForReallocation(string orderNo, string palletCode, PalletStatusAnalysis analysis) { _logger.LogInformation($"开始释放锁定以便重新分配 - 订单: {orderNo}, 托盘: {palletCode}"); // 1. 处理未分拣的出库锁定记录 - 完全释放 if (analysis.HasRemainingLocks) { await ReleaseRemainingLocks(analysis.RemainingLocks); } // 2. 处理已回库的锁定记录 - 删除或标记为无效 await CleanupReturnedLocks(orderNo, palletCode); // 3. 重置订单明细的锁定数量 await ResetOrderDetailLockQuantities(analysis); _logger.LogInformation($"锁定释放完成 - 订单: {orderNo}, 托盘: {palletCode}"); } /// /// 释放未分拣的锁定记录 /// private async Task ReleaseRemainingLocks(List remainingLocks) { var lockIds = remainingLocks.Select(x => x.Id).ToList(); // 将锁定记录状态改为"已释放",或者直接删除 // 标记为已释放 await _outStockLockInfoService.Db.Updateable() .SetColumns(it => new Dt_OutStockLockInfo { Status = (int)OutLockStockStatusEnum.已释放, // 需要新增这个状态 // ReleaseTime = DateTime.Now, Operator = App.User.UserName }) .Where(it => lockIds.Contains(it.Id)) .ExecuteCommandAsync(); // 直接删除(更彻底) // await _outStockLockInfoService.Db.Deleteable() // .Where(it => lockIds.Contains(it.Id)) // .ExecuteCommandAsync(); _logger.LogInformation($"释放{remainingLocks.Count}条未分拣锁定记录"); } /// /// 清理已回库的锁定记录 /// private async Task CleanupReturnedLocks(string orderNo, string palletCode) { // 查找所有状态为回库中的锁定记录并释放 var returnedLocks = await _outStockLockInfoService.Db.Queryable() .Where(it => it.OrderNo == orderNo && it.PalletCode == palletCode && it.Status == (int)OutLockStockStatusEnum.回库中) .ToListAsync(); if (returnedLocks.Any()) { var returnedLockIds = returnedLocks.Select(x => x.Id).ToList(); await _outStockLockInfoService.Db.Updateable() .SetColumns(it => new Dt_OutStockLockInfo { Status = (int)OutLockStockStatusEnum.已释放, //ReleaseTime = DateTime.Now, Operator = App.User.UserName }) .Where(it => returnedLockIds.Contains(it.Id)) .ExecuteCommandAsync(); _logger.LogInformation($"清理{returnedLocks.Count}条回库中锁定记录"); } } /// /// 重置订单明细的锁定数量 /// private async Task ResetOrderDetailLockQuantities(PalletStatusAnalysis analysis) { // 收集所有受影响的订单明细ID var affectedOrderDetailIds = new HashSet(); if (analysis.HasRemainingLocks) { foreach (var lockInfo in analysis.RemainingLocks) { affectedOrderDetailIds.Add(lockInfo.OrderDetailId); } } // 重置这些订单明细的锁定数量 foreach (var orderDetailId in affectedOrderDetailIds) { await _outboundOrderDetailService.Db.Updateable() .SetColumns(it => new Dt_OutboundOrderDetail { LockQuantity = 0, // 重置锁定数量 OrderDetailStatus = OrderDetailStatusEnum.New.ObjToInt() // 重置状态为新建 }) .Where(it => it.Id == orderDetailId) .ExecuteCommandAsync(); } _logger.LogInformation($"重置{affectedOrderDetailIds.Count}个订单明细的锁定数量"); } private async Task HandleRemainingLocksReturn(List remainingLocks, int stockId) { var lockIds = remainingLocks.Select(x => x.Id).ToList(); // 更新出库锁定记录状态为回库中 await _outStockLockInfoService.Db.Updateable() .SetColumns(it => new Dt_OutStockLockInfo { Status = (int)OutLockStockStatusEnum.回库中, }) .Where(it => lockIds.Contains(it.Id)) .ExecuteCommandAsync(); // 处理库存记录 foreach (var lockInfo in remainingLocks) { decimal returnQty = lockInfo.AssignQuantity - lockInfo.PickedQty; if (returnQty <= 0) { _logger.LogWarning($"锁定记录{lockInfo.Id}无需回库,分配数量: {lockInfo.AssignQuantity}, 已拣选: {lockInfo.PickedQty}"); continue; } _logger.LogInformation($"处理锁定记录回库 - 锁定ID: {lockInfo.Id}, 条码: {lockInfo.CurrentBarcode}, 回库数量: {returnQty}"); // 查找对应的库存明细 var stockDetail = await _stockInfoDetailService.Db.Queryable() .Where(it => it.Barcode == lockInfo.CurrentBarcode && it.StockId == lockInfo.StockId) .FirstAsync(); if (stockDetail != null) { stockDetail.StockQuantity += returnQty; // 恢复库存状态 stockDetail.OutboundQuantity = Math.Max(0, stockDetail.OutboundQuantity - returnQty); stockDetail.Status = StockStatusEmun.入库确认.ObjToInt(); await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync(); } else { _logger.LogWarning($"未找到对应的库存明细 - 条码: {lockInfo.CurrentBarcode}, 库存ID: {lockInfo.StockId}"); // 创建新的库存记录 //var newStockDetail = new Dt_StockInfoDetail //{ // StockId = lockInfo.StockId, // MaterielCode = lockInfo.MaterielCode, // MaterielName = lockInfo.MaterielName, // OrderNo = lockInfo.OrderNo, // BatchNo = lockInfo.BatchNo, // StockQuantity = returnQty, // OutboundQuantity = 0, // Barcode = lockInfo.CurrentBarcode, // InboundOrderRowNo = "", // Status = StockStatusEmun.入库确认.ObjToInt(), // SupplyCode = lockInfo.SupplyCode, // WarehouseCode = lockInfo.WarehouseCode, // Unit = lockInfo.Unit, //}; //await _stockInfoDetailService.Db.Insertable(newStockDetail).ExecuteCommandAsync(); } try { var orderDetail = await _outboundOrderDetailService.Db.Queryable() .FirstAsync(x => x.Id == lockInfo.OrderDetailId); if (orderDetail != null) { decimal newLockQuantity = Math.Max(0, orderDetail.LockQuantity - returnQty); await _outboundOrderDetailService.Db.Updateable() .SetColumns(it => new Dt_OutboundOrderDetail { LockQuantity = newLockQuantity }) .Where(it => it.Id == lockInfo.OrderDetailId) .ExecuteCommandAsync(); _logger.LogInformation($"更新订单明细锁定数量 - OrderDetailId: {lockInfo.OrderDetailId}, " + $"扣减锁定: {returnQty}, 新锁定数量: {newLockQuantity}"); } } catch (Exception ex) { _logger.LogError($"更新订单明细锁定数量失败 - OrderDetailId: {lockInfo.OrderDetailId}, Error: {ex.Message}"); } } } private async Task UpdateOrderDetailsOnReturn(List remainingLocks) { // 按订单明细分组 var orderDetailGroups = remainingLocks.GroupBy(x => x.OrderDetailId); foreach (var group in orderDetailGroups) { var orderDetailId = group.Key; var totalReturnQty = group.Sum(x => x.AssignQuantity - x.PickedQty); // 获取当前订单明细 var orderDetail = await _outboundOrderDetailService.Db.Queryable() .FirstAsync(x => x.Id == orderDetailId); if (orderDetail != null) { // 调整已拣选数量和已出库数量 decimal newPickedQty = Math.Max(0, orderDetail.PickedQty - totalReturnQty); decimal newOverOutQuantity = Math.Max(0, orderDetail.OverOutQuantity - totalReturnQty); await _outboundOrderDetailService.Db.Updateable() .SetColumns(it => new Dt_OutboundOrderDetail { PickedQty = newPickedQty, OverOutQuantity = newOverOutQuantity, }) .Where(it => it.Id == orderDetailId) .ExecuteCommandAsync(); } } } private async Task HandlePalletStockGoodsReturn(List palletStockGoods) { _logger.LogInformation($"回库操作:发现{palletStockGoods.Count}个库存明细需要回库,等待AGV搬运"); foreach (var stockGood in palletStockGoods) { _logger.LogInformation($"待回库货物 - 条码: {stockGood.Barcode}, 数量: {stockGood.StockQuantity}, 当前状态: {stockGood.Status}"); if (stockGood.Status != StockStatusEmun.出库完成.ObjToInt()) { stockGood.OutboundQuantity = 0; stockGood.Status = StockStatusEmun.入库确认.ObjToInt(); await _stockInfoDetailService.Db.Updateable(stockGood).ExecuteCommandAsync(); _logger.LogInformation($"库存货物回库完成 - 条码: {stockGood.Barcode}, 新状态: {stockGood.Status}"); } else { _logger.LogWarning($"跳过已出库完成的货物 - 条码: {stockGood.Barcode}"); } } } private async Task HandleSplitRecordsReturn(List splitRecords, string orderNo, string palletCode) { var validRecords = splitRecords.Where(x => x.Status != (int)SplitPackageStatusEnum.已拣选).ToList(); if (!validRecords.Any()) { _logger.LogInformation("没有需要回库的拆包记录"); return; } _logger.LogInformation($"更新{validRecords.Count}条拆包记录状态为已回库"); // 更新拆包记录状态 await _splitPackageService.Db.Updateable() .SetColumns(x => new Dt_SplitPackageRecord { Status = (int)SplitPackageStatusEnum.已回库, Operator = App.User.UserName }) .Where(x => validRecords.Select(r => r.Id).Contains(x.Id)) .ExecuteCommandAsync(); } private async Task UpdateStockInfoStatus(Dt_StockInfo stockInfo) { _logger.LogInformation($"回库操作:托盘{stockInfo.PalletCode}等待AGV回库搬运"); // 更新库存主表状态 stockInfo.StockStatus = StockStatusEmun.入库确认.ObjToInt(); await _stockInfoService.Db.Updateable(stockInfo).ExecuteCommandAsync(); } /// /// 创建回库任务 /// /// /// /// /// /// private async Task CreateReturnTaskAndHandleESS(string orderNo, string palletCode, Dt_Task originalTask, TaskTypeEnum taskTypeEnum, int palletType) { var firstLocation = await _locationInfoService.Db.Queryable() .FirstAsync(x => x.LocationCode == originalTask.SourceAddress); // 分配新货位 var newLocation = _locationInfoService.AssignLocation(firstLocation.LocationType); Dt_Task returnTask = new() { CurrentAddress = stations[originalTask.TargetAddress], Grade = 0, PalletCode = palletCode, NextAddress = "", // OrderNo = originalTask.OrderNo, OrderNo = orderNo, Roadway = newLocation.RoadwayNo, SourceAddress = stations[originalTask.TargetAddress], TargetAddress = newLocation.LocationCode, TaskStatus = TaskStatusEnum.New.ObjToInt(), TaskType = taskTypeEnum.ObjToInt(), PalletType = palletType, WarehouseId = originalTask.WarehouseId }; // 保存回库任务 await _taskRepository.Db.Insertable(returnTask).ExecuteCommandAsync(); var targetAddress = originalTask.TargetAddress; // 删除原始出库任务 _taskRepository.DeleteAndMoveIntoHty(originalTask, OperateTypeEnum.自动完成); await _taskRepository.Db.Deleteable(originalTask).ExecuteCommandAsync(); // 给 ESS 发送流动信号和创建任务 await SendESSCommands(palletCode, targetAddress, returnTask); } /// /// 给ESS下任务 /// /// /// /// /// /// private async Task SendESSCommands(string palletCode, string targetAddress, Dt_Task returnTask) { try { // 1. 发送流动信号 var moveResult = await _eSSApiService.MoveContainerAsync(new WIDESEA_DTO.Basic.MoveContainerRequest { slotCode = movestations[targetAddress], containerCode = palletCode }); //if (moveResult) //{ // 2. 创建回库任务 var essTask = new TaskModel() { taskType = "putaway", taskGroupCode = "", groupPriority = 0, tasks = new List{ new() { taskCode = returnTask.TaskNum.ToString(), taskPriority = 0, taskDescribe = new TaskDescribeType { containerCode = palletCode, containerType = "CT_KUBOT_STANDARD", fromLocationCode = stations.GetValueOrDefault(targetAddress) ?? "", toStationCode = "", toLocationCode = returnTask.TargetAddress, deadline = 0, storageTag = "" } } } }; var resultTask = await _eSSApiService.CreateTaskAsync(essTask); _logger.LogInformation($"ReturnRemaining 创建任务成功: {resultTask}"); //} } catch (Exception ex) { _logger.LogError($"ReturnRemaining ESS命令发送失败: {ex.Message}"); throw new Exception($"ESS系统通信失败: {ex.Message}"); } } #endregion #region 订单状态管理 private async Task CheckAndUpdateOrderStatus(string orderNo) { try { var orderDetails = await _outboundOrderDetailService.Db.Queryable() .LeftJoin((o, item) => o.OrderId == item.Id) .Where((o, item) => item.OrderNo == orderNo) .Select((o, item) => o) .ToListAsync(); bool allCompleted = true; bool hasPartial = false; bool hasLocked = false; foreach (var detail in orderDetails) { if (detail.OverOutQuantity < detail.NeedOutQuantity) { allCompleted = false; } if (detail.OrderDetailStatus == OrderDetailStatusEnum.Outbound.ObjToInt()) { hasPartial = true; } //if (detail.OrderDetailStatus == OrderDetailStatusEnum.Locked.ObjToInt()) //{ // hasLocked = true; //} } var outboundOrder = await _outboundOrderService.Db.Queryable() .FirstAsync(x => x.OrderNo == orderNo); if (outboundOrder == null) return; int newStatus; if (allCompleted) { newStatus = (int)OutOrderStatusEnum.出库完成; } else if (hasPartial) { newStatus = (int)OutOrderStatusEnum.出库中; } else { newStatus = (int)OutOrderStatusEnum.未开始; } if (outboundOrder.OrderStatus != newStatus) { await _outboundOrderService.Db.Updateable() .SetColumns(x => new Dt_OutboundOrder { OrderStatus = newStatus, Operator = App.User.UserName, }) .Where(x => x.OrderNo == orderNo) .ExecuteCommandAsync(); _logger.LogInformation($"订单状态更新 - OrderNo: {orderNo}, 旧状态: {outboundOrder.OrderStatus}, 新状态: {newStatus}"); } } catch (Exception ex) { _logger.LogError($"CheckAndUpdateOrderStatus失败 - OrderNo: {orderNo}, Error: {ex.Message}"); } } /// /// 更新订单明细状态(基于已出库数量) /// private async Task UpdateOrderDetailStatus(int orderDetailId) { var orderDetail = await _outboundOrderDetailService.Db.Queryable() .FirstAsync(x => x.Id == orderDetailId); if (orderDetail == null) return; int newStatus = orderDetail.OrderDetailStatus; if (orderDetail.OverOutQuantity >= orderDetail.NeedOutQuantity) { // 已出库数量 >= 需求数量,标记为完成 newStatus = OrderDetailStatusEnum.Over.ObjToInt(); } else if (orderDetail.OverOutQuantity > 0) { // 有部分出库,但未完成 newStatus = OrderDetailStatusEnum.Outbound.ObjToInt(); } else if (orderDetail.LockQuantity > 0) { // 有锁定数量,但未出库 //newStatus = OrderDetailStatusEnum.Locked.ObjToInt(); } else { // 新建状态 newStatus = OrderDetailStatusEnum.New.ObjToInt(); } // 只有状态发生变化时才更新 if (orderDetail.OrderDetailStatus != newStatus) { await _outboundOrderDetailService.Db.Updateable() .SetColumns(it => new Dt_OutboundOrderDetail { OrderDetailStatus = newStatus, }) .Where(it => it.Id == orderDetailId) .ExecuteCommandAsync(); } } /// /// 更新订单明细锁定数量 /// private async Task UpdateOrderDetailLockQuantity(int orderDetailId) { var totalLockedQty = await _outStockLockInfoService.Db.Queryable() .Where(x => x.OrderDetailId == orderDetailId && x.Status == (int)OutLockStockStatusEnum.出库中) .SumAsync(x => x.AssignQuantity - x.PickedQty); await _outboundOrderDetailService.Db.Updateable() .SetColumns(it => new Dt_OutboundOrderDetail { LockQuantity = totalLockedQty }) .Where(it => it.Id == orderDetailId) .ExecuteCommandAsync(); } private async Task UpdateOrderStatusForReturn(string orderNo) { try { var orderDetails = await _outboundOrderDetailService.Db.Queryable() .LeftJoin((o, item) => o.OrderId == item.Id) .Where((o, item) => item.OrderNo == orderNo) .Select((o, item) => o) .ToListAsync(); bool allCompleted = true; foreach (var detail in orderDetails) { if (detail.OverOutQuantity < detail.NeedOutQuantity) { allCompleted = false; break; } } var outboundOrder = await _outboundOrderService.Db.Queryable() .FirstAsync(x => x.OrderNo == orderNo); if (outboundOrder == null) return; int newStatus = allCompleted ? (int)OutOrderStatusEnum.出库完成 : (int)OutOrderStatusEnum.出库中; if (outboundOrder.OrderStatus != newStatus) { await _outboundOrderService.Db.Updateable() .SetColumns(x => new Dt_OutboundOrder { OrderStatus = newStatus, Operator = App.User.UserName, }) .Where(x => x.OrderNo == orderNo) .ExecuteCommandAsync(); _logger.LogInformation($"回库操作更新订单状态 - OrderNo: {orderNo}, 新状态: {newStatus}"); } } catch (Exception ex) { _logger.LogError($"UpdateOrderStatusForReturn失败 - OrderNo: {orderNo}, Error: {ex.Message}"); } } private async Task HandleOrderCompletion(Dt_OutboundOrder outboundOrder, string orderNo) { // 调拨出库和重检出库不需要反馈MES if (outboundOrder.OrderType == OutOrderTypeEnum.Allocate.ObjToInt()) { var allocate = _allocateService.Repository.QueryData(x => x.UpperOrderNo == outboundOrder.UpperOrderNo).First(); var allocatefeedmodel = new AllocateDto { ReqCode = Guid.NewGuid().ToString(), ReqTime = DateTime.Now.ToString(), BusinessType = "2", FactoryArea = outboundOrder.FactoryArea, OperationType = 1, Operator = App.User.UserName, OrderNo = outboundOrder.UpperOrderNo, // documentsNO = outboundOrder.OrderNo, // status = outboundOrder.OrderStatus, fromWarehouse = allocate?.FromWarehouse ?? "", toWarehouse = allocate?.ToWarehouse ?? "", Details = new List() }; // 只获取已拣选完成的锁定记录 var lists = await _outStockLockInfoService.Db.Queryable() .Where(x => x.OrderNo == orderNo && x.Status == (int)OutLockStockStatusEnum.拣选完成) .ToListAsync(); var groupedData = lists.GroupBy(item => new { item.MaterielCode, item.lineNo, item.Unit, item.WarehouseCode }) .Select(group => new AllocateDtoDetail { MaterialCode = group.Key.MaterielCode, LineNo = group.Key.lineNo, WarehouseCode = group.Key.WarehouseCode, Qty = group.Sum(x => x.PickedQty), Unit = group.Key.Unit, Barcodes = group.Select(row => new BarcodeInfo { Barcode = row.CurrentBarcode, SupplyCode = row.SupplyCode, BatchNo = row.BatchNo, Unit = row.Unit, Qty = row.PickedQty }).ToList() }).ToList(); allocatefeedmodel.Details = groupedData; var result = await _invokeMESService.FeedbackAllocate(allocatefeedmodel); if (result != null && result.code == 200) { await _outboundOrderDetailService.Db.Updateable() .SetColumns(x => x.ReturnToMESStatus == 1) .Where(x => x.OrderId == outboundOrder.Id).ExecuteCommandAsync(); await _outboundOrderService.Db.Updateable() .SetColumns(x => new Dt_OutboundOrder { ReturnToMESStatus = 1, Operator = App.User.UserName, }).Where(x => x.OrderNo == orderNo).ExecuteCommandAsync(); } } else if (outboundOrder.OrderType == OutOrderTypeEnum.ReCheck.ObjToInt()) { } else { try { var feedmodel = new FeedbackOutboundRequestModel { reqCode = Guid.NewGuid().ToString(), reqTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), business_type = outboundOrder.BusinessType, factoryArea = outboundOrder.FactoryArea, operationType = 1, Operator = App.User.UserName, orderNo = outboundOrder.UpperOrderNo, documentsNO = outboundOrder.OrderNo, status = outboundOrder.OrderStatus, details = new List() }; // 只获取已拣选完成的锁定记录 var lists = await _outStockLockInfoService.Db.Queryable() .Where(x => x.OrderNo == orderNo && (x.Status == (int)OutLockStockStatusEnum.拣选完成 || x.Status == (int)OutLockStockStatusEnum.已回库)) .ToListAsync(); var groupedData = lists.GroupBy(item => new { item.MaterielCode, item.lineNo, item.BarcodeUnit, item.WarehouseCode }) .Select(group => new FeedbackOutboundDetailsModel { materialCode = group.Key.MaterielCode, lineNo = group.Key.lineNo, warehouseCode = group.Key.WarehouseCode, qty = group.Sum(x => x.BarcodeQty), currentDeliveryQty = group.Sum(x => x.BarcodeQty), unit = group.Key.BarcodeUnit, barcodes = group.Select(row => new WIDESEA_DTO.Outbound.BarcodesModel { barcode = row.CurrentBarcode, supplyCode = row.SupplyCode, batchNo = row.BatchNo, unit = row.BarcodeUnit, qty = row.BarcodeQty }).ToList() }).ToList(); feedmodel.details = groupedData; var result = await _invokeMESService.FeedbackOutbound(feedmodel); if (result != null && result.code == 200) { await _outboundOrderDetailService.Db.Updateable() .SetColumns(x => x.ReturnToMESStatus == 1) .Where(x => x.OrderId == outboundOrder.Id) .ExecuteCommandAsync(); await _outboundOrderService.Db.Updateable() .SetColumns(x => new Dt_OutboundOrder { ReturnToMESStatus = 1, Operator = App.User.UserName, }) .Where(x => x.OrderNo == orderNo) .ExecuteCommandAsync(); } _logger.LogError($"FeedbackOutbound成功 - OrderNo: {orderNo}, {JsonSerializer.Serialize(result)}"); } catch (Exception ex) { _logger.LogError($"FeedbackOutbound失败 - OrderNo: {orderNo}, Error: {ex.Message}"); } } } #endregion #region 空托盘 /// /// 清理零库存数据 /// private async Task CleanupZeroStockData(int stockId) { try { // 1. 删除库存数量为0的明细记录 var deleteDetailCount = await _stockInfoDetailService.Db.Deleteable() .Where(x => x.StockId == stockId && x.StockQuantity == 0 && (x.Status == StockStatusEmun.出库完成.ObjToInt() || x.Status == StockStatusEmun.入库完成.ObjToInt())) .ExecuteCommandAsync(); await _stockInfoService.Db.Deleteable() .Where(x => x.Id == stockId).ExecuteCommandAsync(); _logger.LogInformation($"清理零库存明细记录 - StockId: {stockId}, 删除记录数: {deleteDetailCount}"); } catch (Exception ex) { _logger.LogWarning($"清理零库存数据失败 - StockId: {stockId}, Error: {ex.Message}"); // 注意:清理失败不应该影响主流程 } } /// /// 处理任务清理(按订单和托盘) /// private async Task HandleTaskCleanup(string orderNo, string palletCode) { try { // 1. 查找所有与该订单和托盘相关的任务 var tasks = await _taskRepository.Db.Queryable().Where(x => x.OrderNo == orderNo && x.PalletCode == palletCode).ToListAsync(); if (tasks.Any()) { foreach (var task in tasks) { task.TaskStatus = (int)TaskStatusEnum.Finish; } // await _taskRepository.Db.Updateable(tasks).ExecuteCommandAsync(); _taskRepository.DeleteAndMoveIntoHty(tasks, OperateTypeEnum.自动完成); _logger.LogInformation($"完成{tasks.Count}个托盘任务 - 订单: {orderNo}, 托盘: {palletCode}"); } } catch (Exception ex) { _logger.LogWarning($"处理任务清理失败 - OrderNo: {orderNo}, PalletCode: {palletCode}, Error: {ex.Message}"); throw new Exception($"任务清理失败: {ex.Message}"); } } /// /// 更新订单相关数据 /// private async Task UpdateOrderData(string orderNo, string palletCode) { try { // 检查订单是否还有其他托盘在处理中 var otherActivePallets = await _outStockLockInfoService.Db.Queryable() .Where(x => x.OrderNo == orderNo && x.PalletCode != palletCode && (x.Status == (int)OutLockStockStatusEnum.出库中 || x.Status == (int)OutLockStockStatusEnum.回库中)) .AnyAsync(); var otherActiveTasks = await _taskRepository.Db.Queryable() .Where(x => x.OrderNo == orderNo && x.PalletCode != palletCode // && x.TaskStatus.In((int)TaskStatusEnum.待执行, (int)TaskStatusEnum.执行中) ) .AnyAsync(); // 如果没有其他托盘在处理,检查订单是否应该完成 if (!otherActivePallets && !otherActiveTasks) { await CheckAndUpdateOrderCompletion(orderNo); } else { _logger.LogInformation($"订单 {orderNo} 还有其他托盘在处理,不更新订单状态"); } // 3. 更新拣选记录状态(可选) await UpdatePickingRecordsStatus(orderNo, palletCode); } catch (Exception ex) { _logger.LogWarning($"更新订单数据失败 - OrderNo: {orderNo}, PalletCode: {palletCode}, Error: {ex.Message}"); throw new Exception($"更新订单数据失败: {ex.Message}"); } } /// /// 检查并更新订单完成状态 /// private async Task CheckAndUpdateOrderCompletion(string orderNo) { var orderDetails = await _outboundOrderDetailService.Db.Queryable() .LeftJoin((o, item) => o.OrderId == item.Id) .Where((o, item) => item.OrderNo == orderNo) .Select((o, item) => o) .ToListAsync(); bool allCompleted = true; foreach (var detail in orderDetails) { if (detail.OverOutQuantity < detail.NeedOutQuantity) { allCompleted = false; break; } } var outboundOrder = await _outboundOrderService.Db.Queryable() .FirstAsync(x => x.OrderNo == orderNo); if (outboundOrder != null && allCompleted && outboundOrder.OrderStatus != (int)OutOrderStatusEnum.出库完成) { outboundOrder.OrderStatus = (int)OutOrderStatusEnum.出库完成; outboundOrder.Operator = App.User.UserName; await _outboundOrderService.Db.Updateable(outboundOrder).ExecuteCommandAsync(); _logger.LogInformation($"订单 {orderNo} 已标记为出库完成"); // 向MES反馈订单完成(如果需要) //await HandleOrderCompletion(outboundOrder, orderNo); } } /// /// 更新拣选记录状态 /// private async Task UpdatePickingRecordsStatus(string orderNo, string palletCode) { try { // 可以将相关的拣选记录标记为已完成 var pickingRecords = await Db.Queryable() .Where(x => x.OrderNo == orderNo && x.PalletCode == palletCode) .ToListAsync(); // 这里可以根据需要更新拣选记录的状态字段 // 例如:pickingRecord.Status = (int)PickingStatusEnum.已完成; _logger.LogInformation($"找到{pickingRecords.Count}条拣选记录 - 订单: {orderNo}, 托盘: {palletCode}"); } catch (Exception ex) { _logger.LogWarning($"更新拣选记录状态失败: {ex.Message}"); } } #endregion #region 辅助方法 /// /// 统一分析托盘状态 - 返回托盘的完整状态信息 /// private async Task AnalyzePalletStatus(string orderNo, string palletCode, int stockId) { var result = new PalletStatusAnalysis { OrderNo = orderNo, PalletCode = palletCode, StockId = stockId }; // 分析未分拣的出库锁定记录 var remainingLocks = await _outStockLockInfoService.Db.Queryable() .Where(it => it.OrderNo == orderNo && it.PalletCode == palletCode && it.Status == (int)OutLockStockStatusEnum.出库中) .ToListAsync(); if (remainingLocks.Any()) { result.HasRemainingLocks = true; result.RemainingLocks = remainingLocks; result.RemainingLocksReturnQty = remainingLocks.Sum(x => x.AssignQuantity - x.PickedQty); _logger.LogInformation($"发现{remainingLocks.Count}条未分拣锁定记录,总数量: {result.RemainingLocksReturnQty}"); } // 分析托盘上的库存货物 var palletStockGoods = await _stockInfoDetailService.Db.Queryable() .Where(it => it.StockId == stockId && (it.Status == StockStatusEmun.入库确认.ObjToInt() || it.Status == StockStatusEmun.入库完成.ObjToInt() || it.Status == StockStatusEmun.出库锁定.ObjToInt())) .Where(it => it.StockQuantity > 0) .ToListAsync(); if (palletStockGoods.Any()) { result.HasPalletStockGoods = true; result.PalletStockGoods = palletStockGoods; result.PalletStockReturnQty = palletStockGoods.Sum(x => x.StockQuantity); _logger.LogInformation($"发现{palletStockGoods.Count}个库存货物,总数量: {result.PalletStockReturnQty}"); // 记录详细状态分布 var statusGroups = palletStockGoods.GroupBy(x => x.Status); foreach (var group in statusGroups) { _logger.LogInformation($"库存状态{group.Key}: {group.Count()}个货物,数量: {group.Sum(x => x.StockQuantity)}"); } } //分析拆包记录 var splitRecords = await _splitPackageService.Db.Queryable() .Where(it => it.OrderNo == orderNo && it.PalletCode == palletCode && !it.IsReverted && it.Status != (int)SplitPackageStatusEnum.已拣选 && it.Status != (int)SplitPackageStatusEnum.已回库) .ToListAsync(); if (splitRecords.Any()) { result.HasSplitRecords = true; result.SplitRecords = splitRecords; result.SplitReturnQty = await CalculateSplitReturnQuantity(splitRecords, stockId); _logger.LogInformation($"发现{splitRecords.Count}条未拣选拆包记录,总数量: {result.SplitReturnQty}"); } // 4. 计算总回库数量和空托盘状态 result.TotalReturnQty = result.RemainingLocksReturnQty + result.PalletStockReturnQty + result.SplitReturnQty; result.HasItemsToReturn = result.TotalReturnQty > 0; result.IsEmptyPallet = !result.HasItemsToReturn; // 5. 检查是否有进行中的任务 result.HasActiveTasks = await _taskRepository.Db.Queryable() .Where(x => x.OrderNo == orderNo && x.TaskType == TaskTypeEnum.InPick.ObjToInt() && x.PalletCode == palletCode && x.TaskStatus == (int)TaskStatusEnum.New) .AnyAsync(); _logger.LogInformation($"托盘状态分析完成 - 订单: {orderNo}, 托盘: {palletCode}, " + $"总回库数量: {result.TotalReturnQty}, 是否空托盘: {result.IsEmptyPallet}, " + $"有进行中任务: {result.HasActiveTasks}"); return result; } /// /// 检查托盘是否为空 /// private async Task IsPalletEmpty(string orderNo, string palletCode) { try { // 获取库存信息 var stockInfo = await _stockInfoService.Db.Queryable() .Where(x => x.PalletCode == palletCode) .FirstAsync(); if (stockInfo == null) return false; // 使用统一的状态分析 var statusAnalysis = await AnalyzePalletStatus(orderNo, palletCode, stockInfo.Id); return statusAnalysis.IsEmptyPallet; } catch (Exception ex) { _logger.LogWarning($"检查托盘是否为空失败 - OrderNo: {orderNo}, PalletCode: {palletCode}, Error: {ex.Message}"); return false; } } /// /// 检查并处理空托盘 /// private async Task CheckAndHandleEmptyPallet(string orderNo, string palletCode) { try { // 1. 获取库存信息 var stockInfo = await _stockInfoService.Db.Queryable() .Where(x => x.PalletCode == palletCode) .FirstAsync(); if (stockInfo == null) { _logger.LogWarning($"未找到托盘 {palletCode} 的库存信息"); return false; } // 2. 使用统一的状态分析 var statusAnalysis = await AnalyzePalletStatus(orderNo, palletCode, stockInfo.Id); // 3. 检查是否为空托盘且没有进行中的任务 if (!statusAnalysis.IsEmptyPallet || statusAnalysis.HasActiveTasks) { return false; } _logger.LogInformation($"检测到空托盘,开始自动处理 - 订单: {orderNo}, 托盘: {palletCode}"); //// 清理零库存数据 //await CleanupZeroStockData(stockInfo.Id); //// 更新库存主表状态为空托盘 //await UpdateStockInfoAsEmpty(stockInfo); //// 处理出库锁定记录 //await HandleOutStockLockRecords(orderNo, palletCode); //// 处理任务状态 //await HandleTaskStatusForEmptyPallet(orderNo, palletCode); //// 更新订单数据 //await UpdateOrderDataForEmptyPallet(orderNo, palletCode); ////记录操作历史 //await RecordAutoEmptyPalletOperation(orderNo, palletCode); _logger.LogInformation($"空托盘自动处理完成 - 订单: {orderNo}, 托盘: {palletCode}"); return true; } catch (Exception ex) { _logger.LogError($"自动处理空托盘失败 - OrderNo: {orderNo}, PalletCode: {palletCode}, Error: {ex.Message}"); return false; } } private async Task GenerateNewBarcode() { var seq = await _dailySequenceService.GetNextSequenceAsync(); return "WSLOT" + DateTime.Now.ToString("yyyyMMdd") + seq.ToString()?.PadLeft(5, '0'); } private async Task CreateSplitLockInfo(Dt_OutStockLockInfo originalLock, decimal quantity, string newBarcode) { var newLockInfo = new Dt_OutStockLockInfo { OrderNo = originalLock.OrderNo, OrderDetailId = originalLock.OrderDetailId, BatchNo = originalLock.BatchNo, MaterielCode = originalLock.MaterielCode, MaterielName = originalLock.MaterielName, StockId = originalLock.StockId, OrderQuantity = quantity, OriginalQuantity = quantity, AssignQuantity = quantity, PickedQty = quantity, LocationCode = originalLock.LocationCode, PalletCode = originalLock.PalletCode, TaskNum = originalLock.TaskNum, Status = (int)OutLockStockStatusEnum.拣选完成, Unit = originalLock.Unit, SupplyCode = originalLock.SupplyCode, OrderType = originalLock.OrderType, CurrentBarcode = newBarcode, OriginalLockQuantity = quantity, IsSplitted = 1, ParentLockId = originalLock.Id, Operator = App.User.UserName, FactoryArea = originalLock.FactoryArea, lineNo = originalLock.lineNo, WarehouseCode = originalLock.WarehouseCode, BarcodeQty = originalLock.BarcodeQty, BarcodeUnit = originalLock.BarcodeUnit, }; var newLockId = await _outStockLockInfoService.Db.Insertable(newLockInfo).ExecuteReturnIdentityAsync(); newLockInfo.Id = newLockId; return newLockInfo; } private async Task RecordSplitHistory(Dt_OutStockLockInfo lockInfo, Dt_StockInfoDetail stockDetail, decimal splitQty, decimal remainQty, string newBarcode) { var splitHistory = new Dt_SplitPackageRecord { FactoryArea = lockInfo.FactoryArea, TaskNum = lockInfo.TaskNum, OutStockLockInfoId = lockInfo.Id, StockId = stockDetail.StockId, Operator = App.User.UserName, IsReverted = false, OriginalBarcode = stockDetail.Barcode, NewBarcode = newBarcode, SplitQty = splitQty, RemainQuantity = remainQty, MaterielCode = lockInfo.MaterielCode, SplitTime = DateTime.Now, OrderNo = lockInfo.OrderNo, PalletCode = lockInfo.PalletCode, Status = (int)SplitPackageStatusEnum.已拣选 }; await _splitPackageService.Db.Insertable(splitHistory).ExecuteCommandAsync(); } private List CreateSplitResults(Dt_OutStockLockInfo lockInfo, decimal splitQty, decimal remainQty, string newBarcode, string originalBarcode) { return new List { new SplitResult { materialCode = lockInfo.MaterielCode, supplierCode = lockInfo.SupplyCode, quantityTotal = splitQty.ToString("F2"), batchNumber = newBarcode, batch = lockInfo.BatchNo, factory = lockInfo.FactoryArea, date = DateTime.Now.ToString("yyyy-MM-dd"), }, new SplitResult { materialCode = lockInfo.MaterielCode, supplierCode = lockInfo.SupplyCode, quantityTotal = remainQty.ToString("F2"), batchNumber = originalBarcode, batch = lockInfo.BatchNo, factory = lockInfo.FactoryArea, date = DateTime.Now.ToString("yyyy-MM-dd"), } }; } private async Task UpdateSplitRecordsStatus(string barcode) { var relatedSplitRecords = await _splitPackageService.Db.Queryable() .Where(it => it.OriginalBarcode == barcode || it.NewBarcode == barcode) .Where(it => !it.IsReverted) .ToListAsync(); foreach (var record in relatedSplitRecords) { record.Status = (int)SplitPackageStatusEnum.已拣选; await _splitPackageService.Db.Updateable(record).ExecuteCommandAsync(); } } private async Task GenerateTaskNumber() { return await _dailySequenceService.GetNextSequenceAsync(); } private WebResponseContent CreatePickingResponse(PickingResult result, string adjustedReason) { //if (result.SplitResults.Any()) //{ // var responseData = new { SplitResults = result.SplitResults, AdjustedReason = "" }; // if (!string.IsNullOrEmpty(adjustedReason)) // { // responseData = new { SplitResults = result.SplitResults, AdjustedReason = adjustedReason }; // } // return WebResponseContent.Instance.OK("拣选确认成功,已自动拆包", responseData); //} //if (!string.IsNullOrEmpty(adjustedReason)) //{ // return WebResponseContent.Instance.OK($"拣选确认成功({adjustedReason})"); //} //return WebResponseContent.Instance.OK("拣选确认成功"); if (result.SplitResults.Any()) { return WebResponseContent.Instance.OK("拣选确认成功,已自动拆包", new { SplitResults = result.SplitResults }); } return WebResponseContent.Instance.OK("拣选确认成功", new { SplitResults = new List() }); } #region 虚拟出入库 public WebResponseContent GetAvailablePurchaseOrders() { List InOders = _inboundOrderRepository.QueryData().Where(x => x.OrderStatus != InOrderStatusEnum.入库完成.ObjToInt()).ToList(); List InOderCodes = InOders.Select(x => x.UpperOrderNo).ToList(); return WebResponseContent.Instance.OK("成功", data: InOderCodes); } public WebResponseContent GetAvailablePickingOrders() { List outOders = _outboundOrderService.Db.Queryable().Where(x => x.OrderStatus != OutOrderStatusEnum.出库完成.ObjToInt()).ToList(); List outOderCodes = outOders.Select(x => x.UpperOrderNo).ToList(); return WebResponseContent.Instance.OK("成功", data: outOderCodes); } public WebResponseContent BarcodeValidate(NoStockOutModel noStockOut) { try { Dt_InboundOrder inboundOrder = Db.Queryable().Where(x => x.UpperOrderNo == noStockOut.inOder && x.OrderStatus != InOrderStatusEnum.入库完成.ObjToInt()).Includes(x => x.Details).First(); if (inboundOrder == null) { return WebResponseContent.Instance.Error($"未找到采购单:{noStockOut.inOder}"); } var matchedDetail = inboundOrder.Details.FirstOrDefault(detail => detail.Barcode == noStockOut.barCode && detail.OrderDetailStatus != OrderDetailStatusEnum.Over.ObjToInt()); if (matchedDetail == null) { return WebResponseContent.Instance.Error($"在采购单 {noStockOut.inOder} 中未找到条码为 {noStockOut.barCode} 的明细。"); } matchedDetail.NoStockOutQty = 0; Dt_OutboundOrder outboundOrder = Db.Queryable().Where(x => x.UpperOrderNo == noStockOut.outOder && x.OrderStatus != OutOrderStatusEnum.出库完成.ObjToInt()).Includes(x => x.Details).First(); if (outboundOrder == null) { return WebResponseContent.Instance.Error($"未找到出库单:{noStockOut.inOder}"); } var matchedCode = outboundOrder.Details.FirstOrDefault(detail => detail.MaterielCode == matchedDetail.MaterielCode && detail.OrderDetailStatus != OrderDetailStatusEnum.Over.ObjToInt()); if (matchedCode == null) { return WebResponseContent.Instance.Error($"在出库单的物料编码中未找到与采购单中的{matchedDetail.MaterielCode} 对应的物料。"); } matchedCode.NoStockOutQty = 0; //剩余入库数量即虚拟出入库剩余可出数量 decimal outQuantity = matchedDetail.OrderQuantity - matchedDetail.ReceiptQuantity; if (outQuantity == 0) { return WebResponseContent.Instance.Error($"该采购单中的条码对应的可出数量为0"); } if (matchedCode.OrderQuantity < outQuantity) { return WebResponseContent.Instance.Error($"该采购单中的条码对应的可出数量超出出库单出库数量{matchedDetail.OrderQuantity - matchedCode.OrderQuantity},不满足整包出库"); } //单据出库锁定数量 matchedDetail.NoStockOutQty += outQuantity; matchedCode.NoStockOutQty += outQuantity; if ((matchedCode.LockQuantity + matchedCode.NoStockOutQty) > matchedCode.OrderQuantity) { return WebResponseContent.Instance.Error($"出库单明细数量溢出{matchedCode.LockQuantity - matchedCode.OrderQuantity}"); } matchedDetail.OrderDetailStatus = OrderDetailStatusEnum.Inbounding.ObjToInt(); matchedCode.OrderDetailStatus = OrderDetailStatusEnum.AssignOver.ObjToInt(); _unitOfWorkManage.BeginTran(); _inboundOrderDetailService.UpdateData(matchedDetail); _outboundOrderDetailService.UpdateData(matchedCode); _unitOfWorkManage.CommitTran(); return WebResponseContent.Instance.OK(); } catch (Exception ex) { _unitOfWorkManage.RollbackTran(); return WebResponseContent.Instance.Error(ex.Message); } } public WebResponseContent DeleteBarcode(NoStockOutModel noStockOut) { try { Dt_InboundOrder inboundOrder = Db.Queryable().Where(x => x.UpperOrderNo == noStockOut.inOder && x.OrderStatus != InOrderStatusEnum.入库完成.ObjToInt()).Includes(x => x.Details).First(); if (inboundOrder == null) { return WebResponseContent.Instance.Error($"未找到采购单:{noStockOut.inOder}"); } var matchedDetail = inboundOrder.Details.FirstOrDefault(detail => detail.Barcode == noStockOut.barCode && detail.OrderDetailStatus != OrderDetailStatusEnum.Over.ObjToInt()); if (matchedDetail == null) { return WebResponseContent.Instance.Error($"在采购单 {noStockOut.inOder} 中未找到条码为 {noStockOut.barCode} 的明细。"); } matchedDetail.NoStockOutQty = 0; if (matchedDetail.ReceiptQuantity == 0 && matchedDetail.OverInQuantity == 0) { matchedDetail.OrderDetailStatus = OrderDetailStatusEnum.New.ObjToInt(); } Dt_OutboundOrder outboundOrder = Db.Queryable().Where(x => x.UpperOrderNo == noStockOut.outOder && x.OrderStatus != OutOrderStatusEnum.出库完成.ObjToInt()).Includes(x => x.Details).First(); if (outboundOrder == null) { return WebResponseContent.Instance.Error($"未找到出库单:{noStockOut.inOder}"); } var matchedCode = outboundOrder.Details.FirstOrDefault(detail => detail.MaterielCode == matchedDetail.MaterielCode && detail.OrderDetailStatus != OrderDetailStatusEnum.Over.ObjToInt()); if (matchedCode == null) { return WebResponseContent.Instance.Error($"在出库单的物料编码中未找到与采购单中的{matchedDetail.MaterielCode} 对应的物料。"); } matchedCode.NoStockOutQty = 0; if (matchedCode.LockQuantity == 0 && matchedCode.OverOutQuantity == 0) { matchedCode.OrderDetailStatus = OrderDetailStatusEnum.New.ObjToInt(); } _unitOfWorkManage.BeginTran(); _inboundOrderDetailService.UpdateData(matchedDetail); _outboundOrderDetailService.UpdateData(matchedCode); _unitOfWorkManage.CommitTran(); return WebResponseContent.Instance.OK(); } catch (Exception ex) { _unitOfWorkManage.RollbackTran(); return WebResponseContent.Instance.Error(ex.Message); } } public async Task NoStockOutSubmit(NoStockOutSubmit noStockOutSubmit) { try { Dt_InboundOrder inboundOrder = _inboundOrderRepository.Db.Queryable().Where(x => x.UpperOrderNo == noStockOutSubmit.InOderSubmit && x.OrderStatus != InOrderStatusEnum.入库完成.ObjToInt()).Includes(x => x.Details).First(); if (inboundOrder == null) { return WebResponseContent.Instance.Error($"未找到采购单:{noStockOutSubmit.InOderSubmit}"); } Dt_OutboundOrder outboundOrder = _inboundOrderRepository.Db.Queryable().Where(x => x.UpperOrderNo == noStockOutSubmit.OutOderSubmit && x.OrderStatus != OutOrderStatusEnum.出库完成.ObjToInt()).Includes(x => x.Details).First(); if (outboundOrder == null) { return WebResponseContent.Instance.Error($"未找到出库单:{noStockOutSubmit.OutOderSubmit}"); } List inboundOrderDetails = new List(); List outboundOrderDetails = new List(); foreach (var BarCode in noStockOutSubmit.BarCodeSubmit) { var inboundOrderDetail = inboundOrder.Details.FirstOrDefault(detail => detail.Barcode == BarCode && detail.OrderDetailStatus != OrderDetailStatusEnum.Over.ObjToInt()); if (inboundOrderDetail == null) { return WebResponseContent.Instance.Error($"在采购单 {noStockOutSubmit.InOderSubmit} 中未找到条码为 {BarCode} 的明细。"); } var outboundOrderDetail = outboundOrder.Details.FirstOrDefault(detail => detail.MaterielCode == inboundOrderDetail.MaterielCode && detail.OrderDetailStatus != OrderDetailStatusEnum.Over.ObjToInt()); if (outboundOrderDetail == null) { return WebResponseContent.Instance.Error($"在出库单的物料编码中未找到与采购单中的{inboundOrderDetail.MaterielCode} 对应的物料。"); } inboundOrderDetail.ReceiptQuantity += inboundOrderDetail.NoStockOutQty; inboundOrderDetail.OverInQuantity = inboundOrderDetail.ReceiptQuantity; inboundOrderDetail.OrderDetailStatus = OrderDetailStatusEnum.Over.ObjToInt(); inboundOrderDetails.Add(inboundOrderDetail); outboundOrderDetail.LockQuantity += outboundOrderDetail.NoStockOutQty; outboundOrderDetail.OverOutQuantity = outboundOrderDetail.LockQuantity; if (outboundOrderDetail.OrderQuantity == outboundOrderDetail.OverOutQuantity) { outboundOrderDetail.OrderDetailStatus = OrderDetailStatusEnum.Over.ObjToInt(); } outboundOrderDetails.Add(outboundOrderDetail); } //判断入库单据明细是否全部是完成状态 int e = inboundOrder.Details.Count(); int w = inboundOrder.Details.Where(x => x.OrderDetailStatus == OrderDetailStatusEnum.Over.ObjToInt()).Count(); bool inoderOver = inboundOrder.Details.Count() == inboundOrder.Details.Where(x => x.OrderDetailStatus == OrderDetailStatusEnum.Over.ObjToInt()).Count(); if (inoderOver) { inboundOrder.OrderStatus = InOrderStatusEnum.入库完成.ObjToInt(); } else { inboundOrder.OrderStatus = InOrderStatusEnum.入库中.ObjToInt(); } //判断出库单据明细是否全部是完成状态 bool outOderOver = outboundOrder.Details.Count() == outboundOrder.Details.Where(x => x.OrderDetailStatus == OrderDetailStatusEnum.Over.ObjToInt()).Count(); if (outOderOver) { outboundOrder.OrderStatus = OutOrderStatusEnum.出库完成.ObjToInt(); } else { outboundOrder.OrderStatus = OutOrderStatusEnum.出库中.ObjToInt(); } //数据处理 _unitOfWorkManage.BeginTran(); _inboundOrderDetailService.UpdateData(inboundOrderDetails); _outboundOrderDetailService.UpdateData(outboundOrderDetails); _inboundOrderRepository.UpdateData(inboundOrder); _outboundOrderService.UpdateData(outboundOrder); _unitOfWorkManage.CommitTran(); if (inboundOrder.OrderStatus == InOrderStatusEnum.入库完成.ObjToInt()) { var feedmodel = new FeedbackInboundRequestModel { reqCode = Guid.NewGuid().ToString(), reqTime = DateTime.Now.ToString(), business_type = inboundOrder.BusinessType, factoryArea = inboundOrder.FactoryArea, operationType = 1, Operator = inboundOrder.Operator, orderNo = inboundOrder.UpperOrderNo, status = inboundOrder.OrderStatus, details = new List() }; var groupedData = inboundOrder.Details.GroupBy(item => new { item.MaterielCode, item.SupplyCode, item.BatchNo, item.lineNo, item.BarcodeUnit, item.WarehouseCode }) .Select(group => new FeedbackInboundDetailsModel { materialCode = group.Key.MaterielCode, supplyCode = group.Key.SupplyCode, batchNo = group.Key.BatchNo, lineNo = group.Key.lineNo, warehouseCode = group.Key.WarehouseCode, qty = group.Sum(x => x.BarcodeQty), // warehouseCode= "1072", unit = group.Key.BarcodeUnit, barcodes = group.Select(row => new FeedbackBarcodesModel { barcode = row.Barcode, qty = row.BarcodeQty }).ToList() }).ToList(); feedmodel.details = groupedData; var result = await _invokeMESService.FeedbackInbound(feedmodel); if (result != null && result.code == 200) { _inboundOrderRepository.Db.Updateable().SetColumns(it => new Dt_InboundOrder { ReturnToMESStatus = 1 }) .Where(it => it.Id == inboundOrder.Id).ExecuteCommand(); _inboundOrderDetailService.Db.Updateable().SetColumns(it => new Dt_InboundOrderDetail { ReturnToMESStatus = 1 }) .Where(it => it.OrderId == inboundOrder.Id).ExecuteCommand(); } } if (outboundOrder.OrderStatus == OutOrderStatusEnum.出库完成.ObjToInt()) { var feedmodel = new FeedbackOutboundRequestModel { reqCode = Guid.NewGuid().ToString(), reqTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), business_type = outboundOrder.BusinessType, factoryArea = outboundOrder.FactoryArea, operationType = 1, Operator = App.User.UserName, orderNo = outboundOrder.UpperOrderNo, documentsNO = outboundOrder.OrderNo, status = outboundOrder.OrderStatus, details = new List() }; foreach (var detail in outboundOrder.Details) { // 获取该明细对应的条码信息(从锁定记录) var detailLocks = await _outStockLockInfoService.Db.Queryable() .Where(x => x.OrderNo == outboundOrder.OrderNo && x.OrderDetailId == detail.Id && (x.Status == (int)OutLockStockStatusEnum.拣选完成 || x.Status == (int)OutLockStockStatusEnum.已回库)) .ToListAsync(); var groupdata = detailLocks.GroupBy(item => new { item.MaterielCode, item.lineNo, item.BarcodeUnit, item.WarehouseCode }) .Select(group => new FeedbackOutboundDetailsModel { materialCode = group.Key.MaterielCode, lineNo = group.Key.lineNo, warehouseCode = group.Key.WarehouseCode, qty = group.Sum(x => x.BarcodeQty), currentDeliveryQty = group.Sum(x => x.BarcodeQty), unit = group.Key.BarcodeUnit, barcodes = group.Select(lockInfo => new WIDESEA_DTO.Outbound.BarcodesModel { barcode = lockInfo.CurrentBarcode, supplyCode = lockInfo.SupplyCode, batchNo = lockInfo.BatchNo, unit = lockInfo.BarcodeUnit, qty = lockInfo.BarcodeQty }).ToList() }).ToList(); feedmodel.details.AddRange(groupdata); } var result = await _invokeMESService.FeedbackOutbound(feedmodel); if (result != null && result.code == 200) { await _outboundOrderDetailService.Db.Updateable() .SetColumns(x => x.ReturnToMESStatus == 1) .Where(x => x.OrderId == outboundOrder.Id) .ExecuteCommandAsync(); await _outboundOrderService.Db.Updateable() .SetColumns(x => x.ReturnToMESStatus == 1) .Where(x => x.Id == outboundOrder.Id) .ExecuteCommandAsync(); } } return WebResponseContent.Instance.OK(); } catch (Exception ex) { _unitOfWorkManage.RollbackTran(); return WebResponseContent.Instance.Error(ex.Message); } } #endregion public WebResponseContent UnPalletQuantity(string orderNo) { // 初始化返回DTO(默认值都为0,避免null) var resultDTO = new PalletSumQuantityDTO { StockSumQuantity = 0, StockCount = 0, UniqueUnit = "" }; WebResponseContent content = new WebResponseContent(); try { if (string.IsNullOrWhiteSpace(orderNo)) { return content.Error("传入的订单号orderNo为空或空白"); } var orderDetail = Db.Queryable().Where(s => s.OrderNo == orderNo).ToList(); if (orderDetail == null) { return content.Error("未找到单据"); } var unitGroups = orderDetail.GroupBy(d => d.BarcodeUnit).ToList(); if (unitGroups.Count == 1) { resultDTO.UniqueUnit = unitGroups.First().Key; } else { resultDTO.UniqueUnit = ""; } var validDetails = _stockInfoDetailService.Db.Queryable().Where(s => s.OrderNo == orderNo).ToList(); resultDTO.StockSumQuantity = orderDetail.Sum(d => d.PickQuantity); resultDTO.StockCount = orderDetail.Count; if (validDetails.Any()) { resultDTO.StockSumQuantity -= validDetails.Sum(d => d.StockQuantity); // 明细记录数:符合条件的有效记录条数 resultDTO.StockCount -= validDetails.Count; } return content.OK("", resultDTO); } catch (Exception ex) { return content.Error("SumQuantity 统计库存数量失败,订单号:{OrderNo}"); } } public WebResponseContent BarcodeMaterielGroup(BarcodeMaterielGroupDTO materielGroupDTO) { WebResponseContent content = new WebResponseContent(); try { (bool, string, object?) result2 = ModelValidate.ValidateModelData(materielGroupDTO); if (!result2.Item1) return content = WebResponseContent.Instance.Error(result2.Item2); // materielGroupDTO.WarehouseCode var code = _warehouseAreaRepository.Db.Queryable().Where(x => x.Code == materielGroupDTO.WarehouseType).Select(x => x.Code).First(); if (string.IsNullOrEmpty(code)) { return content = WebResponseContent.Instance.Error($"仓库中没有该{materielGroupDTO.WarehouseType}编号。"); } if (materielGroupDTO.orderTypes == InOrderTypeEnum.ReCheck.ObjToInt()) { var dborder = _reCheckOrderService.Db.Queryable().First(x => x.OrderNo == materielGroupDTO.OrderNo); if (dborder != null && dborder.SignSeq == 0) { return content.Error("只有拿到重检结果才能入库!"); } else { return content.Error("没有找到重检单据数据。"); } } // Dt_InboundOrder inboundOrder = GetInboundOrder(materielGroupDTO.OrderNo); var dbinboundOrderDetails = Db.Queryable().Where(x => x.OrderNo == materielGroupDTO.OrderNo && !x.IsCancelled && x.Barcode == materielGroupDTO.Barcodes).ToList(); if (dbinboundOrderDetails != null && !dbinboundOrderDetails.Any()) { return content = WebResponseContent.Instance.Error($"单据中没有该{materielGroupDTO.Barcodes}条码数据。"); } List materielCodes = dbinboundOrderDetails.GroupBy(x => x.Barcode).Select(x => x.Key).ToList(); Dt_StockInfo? stockInfo = _stockService.StockInfoService.GetStockByPalletCode(materielGroupDTO.PalletCode); (bool, string, object?) result = CheckMaterielGroupParam(materielGroupDTO, materielCodes, stockInfo); if (!result.Item1) return content = WebResponseContent.Instance.Error(result.Item2); if (stockInfo == null) { stockInfo = new Dt_StockInfo() { PalletType = (int)PalletTypeEnum.None, LocationType = materielGroupDTO.locationType.ObjToInt() }; stockInfo.Details = new List(); } var inboindId = 0; Dt_InboundOrder dt_InboundOrder = null; var dbinbound = _inboundOrderRepository.QueryData(x => x.InboundOrderNo == dbinboundOrderDetails.First().OrderNo).First(); if (dbinbound == null) { dt_InboundOrder = new Dt_InboundOrder { WarehouseId = 0, InboundOrderNo = dbinboundOrderDetails.First()?.OrderNo, UpperOrderNo = dbinboundOrderDetails.First()?.OrderNo, SupplierId = dbinboundOrderDetails.First()?.SupplyCode, OrderType = materielGroupDTO.orderTypes, BusinessType = materielGroupDTO.orderTypes.ToString(), FactoryArea = dbinboundOrderDetails.First()?.FactoryArea, Remark = "", Details = new List() }; inboindId = _inboundOrderRepository.AddData(dt_InboundOrder); } else { dt_InboundOrder = new Dt_InboundOrder { Details = new List() }; inboindId = dbinbound.Id; } foreach (var item in dbinboundOrderDetails) { stockInfo.Details.Add(new Dt_StockInfoDetail { StockId = stockInfo == null ? 0 : stockInfo.Id, Barcode = item.Barcode, MaterielCode = item.MaterielCode, BatchNo = item.BatchNo, Unit = item.BarcodeUnit, InboundOrderRowNo = item.lineNo, SupplyCode = item.SupplyCode, WarehouseCode = materielGroupDTO.WarehouseType, StockQuantity = item.PickQuantity, BarcodeQty = item.BarcodeQty, BarcodeUnit = item.BarcodeUnit, FactoryArea = item.FactoryArea, Status = 0, OrderNo = item.OrderNo, BusinessType = InOrderTypeEnum.InternalAllocat.ObjToInt().ToString() }); item.WarehouseCode = item.WarehouseCode; dt_InboundOrder.Details.Add(new Dt_InboundOrderDetail { OrderId = inboindId, MaterielCode = item.MaterielCode, MaterielName = "", BatchNo = item.BatchNo, OrderQuantity = item.PickQuantity, ReceiptQuantity = 0, OverInQuantity = 0, Unit = item.BarcodeUnit, RowNo = 0, lineNo = item.lineNo, SupplyCode = item.SupplyCode, WarehouseCode = item.WarehouseCode, Barcode = item.Barcode, OutBoxbarcodes = "", BarcodeQty = (decimal)item.BarcodeQty, BarcodeUnit = item.BarcodeUnit }); } _inboundOrderDetailService.Db.Insertable(dt_InboundOrder.Details); if (stockInfo.Id == 0) { stockInfo.PalletCode = materielGroupDTO.PalletCode; stockInfo.StockStatus = StockStatusEmun.组盘暂存.ObjToInt(); } stockInfo.PalletType = (int)PalletTypeEnum.None; List updateDetailIds = dbinboundOrderDetails.Select(x => x.Id).ToList(); try { _unitOfWorkManage.BeginTran(); _stockService.StockInfoService.AddMaterielGroup(stockInfo); _unitOfWorkManage.CommitTran(); return WebResponseContent.Instance.OK(); } catch (Exception ex) { _unitOfWorkManage.RollbackTran(); return WebResponseContent.Instance.Error(ex.Message); } } catch (Exception ex) { content = WebResponseContent.Instance.Error(ex.Message); } finally { } return content; } public (bool, string, object?) CheckMaterielGroupParam(BarcodeMaterielGroupDTO materielGroupDTO, List barcodeCodes, Dt_StockInfo stockInfo) { (bool, string, object?) result = ModelValidate.ValidateModelData(materielGroupDTO); if (!result.Item1) return result; if (_taskRepository.QueryFirst(x => x.PalletCode == materielGroupDTO.PalletCode) != null) { return (false, "该托盘号已有任务", materielGroupDTO); } if (stockInfo != null && !string.IsNullOrEmpty(stockInfo.LocationCode) && stockInfo.StockStatus != StockStatusEmun.组盘暂存.ObjToInt()) { return (false, "已上架的托盘不能再次组盘", materielGroupDTO); } if (_stockService.StockInfoDetailService.ExistBarcodes(barcodeCodes)) { return (false, $"{barcodeCodes[0]} 条码在库存中已存在", materielGroupDTO); } return (true, "成功", materielGroupDTO); } #endregion } #region 支持类定义 public class ValidationResult { public bool IsValid { get; set; } public T Data { get; set; } public string ErrorMessage { get; set; } public static ValidationResult Success(T data) { return new ValidationResult { IsValid = true, Data = data }; } public static ValidationResult Error(string message) { return new ValidationResult { IsValid = false, ErrorMessage = message }; } } public class PickingResult { public Dt_OutStockLockInfo FinalLockInfo { get; set; } public string FinalBarcode { get; set; } public int FinalStockId { get; set; } public decimal ActualPickedQty { get; set; } public List SplitResults { get; set; } = new List(); } public class ReturnAnalysisResult { public bool HasItemsToReturn { get; set; } public bool HasRemainingLocks { get; set; } public bool HasPalletStockGoods { get; set; } public bool HasSplitRecords { get; set; } public decimal RemainingLocksReturnQty { get; set; } public decimal PalletStockReturnQty { get; set; } public decimal SplitReturnQty { get; set; } public decimal TotalReturnQty { get; set; } public List RemainingLocks { get; set; } = new List(); public List PalletStockGoods { get; set; } = new List(); public List SplitRecords { get; set; } = new List(); } public class PalletStatusAnalysis { public string OrderNo { get; set; } public string PalletCode { get; set; } public int StockId { get; set; } // 回库相关属性 public bool HasItemsToReturn { get; set; } public bool HasRemainingLocks { get; set; } public bool HasPalletStockGoods { get; set; } public bool HasSplitRecords { get; set; } public decimal RemainingLocksReturnQty { get; set; } public decimal PalletStockReturnQty { get; set; } public decimal SplitReturnQty { get; set; } public decimal TotalReturnQty { get; set; } // 未分配的锁定记录(如自动拆包产生的) public bool HasUnallocatedLocks { get; set; } public List UnallocatedLocks { get; set; } = new List(); public decimal UnallocatedLocksReturnQty { get; set; } public List RemainingLocks { get; set; } = new List(); public List PalletStockGoods { get; set; } = new List(); public List SplitRecords { get; set; } = new List(); public List AllBarcodes { get; set; } = new List(); // 空托盘相关属性 public bool IsEmptyPallet { get; set; } public bool HasActiveTasks { get; set; } // 便利方法 public bool CanReturn => HasItemsToReturn && !HasActiveTasks; public bool CanRemove => IsEmptyPallet && !HasActiveTasks; } public class PickingContext { public string OrderNo { get; set; } public string PalletCode { get; set; } public string Barcode { get; set; } public string Operator { get; set; } public Dt_OutStockLockInfo LockInfo { get; set; } public Dt_OutboundOrderDetail OrderDetail { get; set; } public Dt_StockInfoDetail StockDetail { get; set; } public decimal ActualQuantity { get; set; } public string AdjustedReason { get; set; } } public class CancelPickingContext { public string OrderNo { get; set; } public string PalletCode { get; set; } public string Barcode { get; set; } public string Operator { get; set; } public decimal CancelQuantity { get; set; } public Dt_PickingRecord PickingRecord { get; set; } public Dt_OutStockLockInfo LockInfo { get; set; } public Dt_OutboundOrderDetail OrderDetail { get; set; } } #endregion }