heshaofeng
2026-03-20 c9db6bb46441d1be1404ca71ff2d38a65e7cac89
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundService.cs
@@ -1,14 +1,16 @@
using System.Reflection.Emit;
using AutoMapper;
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;
@@ -29,6 +31,7 @@
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;
@@ -65,6 +68,8 @@
        private readonly IRepository<Dt_AllocateMaterialInfo> _allocateMaterialInfoRepository;
        public readonly IRepository<Dt_InboundOrderDetail> _inboundOrderDetailRepository;
        public readonly IRepository<Dt_InboundOrder> _inboundOrderRepository;
        public readonly IRepository<Dt_WarehouseArea> _warehouseAreaRepository;
        public readonly IRepository<Dt_LocationType> _locationTypeRepository;
        private Dictionary<string, string> stations = new Dictionary<string, string>
        {
@@ -78,7 +83,7 @@
            {"3-1","3-5" },
        };
        public OutboundService(IMapper mapper, IUnitOfWorkManage unitOfWorkManage, IRepository<Dt_OutboundOrderDetail> detailRepository, IRepository<Dt_OutboundOrder> outboundRepository, IRepository<Dt_OutStockLockInfo> outboundLockInfoRepository, IRepository<Dt_StockInfo> stockInfoRepository, IRepository<Dt_StockInfoDetail> stockDetailRepository, IRepository<Dt_StockQuantityChangeRecord> stockChangeRepository, IRepository<Dt_StockInfoDetail_Hty> stockDetailHistoryRepository, IBasicService basicService, IOutboundOrderDetailService outboundOrderDetailService, IOutboundOrderService outboundOrderService, IOutStockLockInfoService outboundStockLockInfoService, IFeedbackMesService feedbackMesService, IRepository<Dt_Task> taskRepository, ILocationInfoService locationInfoService, IESSApiService eSSApiService, IRepository<Dt_AllocateOrder> allocateOrderRepository, IRepository<Dt_AllocateMaterialInfo> allocateMaterialInfoRepository, IRepository<Dt_InboundOrderDetail> inboundOrderDetailRepository, IRepository<Dt_InboundOrder> inboundOrderRepository)
        public OutboundService(IMapper mapper, IUnitOfWorkManage unitOfWorkManage, IRepository<Dt_OutboundOrderDetail> detailRepository, IRepository<Dt_OutboundOrder> outboundRepository, IRepository<Dt_OutStockLockInfo> outboundLockInfoRepository, IRepository<Dt_StockInfo> stockInfoRepository, IRepository<Dt_StockInfoDetail> stockDetailRepository, IRepository<Dt_StockQuantityChangeRecord> stockChangeRepository, IRepository<Dt_StockInfoDetail_Hty> stockDetailHistoryRepository, IBasicService basicService, IOutboundOrderDetailService outboundOrderDetailService, IOutboundOrderService outboundOrderService, IOutStockLockInfoService outboundStockLockInfoService, IFeedbackMesService feedbackMesService, IRepository<Dt_Task> taskRepository, ILocationInfoService locationInfoService, IESSApiService eSSApiService, IRepository<Dt_AllocateOrder> allocateOrderRepository, IRepository<Dt_AllocateMaterialInfo> allocateMaterialInfoRepository, IRepository<Dt_InboundOrderDetail> inboundOrderDetailRepository, IRepository<Dt_InboundOrder> inboundOrderRepository, IRepository<Dt_LocationType> locationTypeRepository, IRepository<Dt_WarehouseArea> warehouseAreaRepository)
        {
            _mapper = mapper;
            _unitOfWorkManage = unitOfWorkManage;
@@ -103,6 +108,8 @@
            _allocateMaterialInfoRepository = allocateMaterialInfoRepository;
            _inboundOrderDetailRepository = inboundOrderDetailRepository;
            _inboundOrderRepository = inboundOrderRepository;
            _locationTypeRepository = locationTypeRepository;
            _warehouseAreaRepository = warehouseAreaRepository;
        }
        public WebResponseContent PrintFromData (string barcode)
