| | |
| | | private readonly IRepository<Dt_StockInfo> _stockInfoRepository; |
| | | private readonly IRecordService _recordService; |
| | | private readonly IRepository<Dt_Warehouse> _warehouseRepository; |
| | | private readonly IUnitOfWorkManage _unitOfWorkManage; |
| | | |
| | | /// <summary> |
| | | /// 构造函数 |
| | |
| | | /// <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> |
| | |
| | | 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); |
| | | } |
| | | |
| | | // 返回当前库位的出库任务 |
| | |
| | | } |
| | | |
| | | /// <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 => |
| | |
| | | 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) |
| | |
| | | } |
| | | |
| | | /// <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> |