using AutoMapper; using Dm.filter; using MailKit.Search; using Mapster; using Microsoft.IdentityModel.Tokens; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; using OfficeOpenXml.FormulaParsing.Excel.Functions.Math; using Org.BouncyCastle.Asn1.Ocsp; using Org.BouncyCastle.Crypto; using SqlSugar; using System; using System.Reflection.Emit; 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.Outbound; using WIDESEA_DTO.ReturnMES; using WIDESEA_IBasicService; using WIDESEA_IOutboundService; using WIDESEA_IRecordService; using WIDESEA_IStockService; using WIDESEA_Model.Models; using WIDESEA_Model.Models.Basic; 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; public readonly IRepository _inboundOrderDetailRepository; public readonly IRepository _inboundOrderRepository; public readonly IRepository _warehouseAreaRepository; public readonly IRepository _locationTypeRepository; public readonly IRepository _outboundOrderDetailRepository; 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, IRepository inboundOrderDetailRepository, IRepository inboundOrderRepository, IRepository locationTypeRepository, IRepository warehouseAreaRepository, IRepository outboundOrderDetailRepository) { _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; _inboundOrderDetailRepository = inboundOrderDetailRepository; _inboundOrderRepository = inboundOrderRepository; _locationTypeRepository = locationTypeRepository; _warehouseAreaRepository = warehouseAreaRepository; _outboundOrderDetailRepository = outboundOrderDetailRepository; } public WebResponseContent PrintFromData (string barcode) { var detail = _inboundOrderDetailRepository.QueryFirst(x => x.Barcode == barcode); if(detail == null) { return WebResponseContent.Instance.Error(); } var inbound = _inboundOrderRepository.QueryFirst(x=>x.Id == detail.OrderId); if(inbound == null) { return WebResponseContent.Instance.Error(); } var printFormData = new PrintFromDataDTO { materialCode = detail.MaterielCode, materialName = detail.MaterielName, materialSpec = detail.Unit, batchNo = detail.BatchNo, pruchaseOrderNo = inbound.UpperOrderNo, factoryArea = inbound.FactoryArea, suplierCode = detail.SupplyCode, quantity = detail.BarcodeQty }; return WebResponseContent.Instance.OK(data:printFormData); } #region 出库分配 /// /// 分拣出库操作 /// /// 分拣出库请求 /// 分拣出库响应 public WebResponseContent ProcessPickingOutbound(PickingOutboundRequestDTO request) { WebResponseContent content = WebResponseContent.Instance; PickingOutboundResponseDTO response = new PickingOutboundResponseDTO(); decimal totalNeedAllocate = 0; // 总需求分配量 decimal totalActualAllocate = 0; // 实际总分配量 string targetWarehouse = string.Empty;// 目标仓库 string targetLocationCode = string.Empty; // 目标货位 bool isWholeCaseOutbound = false; // 是否整箱出库 List wholeCasePallets = new List(); Dictionary palletLocationMap = new Dictionary(); Dictionary palletIsWholeCaseMap = new Dictionary(); int? targetLocationType = null; 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); if (outboundOrder.OrderType == 117) { // 匹配当前单据的锁定记录 if (outboundOrder.OrderNo == item.OrderNo) { // 查询库存信息 var stockInfo = _stockInfoRepository.QueryFirst(x => x.PalletCode == item.PalletCode); if (stockInfo == null) { content = WebResponseContent.Instance.Error($"托盘{item.PalletCode}未查询到库存信息,无法处理整箱出库"); _unitOfWorkManage.RollbackTran(); return content; } // 计算库存总量,判断是否整箱(增加0值保护) decimal stockQuantity = _stockDetailRepository.QueryData(x => x.StockId == stockInfo.Id).Sum(x => x.StockQuantity); if (stockQuantity > 0 && stockQuantity == item.AssignQuantity) { // 标记当前托盘为整箱(核心修复:按托盘维度记录状态) if (!palletIsWholeCaseMap.ContainsKey(item.PalletCode)) { palletIsWholeCaseMap.Add(item.PalletCode, true); } else { palletIsWholeCaseMap[item.PalletCode] = true; } // 目标仓库/货位类型只查询一次(性能优化+空值保护) if (string.IsNullOrEmpty(targetWarehouse)) { targetWarehouse = GetToWarehouseByOrderNo(request.OrderNo); if (string.IsNullOrEmpty(targetWarehouse)) { content = WebResponseContent.Instance.Error("智仓调智仓整箱出库单据未配置目标仓库"); _unitOfWorkManage.RollbackTran(); return content; } // 替换First()为FirstOrDefault(),避免空值异常 string warehouseAreaName = _warehouseAreaRepository.Db.Queryable() .Where(x => x.Code == targetWarehouse) .Select(x => x.Name) .First(); if (string.IsNullOrEmpty(warehouseAreaName)) { content = WebResponseContent.Instance.Error($"目标仓库{targetWarehouse}未查询到对应的库区名称"); _unitOfWorkManage.RollbackTran(); return content; } int? locationType = _locationTypeRepository.Db.Queryable() .Where(x => string.Equals(x.LocationTypeDesc, warehouseAreaName, StringComparison.OrdinalIgnoreCase)) .Select(x => x.LocationType) .First(); if (!locationType.HasValue) { content = WebResponseContent.Instance.Error($"库区{warehouseAreaName}未匹配到对应的货位类型"); _unitOfWorkManage.RollbackTran(); return content; } targetLocationType = locationType.Value; } // 分配目标货位(每个托盘独立货位) if (!palletLocationMap.ContainsKey(item.PalletCode) && targetLocationType.HasValue) { Dt_LocationInfo locationInfo = _locationInfoService.AssignLocation(targetLocationType.Value); if (locationInfo == null || string.IsNullOrEmpty(locationInfo.LocationCode)) { content = WebResponseContent.Instance.Error($"货位类型{targetLocationType.Value}未分配到可用货位(托盘:{item.PalletCode})"); _unitOfWorkManage.RollbackTran(); return content; } palletLocationMap.Add(item.PalletCode, locationInfo.LocationCode); } // 加入整箱托盘列表 if (!wholeCasePallets.Contains(item.PalletCode)) { wholeCasePallets.Add(item.PalletCode); } } else { if (!palletIsWholeCaseMap.ContainsKey(item.PalletCode)) { palletIsWholeCaseMap.Add(item.PalletCode, false); } else { palletIsWholeCaseMap[item.PalletCode] = false; } } } } } // 处理任务 foreach (var item in materielPickedDetails.Tasks) { if (outboundOrder.OrderType == 117 && palletIsWholeCaseMap.ContainsKey(item.PalletCode) && palletIsWholeCaseMap[item.PalletCode] && palletLocationMap.ContainsKey(item.PalletCode)) { item.TaskType = (int)TaskTypeEnum.Relocation; item.TargetAddress = palletLocationMap[item.PalletCode]; } 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 = 0; if (detail.LockQuantity > detail.OverOutQuantity && detail.OverOutQuantity > 0) { maxLockableQty = detail.OrderQuantity - detail.LockQuantity; } else if(detail.OverOutQuantity > 0) { maxLockableQty = detail.OrderQuantity - detail.OverOutQuantity; } else { maxLockableQty = detail.OrderQuantity - detail.OverOutQuantity - detail.LockQuantity; } 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); if (outboundOrder.OrderType == 117 && wholeCasePallets.Any()) { foreach (var palletCode in wholeCasePallets) { var completeReq = new OutboundCompletePalletRequestDTO { OrderNo = request.OrderNo, PalletCode = palletCode }; var res = CompleteOutboundWithPallet(completeReq); if (!res.Status) { _unitOfWorkManage.RollbackTran(); return res; } } } _unitOfWorkManage.CommitTran(); // 4. 构造响应:区分「全部分配」和「部分分配」 string responseMsg = totalActualAllocate == totalNeedAllocate ? "分拣任务分配成功" : $"分拣任务分配完成(实际分配{totalActualAllocate},需求{totalNeedAllocate},库存不足部分未分配)"; Dt_OutboundOrder outboundOrder1 = _outboundRepository.Db.Queryable().Where(x => x.OrderNo == request.OrderNo).Includes(x=>x.Details).First(); if(totalActualAllocate == 0 && !outboundOrder1.Details.Any(x=>x.LockQuantity >0)) { UpdateOutboundOrderStatus(request.OrderNo, (int)OutOrderStatusEnum.未开始); return WebResponseContent.Instance.Error("分配库存数量为0,无法出库"); } 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 String GetToWarehouseByOrderNo(string orderNo) { var order =_allocateOrderRepository.QueryFirst(x => x.OrderNo == orderNo); return order.ToWarehouse; } /// /// 计算出库数量逻辑(原有逻辑不变) /// 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); if(item.ReturnToMESStatus == 0) { item.CurrentDeliveryQty = item.LockQuantity; } else { item.CurrentDeliveryQty += item.LockQuantity - item.OverOutQuantity; } item.OverOutQuantity = 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 (CheckOutboundOrderDetailCompletedByMatCode(request.OrderNo, lockInfo.MaterielCode, outboundOrderDetails)) { 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; var idStr = outboundOrderDetails.First().Id.ToString(); var stockLockInfos = _outboundLockInfoRepository.QueryData(x => (x.OrderDetailIds == idStr || x.OrderDetailIds.StartsWith(idStr + ",") || x.OrderDetailIds.EndsWith("," + idStr) || x.OrderDetailIds.Contains("," + idStr + ",")) && x.OrderNo == request.OrderNo && x.MaterielCode == stockInfoDetail.MaterielCode) .Where(supWhere) .Where(wareWhere) .ToList(); if (stockLockInfos != null && stockLockInfos.Any()) { _outboundLockInfoRepository.DeleteAndMoveIntoHty(stockLockInfos, WIDESEA_Core.Enums.OperateTypeEnum.自动删除); } outboundOrderDetails.FirstOrDefault().OrderDetailStatus = (int)OrderDetailStatusEnum.Over; _detailRepository.UpdateData(outboundOrderDetails); } // 检查出库单是否完成 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); // 删除库存明细记录 var orderNo =_outboundRepository.QueryFirst(x => x.OrderNo == request.OrderNo); if(orderNo.OrderType != 117) { _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); if (item.ReturnToMESStatus == 0) { item.CurrentDeliveryQty = item.LockQuantity; } else { item.CurrentDeliveryQty += item.LockQuantity - item.OverOutQuantity; } item.OverOutQuantity = item.LockQuantity; } if (item.OverOutQuantity == item.OrderQuantity) { item.OrderDetailStatus = (int)OrderDetailStatusEnum.Over; } 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)) { 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; var idStr = outboundOrderDetails.First().Id.ToString(); var stockLockInfos = _outboundLockInfoRepository.QueryData(x => (x.OrderDetailIds == idStr || x.OrderDetailIds.StartsWith(idStr + ",") || x.OrderDetailIds.EndsWith("," + idStr) || x.OrderDetailIds.Contains("," + idStr + ",")) && 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)) { if(outboundOrder.OrderType != OutOrderTypeEnum.InternalAllocat.ObjToInt()) { UpdateOutboundOrderStatus(request.OrderNo, OutOrderStatusEnum.出库完成.ObjToInt()); if (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, Barcode = stockDetail.Barcode, 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, ValidDate = stockDetail.ValidDate, 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, List outboundOrderDetails) { if (string.IsNullOrEmpty(orderNo) || string.IsNullOrEmpty(materialCode) || outboundOrderDetails == null || !outboundOrderDetails.Any()) return false; // 查询主订单,不存在直接返回false Dt_OutboundOrder outboundOrder = _outboundRepository.QueryFirst(x => x.OrderNo == orderNo); if (outboundOrder == null) return false; var firstDetail = outboundOrderDetails.FirstOrDefault(); string supplyCode = firstDetail.SupplyCode; string warehouseCode = firstDetail.WarehouseCode; List ids = outboundOrderDetails.Select(x => x.Id).ToList(); List details = _detailRepository.QueryData(x => x.OrderId == outboundOrder.Id && x.MaterielCode == materialCode && ids.Contains(x.Id) && (string.IsNullOrEmpty(supplyCode) || x.SupplyCode == supplyCode) && (string.IsNullOrEmpty(warehouseCode) || x.WarehouseCode == warehouseCode) ); if (!details.Any()) return false; 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(); Dt_Task task = _taskRepository.QueryFirst(x => x.PalletCode == palletCode); if (task != null) { return WebResponseContent.Instance.Error("任务信息列表存在该托盘的任务信息,不可取走空箱,请检查任务是否完成"); } 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.StockId == stock.Id && x.PalletCode == palletCode); if (lockInfo != null && lockInfo.SortedQuantity != lockInfo.AssignQuantity) { // 1. 计算需要回滚的总数量 decimal? rollbackTotalQuantity = lockInfo.AssignQuantity - lockInfo.SortedQuantity; // 确保回滚数量为正数 if (rollbackTotalQuantity <= 0) { // 没有需要回滚的数量 stock.StockStatus = (int)StockStatusEmun.入库确认; stock.LocationCode = ""; } try { //处理OrderDetailIds,分割并转换为ID列表 List orderDetailIds = new List(); if (!string.IsNullOrEmpty(lockInfo.OrderDetailIds)) { orderDetailIds = lockInfo.OrderDetailIds.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) .Select(id => { if (long.TryParse(id.Trim(), out long result)) { return result; } return 0; // 无效ID标记为0 }) .Where(id => id > 0) .OrderByDescending(id => id) .ToList(); } if (orderDetailIds.Count == 0) { return WebResponseContent.Instance.Error("单据锁定出库单明细Id无效,检查锁定出库数据是否正确"); } //查询对应的订单明细 List orderDetails = _outboundRepository.Db.Queryable() .Where(x => orderDetailIds.Contains(x.Id)) .ToList(); if (orderDetails.Count == 0) { return WebResponseContent.Instance.Error("未找到可回滚明细,请检查出库单明细"); } decimal remainingRollbackQty = (decimal)rollbackTotalQuantity; foreach (var detail in orderDetails) { if (remainingRollbackQty <= 0) { break; } // 计算该明细的可回滚数量 decimal availableRollbackQty = detail.LockQuantity - detail.OverOutQuantity - detail.MoveQty; availableRollbackQty = Math.Max(0, availableRollbackQty); if (availableRollbackQty <= 0) { continue; // 该明细无可回滚数量,跳过 } // 计算本次实际回滚数量(取可回滚数量和剩余需要回滚数量的较小值) decimal actualRollbackQty = Math.Min(availableRollbackQty, remainingRollbackQty); detail.LockQuantity -= actualRollbackQty; detail.LockQuantity = Math.Max(0, detail.LockQuantity); _detailRepository.UpdateData(detail); //更新剩余需要回滚的数量 remainingRollbackQty -= actualRollbackQty; } if (remainingRollbackQty > 0) { return WebResponseContent.Instance.Error($"剩余回滚数量{remainingRollbackQty}"); } _outboundLockInfoRepository.DeleteAndMoveIntoHty(lockInfo, WIDESEA_Core.Enums.OperateTypeEnum.人工删除); } catch (Exception ex) { return WebResponseContent.Instance.Error(ex.Message); } } 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); if(newLocation == null) { return WebResponseContent.Instance.Error("没有空闲库位可回库"); } 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 #region 撤销拣选 /// /// 撤销拣选条码(反向回滚出库拣选操作) /// /// 撤销拣选请求(至少包含条码、订单号、托盘号) /// 撤销响应 public WebResponseContent ReversePicking(ReversePickingRequestDTO request) { WebResponseContent content = WebResponseContent.Instance; ReversePickingResponseDTO response = new ReversePickingResponseDTO(); try { if (string.IsNullOrWhiteSpace(request.Barcode) || string.IsNullOrWhiteSpace(request.OrderNo) || string.IsNullOrWhiteSpace(request.PalletCode)) { response.Success = false; response.Message = "条码、订单号、托盘号不能为空"; return WebResponseContent.Instance.Error(response.Message); } Dt_StockInfo stockInfo = _stockInfoRepository.QueryFirst(x => x.PalletCode == request.PalletCode); if (stockInfo == null) { response.Success = false; response.Message = $"托盘号 {request.PalletCode} 对应的库存不存在"; return WebResponseContent.Instance.Error(response.Message); } 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_Hty historyDetail = _stockDetailHistoryRepository.QueryFirst(x => x.Barcode == request.Barcode && (x.OperateType == "出库完成" || x.OperateType == "拆包-原始记录")); if (historyDetail != null) { double minutesDiff = (DateTime.Now - historyDetail.InsertTime).TotalMinutes; if (minutesDiff >= 30) { return WebResponseContent.Instance.Error($"条码{request.Barcode}已无法撤销"); } } else { return WebResponseContent.Instance.Error($"条码{request.Barcode}已无法撤销"); } Dt_OutStockLockInfo lockInfo = _outboundLockInfoRepository.QueryFirst(x => x.OrderNo == request.OrderNo && x.StockId == stockInfo.Id && x.MaterielCode == historyDetail.MaterielCode && x.PalletCode == stockInfo.PalletCode); if (lockInfo == null) { return WebResponseContent.Instance.Error("该托盘已全部拣选完,不允许撤销"); } var details = _outboundOrderDetailRepository.QueryData(x => x.OrderId == outboundOrder.Id); var outboundDetails = details .Where(x => x.MaterielCode == historyDetail.MaterielCode && !string.IsNullOrEmpty(lockInfo.OrderDetailIds) && lockInfo.OrderDetailIds.Split(",") .Any(idStr => idStr.Trim() == x.Id.ToString()) ).ToList(); var detail = outboundDetails.FirstOrDefault(); if (detail != null && !string.IsNullOrEmpty(detail.ReturnJsonData)) { try { string deleteBarcode = request.Barcode; var list = JsonConvert.DeserializeObject>(detail.ReturnJsonData); if (list != null) { var filteredList = list.Where(x => x.barcode != deleteBarcode).ToList(); detail.ReturnJsonData = JsonConvert.SerializeObject(filteredList); _outboundOrderDetailRepository.UpdateData(detail); } } catch { } } _unitOfWorkManage.BeginTran(); try { bool isUnpack = historyDetail.OperateType == "拆包-原始记录"; if (isUnpack) { return WebResponseContent.Instance.Error("该条码已拆包,不允许撤销"); } else { ReverseFullOutboundOperation(historyDetail, stockInfo, request); } RollbackOutboundOrderDetails(historyDetail.MaterielCode, request.OrderNo, historyDetail.StockQuantity, stockInfo.Id); lockInfo.SortedQuantity = Math.Max(0, (decimal)(lockInfo.SortedQuantity - historyDetail.StockQuantity)); _outboundLockInfoRepository.UpdateData(lockInfo); _stockDetailHistoryRepository.DeleteData(historyDetail); DeleteStockChangeRecord(request.Barcode, request.OrderNo); if (!CheckOutboundOrderCompleted(request.OrderNo)) { UpdateOutboundOrderStatus(request.OrderNo, OutOrderStatusEnum.出库中.ObjToInt()); } _unitOfWorkManage.CommitTran(); response.Success = true; response.Message = $"条码 {request.Barcode} 撤销拣选成功"; response.Barcode = request.Barcode; response.RestoredQuantity = historyDetail.StockQuantity; content = WebResponseContent.Instance.OK(data: response); } catch (Exception ex) { _unitOfWorkManage.RollbackTran(); response.Success = false; response.Message = $"撤销拣选失败:{ex.Message}"; content = WebResponseContent.Instance.Error(response.Message); } } catch (Exception ex) { response.Success = false; response.Message = $"处理撤销拣选失败:{ex.Message}"; content = WebResponseContent.Instance.Error(response.Message); } return content; } /// /// 撤销拆包出库操作(反向恢复拆包前状态) /// private void ReverseUnpackOperation(Dt_StockInfoDetail_Hty historyDetail, Dt_StockInfo stockInfo, ReversePickingRequestDTO request, Dt_OutboundOrder outboundOrder) { Dt_StockInfoDetail currentDetail = _stockDetailRepository.QueryFirst(x => x.Barcode == request.Barcode && x.StockId == stockInfo.Id && x.MaterielCode == historyDetail.MaterielCode); if (currentDetail != null) { currentDetail.StockQuantity = historyDetail.StockQuantity; currentDetail.Remark = $"撤销拆包拣选,恢复原始数量:{historyDetail.StockQuantity},操作者:{request.Operator}"; _stockDetailRepository.UpdateData(currentDetail); } else { Dt_StockInfoDetail restoreDetail = new Dt_StockInfoDetail { StockId = historyDetail.StockId, MaterielCode = historyDetail.MaterielCode, MaterielName = historyDetail.MaterielName, Barcode =historyDetail.Barcode, OrderNo = historyDetail.OrderNo, BatchNo = historyDetail.BatchNo, ProductionDate = historyDetail.ProductionDate, EffectiveDate = historyDetail.EffectiveDate, SerialNumber = historyDetail.SerialNumber, StockQuantity = historyDetail.StockQuantity, OutboundQuantity = historyDetail.OutboundQuantity, Status = historyDetail.Status, Unit = historyDetail.Unit, InboundOrderRowNo = historyDetail.InboundOrderRowNo, SupplyCode = historyDetail.SupplyCode, Creater = request.Operator, CreateDate = DateTime.Now, FactoryArea = historyDetail.FactoryArea, WarehouseCode = historyDetail.WarehouseCode, Remark = $"撤销拆包拣选,重新创建库存明细,条码:{request.Barcode},操作者:{request.Operator}" }; _stockDetailRepository.AddData(restoreDetail); } List materialCodeInfos = _basicService.MaterielCodeInfoService.Repository.QueryData(x => x.OldBarcode == request.Barcode && x.OrderNo == request.OrderNo); if (materialCodeInfos.Any()) { _basicService.MaterielCodeInfoService.Repository.DeleteData(materialCodeInfos); } if (outboundOrder.OrderType == InOrderTypeEnum.InternalAllocat.ObjToInt()) { Dt_AllocateMaterialInfo allocateMaterialInfo = _allocateMaterialInfoRepository.QueryFirst(x => x.Barcode == request.Barcode && x.OrderNo == request.OrderNo); if (allocateMaterialInfo != null) { _allocateMaterialInfoRepository.DeleteData(allocateMaterialInfo); } } } /// /// 撤销完整出库操作(恢复被删除的库存明细) /// private void ReverseFullOutboundOperation(Dt_StockInfoDetail_Hty historyDetail, Dt_StockInfo stockInfo, ReversePickingRequestDTO request) { Dt_StockInfoDetail restoreDetail = new Dt_StockInfoDetail { StockId = historyDetail.StockId, MaterielCode = historyDetail.MaterielCode, MaterielName = historyDetail.MaterielName, Barcode = historyDetail.Barcode, OrderNo = historyDetail.OrderNo, BatchNo = historyDetail.BatchNo, ProductionDate = historyDetail.ProductionDate, EffectiveDate = historyDetail.EffectiveDate, SerialNumber = historyDetail.SerialNumber, StockQuantity = historyDetail.StockQuantity, OutboundQuantity = historyDetail.OutboundQuantity - historyDetail.StockQuantity, Status = historyDetail.Status, Unit = historyDetail.Unit, InboundOrderRowNo = historyDetail.InboundOrderRowNo, SupplyCode = historyDetail.SupplyCode, Creater = request.Operator, CreateDate = DateTime.Now, FactoryArea = historyDetail.FactoryArea, WarehouseCode = historyDetail.WarehouseCode, Remark = $"撤销完整出库拣选,恢复库存明细,条码:{request.Barcode},操作者:{request.Operator}" }; _stockDetailRepository.AddData(restoreDetail); if (request.OrderType == InOrderTypeEnum.InternalAllocat.ObjToInt()) { Dt_AllocateMaterialInfo allocateMaterialInfo = _allocateMaterialInfoRepository.QueryFirst(x => x.Barcode == request.Barcode && x.OrderNo == request.OrderNo); if (allocateMaterialInfo != null) { _allocateMaterialInfoRepository.DeleteData(allocateMaterialInfo); } } } /// /// 回滚出库单明细(扣减已出库数量) /// private void RollbackOutboundOrderDetails(string materielCode, string orderNo, decimal rollbackQuantity, int stockId) { Dt_OutboundOrder outboundOrder = _outboundRepository.QueryFirst(x => x.OrderNo == orderNo); List details = _detailRepository.QueryData(x => x.OrderId == outboundOrder.Id && x.MaterielCode == materielCode && x.OverOutQuantity > 0); if (!details.Any()) return; decimal remainingRollbackQty = rollbackQuantity; foreach (var detail in details) { if (remainingRollbackQty <= 0) break; decimal rollbackQty = Math.Min(remainingRollbackQty, detail.OverOutQuantity); detail.OverOutQuantity -= rollbackQty; detail.CurrentDeliveryQty -= rollbackQty; if (detail.OrderDetailStatus == (int)OrderDetailStatusEnum.Over && detail.OverOutQuantity < detail.OrderQuantity - detail.MoveQty) { detail.OrderDetailStatus = (int)OrderDetailStatusEnum.New; } if (!string.IsNullOrEmpty(detail.ReturnJsonData)) { List barcodesList = JsonConvert.DeserializeObject>(detail.ReturnJsonData) ?? new List(); var targetBarcode = barcodesList.FirstOrDefault(x => x.Barcode == materielCode); if (targetBarcode != null) { barcodesList.Remove(targetBarcode); detail.ReturnJsonData = JsonConvert.SerializeObject(barcodesList, new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() }); } } _detailRepository.UpdateData(detail); remainingRollbackQty -= rollbackQty; } } /// /// 删除库存变动记录(拣选时生成的) /// private void DeleteStockChangeRecord(string barcode, string orderNo) { Dt_StockQuantityChangeRecord changeRecord = _stockChangeRepository.QueryFirst(x => x.OriginalSerilNumber == barcode && x.OrderNo == orderNo && x.ChangeType == (int)StockChangeTypeEnum.Outbound); if (changeRecord != null) { _stockChangeRepository.DeleteData(changeRecord); } } #endregion } /// /// 条码明细实体 /// public class BarcodeItem { /// /// 条码 /// public string barcode { get; set; } /// /// 数量 /// public decimal qty { get; set; } /// /// 供应商编码 /// public string supplyCode { get; set; } /// /// 批次号 /// public string batchNo { get; set; } /// /// 单位 /// public string unit { get; set; } } }