pan
2025-11-21 51579a9535b27cdf71cd6d1a8e5d45c581d49467
提交
已修改2个文件
312 ■■■■■ 文件已修改
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_Common/StockEnum/OutLockStockStatusEnum.cs 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundPickingService.cs 310 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_Common/StockEnum/OutLockStockStatusEnum.cs
@@ -21,7 +21,7 @@
        å·²æ‹†åŒ… = 1,
        å·²æ’¤é”€ = 2,
        å·²æ‹£é€‰ = 3,
        å·²å›žåº“ = 4
        å·²å›žåº“ = 4,
    }
    public enum OutLockStockStatusEnum
    {
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundPickingService.cs
@@ -251,42 +251,19 @@
            }
        }
        public async Task<WebResponseContent> ConfirmPicking(string orderNo, string palletCode, string barcode)
        {
            #region "测试打印"
            // var splitResults=new List<SplitResult>();
            //  splitResults.Add(new SplitResult
            //{
            //    materialCode = "AAAAbbb",
            //    supplierCode = "CVBG",
            //    quantityTotal = "1234",
            //    batchNumber = "WMLOT25111900032",
            //    batch = "A234re",
            //    factory = "01",
            //    date = DateTime.Now.ToString("yyyy-MM-dd"),
            //});
            //splitResults.Add(new SplitResult
            //{
            //    materialCode = "CCDF",
            //    supplierCode = "QWCVBG",
            //    quantityTotal = "1234",
            //    batchNumber = "WMLOT25111900032",
            //    batch = "A234re",
            //    factory = "01",
            //    date = DateTime.Now.ToString("yyyy-MM-dd"),
            //});
            // return WebResponseContent.Instance.OK("拣选确认成功,已自动拆包", new { SplitResults = splitResults });
            #endregion
            try
            {
                _unitOfWorkManage.BeginTran();
                // 1. æŸ¥æ‰¾å‡ºåº“锁定信息
                // 1. éªŒè¯è¾“入参数
                if (string.IsNullOrEmpty(orderNo) || string.IsNullOrEmpty(palletCode) || string.IsNullOrEmpty(barcode))
                {
                    throw new Exception("订单号、托盘码和条码不能为空");
                }
                // 2. æŸ¥æ‰¾å‡ºåº“锁定信息
                var lockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                    .Where(it => it.OrderNo == orderNo &&
                               it.Status == (int)OutLockStockStatusEnum.出库中 &&
@@ -334,12 +311,28 @@
                    throw new Exception($"条码{barcode}已经分拣过,不能重复分拣");
                }
                var outorderdetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
                // èŽ·å–è®¢å•æ˜Žç»†å¹¶æ£€æŸ¥æ•°é‡é™åˆ¶
                var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
                    .FirstAsync(x => x.Id == lockInfo.OrderDetailId);
                if (outorderdetail != null && (lockInfo.AssignQuantity + outorderdetail.OverOutQuantity) > outorderdetail.NeedOutQuantity)
                if (orderDetail == null)
                    throw new Exception($"未找到订单明细,ID: {lockInfo.OrderDetailId}");
                // å…³é”®ä¿®å¤ï¼šæ£€æŸ¥ç´¯è®¡æ‹£é€‰æ•°é‡æ˜¯å¦ä¼šè¶…过订单数量
                decimal actualQty = lockInfo.AssignQuantity - lockInfo.PickedQty;
                decimal remainingOrderQty = orderDetail.NeedOutQuantity - orderDetail.OverOutQuantity;
                if (actualQty > remainingOrderQty)
                {
                    throw new Exception($"条码{barcode}的出库数量将导致订单明细已出库数量超过需求数量");
                    // å¦‚果分配数量大于剩余订单数量,调整实际拣选数量
                    actualQty = remainingOrderQty;
                    if (actualQty <= 0)
                    {
                        throw new Exception($"订单{orderNo}的需求数量已满足,无法继续分拣");
                    }
                    _logger.LogWarning($"调整分拣数量:原分配{lockInfo.AssignQuantity - lockInfo.PickedQty},调整为{actualQty},订单需求{orderDetail.NeedOutQuantity},已出库{orderDetail.OverOutQuantity}");
                }
                var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
@@ -349,27 +342,29 @@
                if (stockDetail == null)
                    return WebResponseContent.Instance.Error("无效的条码或物料编码");
                decimal actualQty = lockInfo.AssignQuantity - lockInfo.PickedQty;
                decimal stockQuantity = stockDetail.StockQuantity;
                List<SplitResult> splitResults = new List<SplitResult>();
                Dt_OutStockLockInfo finalLockInfo = lockInfo;
                var finalBarcode = barcode;
                var finalStockId = stockDetail.Id;
                decimal actualPickedQty = actualQty; // å®žé™…拣选数量
                decimal actualPickedQty = actualQty;
                if (actualQty < stockQuantity)
                {
                    // æƒ…况1: åˆ†é…æ•°é‡å°äºŽåº“存数量,需要自动拆包
                    decimal remainingStockQty = stockQuantity - actualQty;
                    // æ›´æ–°åŽŸæ¡ç åº“å­˜ä¸ºå‰©ä½™æ•°é‡
                    stockDetail.StockQuantity = remainingStockQty;
                    stockDetail.OutboundQuantity = remainingStockQty;
                    await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
                    // ç”Ÿæˆæ–°æ¡ç ç”¨äºŽè®°å½•拣选数量
                    var seq = await _dailySequenceService.GetNextSequenceAsync();
                    string newBarcode = "WSLOT" + DateTime.Now.ToString("yyyyMMdd") + seq.ToString()?.PadLeft(5, '0');
                    // ä¸ºæ–°æ¡ç åˆ›å»ºå‡ºåº“锁定信息
                    var newLockInfo = new Dt_OutStockLockInfo
                    {
                        OrderNo = lockInfo.OrderNo,
@@ -398,6 +393,7 @@
                    var newLockId = await _outStockLockInfoService.Db.Insertable(newLockInfo).ExecuteReturnIdentityAsync();
                    newLockInfo.Id = newLockId;
                    // è®°å½•拆包历史
                    var splitHistory = new Dt_SplitPackageRecord
                    {
                        FactoryArea = lockInfo.FactoryArea,
@@ -418,6 +414,7 @@
                    };
                    await _splitPackageService.Db.Insertable(splitHistory).ExecuteCommandAsync();
                    // æ›´æ–°åŽŸé”å®šä¿¡æ¯ä¸ºå‰©ä½™åº“å­˜æ•°é‡
                    lockInfo.AssignQuantity = remainingStockQty;
                    lockInfo.PickedQty = 0;
                    await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
@@ -444,11 +441,8 @@
                        date = DateTime.Now.ToString("yyyy-MM-dd"),
                    });
                    barcode = newBarcode;
                    lockInfo = newLockInfo;
                    finalLockInfo = newLockInfo;
                    finalBarcode = newBarcode;
                    finalStockId = stockDetail.Id;
                }
                else if (actualQty == stockQuantity)
                {
@@ -460,10 +454,6 @@
                    lockInfo.PickedQty += actualQty;
                    lockInfo.Status = (int)OutLockStockStatusEnum.拣选完成;
                    await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
                    finalLockInfo = lockInfo;
                    finalBarcode = barcode;
                    finalStockId = stockDetail.Id;
                }
                else
                {
@@ -478,29 +468,35 @@
                    lockInfo.AssignQuantity = remainingAssignQty;
                    await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
                    var _relatedSplitRecords = await _splitPackageService.Db.Queryable<Dt_SplitPackageRecord>()
                    var relatedSplitRecords = await _splitPackageService.Db.Queryable<Dt_SplitPackageRecord>()
                        .Where(it => it.OriginalBarcode == barcode || it.NewBarcode == barcode)
                        .Where(it => !it.IsReverted)
                        .ToListAsync();
                    foreach (var record in _relatedSplitRecords)
                    foreach (var record in relatedSplitRecords)
                    {
                        record.Status = (int)SplitPackageStatusEnum.已拣选;
                        await _splitPackageService.Db.Updateable(record).ExecuteCommandAsync();
                    }
                    finalLockInfo = lockInfo;
                    finalBarcode = barcode;
                    finalStockId = stockDetail.Id;
                    actualPickedQty = stockOutQty; // å®žé™…拣选数量调整为库存数量
                    actualPickedQty = stockOutQty;
                }
                // æ›´æ–°è®¢å•明细的拣选数量和已出库数量
                // å…³é”®ä¿®å¤ï¼šå†æ¬¡æ£€æŸ¥è®¢å•数量限制
                decimal newOverOutQuantity = orderDetail.OverOutQuantity + actualPickedQty;
                decimal newPickedQty = orderDetail.PickedQty + actualPickedQty;
                if (newOverOutQuantity > orderDetail.NeedOutQuantity)
                {
                    throw new Exception($"分拣后将导致已出库数量({newOverOutQuantity})超过订单需求数量({orderDetail.NeedOutQuantity})");
                }
                // æ›´æ–°è®¢å•明细
                await _outboundOrderDetailService.Db.Updateable<Dt_OutboundOrderDetail>()
                    .SetColumns(it => new Dt_OutboundOrderDetail
                    {
                        PickedQty = it.PickedQty + actualPickedQty,
                        OverOutQuantity = it.OverOutQuantity + actualPickedQty
                        PickedQty = newPickedQty,
                        OverOutQuantity = newOverOutQuantity
                    })
                    .Where(it => it.Id == lockInfo.OrderDetailId)
                    .ExecuteCommandAsync();
@@ -541,7 +537,7 @@
                    return WebResponseContent.Instance.OK("拣选确认成功,已自动拆包", new { SplitResults = splitResults });
                }
                return WebResponseContent.Instance.OK("拣选确认成功");
                return WebResponseContent.Instance.OK("拣选确认成功", new { SplitResults = new List<SplitResult>() });
            }
            catch (Exception ex)