@@ -144,6 +151,14 @@
            PickingOutboundResponseDTO response = new PickingOutboundResponseDTO();
            decimal totalNeedAllocate = 0; // æ€»éœ€æ±‚分配量
            decimal totalActualAllocate = 0; // å®žé™…总分配量
            string targetWarehouse = string.Empty;// ç›®æ ‡ä»“库
            string targetLocationCode = string.Empty; // ç›®æ ‡è´§ä½
            bool isWholeCaseOutbound = false; // æ˜¯å¦æ•´ç®±å‡ºåº“
            List<string> wholeCasePallets = new List<string>();
            Dictionary<string, string> palletLocationMap = new Dictionary<string, string>();
            Dictionary<string, bool> palletIsWholeCaseMap = new Dictionary<string, bool>();
            int? targetLocationType = null;
            try
            {
@@ -159,6 +174,7 @@
                // è®°å½•总需求数量
                totalNeedAllocate = calculationResult.MaterielCalculations.Sum(x => x.UnallocatedQuantity);
                // 2. å¤„理物料分配
                List<PickedStockDetailDTO> pickedDetails = new List<PickedStockDetailDTO>();
                Dt_OutboundOrder outboundOrder = calculationResult.OutboundOrder;
@@ -192,11 +208,116 @@
                            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<Dt_WarehouseArea>()
                                            .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<Dt_LocationType>()
                                            .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);
                    }
@@ -209,7 +330,19 @@
                    foreach (var detail in materielCalc.Details)
                    {
                        if (remainingToLock <= 0) break;
                        decimal maxLockableQty = detail.OrderQuantity - detail.OverOutQuantity;
                        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;
@@ -234,14 +367,33 @@
                
                if (tasks.Any()) _taskRepository.AddData(tasks);
                    _unitOfWorkManage.CommitTran();
                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},库存不足部分未分配)";
                if(totalActualAllocate == 0 && !outboundOrder.Details.Any(x=>x.LockQuantity >0))
                Dt_OutboundOrder outboundOrder1 = _outboundRepository.Db.Queryable<Dt_OutboundOrder>().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,无法出库");
@@ -258,6 +410,18 @@
                content = WebResponseContent.Instance.Error("处理拣货出库失败:" + ex.Message);
            }
            return content;
        }
        /// <summary>
        /// æ ¹æ®å•据号获取目标仓库
        /// </summary>
        /// <param name="orderNo"></param>
        /// <returns></returns>
        public String GetToWarehouseByOrderNo(string orderNo)
        {
            var order =_allocateOrderRepository.QueryFirst(x => x.OrderNo == orderNo);
            return order.ToWarehouse;
        }
        /// <summary>
@@ -1156,7 +1320,7 @@
                            }
                            else
                            {
                                item.CurrentDeliveryQty = item.LockQuantity - item.OverOutQuantity;
                                item.CurrentDeliveryQty += item.LockQuantity - item.OverOutQuantity;
                            }
                            item.OverOutQuantity = item.LockQuantity;
                        }
@@ -1205,7 +1369,25 @@
                    response.Success = true;
                    response.Message = "出库完成";
                    response.UpdatedDetails = updateDetails;
                    if (CheckOutboundOrderDetailCompletedByMatCode(request.OrderNo, lockInfo.MaterielCode, outboundOrderDetails))
                    {
                        Func<Dt_OutStockLockInfo, bool> supWhere = x => string.IsNullOrEmpty(outboundOrderDetails.First().SupplyCode) ? true : x.SupplyCode == outboundOrderDetails.First().SupplyCode;
                        Func<Dt_OutStockLockInfo, bool> wareWhere = x => string.IsNullOrEmpty(outboundOrderDetails.First().WarehouseCode) ? true : x.WarehouseCode == outboundOrderDetails.First().WarehouseCode;
                        List<Dt_OutStockLockInfo> stockLockInfos = _outboundLockInfoRepository.QueryData(x =>
                                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))
                    {
@@ -1311,9 +1493,15 @@
            _stockDetailHistoryRepository.AddData(historyRecords);
            // åˆ é™¤åº“存明细记录
            _stockDetailRepository.DeleteData(stockInfo.Details);
            var orderNo =_outboundRepository.QueryFirst(x => x.OrderNo == request.OrderNo);
            if(orderNo.OrderType != 117)
            {
                _stockDetailRepository.DeleteData(stockInfo.Details);
            }
            _stockChangeRepository.AddData(changeRecords);
        }
        #endregion
        #region æ‹£é€‰
