pan
2025-11-22 43d2d040be7416230db1c562526f2743e87d75ea
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundPickingService.cs
@@ -148,7 +148,13 @@
        }
        #region æ ¸å¿ƒä¸šåŠ¡æµç¨‹
        /// <summary>
        /// æ‹£é€‰
        /// </summary>
        /// <param name="orderNo"></param>
        /// <param name="palletCode"></param>
        /// <param name="barcode"></param>
        /// <returns></returns>
        public async Task<WebResponseContent> ConfirmPicking(string orderNo, string palletCode, string barcode)
        {
            try
@@ -189,11 +195,21 @@
                return WebResponseContent.Instance.Error($"拣选确认失败:{ex.Message}");
            }
        }
        /// <summary>
        /// å–消拣选
        /// </summary>
        /// <param name="orderNo"></param>
        /// <param name="palletCode"></param>
        /// <param name="barcode"></param>
        /// <returns></returns>
        public async Task<WebResponseContent> CancelPicking(string orderNo, string palletCode, string barcode)
        {
            try
            {
                if (await IsPalletReturned(palletCode))
                {
                    return WebResponseContent.Instance.Error($"托盘{palletCode}已经回库,不能取消分拣");
                }
                _unitOfWorkManage.BeginTran();
                // 1. å‰ç½®éªŒè¯
@@ -217,7 +233,13 @@
                return WebResponseContent.Instance.Error($"取消分拣失败:{ex.Message}");
            }
        }
        /// <summary>
        /// å›žåº“
        /// </summary>
        /// <param name="orderNo"></param>
        /// <param name="palletCode"></param>
        /// <param name="reason"></param>
        /// <returns></returns>
        public async Task<WebResponseContent> ReturnRemaining(string orderNo, string palletCode, string reason)
        {
            try
@@ -229,8 +251,7 @@
                    return WebResponseContent.Instance.Error("订单号和托盘码不能为空");
                // 2. èŽ·å–åº“å­˜å’Œä»»åŠ¡ä¿¡æ¯
                var stockInfo = await _stockInfoService.Db.Queryable<Dt_StockInfo>()
                    .FirstAsync(x => x.PalletCode == palletCode);
                var stockInfo = await _stockInfoService.Db.Queryable<Dt_StockInfo>().FirstAsync(x => x.PalletCode == palletCode);
                if (stockInfo == null)
                    return WebResponseContent.Instance.Error($"未找到托盘 {palletCode} å¯¹åº”的库存信息");
@@ -242,7 +263,7 @@
                // 3. åˆ†æžéœ€è¦å›žåº“的货物
                var returnAnalysis = await AnalyzeReturnItems(orderNo, palletCode, stockInfo.Id);
                if (!returnAnalysis.HasItemsToReturn)
                    return await HandleNoReturnItems(orderNo, palletCode);
                    return await HandleNoReturnItems(orderNo, palletCode,task);
                // 4. æ‰§è¡Œå›žåº“操作
                await ExecuteReturnOperations(orderNo, palletCode, stockInfo, task, returnAnalysis);
@@ -301,7 +322,8 @@
            // 6. èŽ·å–åº“å­˜æ˜Žç»†
            var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                .Where(x => x.Barcode == barcode && x.StockId == lockInfo.StockId)
                .Where(x => x.Barcode == barcode && x.StockId == lockInfo.StockId &&
                   x.Status != StockStatusEmun.入库确认.ObjToInt())
                .FirstAsync();
            if (stockDetail == null)
@@ -333,18 +355,13 @@
                           it.Status == (int)OutLockStockStatusEnum.出库中 &&
                           it.PalletCode == palletCode &&
                           it.CurrentBarcode == barcode &&
                           it.AssignQuantity > it.PickedQty)
                .FirstAsync();
                           it.AssignQuantity > it.PickedQty).FirstAsync();
            if (lockInfo == null)
            {
                // æŸ¥æ‰¾åŒä¸€è®¢å•下的记录
                lockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                    .Where(it => it.OrderNo == orderNo &&
                               it.CurrentBarcode == barcode &&
                               it.Status == (int)OutLockStockStatusEnum.出库中 &&
                               it.AssignQuantity > it.PickedQty)
                    .FirstAsync();
                    .Where(it => it.OrderNo == orderNo &&  it.CurrentBarcode == barcode && it.Status == (int)OutLockStockStatusEnum.出库中 && it.AssignQuantity > it.PickedQty).FirstAsync();
                if (lockInfo == null)
                {
@@ -352,8 +369,7 @@
                    var completedLockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                        .Where(it => it.CurrentBarcode == barcode &&
                                   (it.Status == (int)OutLockStockStatusEnum.拣选完成 ||
                                    it.PickedQty >= it.AssignQuantity))
                        .FirstAsync();
                                    it.PickedQty >= it.AssignQuantity)).FirstAsync();
                    if (completedLockInfo != null)
                        throw new Exception($"条码{barcode}已经完成分拣,不能重复分拣");
