wanshenmean
3 天以前 0e214856df682998cd52df74c851502c571ba183
feat(出库任务): 优化二深位出库的移库逻辑并添加事务支持

重构移库任务处理逻辑,优先检查相对库位是否有活跃任务
添加事务支持确保移库操作的原子性
优化空库位查找逻辑,支持降级获取一深位
完善异常处理和状态锁定机制
已修改1个文件
101 ■■■■ 文件已修改
Code/WMS/WIDESEA_WMSServer/WIDESEA_BasicService/LocationInfoService.cs 101 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_BasicService/LocationInfoService.cs
@@ -26,6 +26,7 @@
        private readonly IRepository<Dt_StockInfo> _stockInfoRepository;
        private readonly IRecordService _recordService;
        private readonly IRepository<Dt_Warehouse> _warehouseRepository;
        private readonly IUnitOfWorkManage _unitOfWorkManage;
        /// <summary>
        /// 构造函数
@@ -33,19 +34,22 @@
        /// <param name="baseDal">基础数据访问对象</param>
        /// <param name="taskRepository">任务仓储</param>
        /// <param name="stockInfoRepository">库存信息仓储</param>
        /// <param name="unitOfWorkManage">工作单元管理器</param>
        public LocationInfoService(
            IRepository<Dt_LocationInfo> baseDal,
            IRepository<Dt_Task> taskRepository,
            IRepository<Dt_StockInfo> stockInfoRepository,
            IRepository<Dt_Warehouse> warehouseRepository,
            IMapper mapper,
            IRecordService recordService) : base(baseDal)
            IRecordService recordService,
            IUnitOfWorkManage unitOfWorkManage) : base(baseDal)
        {
            _taskRepository = taskRepository;
            _stockInfoRepository = stockInfoRepository;
            _mapper = mapper;
            _recordService = recordService;
            _warehouseRepository = warehouseRepository;
            _unitOfWorkManage = unitOfWorkManage;
        }
        /// <summary>
@@ -270,24 +274,29 @@
                    return content.Error("任务不存在");
                var location = await BaseDal.QueryFirstAsync(x => x.LocationCode == outboundTask.SourceAddress && x.RoadwayNo == outboundTask.Roadway);
                if (location == null)
                    return content.Error("未找到源货位信息");
                // 检查是否需要进行移库
                // 检查是否需要进行移库(二深位出库需要先移走一深位)
                if (CheckForInternalTransfer(location))
                {
                    // 计算对应位置的相对库位(奇数行的下一行或者偶数行的上一行)
                    var newLocationID = GetRelativeLocationID(location);
                    var relativeLocationCode = GetRelativeLocationID(location);
                    // 获取新的库位的任务
                    var internalTransferTask = await _taskRepository.QueryFirstAsync(x => x.SourceAddress == newLocationID && x.Roadway == outboundTask.Roadway);
                    // 查找相对库位上的活跃任务(排除已完成、已取消、异常等终态)
                    var activeTask = await _taskRepository.QueryFirstAsync(x =>
                        x.SourceAddress == relativeLocationCode
                        && x.Roadway == outboundTask.Roadway
                        && (x.TaskStatus == TaskOutStatusEnum.OutNew.GetHashCode()));
                    // 如果新的库位没有找到对应的任务
                    if (internalTransferTask == null)
                    // 如果相对库位没有活跃任务,尝试创建移库任务
                    if (activeTask == null)
                    {
                        return content.OK("获取到移库任务", await HandleNoTaskAtLocation(newLocationID, outboundTask));
                        return await HandleNoTaskAtLocation(relativeLocationCode, outboundTask);
                    }
                    // 直接返回一深位出库任务
                    return content.OK("获取到一深位出库任务", internalTransferTask);
                    return content.OK("获取到一深位出库任务", activeTask);
                }
                // 返回当前库位的出库任务
