pan
2025-11-22 7bafd627066668f8162690890f106d00e18d7bcd
提交
已修改2个文件
150 ■■■■ 文件已修改
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_BasicService/LocationInfoService.cs 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundPickingService.cs 140 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_BasicService/LocationInfoService.cs
@@ -140,7 +140,7 @@
                List<string> lockLocationCodes = locationCaches.Select(x => x.LocationCode).ToList();
                Dictionary<string, OrderByType> orderBy = new Dictionary<string, OrderByType>()
                Dictionary<string, SqlSugar.OrderByType> orderBy = new Dictionary<string, OrderByType>()
                {
                    { nameof(Dt_LocationInfo.RoadwayNo),OrderByType.Asc },
                    { nameof(Dt_LocationInfo.Layer),OrderByType.Asc },
@@ -153,6 +153,10 @@
                if (first != null)
                {
                    locationCaches.Add(new LocationCache { LocationCode = first?.LocationCode, DateTime = DateTime.Now });
                    Db.Updateable<Dt_LocationInfo>().SetColumns(x => new Dt_LocationInfo
                    {
                        LocationStatus = (int)LocationStatusEnum.InStockLock,
                    }).Where(x => x.Id == first.Id).ExecuteCommand();
                }
                return first;
@@ -182,8 +186,10 @@
                };
                var first = BaseDal.QueryFirst(x => x.LocationStatus == LocationStatusEnum.Free.ObjToInt() && x.EnableStatus != EnableStatusEnum.Disable.ObjToInt() && !lockLocationCodes.Contains(x.LocationCode), orderBy);//查询空货位信息并排除5分钟内分配的货位,根据层、列、深度、行排序
                if (first != null)
                {
                locationCaches.Add(new LocationCache { LocationCode = first.LocationCode, DateTime = DateTime.Now });
                }
                return first;
            }
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundPickingService.cs
@@ -161,27 +161,32 @@
            {
                _unitOfWorkManage.BeginTran();
                // 1. å‰ç½®éªŒè¯å’Œä¸šåŠ¡æ£€æŸ¥
                var validationResult = await ValidatePickingRequest(orderNo, palletCode, barcode);
                if (!validationResult.IsValid)
                    return WebResponseContent.Instance.Error(validationResult.ErrorMessage);
                var (lockInfo, orderDetail, stockDetail) = validationResult.Data;
                // 2. è®¡ç®—实际拣选数量
                // è®¡ç®—实际拣选数量
                var quantityResult = await CalculateActualPickingQuantity(lockInfo, orderDetail, stockDetail);
                if (!quantityResult.IsValid)
                    return WebResponseContent.Instance.Error(quantityResult.ErrorMessage);
                var (actualQty, adjustedReason) = quantityResult.Data;
                // 3. æ‰§è¡Œåˆ†æ‹£é€»è¾‘
                var overPickingValidation = await ValidateOverPicking(orderDetail.Id, actualQty);
                if (!overPickingValidation.IsValid)
                {
                    return WebResponseContent.Instance.Error(overPickingValidation.ErrorMessage);
                }
                //  æ‰§è¡Œåˆ†æ‹£é€»è¾‘
                var pickingResult = await ExecutePickingLogic(lockInfo, orderDetail, stockDetail, orderNo, palletCode, barcode, actualQty);
                // 4. æ›´æ–°ç›¸å…³æ•°æ®
                // æ›´æ–°ç›¸å…³æ•°æ®
                await UpdateOrderRelatedData(orderDetail.Id, pickingResult.ActualPickedQty, orderNo);
                // 5. è®°å½•操作历史
                // è®°å½•操作历史
                await RecordPickingHistory(pickingResult, orderNo, palletCode);
                _unitOfWorkManage.CommitTran();
@@ -388,18 +393,29 @@
            decimal remainingOrderQty = orderDetail.NeedOutQuantity - orderDetail.OverOutQuantity;
            decimal stockQuantity = stockDetail.StockQuantity;
            if (plannedQty <= 0)
            {
                return ValidationResult<(decimal, string)>.Error($"计划拣选数量必须大于0,当前: {plannedQty}");
            }
            if (remainingOrderQty <= 0)
            {
                return ValidationResult<(decimal, string)>.Error($"订单剩余需求数量必须大于0,当前: {remainingOrderQty}");
            }
            if (stockQuantity <= 0)
            {
                return ValidationResult<(decimal, string)>.Error($"库存数量必须大于0,当前: {stockQuantity}");
            }
            // ä¸‰é‡æ£€æŸ¥ï¼šå–最小值
            decimal actualQty = plannedQty;
            string adjustedReason = null;
            // æ£€æŸ¥1:订单数量限制
            if (plannedQty > remainingOrderQty && remainingOrderQty > 0)
            if (plannedQty > remainingOrderQty)
            {
                actualQty = remainingOrderQty;
                adjustedReason = $"订单数量限制:从{plannedQty}调整为{actualQty}";
            }
            // æ£€æŸ¥2:库存数量限制
            if (actualQty > stockQuantity)
            {
                actualQty = stockQuantity;
@@ -407,11 +423,18 @@
                    ? $"{adjustedReason},库存数量限制:进一步调整为{actualQty}"
                    : $"库存数量限制:从{plannedQty}调整为{actualQty}";
            }
            // æ£€æŸ¥3:实际可拣选数量必须大于0
            if (actualQty <= 0)
            {
                return ValidationResult<(decimal, string)>.Error($"无法分拣:计划数量{plannedQty},剩余订单{remainingOrderQty},库存{stockQuantity}");
                return ValidationResult<(decimal, string)>.Error($"无法分拣:计算后的实际数量为{actualQty}");
            }
            decimal projectedOverOut = orderDetail.OverOutQuantity + actualQty;
            if (projectedOverOut > orderDetail.NeedOutQuantity)
            {
                // å¦‚果会超拣,调整为刚好满足需求的数量
                actualQty = orderDetail.NeedOutQuantity - orderDetail.OverOutQuantity;
                adjustedReason = adjustedReason != null
                    ? $"{adjustedReason},防超拣限制:最终调整为{actualQty}"
                    : $"防超拣限制:从{plannedQty}调整为{actualQty}";
            }
            if (adjustedReason != null)
@@ -422,6 +445,27 @@
            return ValidationResult<(decimal, string)>.Success((actualQty, adjustedReason));
        }
        /// <summary>
        /// ä¸“门验证是否会发生超拣
        /// </summary>
        private async Task<ValidationResult<bool>> ValidateOverPicking(int orderDetailId, decimal pickingQty)
        {
            var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
                .FirstAsync(x => x.Id == orderDetailId);
            if (orderDetail == null)
                return ValidationResult<bool>.Error("未找到订单明细");
            decimal projectedOverOut = orderDetail.OverOutQuantity + pickingQty;
            if (projectedOverOut > orderDetail.NeedOutQuantity)
            {
                return ValidationResult<bool>.Error(
                    $"分拣后将导致超拣:当前已出库{orderDetail.OverOutQuantity},本次分拣{pickingQty},合计{projectedOverOut},超过需求{orderDetail.NeedOutQuantity}");
            }
            return ValidationResult<bool>.Success(true);
        }
        private async Task<PickingResult> ExecutePickingLogic(
            Dt_OutStockLockInfo lockInfo, Dt_OutboundOrderDetail orderDetail, Dt_StockInfoDetail stockDetail,
            string orderNo, string palletCode, string barcode, decimal actualQty)