@@ -1533,6 +1721,10 @@
                            item.OverOutQuantity = item.LockQuantity;
                        }
                        if (item.OverOutQuantity == item.OrderQuantity)
                        {
                            item.OrderDetailStatus = (int)OrderDetailStatusEnum.Over;
                        }
                        updateDetails.Add(item);
                        List<Barcodes> barcodesList = new List<Barcodes>();
@@ -1606,7 +1798,7 @@
                        _feedbackMesService.BarcodeFeedback(newBarcode);
                    }
                    if (CheckOutboundOrderDetailCompletedByMatCode(request.OrderNo, lockInfo.MaterielCode, outboundOrderDetails.First()))
                    if (CheckOutboundOrderDetailCompletedByMatCode(request.OrderNo, lockInfo.MaterielCode, outboundOrderDetails))
                    {
                        Func<Dt_OutStockLockInfo, bool> supWhere = x => string.IsNullOrEmpty(outboundOrderDetails.First().SupplyCode) ? true : x.SupplyCode == outboundOrderDetails.First().SupplyCode;
@@ -1625,11 +1817,14 @@
                    // æ£€æŸ¥å‡ºåº“单是否完成
                    if (CheckOutboundOrderCompleted(request.OrderNo))
                    {
                        UpdateOutboundOrderStatus(request.OrderNo, OutOrderStatusEnum.出库完成.ObjToInt());
                        if (outboundOrder.OrderType != OutOrderTypeEnum.InternalAllocat.ObjToInt() && outboundOrder.CreateType!=OrderCreateTypeEnum.CreateInSystem.ObjToInt())
                        if(outboundOrder.OrderType != OutOrderTypeEnum.InternalAllocat.ObjToInt())
                        {
                            _feedbackMesService.OutboundFeedback(outboundOrder.OrderNo);
                            UpdateOutboundOrderStatus(request.OrderNo, OutOrderStatusEnum.出库完成.ObjToInt());
                            if (outboundOrder.CreateType != OrderCreateTypeEnum.CreateInSystem.ObjToInt())
                            {
                                _feedbackMesService.OutboundFeedback(outboundOrder.OrderNo);
                            }
                        }
                    }
                }
@@ -1787,6 +1982,7 @@
                OperateType = "出库完成",
                InsertTime = DateTime.Now,
                StockId = stockDetail.StockId,
                Barcode = stockDetail.Barcode,
                MaterielCode = stockDetail.MaterielCode,
                MaterielName = stockDetail.MaterielName,
                OrderNo = stockDetail.OrderNo,
@@ -1804,6 +2000,7 @@
                Creater = stockDetail.Creater,
                CreateDate = stockDetail.CreateDate,
                WarehouseCode = stockDetail.WarehouseCode,
                ValidDate = stockDetail.ValidDate,
                Remark = $"出库完成删除,条码:{request.Barcode},原数量:{stockDetail.StockQuantity},出库数量:{actualOutboundQuantity},操作者:{request.Operator}"
            };
            _stockDetailHistoryRepository.AddData(historyRecord);
@@ -1960,14 +2157,30 @@
        /// <summary>
        /// æ£€æŸ¥å‡ºåº“单明细是否完成
        /// </summary>
        public bool CheckOutboundOrderDetailCompletedByMatCode(string orderNo, string materialCode, Dt_OutboundOrderDetail outboundOrderDetail)
        public bool CheckOutboundOrderDetailCompletedByMatCode(string orderNo, string materialCode, List<Dt_OutboundOrderDetail> 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;
            List<Dt_OutboundOrderDetail> 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));
            var firstDetail = outboundOrderDetails.FirstOrDefault();
            string supplyCode = firstDetail.SupplyCode;
            string warehouseCode = firstDetail.WarehouseCode;
            List<int> ids = outboundOrderDetails.Select(x => x.Id).ToList();
            // æ£€æŸ¥æ‰€æœ‰æ˜Žç»†çš„已出数量是否都等于单据数量
            List<Dt_OutboundOrderDetail> 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);
        }