@@ -319,12 +328,13 @@
        }
        /// <summary>
        /// 处理没有任务的库位情况
        /// 处理没有活跃任务的库位情况
        /// 判断是否有库存,有则生成移库任务,无则直接返回出库任务
        /// </summary>
        /// <param name="newLocationID">新的库位ID</param>
        /// <param name="outboundTask">出库任务</param>
        /// <returns>生成的移库任务或原始出库任务</returns>
        private async Task<Dt_Task> HandleNoTaskAtLocation(string newLocationID, Dt_Task outboundTask)
        /// <param name="newLocationID">相对库位编码</param>
        /// <param name="outboundTask">原始出库任务</param>
        /// <returns>操作结果(包含移库任务或原始出库任务)</returns>
        private async Task<WebResponseContent> HandleNoTaskAtLocation(string newLocationID, Dt_Task outboundTask)
        {
            // 判断该位置是否有库存
            var stockInfo = await _stockInfoRepository.QueryDataNavFirstAsync(x =>
@@ -334,13 +344,30 @@
            if (stockInfo == null)
            {
                // 如果没有库存,直接返回当前出库任务
                return outboundTask;
                return WebResponseContent.Instance.OK("当前出库任务", outboundTask);
            }
            // 如果有库存,生成移库任务
            var emptyLocation = await GetTransferLocationEmptyAsync(outboundTask.Roadway);
            var taskNo = await _taskRepository.GetTaskNo();
            // 有库存时,在事务中创建移库任务并锁定相关资源
            return await _unitOfWorkManage.BeginTranAsync(async () =>
            {
                // 事务内再次确认没有活跃任务(防止并发重复创建移库任务)
                var existingTask = await _taskRepository.QueryFirstAsync(x =>
                    x.SourceAddress == newLocationID
                    && x.Roadway == outboundTask.Roadway
                    && (x.TaskStatus == TaskOutStatusEnum.OutNew.GetHashCode()));
                if (existingTask != null)
                {
                    return WebResponseContent.Instance.OK("获取到已有任务", existingTask);
                }
                // 获取目标空库位
                var emptyLocation = await GetTransferLocationEmptyAsync(outboundTask.Roadway);
                if (emptyLocation == null)
                {
                    return WebResponseContent.Instance.Error("未找到可用的空库位用于移库");
                }
                var taskNo = await _taskRepository.GetTaskNo();
            var newTransferTask = new Dt_Task
            {
                CreateDate = DateTime.Now,
@@ -364,7 +391,7 @@
            var beforeSourceLocation = stockInfo.LocationDetails == null ? null : CloneLocationSnapshot(stockInfo.LocationDetails);
            var beforeTargetLocation = CloneLocationSnapshot(emptyLocation);
            // 创建移库任务后,立即锁定库存和相关货位,避免并发重复分配
                // 创建移库任务后,立即锁定库存和相关货位
            stockInfo.StockStatus = StockStatusEmun.移库锁定.GetHashCode();
            var updateStockResult = await _stockInfoRepository.UpdateDataAsync(stockInfo);
@@ -381,7 +408,7 @@
            var updateLocationResult = await BaseDal.UpdateDataAsync(locationsToUpdate);
            if (!updateStockResult || !updateLocationResult)
            {
                throw new Exception("创建移库任务后更新库存状态或货位状态失败");
                    return WebResponseContent.Instance.Error("创建移库任务后更新库存状态或货位状态失败");
            }
            var saveStockRecordResult = await _recordService.AddStockChangeRecordAsync(
@@ -393,7 +420,7 @@
                "移库任务预占库存");
            if (!saveStockRecordResult)
            {
                throw new Exception("创建移库任务后记录库存变更失败");
                    return WebResponseContent.Instance.Error("创建移库任务后记录库存变更失败");
            }
            if (beforeSourceLocation != null && stockInfo.LocationDetails != null)
@@ -408,7 +435,7 @@
                    "移库任务锁定源货位");
                if (!saveSourceLocationRecordResult)
                {
                    throw new Exception("创建移库任务后记录源货位变更失败");
                        return WebResponseContent.Instance.Error("创建移库任务后记录源货位变更失败");
                }
            }
@@ -422,10 +449,11 @@
                "移库任务锁定目标货位");
            if (!saveTargetLocationRecordResult)
            {
                throw new Exception("创建移库任务后记录目标货位变更失败");
                    return WebResponseContent.Instance.Error("创建移库任务后记录目标货位变更失败");
            }
            return createdTask;
                return WebResponseContent.Instance.OK("获取到移库任务", createdTask);
            });
        }
        private static Dt_LocationInfo CloneLocationSnapshot(Dt_LocationInfo location)
@@ -493,13 +521,30 @@
        }
        /// <summary>
        /// 根据巷道获取二深位的空库位
        /// 根据巷道获取空库位用于移库(优先二深位,无则降级取一深位)
        /// </summary>
        /// <param name="roadway">巷道编号</param>
        /// <returns>货位对象</returns>
        private async Task<Dt_LocationInfo> GetTransferLocationEmptyAsync(string roadway)
        /// <returns>货位对象,未找到则返回null</returns>
        private async Task<Dt_LocationInfo?> GetTransferLocationEmptyAsync(string roadway)
        {
            return await BaseDal.QueryFirstAsync(x => x.Depth == 2 && x.LocationStatus == LocationStatusEnum.Free.GetHashCode() && x.RoadwayNo == roadway);
            var freeStatus = LocationStatusEnum.Free.GetHashCode();
            // 优先获取二深位空库位
            var location = await BaseDal.QueryFirstAsync(x =>
                x.Depth == 2
                && x.LocationStatus == freeStatus
                && x.RoadwayNo == roadway);
            // 二深位无空闲时降级获取一深位
            if (location == null)
            {
                location = await BaseDal.QueryFirstAsync(x =>
                    x.Depth == 1
                    && x.LocationStatus == freeStatus
                    && x.RoadwayNo == roadway);
            }
            return location;
        }
        /// <summary>