using System.Reflection.Emit; using AutoMapper; using Dm.filter; using MailKit.Search; using Mapster; using Microsoft.IdentityModel.Tokens; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; using Org.BouncyCastle.Asn1.Ocsp; using Org.BouncyCastle.Crypto; using SqlSugar; using WIDESEA_BasicService; 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.CodeConfigEnum; using WIDESEA_Core.Helper; using WIDESEA_DTO.Base; using WIDESEA_DTO.Basic; using WIDESEA_DTO.CalcOut; using WIDESEA_DTO.ReturnMES; using WIDESEA_IBasicService; using WIDESEA_IOutboundService; using WIDESEA_IRecordService; using WIDESEA_IStockService; using WIDESEA_Model.Models; using WIDESEA_Model.Models.Check; using static HslCommunication.Profinet.Knx.KnxCode; namespace WIDESEA_OutboundService { public partial class OutboundService : IOutboundService { private readonly IMapper _mapper; public IUnitOfWorkManage _unitOfWorkManage { get; } public IOutboundOrderDetailService OutboundOrderDetailService { get; } public IOutboundOrderService OutboundOrderService { get; } public IOutStockLockInfoService OutboundStockLockInfoService { get; } private readonly ISqlSugarClient Db; private readonly IBasicService _basicService; private readonly IRepository _detailRepository; private readonly IRepository _outboundRepository; private readonly IRepository _outboundLockInfoRepository; private readonly IRepository _stockInfoRepository; private readonly IRepository _stockDetailRepository; private readonly IRepository _locationInfoRepository; private readonly IRepository _stockChangeRepository; private readonly IRepository _stockDetailHistoryRepository; private readonly IFeedbackMesService _feedbackMesService; private readonly IRepository _taskRepository; private readonly ILocationInfoService _locationInfoService; private readonly IESSApiService _eSSApiService; private readonly IRepository _allocateOrderRepository; private readonly IRepository _allocateMaterialInfoRepository; 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 OutboundService(IMapper mapper, IUnitOfWorkManage unitOfWorkManage, IRepository detailRepository, IRepository outboundRepository, IRepository outboundLockInfoRepository, IRepository stockInfoRepository, IRepository stockDetailRepository, IRepository stockChangeRepository, IRepository stockDetailHistoryRepository, IBasicService basicService, IOutboundOrderDetailService outboundOrderDetailService, IOutboundOrderService outboundOrderService, IOutStockLockInfoService outboundStockLockInfoService, IFeedbackMesService feedbackMesService, IRepository taskRepository, ILocationInfoService locationInfoService, IESSApiService eSSApiService, IRepository allocateOrderRepository, IRepository allocateMaterialInfoRepository) { _mapper = mapper; _unitOfWorkManage = unitOfWorkManage; Db = _unitOfWorkManage.GetDbClient(); OutboundOrderDetailService = outboundOrderDetailService; OutboundOrderService = outboundOrderService; OutboundStockLockInfoService = outboundStockLockInfoService; _detailRepository = detailRepository; _outboundRepository = outboundRepository; _outboundLockInfoRepository = outboundLockInfoRepository; _stockInfoRepository = stockInfoRepository; _stockDetailRepository = stockDetailRepository; _locationInfoRepository = basicService.LocationInfoService.Repository; _stockChangeRepository = stockChangeRepository; _stockDetailHistoryRepository = stockDetailHistoryRepository; _basicService = basicService; _feedbackMesService = feedbackMesService; _taskRepository = taskRepository; _locationInfoService = locationInfoService; _eSSApiService = eSSApiService; _allocateOrderRepository = allocateOrderRepository; _allocateMaterialInfoRepository = allocateMaterialInfoRepository; } #region 出库分配 /// /// 分拣出库操作 /// /// 分拣出库请求 /// 分拣出库响应 public WebResponseContent ProcessPickingOutbound(PickingOutboundRequestDTO request) { WebResponseContent content = WebResponseContent.Instance; PickingOutboundResponseDTO response = new PickingOutboundResponseDTO(); decimal totalNeedAllocate = 0; // 总需求分配量 decimal totalActualAllocate = 0; // 实际总分配量 try { _unitOfWorkManage.BeginTran(); // 1. 计算出库数量逻辑 OutboundCalculationDTO calculationResult = CalcOutboundQuantity(request); if (!calculationResult.CanOutbound) { content = WebResponseContent.Instance.Error("无法处理拣货出库:" + calculationResult.ErrorMessage); _unitOfWorkManage.RollbackTran(); return content; } // 记录总需求数量 totalNeedAllocate = calculationResult.MaterielCalculations.Sum(x => x.UnallocatedQuantity); // 2. 处理物料分配 List pickedDetails = new List(); Dt_OutboundOrder outboundOrder = calculationResult.OutboundOrder; List outStockLockInfos = new List(); List outboundOrderDetails = new List(); List tasks = new List(); foreach (var materielCalc in calculationResult.MaterielCalculations) { var materielPickedDetails = ProcessMaterielTaskGeneration(outboundOrder, materielCalc, request, calculationResult.FactoryArea); // 计算当前物料实际分配量 decimal actualAllocatedQuantity = materielPickedDetails.PickedDetails.Sum(x => x.OutboundQuantity); actualAllocatedQuantity = Math.Min(actualAllocatedQuantity, materielCalc.UnallocatedQuantity); totalActualAllocate += actualAllocatedQuantity; // 累加至总实际分配量 materielCalc.UnallocatedQuantity = materielCalc.UnallocatedQuantity - actualAllocatedQuantity; // 处理出库锁定记录 foreach (var item in materielPickedDetails.OutStockLockInfo) { Dt_OutStockLockInfo? existLockInfo = materielCalc.OutStockLockInfos.FirstOrDefault(x => x.Id == item.Id && x.Id > 0); if (existLockInfo != null) { existLockInfo = item; Dt_Task? task = tasks.FirstOrDefault(x => x.PalletCode == item.PalletCode); if (task != null) existLockInfo.TaskNum = task.TaskNum; } else { Dt_Task? task = tasks.FirstOrDefault(x => x.PalletCode == item.PalletCode); if (task != null) item.TaskNum = task.TaskNum; materielCalc.OutStockLockInfos.Add(item); } outStockLockInfos.Add(item); } // 处理任务 foreach (var item in materielPickedDetails.Tasks) { if (tasks.FirstOrDefault(x => x.PalletCode == item.PalletCode) == null) tasks.Add(item); } // 汇总分拣明细 pickedDetails.AddRange(materielPickedDetails.PickedDetails); // 按实际分配量更新单据锁定数量 decimal remainingToLock = actualAllocatedQuantity; foreach (var detail in materielCalc.Details) { if (remainingToLock <= 0) break; decimal maxLockableQty = detail.OrderQuantity - detail.OverOutQuantity; if (maxLockableQty <= 0) continue; decimal currentLockQty = Math.Min(remainingToLock, maxLockableQty); detail.LockQuantity += currentLockQty; outboundOrderDetails.Add(detail); remainingToLock -= currentLockQty; } } // 3. 批量更新状态(原有逻辑不变) UpdateOutboundOrderStatus(request.OrderNo, (int)OutOrderStatusEnum.出库中); _detailRepository.UpdateData(outboundOrderDetails); if (pickedDetails.Any()) { UpdateStockStatus(pickedDetails.Select(x => x.PalletCode).ToList(), StockStatusEmun.出库锁定.ObjToInt()); UpdateLocationStatus(pickedDetails.Select(x => x.LocationCode).ToList(), LocationStatusEnum.Lock.ObjToInt()); } //重检单不拣选,去掉锁定记录回库,再次组盘时扣除原条码 if (outboundOrder.OrderType != InOrderTypeEnum.ReCheck.ObjToInt()) { UpdateOutStockLockInfo(outStockLockInfos); } if (tasks.Any()) _taskRepository.AddData(tasks); _unitOfWorkManage.CommitTran(); // 4. 构造响应:区分「全部分配」和「部分分配」 string responseMsg = totalActualAllocate == totalNeedAllocate ? "分拣任务分配成功" : $"分拣任务分配完成(实际分配{totalActualAllocate},需求{totalNeedAllocate},库存不足部分未分配)"; response.Success = true; response.Message = responseMsg; response.Tasks = tasks; response.PickedDetails = pickedDetails; content = WebResponseContent.Instance.OK(responseMsg, response); } catch (Exception ex) { _unitOfWorkManage.RollbackTran(); content = WebResponseContent.Instance.Error("处理拣货出库失败:" + ex.Message); } return content; } /// /// 计算出库数量逻辑(原有逻辑不变) /// public OutboundCalculationDTO CalcOutboundQuantity(PickingOutboundRequestDTO request) { OutboundCalculationDTO result = new OutboundCalculationDTO(); try { Dt_OutboundOrder outboundOrder = _outboundRepository.QueryFirst(x => x.OrderNo == request.OrderNo); if (outboundOrder == null) { result.CanOutbound = false; result.ErrorMessage = $"出库单 {request.OrderNo} 不存在"; return result; } result.FactoryArea = outboundOrder.FactoryArea; List selectedDetails = new List(); if (request.DetailIds == null || !request.DetailIds.Any()) { selectedDetails = _detailRepository.QueryData(x => x.OrderId == outboundOrder.Id); } else { selectedDetails = _detailRepository.QueryData(x => x.OrderId == outboundOrder.Id && request.DetailIds.Contains(x.Id)); } if (!selectedDetails.Any()) { result.CanOutbound = false; result.ErrorMessage = $"未找到选择的出库明细信息"; return result; } if (selectedDetails.Any(x => x.LockQuantity > x.OrderQuantity - x.MoveQty || x.OverOutQuantity > x.OrderQuantity - x.MoveQty)) { List selectDetailIds = selectedDetails.Where(x => x.LockQuantity > x.OrderQuantity - x.MoveQty || x.OverOutQuantity > x.OrderQuantity - x.MoveQty).Select(x => x.Id).ToList(); result.CanOutbound = false; result.ErrorMessage = $"出库明细信息{string.Join(",", selectDetailIds)}已分配完成"; return result; } outboundOrder.Details = selectedDetails; result.OutboundOrder = outboundOrder; result.SelectedDetails = selectedDetails; if (outboundOrder.IsBatch == 0 || request.DetailIds.Count != 1) { result.MaterielCalculations = CalcMaterielOutboundQuantities(outboundOrder, selectedDetails.ToList()); } else { if (!request.OutboundQuantity.HasValue || request.OutboundQuantity.Value <= 0) { result.CanOutbound = false; result.ErrorMessage = "单明细出库时必须指定出库数量且大于0"; return result; } decimal lockQuantity = selectedDetails.Sum(x => x.LockQuantity); decimal orderQuantity = selectedDetails.Sum(x => x.OrderQuantity); decimal moveQuantity = selectedDetails.Sum(x => x.MoveQty); decimal overQuantity = selectedDetails.Sum(x => x.OverOutQuantity); Dt_OutboundOrderDetail? singleDetail = selectedDetails.First(); if (orderQuantity - lockQuantity - moveQuantity < request.OutboundQuantity.Value || orderQuantity - overQuantity - moveQuantity < request.OutboundQuantity.Value) { result.CanOutbound = false; result.ErrorMessage = $"本次出库数量 {request.OutboundQuantity.Value} 超过可出库数量 {orderQuantity - lockQuantity - moveQuantity}"; return result; } decimal inputQuantity = request.OutboundQuantity.Value; List outboundOrderDetails = new List(); foreach (var item in selectedDetails) { inputQuantity -= (item.OrderQuantity - item.MoveQty - item.LockQuantity); outboundOrderDetails.Add(item); if (inputQuantity <= 0) break; } result.MaterielCalculations = new List() { new MaterielOutboundCalculationDTO { MaterielCode = singleDetail.MaterielCode, MaterielName = singleDetail.MaterielName, BatchNo = singleDetail.BatchNo, SupplyCode = singleDetail.SupplyCode, WarehouseCode = singleDetail.WarehouseCode, TotalOrderQuantity = orderQuantity - moveQuantity, TotalOverOutQuantity = overQuantity, AssignedQuantity = lockQuantity, UnallocatedQuantity = request.OutboundQuantity.Value, MovedQuantity = moveQuantity, Details = outboundOrderDetails } }; outboundOrder.Details = outboundOrderDetails; } result.CanOutbound = true; } catch (Exception ex) { result.CanOutbound = false; result.ErrorMessage = ex.Message; } return result; } /// /// 按物料分组计算出库数量(原有逻辑不变) /// private List CalcMaterielOutboundQuantities(Dt_OutboundOrder outboundOrder, List selectedDetails) { return selectedDetails .GroupBy(x => new { x.MaterielCode, x.BatchNo, x.SupplyCode, x.WarehouseCode }) .Select(g => new MaterielOutboundCalculationDTO { MaterielCode = g.Key.MaterielCode, BatchNo = g.Key.BatchNo, SupplyCode = g.Key.SupplyCode, WarehouseCode = g.Key.WarehouseCode, TotalOrderQuantity = g.Sum(x => x.OrderQuantity - x.MoveQty), TotalOverOutQuantity = g.Sum(x => x.OverOutQuantity), AssignedQuantity = g.Sum(x => x.LockQuantity), UnallocatedQuantity = g.Sum(x => x.OrderQuantity - x.LockQuantity - x.MoveQty), MovedQuantity = g.Sum(x => x.MoveQty), Details = g.ToList(), OutStockLockInfos = _outboundLockInfoRepository.QueryData(x => x.MaterielCode == g.Key.MaterielCode && x.BatchNo == g.Key.BatchNo && x.OrderType == (int)outboundOrder.OrderType && x.OrderNo == outboundOrder.OrderNo) }) .ToList(); } /// /// 处理物料任务生成(核心:有多少分多少+移除库存不足异常) /// private (List PickedDetails, List Tasks, List OutStockLockInfo) ProcessMaterielTaskGeneration( Dt_OutboundOrder outboundOrder, MaterielOutboundCalculationDTO materielCalc, PickingOutboundRequestDTO request, string factoryArea) { List pickedDetails = new List(); List generatedTasks = new List(); // 初始空任务列表 List lockInfoList = new List(); decimal remainingQuantity = materielCalc.UnallocatedQuantity; // 1. 优先处理指定库存明细 if (request.StockDetailIds?.Any() == true) { var specifiedResult = AllocateSpecifiedStockDetails(outboundOrder, materielCalc, request, factoryArea, remainingQuantity); pickedDetails.AddRange(specifiedResult.PickedDetails); lockInfoList.AddRange(specifiedResult.LockInfoList); remainingQuantity -= specifiedResult.ActualAllocatedQuantity; // ===== 核心修复:指定库存分配后,立即生成任务(无论剩余量多少)===== if (specifiedResult.PickedDetails.Any()) // 有指定库存分配结果就生成任务 { var specifiedTasks = GenerateTasksForSpecifiedStock(specifiedResult.PickedDetails, request.OutboundTargetLocation); generatedTasks.AddRange(specifiedTasks); // 添加指定库存任务到总任务列表 } // 剩余量<=0时,直接返回(已生成指定库存任务) if (remainingQuantity <= 0) { return (pickedDetails, generatedTasks, lockInfoList); } } // 2. 处理剩余数量的自动分配(原有逻辑不变,自动分配的任务会追加到generatedTasks) List stockQuery = BuildStockQueryWithInfo(materielCalc, factoryArea); var allocatedPalletCodes = pickedDetails.Select(x => x.PalletCode).Distinct().ToList(); stockQuery = stockQuery.Where(x => !allocatedPalletCodes.Contains(x.PalletCode)).ToList(); var stockData = GetBatchAvailableStockQuantities(materielCalc, stockQuery); decimal totalAutoAvailable = stockData.AvailableStockMap.Values.Sum(); decimal autoAllocateQuantity = Math.Min(remainingQuantity, totalAutoAvailable); remainingQuantity -= autoAllocateQuantity; if (autoAllocateQuantity > 0 && stockQuery.Any()) { Dt_OutboundOrderDetail firstDetail = materielCalc.Details.First(); Dictionary> stockDetailMap = stockQuery.ToDictionary(x => x.Id, x => x.Details); Dictionary palletAllocations = new Dictionary(); foreach (var stock in stockQuery) { if (autoAllocateQuantity <= 0) break; decimal availableQuantity = stockData.AvailableStockMap.GetValueOrDefault(stock.Id, 0); if (availableQuantity <= 0) continue; // 自动分配时查询托盘总库存(原有优化逻辑不变) decimal palletMaterielTotalStock = _stockDetailRepository.QueryData( x => x.StockId == stock.Id && x.MaterielCode == materielCalc.MaterielCode ).Sum(x => x.StockQuantity); decimal allocateQuantity = Math.Min(autoAllocateQuantity, availableQuantity); var actualAllocated = AllocateStockQuantity( stock, allocateQuantity, availableQuantity, outboundOrder, firstDetail, request, stockData.LockStockMap.GetValueOrDefault(stock.Id, new List()), stockDetailMap, palletMaterielTotalStock); if (actualAllocated.ActualAllocatedQuantity > 0) { palletAllocations[stock.PalletCode] = actualAllocated.ActualAllocatedQuantity; autoAllocateQuantity -= actualAllocated.ActualAllocatedQuantity; lockInfoList.AddRange(actualAllocated.LockInfoList); } } // 生成自动分配的任务(追加到generatedTasks,不会重复) foreach (var palletCode in palletAllocations.Keys) { Dt_StockInfo stock = stockQuery.First(x => x.PalletCode == palletCode); decimal actualQty = palletAllocations[palletCode]; decimal originalAvailable = stockData.AvailableStockMap.GetValueOrDefault(stock.Id, 0); pickedDetails.Add(new PickedStockDetailDTO { PalletCode = palletCode, MaterielCode = materielCalc.MaterielCode, OutboundQuantity = actualQty, RemainingQuantity = Math.Max(0, originalAvailable - actualQty), LocationCode = stock.LocationCode, OutStockLockInfos = lockInfoList.Where(x => x.PalletCode == palletCode).ToList() }); int taskNum = lockInfoList.FirstOrDefault(x => x.PalletCode == palletCode)?.TaskNum ?? Db.Ado.GetScalar($"SELECT NEXT VALUE FOR SeqTaskNum").ObjToInt(); Dt_Task task = GenerationOutTask(stock, TaskTypeEnum.Outbound, taskNum, request.OutboundTargetLocation); // 过滤重复任务(指定库存的托盘已生成,不会重复添加) if (generatedTasks.FirstOrDefault(x => x.PalletCode == palletCode) == null) generatedTasks.Add(task); } } // 返回「指定库存任务+自动分配任务」的合并列表 return (pickedDetails, generatedTasks, lockInfoList); } /// /// 分配指定库存明细(核心优化:查询托盘物料总库存并传递) /// private (decimal ActualAllocatedQuantity, List PickedDetails, List LockInfoList) AllocateSpecifiedStockDetails( Dt_OutboundOrder outboundOrder, MaterielOutboundCalculationDTO materielCalc, PickingOutboundRequestDTO request, string factoryArea, decimal needAllocateQuantity) { List pickedDetails = new List(); List lockInfoList = new List(); decimal actualAllocated = 0; List specifiedStockDetails = _stockDetailRepository.QueryData( x => request.StockDetailIds.Contains(x.Id) && x.MaterielCode == materielCalc.MaterielCode && x.StockQuantity > 0 && (x.Status == (int)StockStatusEmun.入库完成 || x.Status == (int)StockStatusEmun.手动冻结 || x.Status == (int)StockStatusEmun.手动解锁 || x.Status == (int)StockStatusEmun.过期)); if (!specifiedStockDetails.Any()) { throw new Exception($"指定库存明细ID [{string.Join(",", request.StockDetailIds)}] 不存在或不可用"); } List stockIds = specifiedStockDetails.Select(x => x.StockId).Distinct().ToList(); List specifiedStocks = _stockInfoRepository.QueryData(x => stockIds.Contains(x.Id)); Dictionary stockMap = specifiedStocks.ToDictionary(x => x.Id); foreach (var stockDetail in specifiedStockDetails) { if (needAllocateQuantity <= 0) break; if (!stockMap.ContainsKey(stockDetail.StockId)) continue; Dt_StockInfo stock = stockMap[stockDetail.StockId]; decimal availableQty = stockDetail.StockQuantity; decimal allocateQty = Math.Min(needAllocateQuantity, availableQty); if (allocateQty <= 0) continue; // ===== 核心优化1:查询该托盘下当前物料的总库存 ===== decimal palletMaterielTotalStock = _stockDetailRepository.QueryData( x => x.StockId == stock.Id && x.MaterielCode == materielCalc.MaterielCode ).Sum(x => x.StockQuantity); // 该托盘该物料的总库存(而非本次分配量) var lockInfos = materielCalc.OutStockLockInfos .Where(x => x.StockId == stock.Id && x.MaterielCode == materielCalc.MaterielCode) .ToList(); // ===== 传递总库存到AllocateStockQuantity ===== var allocateResult = AllocateStockQuantity( stock, allocateQty, availableQty, outboundOrder, materielCalc.Details.First(), request, lockInfos, new Dictionary> { { stock.Id, new List { stockDetail } } }, palletMaterielTotalStock // 新增:传入托盘物料总库存 ); if (allocateResult.ActualAllocatedQuantity > 0) { pickedDetails.Add(new PickedStockDetailDTO { PalletCode = stock.PalletCode, MaterielCode = materielCalc.MaterielCode, OutboundQuantity = allocateResult.ActualAllocatedQuantity, RemainingQuantity = Math.Max(0, availableQty - allocateResult.ActualAllocatedQuantity), LocationCode = stock.LocationCode, OutStockLockInfos = allocateResult.LockInfoList }); actualAllocated += allocateResult.ActualAllocatedQuantity; needAllocateQuantity -= allocateResult.ActualAllocatedQuantity; lockInfoList.AddRange(allocateResult.LockInfoList); } } return (actualAllocated, pickedDetails, lockInfoList); } /// /// 为指定库存生成任务(原有逻辑不变) /// private List GenerateTasksForSpecifiedStock(List pickedDetails, string outboundTargetLocation) { List tasks = new List(); var palletCodes = pickedDetails.Select(x => x.PalletCode).Distinct().ToList(); foreach (var palletCode in palletCodes) { Dt_StockInfo stock = _stockInfoRepository.QueryFirst(x => x.PalletCode == palletCode); if (stock == null) continue; int taskNum = pickedDetails.First(x => x.PalletCode == palletCode) .OutStockLockInfos.FirstOrDefault()?.TaskNum ?? Db.Ado.GetScalar($"SELECT NEXT VALUE FOR SeqTaskNum").ObjToInt(); Dt_Task task = GenerationOutTask(stock, TaskTypeEnum.Outbound, taskNum, outboundTargetLocation); if (tasks.FirstOrDefault(x => x.PalletCode == palletCode) == null) tasks.Add(task); } return tasks; } /// /// 构建库存查询条件(原有逻辑不变) /// private List BuildStockQueryWithInfo(MaterielOutboundCalculationDTO materielCalc, string factoryArea) { ISugarQueryable stockDetails = _stockDetailRepository.Db.Queryable() .Where(x => x.MaterielCode == materielCalc.MaterielCode && x.StockQuantity > 0 && (x.Status == (int)StockStatusEmun.入库完成 || x.Status == (int)StockStatusEmun.手动解锁)); if (!string.IsNullOrEmpty(materielCalc.SupplyCode)) stockDetails = stockDetails.Where(x => x.SupplyCode == materielCalc.SupplyCode); if (!string.IsNullOrEmpty(materielCalc.WarehouseCode)) stockDetails = stockDetails.Where(x => x.WarehouseCode == materielCalc.WarehouseCode); if (!string.IsNullOrEmpty(factoryArea)) stockDetails = stockDetails.Where(x => x.FactoryArea == factoryArea); if (!string.IsNullOrEmpty(materielCalc.BatchNo)) stockDetails = stockDetails.Where(x => x.BatchNo == materielCalc.BatchNo); List stockDetailList = stockDetails.ToList(); List locationCodes = _locationInfoRepository.QueryData(x => (x.LocationStatus == LocationStatusEnum.InStock.ObjToInt() || x.LocationStatus == LocationStatusEnum.Lock.ObjToInt()) && x.EnableStatus == EnableStatusEnum.Normal.ObjToInt()).Select(x => x.LocationCode).ToList(); List stockIds = stockDetailList.GroupBy(x => x.StockId).Select(x => x.Key).ToList(); List stockInfos = _stockInfoRepository.QueryData(x => stockIds.Contains(x.Id) && (x.StockStatus == StockStatusEmun.入库完成.ObjToInt()) && !string.IsNullOrEmpty(x.LocationCode) && locationCodes.Contains(x.LocationCode)); foreach (var stockInfo in stockInfos) { stockInfo.Details = stockDetailList.Where(x => x.StockId == stockInfo.Id).ToList(); } return stockInfos; } /// /// 批量获取托盘可用库存信息(原有逻辑不变) /// private (Dictionary AvailableStockMap, Dictionary> LockStockMap) GetBatchAvailableStockQuantities( MaterielOutboundCalculationDTO materielCalc, List stockInfos) { List stockIds = stockInfos.Select(x => x.Id).ToList(); Dictionary availableStockMap = new Dictionary(); Dictionary> lockStockMap = new Dictionary>(); List allocatedData = materielCalc.OutStockLockInfos .Where(x => stockIds.Contains(x.StockId) && x.MaterielCode == materielCalc.MaterielCode).ToList(); foreach (var stockInfo in stockInfos) { decimal totalQuantity = stockInfo.Details.Sum(x => x.StockQuantity); List outStockLockInfos = allocatedData .Where(x => x.StockId == stockInfo.Id && x.MaterielCode == materielCalc.MaterielCode).ToList(); decimal allocatedQuantity = outStockLockInfos.Sum(x => x.AllocatedQuantity); availableStockMap[stockInfo.Id] = Math.Max(0, totalQuantity - allocatedQuantity); lockStockMap[stockInfo.Id] = outStockLockInfos; } return (availableStockMap, lockStockMap); } /// /// 分配库存(核心优化:OriginalQuantity赋值为托盘物料总库存) /// private (decimal ActualAllocatedQuantity, List LockInfoList) AllocateStockQuantity( Dt_StockInfo stockInfo, decimal allocateQuantity, decimal availableQuantity, Dt_OutboundOrder outboundOrder, Dt_OutboundOrderDetail detail, PickingOutboundRequestDTO request, List lockInfos, Dictionary> stockDetailMap = null, decimal palletMaterielTotalStock = 0 // 新增:托盘物料总库存参数 ) { decimal actualAllocatedQuantity = Math.Min(allocateQuantity, availableQuantity); List lockInfoList = new List(); if (actualAllocatedQuantity > 0) { // 检查目标位置一致性 if (lockInfos.Any() && !string.IsNullOrEmpty(lockInfos.First().OutboundTargetLocation) && !string.Equals(lockInfos.First().OutboundTargetLocation, request.OutboundTargetLocation, StringComparison.OrdinalIgnoreCase)) { return (0, lockInfoList); } Dt_OutStockLockInfo? lockInfo = lockInfos.FirstOrDefault(x => x.StockId == stockInfo.Id && x.Status == OutLockStockStatusEnum.已分配.ObjToInt() && x.PalletCode == stockInfo.PalletCode && x.OrderNo == outboundOrder.OrderNo); if (lockInfo != null) { List currentIds = lockInfo.OrderDetailIds?.Split(',').ToList() ?? new List(); if (!currentIds.Contains(detail.Id.ToString())) { currentIds.Add(detail.Id.ToString()); lockInfo.OrderDetailIds = string.Join(",", currentIds); } decimal totalAllocatedQuantity = CalcTotalAllocatedQuantity(lockInfos, stockInfo.Id, detail.MaterielCode); lockInfo.AssignQuantity += actualAllocatedQuantity; lockInfo.AllocatedQuantity = totalAllocatedQuantity; if (palletMaterielTotalStock > 0) lockInfo.OriginalQuantity = palletMaterielTotalStock; lockInfoList.Add(lockInfo); } else { decimal originalQuantity = palletMaterielTotalStock; List allDetailIds = outboundOrder.Details.Where(x => x.OrderId == outboundOrder.Id && x.MaterielCode == detail.MaterielCode && x.BatchNo == detail.BatchNo && x.SupplyCode == detail.SupplyCode && x.WarehouseCode == detail.WarehouseCode) .Select(x => x.Id.ToString()).ToList(); decimal totalAllocatedQuantity = CalcTotalAllocatedQuantity(lockInfos, stockInfo.Id, detail.MaterielCode); lockInfo = new Dt_OutStockLockInfo { OrderNo = request.OrderNo, OrderDetailIds = string.Join(",", allDetailIds), OrderType = outboundOrder.OrderType, BatchNo = detail.BatchNo, MaterielCode = detail.MaterielCode, MaterielName = detail.MaterielName, StockId = stockInfo.Id, OrderQuantity = allDetailIds.Count > 1 ? outboundOrder.Details.Where(x => x.OrderId == outboundOrder.Id && x.MaterielCode == detail.MaterielCode && x.BatchNo == detail.BatchNo && x.SupplyCode == detail.SupplyCode && x.WarehouseCode == detail.WarehouseCode).Sum(x => x.OrderQuantity) : detail.OrderQuantity, OriginalQuantity = originalQuantity, // 现在赋值为托盘物料总库存 AssignQuantity = actualAllocatedQuantity, AllocatedQuantity = totalAllocatedQuantity, LocationCode = stockInfo.LocationCode, PalletCode = stockInfo.PalletCode, Unit = detail.Unit, OutboundTargetLocation = request.OutboundTargetLocation, Status = OutLockStockStatusEnum.已分配.ObjToInt(), SupplyCode = detail.SupplyCode, WarehouseCode = detail.WarehouseCode, FactoryArea = outboundOrder.FactoryArea, TaskNum = Db.Ado.GetScalar($"SELECT NEXT VALUE FOR SeqTaskNum").ObjToInt(), OrderDetailId = 0 }; lockInfoList.Add(lockInfo); } } return (actualAllocatedQuantity, lockInfoList); } /// /// 计算该托盘累计已分配数量(原有逻辑不变) /// private decimal CalcTotalAllocatedQuantity(List lockInfos, int stockId, string materielCode) { List lockRecords = _outboundLockInfoRepository.QueryData(x => x.StockId == stockId && x.MaterielCode == materielCode); return lockRecords?.Sum(x => x.AssignQuantity) ?? 0; } /// /// 生成出库任务(原有逻辑不变) /// public Dt_Task GenerationOutTask(Dt_StockInfo stockInfo, TaskTypeEnum taskType, int taskNum, string outStation) { return new Dt_Task { CurrentAddress = stockInfo.LocationCode, Grade = 0, PalletCode = stockInfo.PalletCode, NextAddress = outStation, Roadway = "", SourceAddress = stockInfo.LocationCode, TargetAddress = outStation, TaskStatus = TaskStatusEnum.New.ObjToInt(), TaskType = taskType.ObjToInt(), TaskNum = taskNum, PalletType = stockInfo.PalletType, WarehouseId = stockInfo.WarehouseId, }; } public bool UpdateOutboundOrderStatus(string orderNo, int status) { try { Dt_OutboundOrder outboundOrder = _outboundRepository.QueryFirst(x => x.OrderNo == orderNo); if (outboundOrder == null) return false; outboundOrder.OrderStatus = status; if(outboundOrder.CreateType == OrderCreateTypeEnum.CreateInSystem.ObjToInt()) { outboundOrder.ReturnToMESStatus = 5; } _outboundRepository.UpdateData(outboundOrder); return true; } catch { return false; } } public bool UpdateStockStatus(List palletCodes, int status) { try { List stockInfos = _stockInfoRepository.QueryData(x => palletCodes.Contains(x.PalletCode)); stockInfos.ForEach(stockInfo => stockInfo.StockStatus = status); _stockInfoRepository.UpdateData(stockInfos); return true; } catch { return false; } } public bool UpdateLocationStatus(List locationCodes, int status) { try { List locationInfos = _locationInfoRepository.QueryData(x => locationCodes.Contains(x.LocationCode)); locationInfos.ForEach(x => x.LocationStatus = status); _locationInfoRepository.UpdateData(locationInfos); return true; } catch { return false; } } public bool UpdateOutStockLockInfo(List outStockLockInfos) { try { List updateData = outStockLockInfos.Where(x => x.Id > 0).ToList(); _outboundLockInfoRepository.UpdateData(updateData); List addData = outStockLockInfos.Where(x => x.Id <= 0).ToList(); _outboundLockInfoRepository.AddData(addData); return true; } catch { return false; } } #endregion #region 整箱出库 public WebResponseContent CompleteOutboundWithPallet(OutboundCompletePalletRequestDTO request) { WebResponseContent content = WebResponseContent.Instance; OutboundCompleteResponseDTO response = new(); try { // 1. 根据托盘号查找库存信息 Dt_StockInfo stockInfo = _stockInfoRepository.Db.Queryable().Where(x => x.PalletCode == request.PalletCode).Includes(x => x.Details).First(); if (stockInfo == null) { response.Success = false; response.Message = $"托盘号 {request.PalletCode} 对应的库存不存在"; return WebResponseContent.Instance.Error(response.Message); } if (!stockInfo.Details.Any()) { response.Success = false; response.Message = $"托盘 {request.PalletCode} 对应的库存明细不存在"; return WebResponseContent.Instance.Error(response.Message); } // 2. 查找出库单信息 Dt_OutboundOrder outboundOrder = _outboundRepository.QueryFirst(o => o.OrderNo == request.OrderNo); if (outboundOrder == null) { response.Success = false; response.Message = $"出库单 {request.OrderNo} 不存在"; return WebResponseContent.Instance.Error(response.Message); } Dt_StockInfoDetail stockInfoDetail = stockInfo.Details.First(); // 3. 查找锁定记录 Dt_OutStockLockInfo lockInfo = _outboundLockInfoRepository.QueryFirst(x => x.OrderNo == request.OrderNo && x.StockId == stockInfo.Id && x.MaterielCode == stockInfoDetail.MaterielCode && x.PalletCode == stockInfo.PalletCode); if (lockInfo == null || lockInfo.AssignQuantity <= 0) { response.Success = false; response.Message = $"该库存没有分配出库量,托盘号:{request.PalletCode}"; return WebResponseContent.Instance.Error(response.Message); } //bool isMatMixed = stockInfo.Details.GroupBy(x => new //{ // x.MaterielCode, // x.MaterielName, // x.BatchNo, // x.SupplyCode, // x.WarehouseCode //}).Count() > 1; bool isMatMixed = false; bool includeBatchNo = !string.IsNullOrEmpty(lockInfo.BatchNo); bool includeSupplyCode = !string.IsNullOrEmpty(lockInfo.SupplyCode); if (includeBatchNo && includeSupplyCode) { isMatMixed = stockInfo.Details.GroupBy(x => new { x.MaterielCode, x.MaterielName, x.BatchNo, x.SupplyCode, x.WarehouseCode }).Count() > 1; } else if (includeBatchNo && !includeSupplyCode) { isMatMixed = stockInfo.Details.GroupBy(x => new { x.MaterielCode, x.MaterielName, x.BatchNo, x.WarehouseCode }).Count() > 1; } else if (!includeBatchNo && includeSupplyCode) { isMatMixed = stockInfo.Details.GroupBy(x => new { x.MaterielCode, x.MaterielName, x.SupplyCode, x.WarehouseCode }).Count() > 1; } else { isMatMixed = stockInfo.Details.GroupBy(x => new { x.MaterielCode, x.MaterielName, x.WarehouseCode }).Count() > 1; } if (isMatMixed) { response.Success = false; response.Message = $"混料托盘 {request.PalletCode} 不能整箱出库"; return WebResponseContent.Instance.Error(response.Message); } // 找出已分配的订单明细Id List detailIds = new List(); string[] ids = lockInfo.OrderDetailIds.Split(","); foreach (string id in ids) { if (int.TryParse(id, out int detailId)) { detailIds.Add(detailId); } } // 4. 查找出库单明细信息 List outboundOrderDetails = FindMatchingOutboundDetails(outboundOrder.Id, stockInfoDetail, detailIds); if (!outboundOrderDetails.Any()) { response.Success = false; response.Message = $"未找到匹配的出库单明细,物料:{stockInfoDetail.MaterielCode},批次:{stockInfoDetail.BatchNo}"; return WebResponseContent.Instance.Error(response.Message); } decimal totalStockQuantity = stockInfo.Details.Sum(x => x.StockQuantity); // 5. 计算实际出库量 decimal actualOutboundQuantity = CalculateActualOutboundQuantity(stockInfo.Details, outboundOrderDetails, lockInfo);// 需出库量 if (actualOutboundQuantity <= 0) { decimal totalAllocatedQuantity = lockInfo.AllocatedQuantity; decimal availableOutboundQuantity = lockInfo.AssignQuantity - totalAllocatedQuantity; decimal detailRemainingQuantity = outboundOrderDetails.Sum(x => x.OrderQuantity - x.OverOutQuantity - x.MoveQty); response.Success = false; response.Message = $"无法出库,托盘号:{request.PalletCode},库存量:{totalStockQuantity},已出库:{totalAllocatedQuantity},分配量:{lockInfo.AssignQuantity},明细剩余:{detailRemainingQuantity}"; return WebResponseContent.Instance.Error(response.Message); } if (lockInfo.AssignQuantity != totalStockQuantity) { response.Success = false; response.Message = $"无法出库,托盘号:{request.PalletCode},库存量:{totalStockQuantity},分配量:{lockInfo.AssignQuantity}"; return WebResponseContent.Instance.Error(response.Message); } // 6. 开启事务 _unitOfWorkManage.BeginTran(); try { // 整箱出库无需拆包 PerformFullOutboundOperation(stockInfo, request, lockInfo.TaskNum.GetValueOrDefault()); if (outboundOrder.OrderType == InOrderTypeEnum.InternalAllocat.ObjToInt()) { Dt_AllocateOrder allocateOrder = _allocateOrderRepository.QueryFirst(x => x.OrderNo == outboundOrder.OrderNo); if (allocateOrder != null) { List allocateMaterialInfos = new List(); foreach (var item in stockInfo.Details) { Dt_AllocateMaterialInfo allocateMaterialInfo = new Dt_AllocateMaterialInfo() { Barcode = item.Barcode, BatchNo = item.BatchNo, FactoryArea = item.FactoryArea, MaterialCode = item.MaterielCode, MaterialName = item.MaterielName, OrderId = outboundOrder.Id, OrderNo = outboundOrder.OrderNo, Quantity = item.StockQuantity, SupplyCode = item.SupplyCode, Unit = item.Unit, WarehouseCode = allocateOrder.ToWarehouse }; allocateMaterialInfos.Add(allocateMaterialInfo); } _allocateMaterialInfoRepository.AddData(allocateMaterialInfos); } } else if(outboundOrder.OrderType == InOrderTypeEnum.ReCheck.ObjToInt()) { List allocateMaterialInfos = new List(); foreach (var item in stockInfo.Details) { Dt_AllocateMaterialInfo allocateMaterialInfo = new Dt_AllocateMaterialInfo() { Barcode = item.Barcode??"", BatchNo = item.BatchNo, FactoryArea = item.FactoryArea, MaterialCode = item.MaterielCode, MaterialName = item.MaterielName, OrderId = outboundOrder.Id, OrderNo = outboundOrder.OrderNo, Quantity = item.StockQuantity, SupplyCode = item.SupplyCode??"", Unit = item.Unit, WarehouseCode = item.WarehouseCode??"" }; allocateMaterialInfos.Add(allocateMaterialInfo); } _allocateMaterialInfoRepository.AddData(allocateMaterialInfos); } decimal allocatedQuantity = actualOutboundQuantity; List updateDetails = new(); foreach (var item in outboundOrderDetails) { if (allocatedQuantity <= 0) break; //if (item.OrderQuantity - item.MoveQty - item.OverOutQuantity >= allocatedQuantity) //{ // item.OverOutQuantity += allocatedQuantity; // allocatedQuantity = 0; //} //else //{ // allocatedQuantity -= (item.OrderQuantity - item.MoveQty - item.OverOutQuantity); // item.OverOutQuantity = item.OrderQuantity - item.MoveQty; //} List barcodesList = new List(); List stockInfoDetails = stockInfo.Details.Where((x => x.StockQuantity > x.OutboundQuantity)).ToList(); decimal itemQuantity = item.LockQuantity - item.OverOutQuantity; decimal unitbarcodeQuantity; foreach (var stockDetail in stockInfoDetails) { if (itemQuantity >= stockDetail.StockQuantity - stockDetail.OutboundQuantity) { unitbarcodeQuantity = stockDetail.StockQuantity - stockDetail.OutboundQuantity; UnitConvertResultDTO currentResult = _basicService.UnitQuantityConvert(item.MaterielCode, item.Unit, item.BarcodeUnit, unitbarcodeQuantity); Barcodes barcodes = new Barcodes { Barcode = stockDetail.Barcode, Qty = currentResult.ToQuantity, SupplyCode = stockDetail?.SupplyCode ?? "", BatchNo = stockDetail?.BatchNo ?? "", Unit = currentResult.ToUnit ?? "" }; itemQuantity -= (stockDetail.StockQuantity - stockDetail.OutboundQuantity); stockDetail.OutboundQuantity = stockDetail.StockQuantity; barcodesList.Add(barcodes); if (itemQuantity <= 0) break; } else { UnitConvertResultDTO currentResult = _basicService.UnitQuantityConvert(item.MaterielCode, item.Unit, item.BarcodeUnit, itemQuantity); Barcodes barcodes = new Barcodes { Barcode = stockDetail.Barcode, Qty = currentResult.ToQuantity, SupplyCode = stockDetail?.SupplyCode ?? "", BatchNo = stockDetail?.BatchNo ?? "", Unit = currentResult.ToUnit ?? "" }; stockDetail.OutboundQuantity += itemQuantity; barcodesList.Add(barcodes); break; } } decimal barcodeQuantity = allocatedQuantity; if (item.LockQuantity - item.OverOutQuantity >= allocatedQuantity) { item.OverOutQuantity += allocatedQuantity; item.CurrentDeliveryQty += allocatedQuantity; allocatedQuantity = 0; } else { barcodeQuantity = item.LockQuantity - item.OverOutQuantity; allocatedQuantity -= (item.LockQuantity - item.OverOutQuantity); item.OverOutQuantity = item.LockQuantity; item.CurrentDeliveryQty = item.LockQuantity; } updateDetails.Add(item); if (!string.IsNullOrEmpty(item.ReturnJsonData)) { barcodesList.AddRange(JsonConvert.DeserializeObject>(item.ReturnJsonData) ?? new List()); } JsonSerializerSettings settings = new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() }; item.ReturnJsonData = JsonConvert.SerializeObject(barcodesList, settings); //重拣出库不需要回传 if (outboundOrder.OrderType == InOrderTypeEnum.ReCheck.ObjToInt()) { item.ReturnJsonData = ""; } } lockInfo.SortedQuantity = lockInfo.SortedQuantity + actualOutboundQuantity; if (lockInfo.SortedQuantity == lockInfo.AssignQuantity) { _outboundLockInfoRepository.DeleteAndMoveIntoHty(lockInfo, WIDESEA_Core.Enums.OperateTypeEnum.自动完成); } else { // 更新锁定记录 _outboundLockInfoRepository.UpdateData(lockInfo); } // 更新出库单明细的已出库数量 _detailRepository.UpdateData(updateDetails); // 更新锁定记录的累计已出库数量(需要更新该托盘该物料的所有相关记录) //UpdateLockInfoAllocatedQuantity(stockInfo.Id, stockDetail.MaterielCode, stockDetail.BatchNo, actualOutboundQuantity); // 提交事务 _unitOfWorkManage.CommitTran(); response.Success = true; response.Message = "出库完成"; response.UpdatedDetails = updateDetails; // 检查出库单是否完成 if (CheckOutboundOrderCompleted(request.OrderNo)) { UpdateOutboundOrderStatus(request.OrderNo, OutOrderStatusEnum.出库完成.ObjToInt()); if (outboundOrder.OrderType != OutOrderTypeEnum.InternalAllocat.ObjToInt()&& outboundOrder.OrderType!= InOrderTypeEnum.ReCheck.ObjToInt()) { _feedbackMesService.OutboundFeedback(outboundOrder.OrderNo); } } } catch (Exception ex) { _unitOfWorkManage.RollbackTran(); response.Success = false; response.Message = $"出库处理失败:{ex.Message}"; return WebResponseContent.Instance.Error(ex.Message); } content = WebResponseContent.Instance.OK(data: response); } catch (Exception ex) { content = WebResponseContent.Instance.Error("处理出库完成失败:" + ex.Message); } return content; } /// /// 计算实际出库数量 /// private decimal CalculateActualOutboundQuantity(List stockDetails, List outboundDetails, Dt_OutStockLockInfo lockInfo) { decimal availableOutboundQuantity = lockInfo.AssignQuantity; decimal detailRemainingQuantity = outboundDetails.Sum(x => x.OrderQuantity - x.OverOutQuantity - x.MoveQty);//outboundDetail.OrderQuantity - outboundDetail.OverOutQuantity; return Math.Min( Math.Min(availableOutboundQuantity, detailRemainingQuantity), stockDetails.Sum(x => x.StockQuantity)); } /// /// 执行完整出库操作(不拆包) /// private void PerformFullOutboundOperation(Dt_StockInfo stockInfo, OutboundCompletePalletRequestDTO request, int taskNum) { List historyRecords = new List(); List changeRecords = new List(); foreach (var item in stockInfo.Details) { // 保存库存明细到历史记录 Dt_StockInfoDetail_Hty historyRecord = new Dt_StockInfoDetail_Hty { SourceId = item.Id, OperateType = "出库完成", InsertTime = DateTime.Now, StockId = item.StockId, MaterielCode = item.MaterielCode, MaterielName = item.MaterielName, OrderNo = item.OrderNo, BatchNo = item.BatchNo, ProductionDate = item.ProductionDate, EffectiveDate = item.EffectiveDate, SerialNumber = item.SerialNumber, StockQuantity = item.StockQuantity, OutboundQuantity = item.StockQuantity, Status = item.Status, Unit = item.Unit, InboundOrderRowNo = item.InboundOrderRowNo, SupplyCode = item.SupplyCode, FactoryArea = item.FactoryArea, WarehouseCode = item.WarehouseCode, Barcode = item.Barcode, CreateDate = item.CreateDate, Creater = item.Creater, Remark = $"整箱出库完成删除,条码:{request.PalletCode},原数量:{item.StockQuantity},出库数量:{item.StockQuantity},操作者:{request.Operator}" }; historyRecords.Add(historyRecord); // 记录库存变动 Dt_StockQuantityChangeRecord changeRecord = new Dt_StockQuantityChangeRecord { StockDetailId = item.Id, PalleCode = stockInfo.PalletCode, MaterielCode = item.MaterielCode, MaterielName = item.MaterielName, BatchNo = item.BatchNo, OriginalSerilNumber = item.Barcode, NewSerilNumber = "", OrderNo = request.OrderNo, TaskNum = taskNum, ChangeType = (int)StockChangeTypeEnum.Outbound, ChangeQuantity = -item.StockQuantity, BeforeQuantity = item.StockQuantity, AfterQuantity = 0, SupplyCode = item.SupplyCode, WarehouseCode = item.WarehouseCode, Remark = $"整箱出库完成删除库存明细,条码:{request.PalletCode},出库数量:{item.StockQuantity},操作者:{request.Operator}" }; changeRecords.Add(changeRecord); } _stockDetailHistoryRepository.AddData(historyRecords); // 删除库存明细记录 _stockDetailRepository.DeleteData(stockInfo.Details); _stockChangeRepository.AddData(changeRecords); } #endregion #region 拣选 /// /// 出库完成处理(扫描条码扣减库存) /// /// 出库完成请求 /// 出库完成响应 public WebResponseContent CompleteOutboundWithBarcode(OutboundCompleteRequestDTO request) { WebResponseContent content = WebResponseContent.Instance; OutboundCompleteResponseDTO response = new(); try { // 1. 根据托盘号查找库存信息 Dt_StockInfo stockInfo = _stockInfoRepository.QueryFirst(x => x.PalletCode == request.PalletCode); if (stockInfo == null) { response.Success = false; response.Message = $"托盘号 {request.PalletCode} 对应的库存不存在"; return WebResponseContent.Instance.Error($"托盘号 {request.PalletCode} 对应的库存不存在"); } // 2. 根据条码查找库存明细 Dt_StockInfoDetail stockDetail = _stockDetailRepository.QueryFirst(x => x.Barcode == request.Barcode); if (stockDetail == null) { response.Success = false; response.Message = $"条码 {request.Barcode} 对应的库存明细不存在"; return WebResponseContent.Instance.Error($"条码 {request.Barcode} 对应的库存明细不存在"); } // 3. 验证库存明细与托盘是否匹配 if (stockDetail.StockId != stockInfo.Id) { response.Success = false; response.Message = $"条码 {request.Barcode} 不属于托盘号 {request.PalletCode} 的库存明细"; return WebResponseContent.Instance.Error($"条码 {request.Barcode} 不属于托盘号 {request.PalletCode} 的库存明细"); } // 4. 查找出库单信息 Dt_OutboundOrder outboundOrder = _outboundRepository.QueryFirst(o => o.OrderNo == request.OrderNo); if (outboundOrder == null) { response.Success = false; response.Message = $"出库单 {request.OrderNo} 不存在"; return WebResponseContent.Instance.Error($"出库单 {request.OrderNo} 不存在"); } // 5. 查找锁定记录 Dt_OutStockLockInfo lockInfo = _outboundLockInfoRepository.QueryFirst(x => x.OrderNo == request.OrderNo && x.StockId == stockInfo.Id && x.MaterielCode == stockDetail.MaterielCode && x.PalletCode == stockInfo.PalletCode); if (lockInfo == null || lockInfo.AssignQuantity <= 0) { response.Success = false; response.Message = $"该库存没有分配出库量,条码:{request.Barcode}"; return WebResponseContent.Instance.Error($"该库存没有分配出库量,条码:{request.Barcode}"); } // 找出已分配的订单明细Id List detailIds = new List(); string[] ids = lockInfo.OrderDetailIds.Split(","); foreach (string id in ids) { if (int.TryParse(id, out int detailId)) { detailIds.Add(detailId); } } // 6. 查找出库单明细信息 List outboundOrderDetails = FindMatchingOutboundDetails(outboundOrder.Id, stockDetail, detailIds); if (!outboundOrderDetails.Any()) { response.Success = false; response.Message = $"未找到匹配的出库单明细,物料:{stockDetail.MaterielCode},批次:{stockDetail.BatchNo}"; return WebResponseContent.Instance.Error($"未找到匹配的出库单明细,物料:{stockDetail.MaterielCode},批次:{stockDetail.BatchNo}"); } // 7. 计算实际出库量 decimal actualOutboundQuantity = CalculateActualOutboundQuantity(stockDetail, outboundOrderDetails, lockInfo);// 需出库量 if (actualOutboundQuantity <= 0) { decimal totalAllocatedQuantity = lockInfo.AllocatedQuantity; decimal availableOutboundQuantity = lockInfo.AssignQuantity - totalAllocatedQuantity; decimal detailRemainingQuantity = outboundOrderDetails.Sum(x => x.OrderQuantity - x.OverOutQuantity - x.MoveQty); response.Success = false; response.Message = $"无法出库,条码:{request.Barcode},库存:{stockDetail.StockQuantity},已出库:{totalAllocatedQuantity},分配量:{lockInfo.AssignQuantity},明细剩余:{detailRemainingQuantity}"; return WebResponseContent.Instance.Error($"无法出库,条码:{request.Barcode},库存:{stockDetail.StockQuantity},已出库:{totalAllocatedQuantity},分配量:{lockInfo.AssignQuantity},明细剩余:{detailRemainingQuantity}"); } //if (actualOutboundQuantity + lockInfo.SortedQuantity > lockInfo.AssignQuantity) //{ // response.Success = false; // response.Message = $"无法出库,条码:{request.Barcode},库存:{stockDetail.StockQuantity},出库量{actualOutboundQuantity + lockInfo.SortedQuantity}大于分配量{lockInfo.AssignQuantity}"; // return WebResponseContent.Instance.Error($"无法出库,条码:{request.Barcode},库存:{stockDetail.StockQuantity},出库量{actualOutboundQuantity + lockInfo.SortedQuantity}大于分配量{lockInfo.AssignQuantity}"); //} // 8. 判断是否需要拆包(当出库数量小于库存数量时需要拆包) bool isUnpacked = actualOutboundQuantity < stockDetail.StockQuantity; List returnDTOs = new List(); string newBarcode = string.Empty; // 9. 开启事务 _unitOfWorkManage.BeginTran(); try { decimal beforeQuantity = stockDetail.StockQuantity; // 原始库存量 Dt_AllocateMaterialInfo allocateMaterialInfo = new Dt_AllocateMaterialInfo(); // 根据是否拆包执行不同的操作 if (isUnpacked) { (string NewBarcode, List MaterialCodeReturnDTOs) result = PerformUnpackOperation(stockDetail, stockInfo, actualOutboundQuantity, request, beforeQuantity, lockInfo.TaskNum.GetValueOrDefault(), outboundOrder.Id, outboundOrder.OrderNo); returnDTOs = result.MaterialCodeReturnDTOs; newBarcode = result.NewBarcode; MaterialCodeReturnDTO returnDTO = returnDTOs.First(x => x.Barcode == newBarcode); if (outboundOrder.OrderType == InOrderTypeEnum.ReCheck.ObjToInt()||outboundOrder.OrderType == InOrderTypeEnum.InternalAllocat.ObjToInt()) { allocateMaterialInfo = new Dt_AllocateMaterialInfo() { Barcode = returnDTO.Barcode, BatchNo = returnDTO.BatchNo, FactoryArea = returnDTO.FactoryArea, MaterialCode = returnDTO.MaterialCode, MaterialName = returnDTO.MaterialName, OrderId = outboundOrder.Id, OrderNo = outboundOrder.OrderNo, Quantity = returnDTO.Quantity, SupplyCode = returnDTO.SuplierCode, Unit = stockDetail.Unit }; } } else { PerformFullOutboundOperation(stockDetail, stockInfo, actualOutboundQuantity, request, beforeQuantity, lockInfo.TaskNum.GetValueOrDefault()); if (outboundOrder.OrderType == InOrderTypeEnum.ReCheck.ObjToInt() || outboundOrder.OrderType == InOrderTypeEnum.InternalAllocat.ObjToInt()) { allocateMaterialInfo = new Dt_AllocateMaterialInfo() { Barcode = stockDetail.Barcode, BatchNo = stockDetail.BatchNo, FactoryArea = stockDetail.FactoryArea, MaterialCode = stockDetail.MaterielCode, MaterialName = stockDetail.MaterielName, OrderId = outboundOrder.Id, OrderNo = outboundOrder.OrderNo, Quantity = stockDetail.StockQuantity, SupplyCode = stockDetail.SupplyCode, Unit = stockDetail.Unit }; } } // 判断是否是智仓调智仓单 if ( outboundOrder.OrderType == InOrderTypeEnum.InternalAllocat.ObjToInt()) { Dt_AllocateOrder allocateOrder = _allocateOrderRepository.QueryFirst(x => x.OrderNo == outboundOrder.OrderNo); if (allocateOrder != null) { allocateMaterialInfo.WarehouseCode = allocateOrder.ToWarehouse; _allocateMaterialInfoRepository.AddData(allocateMaterialInfo); } } decimal allocatedQuantity = actualOutboundQuantity; List updateDetails = new(); foreach (var item in outboundOrderDetails) { if (allocatedQuantity <= 0) break; //if (item.OrderQuantity - item.MoveQty - item.OverOutQuantity >= allocatedQuantity) //{ // item.OverOutQuantity += allocatedQuantity; // allocatedQuantity = 0; //} //else //{ // allocatedQuantity -= (item.OrderQuantity - item.MoveQty - item.OverOutQuantity); // item.OverOutQuantity = item.OrderQuantity - item.MoveQty; //} decimal barcodeQuantity = allocatedQuantity; if (item.LockQuantity - item.OverOutQuantity >= allocatedQuantity) { item.OverOutQuantity += allocatedQuantity; item.CurrentDeliveryQty += allocatedQuantity; allocatedQuantity = 0; } else { barcodeQuantity = item.LockQuantity - item.OverOutQuantity; allocatedQuantity -= (item.LockQuantity - item.OverOutQuantity); item.OverOutQuantity = item.LockQuantity; item.CurrentDeliveryQty = item.LockQuantity; } updateDetails.Add(item); List barcodesList = new List(); UnitConvertResultDTO currentResult = _basicService.UnitQuantityConvert(item.MaterielCode, item.Unit, item.BarcodeUnit, barcodeQuantity); Barcodes barcodes = new Barcodes { Barcode = isUnpacked ? newBarcode : stockDetail?.Barcode, Qty = currentResult.ToQuantity, SupplyCode = stockDetail?.SupplyCode ?? "", BatchNo = stockDetail?.BatchNo ?? "", Unit = currentResult.ToUnit ?? "" }; if (!string.IsNullOrEmpty(item.ReturnJsonData)) { barcodesList = JsonConvert.DeserializeObject>(item.ReturnJsonData) ?? new List(); } barcodesList.Add(barcodes); JsonSerializerSettings settings = new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() }; item.ReturnJsonData = JsonConvert.SerializeObject(barcodesList, settings); } lockInfo.SortedQuantity = lockInfo.SortedQuantity + actualOutboundQuantity; if (lockInfo.SortedQuantity >= lockInfo.AssignQuantity) { _outboundLockInfoRepository.DeleteAndMoveIntoHty(lockInfo, WIDESEA_Core.Enums.OperateTypeEnum.自动完成); } else { // 更新锁定记录 _outboundLockInfoRepository.UpdateData(lockInfo); } // 更新出库单明细的已出库数量 _detailRepository.UpdateData(updateDetails); // 更新锁定记录的累计已出库数量(需要更新该托盘该物料的所有相关记录) //UpdateLockInfoAllocatedQuantity(stockInfo.Id, stockDetail.MaterielCode, stockDetail.BatchNo, actualOutboundQuantity); // 提交事务 _unitOfWorkManage.CommitTran(); // 构建返回信息 ScannedStockDetailDTO scannedDetail = new ScannedStockDetailDTO { StockDetailId = stockDetail.Id, PalletCode = stockInfo.PalletCode, MaterielCode = stockDetail.MaterielCode, MaterielName = stockDetail.MaterielName, BatchNo = stockDetail.BatchNo, OriginalBarcode = request.Barcode, BeforeQuantity = beforeQuantity, AfterQuantity = isUnpacked ? actualOutboundQuantity : 0, ChangeQuantity = -actualOutboundQuantity, IsUnpacked = isUnpacked, MaterialCodes = returnDTOs }; response.Success = true; response.Message = "出库完成"; response.ScannedDetail = scannedDetail; response.UpdatedDetails = updateDetails; if (!string.IsNullOrEmpty(newBarcode)) { // 物料新条码回传 _feedbackMesService.BarcodeFeedback(newBarcode); } if (CheckOutboundOrderDetailCompletedByMatCode(request.OrderNo, lockInfo.MaterielCode, outboundOrderDetails.First())) { Func supWhere = x => string.IsNullOrEmpty(outboundOrderDetails.First().SupplyCode) ? true : x.SupplyCode == outboundOrderDetails.First().SupplyCode; Func wareWhere = x => string.IsNullOrEmpty(outboundOrderDetails.First().WarehouseCode) ? true : x.WarehouseCode == outboundOrderDetails.First().WarehouseCode; List stockLockInfos = _outboundLockInfoRepository.QueryData(x => x.OrderNo == request.OrderNo && x.MaterielCode == stockDetail.MaterielCode).Where(supWhere).Where(wareWhere).ToList(); if (stockLockInfos != null && stockLockInfos.Any()) { _outboundLockInfoRepository.DeleteAndMoveIntoHty(stockLockInfos, WIDESEA_Core.Enums.OperateTypeEnum.自动删除); } } // 检查出库单是否完成 if (CheckOutboundOrderCompleted(request.OrderNo)) { UpdateOutboundOrderStatus(request.OrderNo, OutOrderStatusEnum.出库完成.ObjToInt()); if (outboundOrder.OrderType != OutOrderTypeEnum.InternalAllocat.ObjToInt() && outboundOrder.CreateType!=OrderCreateTypeEnum.CreateInSystem.ObjToInt()) { _feedbackMesService.OutboundFeedback(outboundOrder.OrderNo); } } } catch (Exception ex) { _unitOfWorkManage.RollbackTran(); response.Success = false; response.Message = $"出库处理失败:{ex.Message}"; return WebResponseContent.Instance.Error(ex.Message); } content = WebResponseContent.Instance.OK(data: response); } catch (Exception ex) { content = WebResponseContent.Instance.Error("处理出库完成失败:" + ex.Message); } return content; } /// /// /// /// /// /// private List FindMatchingOutboundDetails(int orderId, Dt_StockInfoDetail stockDetail, List detailIds) { List details = _detailRepository.QueryData(x => x.OrderId == orderId && x.MaterielCode == stockDetail.MaterielCode && x.OrderQuantity - x.MoveQty > x.OverOutQuantity && detailIds.Contains(x.Id)); // 精确匹配:处理null值的批次、供应商、仓库 List exactMatches = details.Where(x => (string.IsNullOrEmpty(x.BatchNo)) || x.BatchNo == stockDetail.BatchNo ).Where(x => (string.IsNullOrEmpty(x.SupplyCode)) || x.SupplyCode == stockDetail.SupplyCode ).Where(x => (string.IsNullOrEmpty(x.WarehouseCode)) || x.WarehouseCode == stockDetail.WarehouseCode ).ToList(); return exactMatches; } /// /// 计算实际出库数量 /// private decimal CalculateActualOutboundQuantity(Dt_StockInfoDetail stockDetail, List outboundDetails, Dt_OutStockLockInfo lockInfo) { // decimal availableOutboundQuantity = lockInfo.AssignQuantity - lockInfo.SortedQuantity; decimal availableOutboundQuantity = lockInfo.AssignQuantity; decimal detailRemainingQuantity = outboundDetails.Sum(x => x.OrderQuantity - x.OverOutQuantity - x.MoveQty);//outboundDetail.OrderQuantity - outboundDetail.OverOutQuantity; return Math.Min( Math.Min(availableOutboundQuantity, detailRemainingQuantity), stockDetail.StockQuantity); } /// /// 执行拆包操作 /// /// /// /// /// /// /// /// public (string NewBarcode, List MaterialCodeReturnDTOs) PerformUnpackOperation(Dt_StockInfoDetail stockDetail, Dt_StockInfo stockInfo, decimal actualOutboundQuantity, OutboundCompleteRequestDTO request, decimal beforeQuantity, int taskNum, int orderId, string orderNo) { string newBarcode = GenerateNewBarcode(); string remark = $"拆包记录,原条码:{request.Barcode},原数量:{stockDetail.StockQuantity},出库条码:{newBarcode}, 出库数量:{actualOutboundQuantity},回库条码:{request.Barcode},回库数量:{stockDetail.StockQuantity - actualOutboundQuantity},操作者:{request.Operator}"; List materialCodeInfos = CreateMaterialCodeInfos(stockDetail, newBarcode, actualOutboundQuantity, remark, taskNum, orderId, orderNo); List returnDTOs = _mapper.Map>(materialCodeInfos); // 保存原始库存明细到历史记录 Dt_StockInfoDetail_Hty originalHistoryRecord = new Dt_StockInfoDetail_Hty { SourceId = stockDetail.Id, OperateType = "拆包-原始记录", InsertTime = DateTime.Now, StockId = stockDetail.StockId, MaterielCode = stockDetail.MaterielCode, MaterielName = stockDetail.MaterielName, OrderNo = stockDetail.OrderNo, BatchNo = stockDetail.BatchNo, ProductionDate = stockDetail.ProductionDate, EffectiveDate = stockDetail.EffectiveDate, SerialNumber = stockDetail.SerialNumber, StockQuantity = stockDetail.StockQuantity, OutboundQuantity = stockDetail.OutboundQuantity, Status = stockDetail.Status, Unit = stockDetail.Unit, InboundOrderRowNo = stockDetail.InboundOrderRowNo, SupplyCode = stockDetail.SupplyCode, Creater = stockDetail.Creater, CreateDate = stockDetail.CreateDate, FactoryArea = stockDetail.FactoryArea, WarehouseCode = stockDetail.WarehouseCode, Remark = $"拆包前原始记录,出库单号:{orderNo},出库单主键:{orderId},原条码:{request.Barcode},原数量:{stockDetail.StockQuantity},出库数量:{actualOutboundQuantity},操作者:{request.Operator}" }; _stockDetailHistoryRepository.AddData(originalHistoryRecord); // 保存剩余部分到历史记录 decimal remainingQuantity = stockDetail.StockQuantity - actualOutboundQuantity; if (remainingQuantity > 0) { // 更新原库存明细 stockDetail.StockQuantity = remainingQuantity; //stockDetail.Barcode = newBarcode; stockDetail.Remark = $"拆包后更新,出库单号:{orderNo},出库单主键:{orderId},原条码:{request.Barcode},新数量:{remainingQuantity},操作者:{request.Operator}"; _stockDetailRepository.UpdateData(stockDetail); } // 记录拆包变动 Dt_StockQuantityChangeRecord unpackChangeRecord = new Dt_StockQuantityChangeRecord { StockDetailId = stockDetail.Id, PalleCode = stockInfo.PalletCode, MaterielCode = stockDetail.MaterielCode, MaterielName = stockDetail.MaterielName, BatchNo = stockDetail.BatchNo, OriginalSerilNumber = request.Barcode, NewSerilNumber = newBarcode, OrderNo = request.OrderNo, TaskNum = taskNum, ChangeType = (int)StockChangeTypeEnum.Outbound, ChangeQuantity = -actualOutboundQuantity, BeforeQuantity = beforeQuantity, AfterQuantity = beforeQuantity - actualOutboundQuantity, SupplyCode = stockDetail.SupplyCode, WarehouseCode = stockDetail.WarehouseCode, Remark = $"拆包出库,出库单号:{orderNo},出库单主键:{orderId},原条码:{request.Barcode},新条码:{newBarcode},出库数量:{actualOutboundQuantity},剩余:{remainingQuantity},操作者:{request.Operator}" }; _stockChangeRepository.AddData(unpackChangeRecord); return (newBarcode, returnDTOs); } /// /// 执行完整出库操作(不拆包) /// public void PerformFullOutboundOperation(Dt_StockInfoDetail stockDetail, Dt_StockInfo stockInfo, decimal actualOutboundQuantity, OutboundCompleteRequestDTO request, decimal beforeQuantity, int taskNum) { // 保存库存明细到历史记录 Dt_StockInfoDetail_Hty historyRecord = new Dt_StockInfoDetail_Hty { SourceId = stockDetail.Id, OperateType = "出库完成", InsertTime = DateTime.Now, StockId = stockDetail.StockId, MaterielCode = stockDetail.MaterielCode, MaterielName = stockDetail.MaterielName, OrderNo = stockDetail.OrderNo, BatchNo = stockDetail.BatchNo, ProductionDate = stockDetail.ProductionDate, EffectiveDate = stockDetail.EffectiveDate, SerialNumber = stockDetail.SerialNumber, StockQuantity = stockDetail.StockQuantity, OutboundQuantity = stockDetail.OutboundQuantity + actualOutboundQuantity, Status = stockDetail.Status, Unit = stockDetail.Unit, InboundOrderRowNo = stockDetail.InboundOrderRowNo, SupplyCode = stockDetail.SupplyCode, FactoryArea = stockDetail.FactoryArea, Creater = stockDetail.Creater, CreateDate = stockDetail.CreateDate, WarehouseCode = stockDetail.WarehouseCode, Remark = $"出库完成删除,条码:{request.Barcode},原数量:{stockDetail.StockQuantity},出库数量:{actualOutboundQuantity},操作者:{request.Operator}" }; _stockDetailHistoryRepository.AddData(historyRecord); // 删除库存明细记录 _stockDetailRepository.DeleteData(stockDetail); // 记录库存变动 Dt_StockQuantityChangeRecord changeRecord = new Dt_StockQuantityChangeRecord { StockDetailId = stockDetail.Id, PalleCode = stockInfo.PalletCode, MaterielCode = stockDetail.MaterielCode, MaterielName = stockDetail.MaterielName, BatchNo = stockDetail.BatchNo, OriginalSerilNumber = request.Barcode, NewSerilNumber = "", OrderNo = request.OrderNo, TaskNum = taskNum, ChangeType = (int)StockChangeTypeEnum.Outbound, ChangeQuantity = -actualOutboundQuantity, BeforeQuantity = beforeQuantity, AfterQuantity = 0, SupplyCode = stockDetail.SupplyCode, WarehouseCode = stockDetail.WarehouseCode, Remark = $"出库完成删除库存明细,条码:{request.Barcode},出库数量:{actualOutboundQuantity},操作者:{request.Operator}" }; _stockChangeRepository.AddData(changeRecord); } /// /// 生成新的条码 /// /// 新条码 private string GenerateNewBarcode() { // 使用时间戳和随机数生成唯一条码 string newBarcode = string.Empty; newBarcode = _basicService.CreateCodeByRule(RuleCodeEnum.NewBarcodeRule.ToString()); return newBarcode; } /// /// /// /// /// /// /// /// /// private List CreateMaterialCodeInfos(Dt_StockInfoDetail stockDetail, string newBarcode, decimal splitQuantity, string remark, int taskNum, int orderId, string orderNo) { List materialCodeInfos = new List(); Dt_MaterielInfo? materielInfo = _basicService.MaterielInfoService.Repository.QueryFirst(x => x.MaterielCode == stockDetail.MaterielCode); Dt_MaterialCodeInfo outMaterialCodeInfo = new Dt_MaterialCodeInfo() { AfterQuantity = splitQuantity, BatchNo = stockDetail.BatchNo, FactoryArea = stockDetail.FactoryArea, MaterialName = materielInfo?.MaterielName ?? stockDetail.MaterielName, MaterialSpec = materielInfo?.MaterielSpec ?? "", MaterialCode = stockDetail.MaterielCode, NewBarcode = newBarcode, OldBarcode = stockDetail.Barcode, OriginalQuantity = stockDetail.StockQuantity, PruchaseOrderNo = stockDetail.OrderNo, SuplierCode = stockDetail.SupplyCode, Unit = stockDetail.Unit, Date = DateTime.Now.ToString("yyyy-MM-dd"), Remark = remark, WarehouseCode = stockDetail.WarehouseCode, OrderNo = orderNo, OrderId = orderId, ReturnStatus = 0 }; materialCodeInfos.Add(outMaterialCodeInfo); Dt_MaterialCodeInfo returnMaterialCodeInfo = new Dt_MaterialCodeInfo() { AfterQuantity = stockDetail.StockQuantity - splitQuantity, BatchNo = stockDetail.BatchNo, FactoryArea = stockDetail.FactoryArea, MaterialName = materielInfo?.MaterielName ?? stockDetail.MaterielName, MaterialSpec = materielInfo?.MaterielSpec ?? "", MaterialCode = stockDetail.MaterielCode, NewBarcode = stockDetail.Barcode, OldBarcode = stockDetail.Barcode, OriginalQuantity = stockDetail.StockQuantity, PruchaseOrderNo = stockDetail.OrderNo, SuplierCode = stockDetail.SupplyCode, Unit = stockDetail.Unit, Date = DateTime.Now.ToString("yyyy-MM-dd"), Remark = remark, WarehouseCode = stockDetail.WarehouseCode, OrderNo = orderNo, OrderId = orderId, ReturnStatus = 0 }; materialCodeInfos.Add(returnMaterialCodeInfo); _basicService.MaterielCodeInfoService.Repository.AddData(materialCodeInfos); return materialCodeInfos; } /// /// 更新该托盘该物料的所有锁定记录的累计已出库数量 /// /// 库存ID /// 物料编号 /// 批次号 /// 本次实际出库数量 /// private void UpdateLockInfoAllocatedQuantity(int stockId, string materielCode, string batchNo, decimal actualOutboundQuantity) { // 查询该托盘该物料的所有锁定记录 List lockRecords = _outboundLockInfoRepository.QueryData(x => x.StockId == stockId && x.MaterielCode == materielCode && x.BatchNo == batchNo); if (lockRecords != null && lockRecords.Any()) { // 更新所有相关记录的AllocatedQuantity foreach (var record in lockRecords) { record.AllocatedQuantity += actualOutboundQuantity; } // 批量更新 _outboundLockInfoRepository.UpdateData(lockRecords); } } /// /// 检查出库单是否完成 /// public bool CheckOutboundOrderCompleted(string orderNo) { Dt_OutboundOrder outboundOrder = _outboundRepository.QueryFirst(x => x.OrderNo == orderNo); if (outboundOrder == null) return false; List details = _detailRepository.QueryData(x => x.OrderId == outboundOrder.Id); // 检查所有明细的已出数量是否都等于单据数量 return details.All(x => x.OverOutQuantity >= x.OrderQuantity - x.MoveQty); } /// /// 检查出库单明细是否完成 /// public bool CheckOutboundOrderDetailCompletedByMatCode(string orderNo, string materialCode, Dt_OutboundOrderDetail outboundOrderDetail) { Dt_OutboundOrder outboundOrder = _outboundRepository.QueryFirst(x => x.OrderNo == orderNo); if (outboundOrder == null) return false; List details = _detailRepository.QueryData(x => x.OrderId == outboundOrder.Id && x.MaterielCode == materialCode && (string.IsNullOrEmpty(outboundOrderDetail.SupplyCode) || x.SupplyCode == outboundOrderDetail.SupplyCode) && (string.IsNullOrEmpty(outboundOrderDetail.WarehouseCode) || x.WarehouseCode == outboundOrderDetail.WarehouseCode)); // 检查所有明细的已出数量是否都等于单据数量 return details.All(x => x.OverOutQuantity >= x.OrderQuantity - x.MoveQty); } #endregion #region 取空箱 public async Task EmptyBox(string palletCode) { WebResponseContent content = new WebResponseContent(); try { var stock = await _stockInfoRepository.Db.Queryable().Includes(x => x.Details).Where(x => x.PalletCode == palletCode).FirstAsync(); if (stock == null) { return content.Error($"未找到托盘{palletCode}库存信息"); } if (stock.Details.Count > 0) { return content.Error($"托盘{palletCode}还存在库存信息不允许取走"); } Dt_StockInfo_Hty stockInfo_Hty = stock.Adapt(); stockInfo_Hty.SourceId = stock.Id; stockInfo_Hty.OperateType = "取空箱"; stockInfo_Hty.InsertTime = DateTime.Now; _unitOfWorkManage.BeginTran(); await _outboundRepository.Db.InsertNav(stockInfo_Hty).IncludesAllFirstLayer().ExecuteCommandAsync(); await _stockInfoRepository.DeleteDataByIdAsync(stock.Id); _unitOfWorkManage.CommitTran(); return content.OK(); } catch (Exception ex) { _unitOfWorkManage.RollbackTran(); return content.Error(ex.Message); } } #endregion #region 回库 public async Task ReturnToWarehouse(string palletCode, string OrderNo, string station) { WebResponseContent content = new WebResponseContent(); try { var stock = await _stockInfoRepository.Db.Queryable().Includes(x => x.Details).Where(x => x.PalletCode == palletCode).FirstAsync(); if (stock == null) { return content.Error($"未找到托盘{palletCode}库存信息不允许回库"); } if (stock.Details.Count <= 0) { stock.PalletType = (int)PalletTypeEnum.Empty; stock.StockStatus = (int)StockStatusEmun.组盘暂存; stock.LocationCode = ""; } else if (stock.Details.Count > 0) { Dt_OutStockLockInfo lockInfo = _outboundLockInfoRepository.QueryFirst(x => x.OrderNo == OrderNo && x.StockId == stock.Id && x.PalletCode == palletCode); if (lockInfo != null && lockInfo.SortedQuantity != lockInfo.AssignQuantity) { return content.Error($"托盘{palletCode}库存未拣选完不允许回库"); } stock.StockStatus = (int)StockStatusEmun.组盘暂存; stock.LocationCode = ""; } var task = await _taskRepository.Db.Queryable() .Where(x => x.PalletCode == palletCode) .FirstAsync(); if (task != null) { return content.Error($"托盘{palletCode}存在任务回库失败!"); } // 分配新货位 var newLocation = _locationInfoService.AssignLocation(stock.LocationType); var newTask = new Dt_Task() { CurrentAddress = stations.GetValueOrDefault(station) ?? "", Grade = 0, PalletCode = palletCode, NextAddress = "", OrderNo = OrderNo, Roadway = newLocation.RoadwayNo, SourceAddress = stations.GetValueOrDefault(station) ?? "", TargetAddress = newLocation.LocationCode, TaskStatus = (int)TaskStatusEnum.New, TaskType = stock.Details.Count > 0 ? (int)TaskTypeEnum.InPick : (int)TaskTypeEnum.InEmpty, PalletType = stock.PalletType, WarehouseId = stock.WarehouseId }; _stockInfoRepository.UpdateData(stock); _taskRepository.AddData(newTask); var moveResult = await _eSSApiService.MoveContainerAsync(new MoveContainerRequest { slotCode = movestations[station], containerCode = palletCode }); return content.OK(); } catch (Exception ex) { return content.Error(ex.Message); } } public WebResponseContent RecheckPicking(RecheckPickingDTO pickingDTO) { try { Dt_ReCheckOrder reCheckOrder = _outboundRepository.Db.Queryable().Where(x => x.OrderNo == pickingDTO.orderNo && x.Result == 0).First(); if(reCheckOrder == null) { return WebResponseContent.Instance.Error($"未找到该待重拣的单据{pickingDTO.orderNo}"); } Dt_StockInfoDetail stockInfoDetail = _stockDetailRepository.QueryFirst(x=>x.Barcode == pickingDTO.barCode && x.Status == StockStatusEmun.手动冻结.ObjToInt() ); if(stockInfoDetail == null) { return WebResponseContent.Instance.Error($"未在库存中找到该冻结/隔离条码 {pickingDTO.barCode}"); } if (stockInfoDetail.MaterielCode != reCheckOrder.MaterielCode || stockInfoDetail.BatchNo != reCheckOrder.BatchNo) { return WebResponseContent.Instance.Error("该条码的物料编码和批次和该重检单不符"); } stockInfoDetail.OrderNo = pickingDTO.orderNo; stockInfoDetail.Status = StockStatusEmun.重检中.ObjToInt(); var currentRemark = _outboundRepository.Db.Queryable() .Where(x => x.OrderNo == pickingDTO.orderNo) .Select(x => x.Remark) .First(); string newRemark; if (string.IsNullOrWhiteSpace(currentRemark)) { newRemark = pickingDTO.barCode; } else { var existingCodes = currentRemark.Split(',', StringSplitOptions.RemoveEmptyEntries) .Select(s => s.Trim()) .ToList(); if (!existingCodes.Contains(pickingDTO.barCode)) { existingCodes.Add(pickingDTO.barCode); newRemark = string.Join(",", existingCodes); } else { newRemark = currentRemark; } } _outboundRepository.Db.Updateable() .SetColumns(x => x.Remark == newRemark) .SetColumns(x=>x.OrderStatus == (int)OutOrderStatusEnum.出库完成) .Where(x => x.OrderNo == pickingDTO.orderNo) .ExecuteCommand(); _stockDetailRepository.UpdateData(stockInfoDetail); return WebResponseContent.Instance.OK(); } catch(Exception ex) { return WebResponseContent.Instance.Error(ex.Message); } } #endregion } }