@@ -551,6 +547,159 @@
                return WebResponseContent.Instance.Error($"拣选确认失败:{ex.Message}");
            }
        }
        public async Task<WebResponseContent> CancelPicking(string orderNo, string palletCode, string barcode)
        {
            try
            {
                _unitOfWorkManage.BeginTran();
                // æŸ¥æ‰¾æ‹£é€‰è®°å½•
                var pickingRecord = await Db.Queryable<Dt_PickingRecord>()
                    .Where(it => it.OrderNo == orderNo &&
                               it.PalletCode == palletCode &&
                               it.Barcode == barcode)
                    .OrderByDescending(it => it.PickTime)
                    .FirstAsync();
                if (pickingRecord == null)
                    return WebResponseContent.Instance.Error("未找到对应的拣选记录");
                // æŸ¥æ‰¾å‡ºåº“锁定信息
                var lockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                    .Where(it => it.Id == pickingRecord.OutStockLockId)
                    .FirstAsync();
                if (lockInfo == null)
                    return WebResponseContent.Instance.Error("未找到对应的出库锁定信息");
                // æ£€æŸ¥æ˜¯å¦å¯ä»¥å–消
                if (lockInfo.Status != (int)OutLockStockStatusEnum.拣选完成)
                    return WebResponseContent.Instance.Error("当前状态不允许取消分拣");
                decimal cancelQty = pickingRecord.PickQuantity;
                // èŽ·å–è®¢å•æ˜Žç»†
                var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
                    .FirstAsync(x => x.Id == pickingRecord.OrderDetailId);
                if (orderDetail == null)
                    throw new Exception($"未找到订单明细,ID: {pickingRecord.OrderDetailId}");
                // å…³é”®ä¿®å¤ï¼šæ£€æŸ¥å–消后数量不会为负数
                decimal newOverOutQuantity = orderDetail.OverOutQuantity - cancelQty;
                decimal newPickedQty = orderDetail.PickedQty - cancelQty;
                if (newOverOutQuantity < 0 || newPickedQty < 0)
                {
                    throw new Exception($"取消分拣将导致已出库数量或已拣选数量为负数");
                }
                // å¤„理取消逻辑
                if (lockInfo.IsSplitted == 1 && lockInfo.ParentLockId.HasValue)
                {
                    await HandleSplitBarcodeCancel(lockInfo, pickingRecord, cancelQty);
                }
                else
                {
                    await HandleNormalBarcodeCancel(lockInfo, pickingRecord, cancelQty);
                }
                // æ›´æ–°è®¢å•明细
                await _outboundOrderDetailService.Db.Updateable<Dt_OutboundOrderDetail>()
                    .SetColumns(it => new Dt_OutboundOrderDetail
                    {
                        PickedQty = newPickedQty,
                        OverOutQuantity = newOverOutQuantity
                    })
                    .Where(it => it.Id == pickingRecord.OrderDetailId)
                    .ExecuteCommandAsync();
                // åˆ é™¤æ‹£é€‰è®°å½•
                await Db.Deleteable<Dt_PickingRecord>()
                    .Where(x => x.Id == pickingRecord.Id)
                    .ExecuteCommandAsync();
                // é‡æ–°æ£€æŸ¥è®¢å•状态
                await CheckAndUpdateOrderStatus(orderNo);
                _unitOfWorkManage.CommitTran();
                return WebResponseContent.Instance.OK($"取消分拣成功,恢复数量:{cancelQty}");
            }
            catch (Exception ex)
            {
                _unitOfWorkManage.RollbackTran();
                _logger.LogError($"CancelPicking失败 - OrderNo: {orderNo}, PalletCode: {palletCode}, Barcode: {barcode}, Error: {ex.Message}");
                return WebResponseContent.Instance.Error($"取消分拣失败:{ex.Message}");
            }
        }
        private async Task HandleSplitBarcodeCancel(Dt_OutStockLockInfo lockInfo, Dt_PickingRecord pickingRecord, decimal cancelQty)
        {
            // æŸ¥æ‰¾çˆ¶é”å®šä¿¡æ¯
            var parentLockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .Where(x => x.Id == lockInfo.ParentLockId.Value)
                .FirstAsync();
            if (parentLockInfo == null)
            {
                throw new Exception("未找到父锁定信息,无法取消拆包分拣");
            }
            // æ¢å¤çˆ¶é”å®šä¿¡æ¯çš„分配数量
            parentLockInfo.AssignQuantity += cancelQty;
            await _outStockLockInfoService.Db.Updateable(parentLockInfo).ExecuteCommandAsync();
            // æ¢å¤åº“å­˜
            var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                .Where(x => x.Barcode == parentLockInfo.CurrentBarcode && x.StockId == parentLockInfo.StockId)
                .FirstAsync();
            if (stockDetail != null)
            {
                stockDetail.StockQuantity += cancelQty;
                stockDetail.OutboundQuantity += cancelQty;
                await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
            }
            // æ›´æ–°æ‹†åŒ…记录状态
            await _splitPackageService.Db.Updateable<Dt_SplitPackageRecord>()
                .SetColumns(x => new Dt_SplitPackageRecord
                {
                    Status = (int)SplitPackageStatusEnum.已撤销,
                    IsReverted = true
                })
                .Where(x => x.NewBarcode == lockInfo.CurrentBarcode && !x.IsReverted)
                .ExecuteCommandAsync();
            // åˆ é™¤æ‹†åŒ…产生的锁定信息
            await _outStockLockInfoService.Db.Deleteable<Dt_OutStockLockInfo>()
                .Where(x => x.Id == lockInfo.Id)
                .ExecuteCommandAsync();
        }
        private async Task HandleNormalBarcodeCancel(Dt_OutStockLockInfo lockInfo, Dt_PickingRecord pickingRecord, decimal cancelQty)
        {
            // æ¢å¤é”å®šä¿¡æ¯
            lockInfo.PickedQty -= cancelQty;
            if (lockInfo.PickedQty < 0) lockInfo.PickedQty = 0;
            lockInfo.Status = (int)OutLockStockStatusEnum.出库中;
            await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
            // æ¢å¤åº“å­˜
            var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                .Where(x => x.Barcode == pickingRecord.Barcode && x.StockId == pickingRecord.StockId)
                .FirstAsync();
            if (stockDetail != null)
            {
                stockDetail.StockQuantity += cancelQty;
                stockDetail.OutboundQuantity += cancelQty;
                await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
            }
        }
        /// <summary>
        /// å›žåº“操作  
        /// </summary>
