wanshenmean
3 天以前 0e214856df682998cd52df74c851502c571ba183
Code/WMS/WIDESEA_WMSServer/WIDESEA_BasicService/LocationInfoService.cs
@@ -25,6 +25,8 @@
        private readonly IRepository<Dt_Task> _taskRepository;
        private readonly IRepository<Dt_StockInfo> _stockInfoRepository;
        private readonly IRecordService _recordService;
        private readonly IRepository<Dt_Warehouse> _warehouseRepository;
        private readonly IUnitOfWorkManage _unitOfWorkManage;
        /// <summary>
        /// 构造函数
@@ -32,17 +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>
@@ -130,22 +137,43 @@
        /// <summary>
        /// 根据巷道获取空闲货位信息
        /// 排序策略:深度优先(二深位优先),其次按层、列、行升序
        /// </summary>
        /// <param name="roadwayNo">巷道编号</param>
        /// <returns>空闲货位信息,如果未找到则返回null</returns>
        /// <returns>空闲货位信息,如果空闲货位不足则返回null</returns>
        public async Task<Dt_LocationInfo?> GetLocationInfo(string roadwayNo)
        {
            var locations = await BaseDal.QueryDataAsync(x =>
                x.EnableStatus == EnableStatusEnum.Normal.GetHashCode() &&
                x.RoadwayNo == roadwayNo &&
                x.LocationStatus == LocationStatusEnum.Free.GetHashCode());
            // HC 巷道使用 Capacity 类型,其他巷道使用 ShelfCapacity 类型
            var locationType = roadwayNo.Contains("HC")
                ? (int)LocationTypeEnum.Capacity
                : (int)LocationTypeEnum.ShelfCapacity;
            return locations?
            var enableStatus = EnableStatusEnum.Normal.GetHashCode();
            var freeStatus = LocationStatusEnum.Free.GetHashCode();
            // 数据库端 COUNT 检查空闲货位数量(仅返回一个数字,无数据传输开销)
            var freeCount = await BaseDal.Db.Queryable<Dt_LocationInfo>()
                .Where(x => x.EnableStatus == enableStatus
                    && x.RoadwayNo == roadwayNo
                    && x.LocationStatus == freeStatus
                    && x.LocationType == locationType)
                .CountAsync();
            // 空闲货位不足最低保留数量时返回null,避免将巷道分配耗尽
            const int minFreeLocationThreshold = 5;
            if (freeCount < minFreeLocationThreshold) return null;
            // 数据库端排序取第一条(只传输单行数据)
            return await BaseDal.Db.Queryable<Dt_LocationInfo>()
                .Where(x => x.EnableStatus == enableStatus
                    && x.RoadwayNo == roadwayNo
                    && x.LocationStatus == freeStatus
                    && x.LocationType == locationType)
                .OrderByDescending(x => x.Depth)
                .OrderBy(x => x.Layer)
                .ThenByDescending(x => x.Depth)
                .ThenBy(x => x.Column)
                .ThenBy(x => x.Row)
                .FirstOrDefault();
                .OrderBy(x => x.Column)
                .OrderBy(x => x.Row)
                .FirstAsync();
        }
        /// <summary>
@@ -167,6 +195,16 @@
        public async Task<Dt_LocationInfo> GetLocationInfoAsync(string locationCode)
        {
            return await BaseDal.QueryFirstAsync(x => x.LocationCode == locationCode);
        }
        /// <summary>
        /// 根据货位ID获取货位信息
        /// </summary>
        /// <param name="id">货位id</param>
        /// <returns>货位信息</returns>
        public async Task<Dt_LocationInfo> GetLocationInfoAsync(int id)
        {
            return await BaseDal.QueryFirstAsync(x => x.Id == id);
        }
        /// <summary>
@@ -236,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);
                }
                // 返回当前库位的出库任务