@@ -531,10 +575,24 @@
            decimal newOverOutQuantity = currentOrderDetail.OverOutQuantity + pickedQty;
            decimal newPickedQty = currentOrderDetail.PickedQty + pickedQty;
            // æœ€ç»ˆæ£€æŸ¥ï¼šç¡®ä¿ä¸ä¼šè¶…过订单需求数量
            if (newOverOutQuantity > currentOrderDetail.NeedOutQuantity)
            {
                throw new Exception($"分拣后将导致已出库数量({newOverOutQuantity})超过订单需求数量({currentOrderDetail.NeedOutQuantity})");
                _logger.LogError($"防超拣检查失败 - OrderDetailId: {orderDetailId}, å·²å‡ºåº“: {newOverOutQuantity}, éœ€æ±‚: {currentOrderDetail.NeedOutQuantity}, æœ¬æ¬¡åˆ†æ‹£: {pickedQty}");
                decimal adjustedQty = currentOrderDetail.NeedOutQuantity - currentOrderDetail.OverOutQuantity;
                if (adjustedQty > 0)
                {
                    _logger.LogWarning($"自动调整分拣数量防止超拣:从{pickedQty}调整为{adjustedQty}");
                    newOverOutQuantity = currentOrderDetail.NeedOutQuantity;
                    newPickedQty = currentOrderDetail.PickedQty + adjustedQty;
                }
                else
                {
                    throw new Exception($"分拣后将导致已出库数量({newOverOutQuantity})超过订单需求数量({currentOrderDetail.NeedOutQuantity}),且无法自动调整");
                }
            }
            // æ›´æ–°è®¢å•明细
@@ -603,6 +661,10 @@
            if (pickingRecord == null)
                return ValidationResult<(Dt_PickingRecord, Dt_OutStockLockInfo, Dt_OutboundOrderDetail)>.Error("未找到对应的拣选记录");
            if (pickingRecord.PickQuantity <= 0)
            {
                return ValidationResult<(Dt_PickingRecord, Dt_OutStockLockInfo, Dt_OutboundOrderDetail)>.Error($"拣选记录数量无效: {pickingRecord.PickQuantity}");
            }
            // æŸ¥æ‰¾é”å®šä¿¡æ¯
            var lockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .Where(it => it.Id == pickingRecord.OutStockLockId)