@@ -894,62 +1043,7 @@
            return outboundFinished && hasRemainingGoods;
        }
        // å–消拣选功能
        public async Task<WebResponseContent> CancelPicking(string orderNo, string palletCode, string barcode)
        {
            try
            {
                _unitOfWorkManage.BeginTran();
                //查找拣选记录
                var pickingRecord = await Db.Queryable<Dt_PickingRecord>()
                    .Where(it => it.OrderNo == orderNo &&
                               it.PalletCode == palletCode &&
                               it.Barcode == barcode)
                    .OrderByDescending(it => it.PickTime)
                    .FirstAsync();
                if (pickingRecord == null)
                    return WebResponseContent.Instance.Error("未找到对应的拣选记录");
                // æŸ¥æ‰¾å‡ºåº“锁定信息
                var lockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                    .Where(it => it.Id == pickingRecord.OutStockLockId)
                    .FirstAsync();
                if (lockInfo == null)
                    return WebResponseContent.Instance.Error("未找到对应的出库锁定信息");
                //检查是否可以取消(状态必须是拣选完成)
                if (lockInfo.Status != (int)OutLockStockStatusEnum.拣选完成)
                    return WebResponseContent.Instance.Error("当前状态不允许取消分拣");
                decimal cancelQty = pickingRecord.PickQuantity;
                // æ£€æŸ¥æ‹†åŒ…链关系
                var splitChain = await GetSplitChain(barcode);
                if (splitChain.Any())
                {
                    // æƒ…况A:处理拆包链的取消(多次手动拆包)
                    await HandleSplitChainCancel(orderNo, palletCode, barcode, cancelQty, lockInfo, pickingRecord, splitChain);
                }
                else
                {
                    // æƒ…况B:处理普通条码的取消
                    await HandleNormalBarcodeCancel(orderNo, palletCode, barcode, cancelQty, lockInfo, pickingRecord);
                }
                _unitOfWorkManage.CommitTran();
                return WebResponseContent.Instance.OK($"取消分拣成功,恢复数量:{cancelQty}");
            }
            catch (Exception ex)
            {
                _unitOfWorkManage.RollbackTran();
                return WebResponseContent.Instance.Error($"取消分拣失败:{ex.Message}");
            }
        }
        /// <summary>
        /// èŽ·å–æ‹†åŒ…é“¾ï¼ˆä»Žå½“å‰æ¡ç è¿½æº¯åˆ°åŽŸå§‹æ¡ç ï¼‰
        /// </summary>