@@ -457,6 +473,7 @@
            // 5. æ›´æ–°åŽŸé”å®šä¿¡æ¯
            lockInfo.AssignQuantity = remainingStockQty;
            lockInfo.PickedQty = 0;
            lockInfo.Operator = App.User.UserName;
            await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
            // 6. è®¾ç½®ç»“æžœ
@@ -477,6 +494,7 @@
            // 2. æ›´æ–°é”å®šä¿¡æ¯
            lockInfo.PickedQty += actualQty;
            lockInfo.Status = (int)OutLockStockStatusEnum.拣选完成;
            lockInfo.Operator = App.User.UserName;
            await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
        }
@@ -495,6 +513,7 @@
            // 2. æ›´æ–°é”å®šä¿¡æ¯
            lockInfo.PickedQty += stockOutQty;
            lockInfo.AssignQuantity = remainingAssignQty;
            lockInfo.Operator = App.User.UserName;
            await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
            // 3. æ›´æ–°æ‹†åŒ…记录状态
@@ -596,6 +615,20 @@
            if (lockInfo.Status != (int)OutLockStockStatusEnum.拣选完成)
                return ValidationResult<(Dt_PickingRecord, Dt_OutStockLockInfo, Dt_OutboundOrderDetail)>.Error("当前状态不允许取消分拣");
            var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
       .Where(it => it.Barcode == barcode && it.StockId == pickingRecord.StockId)
       .FirstAsync();
            if (stockDetail != null)
            {
                // æ£€æŸ¥åº“存状态 - å¦‚果状态是入库确认或入库完成,说明已经回库
                if (stockDetail.Status == StockStatusEmun.入库确认.ObjToInt() ||
                    stockDetail.Status == StockStatusEmun.入库完成.ObjToInt())
                {
                    return ValidationResult<(Dt_PickingRecord, Dt_OutStockLockInfo, Dt_OutboundOrderDetail)>.Error($"条码{barcode}已经回库,不能取消分拣");
                }
            }
            // æ£€æŸ¥è®¢å•状态
            var order = await _outboundOrderService.Db.Queryable<Dt_OutboundOrder>()
                .Where(x => x.OrderNo == orderNo)