@@ -285,113 +328,132 @@
        }
        /// <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 =>
                            x.LocationCode == newLocationID &&
                            x.StockStatus == StockStatusEmun.入库完成.GetHashCode() &&
                            (x.StockStatus == StockStatusEmun.入库完成.GetHashCode() || x.StockStatus == StockStatusEmun.空托盘库存.GetHashCode()) &&
                            x.LocationDetails.LocationStatus == LocationStatusEnum.InStock.GetHashCode());
            if (stockInfo == null)
            {
                // 如果没有库存,直接返回当前出库任务
                return outboundTask;
                return WebResponseContent.Instance.OK("当前出库任务", outboundTask);
            }
            // 如果有库存,生成移库任务
            var emptyLocation = await GetTransferLocationEmptyAsync(outboundTask.Roadway);
            var taskNo = await _taskRepository.GetTaskNo();
            var newTransferTask = new Dt_Task
            // 有库存时,在事务中创建移库任务并锁定相关资源
            return await _unitOfWorkManage.BeginTranAsync(async () =>
            {
                CreateDate = DateTime.Now,
                Creater = App.User.UserName ?? "system",
                CurrentAddress = newLocationID,
                Grade = 99,
                NextAddress = emptyLocation.LocationCode,
                PalletCode = stockInfo.PalletCode,
                Remark = "移库",
                Roadway = stockInfo.LocationDetails.RoadwayNo,
                SourceAddress = newLocationID,
                TaskNum = taskNo,
                TargetAddress = emptyLocation.LocationCode,
                TaskType = TaskRelocationTypeEnum.Relocation.GetHashCode(),
                TaskStatus = TaskRelocationStatusEnum.RelocationNew.GetHashCode(),
                WarehouseId = stockInfo.WarehouseId
            };
                // 事务内再次确认没有活跃任务(防止并发重复创建移库任务)
                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 createdTask = await _taskRepository.Db.Insertable(newTransferTask).ExecuteReturnEntityAsync();
            var beforeStock = CloneStockSnapshot(stockInfo);
            var beforeSourceLocation = stockInfo.LocationDetails == null ? null : CloneLocationSnapshot(stockInfo.LocationDetails);
            var beforeTargetLocation = CloneLocationSnapshot(emptyLocation);
                // 获取目标空库位
                var emptyLocation = await GetTransferLocationEmptyAsync(outboundTask.Roadway);
                if (emptyLocation == null)
                {
                    return WebResponseContent.Instance.Error("未找到可用的空库位用于移库");
                }
            // 创建移库任务后,立即锁定库存和相关货位,避免并发重复分配
            stockInfo.StockStatus = StockStatusEmun.移库锁定.GetHashCode();
            var updateStockResult = await _stockInfoRepository.UpdateDataAsync(stockInfo);
                var taskNo = await _taskRepository.GetTaskNo();
                var newTransferTask = new Dt_Task
                {
                    CreateDate = DateTime.Now,
                    Creater = App.User.UserName ?? "system",
                    CurrentAddress = newLocationID,
                    Grade = 99,
                    NextAddress = emptyLocation.LocationCode,
                    PalletCode = stockInfo.PalletCode,
                    Remark = "移库",
                    Roadway = stockInfo.LocationDetails.RoadwayNo,
                    SourceAddress = newLocationID,
                    TaskNum = taskNo,
                    TargetAddress = emptyLocation.LocationCode,
                    TaskType = TaskRelocationTypeEnum.Relocation.GetHashCode(),
                    TaskStatus = TaskRelocationStatusEnum.RelocationNew.GetHashCode(),
                    WarehouseId = stockInfo.WarehouseId
                };
            var locationsToUpdate = new List<Dt_LocationInfo>();
            if (stockInfo.LocationDetails != null)
            {
                stockInfo.LocationDetails.LocationStatus = LocationStatusEnum.InStockLock.GetHashCode();
                locationsToUpdate.Add(stockInfo.LocationDetails);
            }
                var createdTask = await _taskRepository.Db.Insertable(newTransferTask).ExecuteReturnEntityAsync();
                var beforeStock = CloneStockSnapshot(stockInfo);
                var beforeSourceLocation = stockInfo.LocationDetails == null ? null : CloneLocationSnapshot(stockInfo.LocationDetails);
                var beforeTargetLocation = CloneLocationSnapshot(emptyLocation);
            emptyLocation.LocationStatus = LocationStatusEnum.FreeLock.GetHashCode();
            locationsToUpdate.Add(emptyLocation);
                // 创建移库任务后,立即锁定库存和相关货位
                stockInfo.StockStatus = StockStatusEmun.移库锁定.GetHashCode();
                var updateStockResult = await _stockInfoRepository.UpdateDataAsync(stockInfo);
            var updateLocationResult = await BaseDal.UpdateDataAsync(locationsToUpdate);
            if (!updateStockResult || !updateLocationResult)
            {
                throw new Exception("创建移库任务后更新库存状态或货位状态失败");
            }
                var locationsToUpdate = new List<Dt_LocationInfo>();
                if (stockInfo.LocationDetails != null)
                {
                    stockInfo.LocationDetails.LocationStatus = LocationStatusEnum.InStockLock.GetHashCode();
                    locationsToUpdate.Add(stockInfo.LocationDetails);
                }
            var saveStockRecordResult = await _recordService.AddStockChangeRecordAsync(
                beforeStock,
                stockInfo,
                StockChangeTypeEnum.Relocation,
                createdTask.TaskNum,
                createdTask.OrderNo,
                "移库任务预占库存");
            if (!saveStockRecordResult)
            {
                throw new Exception("创建移库任务后记录库存变更失败");
            }
                emptyLocation.LocationStatus = LocationStatusEnum.FreeLock.GetHashCode();
                locationsToUpdate.Add(emptyLocation);
            if (beforeSourceLocation != null && stockInfo.LocationDetails != null)
            {
                var saveSourceLocationRecordResult = await _recordService.AddLocationChangeRecordAsync(
                    beforeSourceLocation,
                    stockInfo.LocationDetails,
                var updateLocationResult = await BaseDal.UpdateDataAsync(locationsToUpdate);
                if (!updateStockResult || !updateLocationResult)
                {
                    return WebResponseContent.Instance.Error("创建移库任务后更新库存状态或货位状态失败");
                }
                var saveStockRecordResult = await _recordService.AddStockChangeRecordAsync(
                    beforeStock,
                    stockInfo,
                    StockChangeTypeEnum.Relocation,
                    createdTask.TaskNum,
                    createdTask.OrderNo,
                    "移库任务预占库存");
                if (!saveStockRecordResult)
                {
                    return WebResponseContent.Instance.Error("创建移库任务后记录库存变更失败");
                }
                if (beforeSourceLocation != null && stockInfo.LocationDetails != null)
                {
                    var saveSourceLocationRecordResult = await _recordService.AddLocationChangeRecordAsync(
                        beforeSourceLocation,
                        stockInfo.LocationDetails,
                        LocationChangeType.RelocationAssignLocation,
                        createdTask.TaskNum,
                        createdTask.OrderNo,
                        null,
                        "移库任务锁定源货位");
                    if (!saveSourceLocationRecordResult)
                    {
                        return WebResponseContent.Instance.Error("创建移库任务后记录源货位变更失败");
                    }
                }
                var saveTargetLocationRecordResult = await _recordService.AddLocationChangeRecordAsync(
                    beforeTargetLocation,
                    emptyLocation,
                    LocationChangeType.RelocationAssignLocation,
                    createdTask.TaskNum,
                    createdTask.OrderNo,
                    null,
                    "移库任务锁定源货位");
                if (!saveSourceLocationRecordResult)
                    "移库任务锁定目标货位");
                if (!saveTargetLocationRecordResult)
                {
                    throw new Exception("创建移库任务后记录源货位变更失败");
                    return WebResponseContent.Instance.Error("创建移库任务后记录目标货位变更失败");
                }
            }
            var saveTargetLocationRecordResult = await _recordService.AddLocationChangeRecordAsync(
                beforeTargetLocation,
                emptyLocation,
                LocationChangeType.RelocationAssignLocation,
                createdTask.TaskNum,
                createdTask.OrderNo,
                null,
                "移库任务锁定目标货位");
            if (!saveTargetLocationRecordResult)
            {
                throw new Exception("创建移库任务后记录目标货位变更失败");
            }
            return createdTask;
                return WebResponseContent.Instance.OK("获取到移库任务", createdTask);
            });
        }
        private static Dt_LocationInfo CloneLocationSnapshot(Dt_LocationInfo location)