@@ -611,13 +673,38 @@
            if (lockInfo == null)
                return ValidationResult<(Dt_PickingRecord, Dt_OutStockLockInfo, Dt_OutboundOrderDetail)>.Error("未找到对应的出库锁定信息");
            if (lockInfo.PickedQty < pickingRecord.PickQuantity)
            {
                return ValidationResult<(Dt_PickingRecord, Dt_OutStockLockInfo, Dt_OutboundOrderDetail)>.Error(
                    $"取消数量({pickingRecord.PickQuantity})超过锁定信息的已拣选数量({lockInfo.PickedQty})");
            }
            // æ£€æŸ¥çŠ¶æ€æ˜¯å¦å…è®¸å–æ¶ˆ
            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();
            var order = await _outboundOrderService.Db.Queryable<Dt_OutboundOrder>().FirstAsync(x => x.OrderNo == orderNo);
            if (order?.OrderStatus == (int)OutOrderStatusEnum.出库完成)
                return ValidationResult<(Dt_PickingRecord, Dt_OutStockLockInfo, Dt_OutboundOrderDetail)>.Error("订单已出库完成,不允许取消分拣");
            var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>().FirstAsync(x => x.Id == pickingRecord.OrderDetailId);
            if (orderDetail == null)
                return ValidationResult<(Dt_PickingRecord, Dt_OutStockLockInfo, Dt_OutboundOrderDetail)>.Error($"未找到订单明细,ID: {pickingRecord.OrderDetailId}");
            // æ£€æŸ¥è®¢å•明细的已拣选数量是否足够取消
            if (orderDetail.PickedQty < pickingRecord.PickQuantity)
            {
                return ValidationResult<(Dt_PickingRecord, Dt_OutStockLockInfo, Dt_OutboundOrderDetail)>.Error($"取消数量({pickingRecord.PickQuantity})超过订单明细的已拣选数量({orderDetail.PickedQty})");
            }
            // æ£€æŸ¥è®¢å•明细的已出库数量是否足够取消
            if (orderDetail.OverOutQuantity < pickingRecord.PickQuantity)
            {
                return ValidationResult<(Dt_PickingRecord, Dt_OutStockLockInfo, Dt_OutboundOrderDetail)>.Error($"取消数量({pickingRecord.PickQuantity})超过订单明细的已出库数量({orderDetail.OverOutQuantity})");
            }
            var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>().FirstAsync(it => it.Barcode == barcode && it.StockId == pickingRecord.StockId);
            if (stockDetail != null)
            {
@@ -628,21 +715,6 @@
                    return ValidationResult<(Dt_PickingRecord, Dt_OutStockLockInfo, Dt_OutboundOrderDetail)>.Error($"条码{barcode}已经回库,不能取消分拣");
                }
            }
            // æ£€æŸ¥è®¢å•状态
            var order = await _outboundOrderService.Db.Queryable<Dt_OutboundOrder>()
                .Where(x => x.OrderNo == orderNo)
                .FirstAsync();
            if (order?.OrderStatus == (int)OutOrderStatusEnum.出库完成)
                return ValidationResult<(Dt_PickingRecord, Dt_OutStockLockInfo, Dt_OutboundOrderDetail)>.Error("订单已出库完成,不允许取消分拣");
            // èŽ·å–è®¢å•æ˜Žç»†
            var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
                .FirstAsync(x => x.Id == pickingRecord.OrderDetailId);
            if (orderDetail == null)
                return ValidationResult<(Dt_PickingRecord, Dt_OutStockLockInfo, Dt_OutboundOrderDetail)>.Error($"未找到订单明细,ID: {pickingRecord.OrderDetailId}");
            return ValidationResult<(Dt_PickingRecord, Dt_OutStockLockInfo, Dt_OutboundOrderDetail)>.Success((pickingRecord, lockInfo, orderDetail));
        }
@@ -996,13 +1068,13 @@
                await UpdateOrderDetailsOnReturn(analysis.RemainingLocks);
            }
            // æƒ…况2:处理托盘上其他库存货物
            // å¤„理托盘上其他库存货物
            if (analysis.HasPalletStockGoods)
            {
                await HandlePalletStockGoodsReturn(analysis.PalletStockGoods);
            }
            // æƒ…况3:处理拆包记录
            // å¤„理拆包记录
            if (analysis.HasSplitRecords)
            {
                await HandleSplitRecordsReturn(analysis.SplitRecords, orderNo, palletCode);