@@ -613,13 +646,54 @@
            return ValidationResult<(Dt_PickingRecord, Dt_OutStockLockInfo, Dt_OutboundOrderDetail)>.Success((pickingRecord, lockInfo, orderDetail));
        }
        /// <summary>
        /// æ£€æŸ¥æ¡ç æ˜¯å¦å·²ç»å›žåº“
        /// </summary>
        private async Task<bool> IsBarcodeReturned(string barcode, int stockId)
        {
            var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                .Where(it => it.Barcode == barcode && it.StockId == stockId)
                .FirstAsync();
            if (stockDetail == null)
                return false;
            // å¦‚果状态是入库确认或入库完成,说明已经回库
            return stockDetail.Status == StockStatusEmun.入库确认.ObjToInt() ||
                   stockDetail.Status == StockStatusEmun.入库完成.ObjToInt();
        }
        /// <summary>
        /// æ£€æŸ¥é”å®šä¿¡æ¯å¯¹åº”的条码是否已经回库
        /// </summary>
        private async Task<bool> IsLockInfoReturned(Dt_OutStockLockInfo lockInfo)
        {
            var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                .Where(it => it.Barcode == lockInfo.CurrentBarcode && it.StockId == lockInfo.StockId)
                .FirstAsync();
            if (stockDetail == null)
                return false;
            return stockDetail.Status == StockStatusEmun.入库确认.ObjToInt() ||
                   stockDetail.Status == StockStatusEmun.入库完成.ObjToInt();
        }
        private async Task ExecuteCancelLogic(Dt_OutStockLockInfo lockInfo, Dt_PickingRecord pickingRecord,
            Dt_OutboundOrderDetail orderDetail, string orderNo)
        {
            decimal cancelQty = pickingRecord.PickQuantity;
            // 1. æ£€æŸ¥å–消后数量不会为负数
            var currentStockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
        .Where(it => it.Barcode == pickingRecord.Barcode && it.StockId == pickingRecord.StockId)
        .FirstAsync();
            if (currentStockDetail != null &&
                (currentStockDetail.Status == StockStatusEmun.入库确认.ObjToInt() ||
                 currentStockDetail.Status == StockStatusEmun.入库完成.ObjToInt()))
            {
                throw new Exception($"条码{pickingRecord.Barcode}已经回库,无法取消分拣");
            }
            //   æ£€æŸ¥å–消后数量不会为负数
            decimal newOverOutQuantity = orderDetail.OverOutQuantity - cancelQty;
            decimal newPickedQty = orderDetail.PickedQty - cancelQty;
@@ -628,7 +702,7 @@
                throw new Exception($"取消分拣将导致数据异常:已出库{newOverOutQuantity},已拣选{newPickedQty}");
            }
            // 2. å¤„理不同类型的取消
            //  å¤„理不同类型的取消
            if (lockInfo.IsSplitted == 1 && lockInfo.ParentLockId.HasValue)
            {
                await HandleSplitBarcodeCancel(lockInfo, pickingRecord, cancelQty);
@@ -638,15 +712,15 @@
                await HandleNormalBarcodeCancel(lockInfo, pickingRecord, cancelQty);
            }
            // 3. æ›´æ–°è®¢å•明细
            // æ›´æ–°è®¢å•明细
            await UpdateOrderDetailOnCancel(pickingRecord.OrderDetailId, cancelQty);
            // 4. åˆ é™¤æ‹£é€‰è®°å½•
            //  åˆ é™¤æ‹£é€‰è®°å½•
            await Db.Deleteable<Dt_PickingRecord>()
                .Where(x => x.Id == pickingRecord.Id)
                .ExecuteCommandAsync();
            // 5. é‡æ–°æ£€æŸ¥è®¢å•状态
            //  é‡æ–°æ£€æŸ¥è®¢å•状态
            await UpdateOrderStatusForReturn(orderNo);
        }
@@ -660,6 +734,14 @@
            if (parentLockInfo == null)
                throw new Exception("未找到父锁定信息,无法取消拆包分拣");
            if (await IsLockInfoReturned(parentLockInfo))
            {
                throw new Exception($"父条码{parentLockInfo.CurrentBarcode}已经回库,无法取消拆包分拣");
            }
            if (await IsLockInfoReturned(lockInfo))
            {
                throw new Exception($"拆包条码{lockInfo.CurrentBarcode}已经回库,无法取消拆包分拣");
            }
            // æ¢å¤çˆ¶é”å®šä¿¡æ¯çš„分配数量
            parentLockInfo.AssignQuantity += cancelQty;
            await _outStockLockInfoService.Db.Updateable(parentLockInfo).ExecuteCommandAsync();