@@ -459,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>
@@ -494,11 +573,18 @@
        /// <param name="layer">层数</param>
        /// <param name="depth">深度</param>
        /// <returns>货位信息对象</returns>
        private static Dt_LocationInfo CreateLocationInfo(string roadwayNo, int row, int col, int layer, int depth)
        private Dt_LocationInfo CreateLocationInfo(string roadwayNo, int row, int col, int layer, int depth)
        {
            var warehouse = _warehouseRepository.QueryData(x => x.WarehouseCode == roadwayNo).FirstOrDefault();
            if (warehouse == null)
            {
                throw new InvalidOperationException($"未找到巷道编号为 {roadwayNo} 的仓库信息");
            }
            return new Dt_LocationInfo
            {
                WarehouseId = 0,
                WarehouseId = warehouse.WarehouseId,
                Row = row,
                Column = col,
                Layer = layer,
@@ -507,6 +593,7 @@
                EnableStatus = EnableStatusEnum.Normal.GetHashCode(),
                LocationStatus = LocationStatusEnum.Free.GetHashCode(),
                LocationType = LocationTypeEnum.Undefined.GetHashCode(),
                //LocationCode = $"{roadwayNo}-{row:D3}-{col:D3}-{layer:D3}",
                LocationCode = $"{row:D3}-{col:D3}-{layer:D3}",
                LocationName = $"{roadwayNo}巷道{row:D3}行{col:D3}列{layer:D3}层{depth:D2}深"
            };
@@ -514,4 +601,4 @@
        #endregion 私有方法
    }
}
}