@@ -2042,8 +2255,93 @@
                    if (lockInfo != null && lockInfo.SortedQuantity != lockInfo.AssignQuantity)
                    {
                        return content.Error($"托盘{palletCode}库存,在单据{lockInfo.OrderNo}里面还未拣选完成,不允许回库");
                        // 1. è®¡ç®—需要回滚的总数量
                        decimal? rollbackTotalQuantity = lockInfo.AssignQuantity - lockInfo.SortedQuantity;
                        // ç¡®ä¿å›žæ»šæ•°é‡ä¸ºæ­£æ•°
                        if (rollbackTotalQuantity <= 0)
                        {
                            // æ²¡æœ‰éœ€è¦å›žæ»šçš„æ•°é‡
                            stock.StockStatus = (int)StockStatusEmun.入库确认;
                            stock.LocationCode = "";
                        }
                        try
                        {
                            //处理OrderDetailIds,分割并转换为ID列表
                            List<long> orderDetailIds = new List<long>();
                            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<Dt_OutboundOrderDetail> orderDetails = _outboundRepository.Db.Queryable<Dt_OutboundOrderDetail>()
                                .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 = "";
                }
@@ -2159,5 +2457,304 @@
        }
        #endregion
        #region æ’¤é”€æ‹£é€‰
        /// <summary>
        /// æ’¤é”€æ‹£é€‰æ¡ç ï¼ˆåå‘回滚出库拣选操作)
        /// </summary>
        /// <param name="request">撤销拣选请求(至少包含条码、订单号、托盘号)</param>
        /// <returns>撤销响应</returns>
        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}已无法撤销");
                    }
                }
                Dt_OutStockLockInfo lockInfo = _outboundLockInfoRepository.QueryFirst(x =>
                    x.OrderNo == request.OrderNo &&
                    x.StockId == stockInfo.Id &&
                    x.MaterielCode == historyDetail.MaterielCode &&
                    x.PalletCode == stockInfo.PalletCode);
                _unitOfWorkManage.BeginTran();
                try
                {
                    if(lockInfo == null)
                    {
                        return WebResponseContent.Instance.Error("该托盘已全部拣选完,不允许撤销");
                    }
                    bool isUnpack = historyDetail.OperateType == "拆包-原始记录";
                    if (isUnpack)
                    {
                        return WebResponseContent.Instance.Error("该条码已拆包,不允许撤销");
                    }
                    else
                    {
                        ReverseFullOutboundOperation(historyDetail, stockInfo, request);
                    }
                    RollbackOutboundOrderDetails(historyDetail.MaterielCode, request.OrderNo, historyDetail.StockQuantity, stockInfo.Id);
                    if (lockInfo != null)
                    {
                        lockInfo.SortedQuantity = Math.Max(0, (decimal)(lockInfo.SortedQuantity - historyDetail.StockQuantity));
                        if (lockInfo.SortedQuantity == 0)
                        {
                            _outboundLockInfoRepository.UpdateData(lockInfo);
                        }
                        else
                        {
                            _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;
        }
        /// <summary>
        /// æ’¤é”€æ‹†åŒ…出库操作(反向恢复拆包前状态)
        /// </summary>
        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<Dt_MaterialCodeInfo> 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);
                }
            }
        }
        /// <summary>
        /// æ’¤é”€å®Œæ•´å‡ºåº“操作(恢复被删除的库存明细)
        /// </summary>
        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);
                }
            }
        }
        /// <summary>
        /// å›žæ»šå‡ºåº“单明细(扣减已出库数量)
        /// </summary>
        private void RollbackOutboundOrderDetails(string materielCode, string orderNo, decimal rollbackQuantity, int stockId)
        {
            Dt_OutboundOrder outboundOrder = _outboundRepository.QueryFirst(x => x.OrderNo == orderNo);
            List<Dt_OutboundOrderDetail> 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<Barcodes> barcodesList = JsonConvert.DeserializeObject<List<Barcodes>>(detail.ReturnJsonData) ?? new List<Barcodes>();
                    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;
            }
        }
        /// <summary>
        /// åˆ é™¤åº“存变动记录(拣选时生成的)
        /// </summary>
        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
    }
}