@@ -695,6 +777,10 @@
        private async Task HandleNormalBarcodeCancel(Dt_OutStockLockInfo lockInfo, Dt_PickingRecord pickingRecord, decimal cancelQty)
        {
            if (await IsLockInfoReturned(lockInfo))
            {
                throw new Exception($"条码{lockInfo.CurrentBarcode}已经回库,无法取消分拣");
            }
            // æ¢å¤é”å®šä¿¡æ¯
            lockInfo.PickedQty -= cancelQty;
            if (lockInfo.PickedQty < 0) lockInfo.PickedQty = 0;
@@ -750,7 +836,22 @@
            return await _stockInfoService.Db.Queryable<Dt_StockInfo>()
                .FirstAsync(x => x.PalletCode == palletCode);
        }
        /// <summary>
        /// æ£€æŸ¥æ•´ä¸ªæ‰˜ç›˜æ˜¯å¦å·²ç»å›žåº“
        /// </summary>
        private async Task<bool> IsPalletReturned(string palletCode)
        {
            var stockInfo = await _stockInfoService.Db.Queryable<Dt_StockInfo>()
                .Where(x => x.PalletCode == palletCode)
                .FirstAsync();
            if (stockInfo == null)
                return false;
            // å¦‚果托盘状态是入库确认或入库完成,说明已经回库
            return stockInfo.StockStatus == StockStatusEmun.入库确认.ObjToInt() ||
                   stockInfo.StockStatus == StockStatusEmun.入库完成.ObjToInt();
        }
        private async Task<Dt_Task> GetCurrentTask(string orderNo, string palletCode)
        {
            // å…ˆå°è¯•通过订单号和托盘号查找任务
@@ -805,7 +906,7 @@
            // æƒ…况3:检查拆包记录
            var splitRecords = await _splitPackageService.Db.Queryable<Dt_SplitPackageRecord>()
                .Where(it => it.OrderNo == orderNo && it.PalletCode == palletCode && !it.IsReverted)
                .Where(it => it.OrderNo == orderNo && it.PalletCode == palletCode && !it.IsReverted && it.Status != (int)SplitPackageStatusEnum.已回库)
                .ToListAsync();
            if (splitRecords.Any())
@@ -860,7 +961,7 @@
            return totalQty;
        }
        private async Task<WebResponseContent> HandleNoReturnItems(string orderNo, string palletCode)
        private async Task<WebResponseContent> HandleNoReturnItems(string orderNo, string palletCode,Dt_Task originalTask)
        {
            // æ£€æŸ¥æ˜¯å¦æ‰€æœ‰è´§ç‰©éƒ½å·²æ‹£é€‰å®Œæˆ
            var allPicked = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
@@ -869,12 +970,18 @@
            if (allPicked)
            {
                // åˆ é™¤åŽŸå§‹å‡ºåº“ä»»åŠ¡
                await _taskRepository.Db.Deleteable(originalTask).ExecuteCommandAsync();
                return WebResponseContent.Instance.OK("所有货物已拣选完成,托盘为空");
            }
            else
            {
                // åˆ é™¤åŽŸå§‹å‡ºåº“ä»»åŠ¡
                await _taskRepository.Db.Deleteable(originalTask).ExecuteCommandAsync();
                return WebResponseContent.Instance.Error("没有需要回库的剩余货物");
            }
            //空托盘如何处理  è¿˜æœ‰ä¸€ä¸ªå‡ºåº“任务要处理。
        }
        private async Task ExecuteReturnOperations(string orderNo, string palletCode, Dt_StockInfo stockInfo,
@@ -932,7 +1039,7 @@
                {
                    // æ¢å¤åº“存状态
                    stockDetail.OutboundQuantity = Math.Max(0, stockDetail.OutboundQuantity - returnQty);
                    stockDetail.Status = StockStatusEmun.入库完成.ObjToInt();
                    stockDetail.Status = StockStatusEmun.入库确认.ObjToInt();
                    await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
                }
                else
@@ -949,7 +1056,7 @@
                        OutboundQuantity = 0,
                        Barcode = lockInfo.CurrentBarcode,
                        InboundOrderRowNo = "",
                        Status = StockStatusEmun.入库完成.ObjToInt(),
                        Status = StockStatusEmun.入库确认.ObjToInt(),
                        SupplyCode = lockInfo.SupplyCode,
                        WarehouseCode = lockInfo.WarehouseCode,
                        Unit = lockInfo.Unit,
@@ -993,11 +1100,14 @@
        private async Task HandlePalletStockGoodsReturn(List<Dt_StockInfoDetail> palletStockGoods)
        {
            _logger.LogInformation($"回库操作:发现{palletStockGoods.Count}个库存明细需要回库,等待AGV搬运");
            foreach (var stockGood in palletStockGoods)
            {
                // æ¢å¤åº“存状态
                stockGood.OutboundQuantity = 0;
                stockGood.Status = StockStatusEmun.入库完成.ObjToInt();
                _logger.LogInformation($"待回库货物 - æ¡ç : {stockGood.Barcode}, æ•°é‡: {stockGood.StockQuantity}, å½“前状态: {stockGood.Status}");
            // æ¢å¤åº“存状态
            stockGood.OutboundQuantity = 0;
                stockGood.Status = StockStatusEmun.入库确认.ObjToInt();
                await _stockInfoDetailService.Db.Updateable(stockGood).ExecuteCommandAsync();
            }
@@ -1017,11 +1127,19 @@
        private async Task UpdateStockInfoStatus(Dt_StockInfo stockInfo)
        {
            _logger.LogInformation($"回库操作:托盘{stockInfo.PalletCode}等待AGV回库搬运");
            // æ›´æ–°åº“存主表状态
            stockInfo.StockStatus = StockStatusEmun.入库完成.ObjToInt();
            stockInfo.StockStatus = StockStatusEmun.入库确认.ObjToInt();
            await _stockInfoService.Db.Updateable(stockInfo).ExecuteCommandAsync();
        }
        /// <summary>
        /// åˆ›å»ºå›žåº“任务
        /// </summary>
        /// <param name="orderNo"></param>
        /// <param name="palletCode"></param>
        /// <param name="originalTask"></param>
        /// <param name="analysis"></param>
        /// <returns></returns>
        private async Task CreateReturnTaskAndHandleESS(string orderNo, string palletCode, Dt_Task originalTask, ReturnAnalysisResult analysis)
        {
            var firstLocation = await _locationInfoService.Db.Queryable<Dt_LocationInfo>()
@@ -1056,7 +1174,14 @@
            // ç»™ ESS å‘送流动信号和创建任务
            await SendESSCommands(palletCode, targetAddress, returnTask);
        }
        /// <summary>
        /// ç»™ESS下任务
        /// </summary>
        /// <param name="palletCode"></param>
        /// <param name="targetAddress"></param>
        /// <param name="returnTask"></param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        private async Task SendESSCommands(string palletCode, string targetAddress, Dt_Task returnTask)
        {
            try
@@ -1219,7 +1344,7 @@
                    business_type = outboundOrder.BusinessType,
                    factoryArea = outboundOrder.FactoryArea,
                    operationType = 1,
                    Operator = outboundOrder.Operator,
                    Operator = App.User.UserName,
                    orderNo = outboundOrder.UpperOrderNo,
                    status = outboundOrder.OrderStatus,
                    details = new List<FeedbackOutboundDetailsModel>()
@@ -1304,7 +1429,12 @@
                CurrentBarcode = newBarcode,
                OriginalLockQuantity = quantity,
                IsSplitted = 1,
                ParentLockId = originalLock.Id
                ParentLockId = originalLock.Id,
                Operator=  App.User.UserName,
                FactoryArea=originalLock.FactoryArea,
                lineNo=originalLock.lineNo,
                WarehouseCode=originalLock.WarehouseCode,
            };
            var newLockId = await _outStockLockInfoService.Db.Insertable(newLockInfo).ExecuteReturnIdentityAsync();
@@ -1385,15 +1515,30 @@
        private WebResponseContent CreatePickingResponse(PickingResult result, string adjustedReason)
        {
            //if (result.SplitResults.Any())
            //{
            //    var responseData = new { SplitResults = result.SplitResults, AdjustedReason = "" };
            //    if (!string.IsNullOrEmpty(adjustedReason))
            //    {
            //        responseData = new { SplitResults = result.SplitResults, AdjustedReason = adjustedReason };
            //    }
            //    return WebResponseContent.Instance.OK("拣选确认成功,已自动拆包", responseData);
            //}
            //if (!string.IsNullOrEmpty(adjustedReason))
            //{
            //    return WebResponseContent.Instance.OK($"拣选确认成功({adjustedReason})");
            //}
            //return WebResponseContent.Instance.OK("拣选确认成功");
            if (result.SplitResults.Any())
            {
                return WebResponseContent.Instance.OK("拣选确认成功,已自动拆包", new { SplitResults = result.SplitResults });
            }
            return WebResponseContent.Instance.OK("拣选确认成功", new { SplitResults = new List<SplitResult>() });
        }
        #endregion
    }