using AutoMapper; using Microsoft.Extensions.Configuration; using SqlSugar; using System.Text.Json; using WIDESEA_Common.LocationEnum; using WIDESEA_Common.StockEnum; using WIDESEA_Common.TaskEnum; using WIDESEA_Core; using WIDESEA_Core.BaseRepository; using WIDESEA_Core.BaseServices; using WIDESEA_DTO; using WIDESEA_DTO.Task; using WIDESEA_IBasicService; using WIDESEA_IStockService; using WIDESEA_ITaskInfoService; using WIDESEA_Model.Models; namespace WIDESEA_TaskInfoService { public partial class TaskService : ServiceBase>, ITaskService { private readonly IMapper _mapper; private readonly IStockInfoService _stockInfoService; private readonly ILocationInfoService _locationInfoService; private readonly HttpClientHelper _httpClientHelper; private readonly IConfiguration _configuration; public IRepository Repository => BaseDal; private readonly Dictionary _taskOrderBy = new() { { nameof(Dt_Task.Grade), OrderByType.Desc }, { nameof(Dt_Task.CreateDate), OrderByType.Asc }, }; public List TaskTypes => typeof(TaskTypeEnum).GetEnumIndexList(); public List TaskOutboundTypes => typeof(TaskTypeEnum).GetEnumIndexList(); public TaskService( IRepository BaseDal, IMapper mapper, IStockInfoService stockInfoService, ILocationInfoService locationInfoService, HttpClientHelper httpClientHelper, IConfiguration configuration) : base(BaseDal) { _mapper = mapper; _stockInfoService = stockInfoService; _locationInfoService = locationInfoService; _httpClientHelper = httpClientHelper; _configuration = configuration; } #region WCS逻辑处理 /// /// 创建任务(组盘入库任务、空托盘回库任务) /// public async Task CreateTaskInboundAsync(CreateTaskDto taskDto) { try { WebResponseContent content = await GetTasksByPalletCodeAsync(taskDto.PalletCode); if (content.Status) { return content; } if (string.IsNullOrWhiteSpace(taskDto.PalletCode) || string.IsNullOrWhiteSpace(taskDto.Roadway)) { return WebResponseContent.Instance.Error("无效的任务详情"); } if (taskDto.TaskType != TaskTypeEnum.Inbound && taskDto.TaskType != TaskTypeEnum.InEmpty) { return WebResponseContent.Instance.Error("无效的任务详情"); } // 使用 switch 表达式映射任务类型 int taskInboundType = taskDto.TaskType switch { TaskTypeEnum.Inbound => TaskInboundTypeEnum.Inbound.GetHashCode(), TaskTypeEnum.InEmpty => TaskInboundTypeEnum.InEmpty.GetHashCode(), _ => 0 // 理论上不会走到这里,因为已经验证过了 }; var task = new Dt_Task { TaskNum = await BaseDal.GetTaskNo(), PalletCode = taskDto.PalletCode, PalletType = taskDto.PalletType, Roadway = taskDto.Roadway, TaskType = taskInboundType, TaskStatus = TaskInStatusEnum.InNew.GetHashCode(), SourceAddress = taskDto.SourceAddress, TargetAddress = taskDto.TargetAddress, CurrentAddress = taskDto.SourceAddress, NextAddress = taskDto.TargetAddress, WarehouseId = taskDto.WarehouseId, Grade = 1, Creater = "system" }; var result = await Repository.AddDataAsync(task) > 0; if (!result) return WebResponseContent.Instance.Error("任务创建失败"); var wmstaskDto = _mapper.Map(task); return WebResponseContent.Instance.OK("任务创建成功", wmstaskDto); } catch (Exception ex) { return WebResponseContent.Instance.Error($"任务创建失败: {ex.Message}"); } } /// /// 根据指定的任务详情异步创建新的出库任务 /// public async Task CreateTaskOutboundAsync(CreateTaskDto taskDto) { try { var stockResult = await _stockInfoService.GetStockInfoAsync(taskDto.WarehouseId); if (stockResult == null || !stockResult.Any()) return WebResponseContent.Instance.Error("未找到库存信息"); var taskList = stockResult.Select(item => new Dt_Task { WarehouseId = item.WarehouseId, PalletCode = item.PalletCode, PalletType = item.PalletType, SourceAddress = item.LocationCode, TargetAddress = taskDto.TargetAddress, Roadway = item.LocationDetails.RoadwayNo, TaskType = TaskTypeEnum.Outbound.GetHashCode(), TaskStatus = TaskStatusEnum.New.GetHashCode(), Grade = 1, TaskNum = 0, CurrentAddress = item.LocationCode, NextAddress = taskDto.TargetAddress, Creater = "system", }).ToList(); var result = await BaseDal.AddDataAsync(taskList) > 0; var wmstaskDto = result ? _mapper.Map(taskList) : null; return WebResponseContent.Instance.OK(result ? "任务创建成功" : "任务创建失败", wmstaskDto ?? new object()); } catch (Exception ex) { return WebResponseContent.Instance.Error($"任务创建失败: {ex.Message}"); } } /// /// 获取可入库货位 /// public async Task GetTasksLocationAsync(CreateTaskDto taskDto) { try { var task = await BaseDal.QueryFirstAsync(s => s.PalletCode == taskDto.PalletCode); if (task == null) return WebResponseContent.Instance.Error("未找到对应的任务"); var locationInfo = await _locationInfoService.GetLocationInfo(task.Roadway); if (locationInfo == null) return WebResponseContent.Instance.Error("未找到对应的货位"); locationInfo.LocationStatus = LocationStatusEnum.FreeLock.GetHashCode(); task.CurrentAddress = task.SourceAddress; task.NextAddress = locationInfo.LocationCode; task.TargetAddress = locationInfo.LocationCode; task.TaskStatus = TaskInStatusEnum.Line_InFinish.GetHashCode(); var updateResult = await BaseDal.UpdateDataAsync(task); var locationResult = await _locationInfoService.UpdateLocationInfoAsync(locationInfo); return WebResponseContent.Instance.OK( updateResult && locationResult ? "任务更新成功" : "任务更新失败", locationInfo.LocationCode); } catch (Exception ex) { return WebResponseContent.Instance.Error($"获取任务失败: {ex.Message}"); } } /// /// 入库任务完成:添加库存,修改货位状态,删除任务数据,添加历史任务数据 /// public async Task InboundFinishTaskAsync(CreateTaskDto taskDto) { try { var task = await BaseDal.QueryFirstAsync(s => s.PalletCode == taskDto.PalletCode); if (task == null) return WebResponseContent.Instance.Error("未找到对应的任务"); var location = await _locationInfoService.GetLocationInfo(task.Roadway, task.TargetAddress); if (location == null) return WebResponseContent.Instance.Error("未找到对应的货位"); var stockInfo = await _stockInfoService.GetStockInfoAsync(taskDto.PalletCode); stockInfo.LocationCode = location.LocationCode; stockInfo.LocationId = location.Id; stockInfo.OutboundDate = task.Roadway switch { var r when r.Contains("GW") => DateTime.Now.AddHours(2), var r when r.Contains("CW") => DateTime.Now.AddHours(1), _ => DateTime.Now }; stockInfo.StockStatus = StockStatusEmun.入库完成.GetHashCode(); location.LocationStatus = LocationStatusEnum.InStock.GetHashCode(); var updateLocationResult = await _locationInfoService.UpdateLocationInfoAsync(location); var updateStockResult = await _stockInfoService.UpdateStockAsync(stockInfo); if (!updateLocationResult || !updateStockResult) return WebResponseContent.Instance.Error("任务完成失败"); var deleteTaskResult = await BaseDal.DeleteDataAsync(task); if (!deleteTaskResult) return WebResponseContent.Instance.Error("任务完成失败"); var historyTask = _mapper.Map(task); historyTask.InsertTime = DateTime.Now; return WebResponseContent.Instance.OK("任务完成"); } catch (Exception ex) { return WebResponseContent.Instance.Error($"完成任务失败: {ex.Message}"); } } /// /// 出库任务完成 :修改库存,修改货位状态,删除任务数据,添加历史任务数据 /// public async Task OutboundFinishTaskAsync(CreateTaskDto taskDto) { try { var task = await BaseDal.QueryFirstAsync(s => s.PalletCode == taskDto.PalletCode); if (task == null) return WebResponseContent.Instance.Error("未找到对应的任务"); var location = await _locationInfoService.GetLocationInfo(task.Roadway, task.SourceAddress); if (location == null) return WebResponseContent.Instance.Error("未找到对应的货位"); var stockInfo = await _stockInfoService.GetStockInfoAsync(taskDto.PalletCode); stockInfo.LocationCode = location.LocationCode; stockInfo.LocationId = location.Id; stockInfo.OutboundDate = DateTime.Now; location.LocationStatus = LocationStatusEnum.Free.GetHashCode(); var updateLocationResult = await _locationInfoService.UpdateLocationInfoAsync(location); var updateStockResult = await _stockInfoService.UpdateStockAsync(stockInfo); if (!updateLocationResult || !updateStockResult) return WebResponseContent.Instance.Error("任务完成失败"); var deleteTaskResult = await BaseDal.DeleteDataAsync(task); if (!deleteTaskResult) return WebResponseContent.Instance.Error("任务完成失败"); var historyTask = _mapper.Map(task); historyTask.InsertTime = DateTime.Now; return WebResponseContent.Instance.OK("任务完成"); } catch (Exception ex) { return WebResponseContent.Instance.Error($"完成任务失败: {ex.Message}"); } } /// /// 创建空托盘入库任务 /// /// /// public async Task CreateTaskInboundTrayAsync(CreateTaskDto taskDto) { try { WebResponseContent content = await GetTasksByPalletCodeAsync(taskDto.PalletCode); if (content.Status) { return content; } //var tasks = await BaseDal.QueryAsync(s => s.PalletCode == palletCode); //if (tasks == null || !tasks.Any()) // return WebResponseContent.Instance.Error("未找到对应的任务"); //var taskDtos = _mapper.Map>(tasks); return WebResponseContent.Instance.OK("查询成功"/*, taskDtos*/); } catch (Exception ex) { return WebResponseContent.Instance.Error($"查询任务失败: {ex.Message}"); } } /// /// 创建空托盘出库任务 /// /// /// public async Task GetOutBoundTrayTaskAsync(CreateTaskDto taskDto) { try { var stockInfo = await _stockInfoService.Repository.QueryFirstAsync(x => x.LocationDetails.WarehouseId == taskDto.WarehouseId && x.LocationDetails.LocationStatus == LocationStatusEnum.InStock.GetHashCode() && x.StockStatus == StockStatusEmun.空托盘库存.GetHashCode()); if (stockInfo == null) return WebResponseContent.Instance.Error("未找到对应的库存信息"); var task = new Dt_Task() { WarehouseId = stockInfo.LocationDetails.WarehouseId, PalletCode = stockInfo.PalletCode, PalletType = stockInfo.PalletType, SourceAddress = stockInfo.LocationCode, CurrentAddress = stockInfo.LocationCode, NextAddress = taskDto.TargetAddress, TargetAddress = taskDto.TargetAddress, Roadway = stockInfo.LocationDetails.RoadwayNo, TaskType = TaskTypeEnum.OutEmpty.GetHashCode(), TaskStatus = TaskStatusEnum.New.GetHashCode(), Grade = 1, TaskNum = await BaseDal.GetTaskNo(), Creater = "system", }; var taskDtos = _mapper.Map>(task); return WebResponseContent.Instance.OK("查询成功", taskDtos); } catch (Exception ex) { return WebResponseContent.Instance.Error($"查询任务失败: {ex.Message}"); } } /// /// 修改任务状态(根据任务ID修改为指定状态) /// /// /// /// public async Task UpdateTaskByStatusAsync(int taskId, int newStatus) { try { var tasks = await BaseDal.QueryFirstAsync(s => s.TaskNum == taskId); if (tasks == null) return WebResponseContent.Instance.Error("未找到对应的任务"); tasks.TaskStatus = newStatus; await BaseDal.UpdateDataAsync(tasks); return WebResponseContent.Instance.OK("修改成功", tasks); } catch (Exception ex) { return WebResponseContent.Instance.Error($"修改失败: {ex.Message}"); } } /// /// 查找托盘是否有任务 /// /// /// private async Task GetTasksByPalletCodeAsync(string palletCode) { try { var tasks = await BaseDal.QueryFirstAsync(s => s.PalletCode == palletCode); if (tasks == null) return WebResponseContent.Instance.Error("未找到对应的任务"); var taskDtos = _mapper.Map>(tasks); return WebResponseContent.Instance.OK("查询成功", taskDtos); } catch (Exception ex) { return WebResponseContent.Instance.Error($"查询任务失败: {ex.Message}"); } } /// /// 根据巷道确定目标地址 /// private string DetermineTargetAddress(string roadway, Dictionary addressMap) { if (string.IsNullOrWhiteSpace(roadway)) return "10080"; // 默认地址 foreach (var kvp in addressMap) { if (roadway.Contains(kvp.Key)) return kvp.Value; } return "10080"; // 默认地址 } /// /// 自动创建出库任务 - 查询到期库存并创建任务 /// public async Task CreateAutoOutboundTasksAsync() { try { // 1. 查询到期库存 var expiredStocks = await _stockInfoService.Repository .QueryDataAsync(s => s.OutboundDate <= DateTime.Now && s.StockStatus == StockStatusEmun.入库完成.GetHashCode()); if (expiredStocks == null || !expiredStocks.Any()) { return WebResponseContent.Instance.OK("无到期库存需要处理"); } // 批量加载位置详情(优化 N+1 查询问题) var locationIds = expiredStocks .Where(s => s.LocationId > 0) .Select(s => s.LocationId) .Distinct() .Cast() .ToList(); if (locationIds.Any()) { var locations = await _locationInfoService.Repository .QureyDataByIdsAsync(locationIds); // 创建位置字典以便快速查找 var locationDict = locations.ToDictionary(l => l.Id, l => l); // 为每个库存关联位置详情 foreach (var stock in expiredStocks) { if (stock.LocationId > 0 && locationDict.ContainsKey(stock.LocationId)) { stock.LocationDetails = locationDict[stock.LocationId]; } } } // 过滤有位置且位置有库存的记录 expiredStocks = expiredStocks .Where(s => s.LocationDetails != null && s.LocationDetails.LocationStatus == LocationStatusEnum.InStock.GetHashCode()) .ToList(); if (!expiredStocks.Any()) { return WebResponseContent.Instance.OK("无符合条件的到期库存"); } // 2. 检查已存在的任务 var palletCodes = expiredStocks.Select(s => s.PalletCode).ToList(); var existingTasks = await Repository.QueryDataAsync(t => palletCodes.Contains(t.PalletCode) && (t.TaskStatus == TaskStatusEnum.New.GetHashCode() || t.TaskStatus == TaskStatusEnum.SC_Executing.GetHashCode() || t.TaskStatus == TaskInStatusEnum.InNew.GetHashCode())); var processedPallets = existingTasks.Select(t => t.PalletCode).ToHashSet(); // 3. 筛选需要处理的库存 var stocksToProcess = expiredStocks .Where(s => !processedPallets.Contains(s.PalletCode)) .ToList(); if (!stocksToProcess.Any()) { return WebResponseContent.Instance.OK("所有到期库存已存在任务"); } // 4. 获取配置的目标地址映射 var targetAddressMap = _configuration.GetSection("AutoOutboundTask:TargetAddresses") .Get>() ?? new Dictionary(); // 5. 批量创建任务 var taskList = new List(); foreach (var stock in stocksToProcess) { // 根据巷道确定目标地址 var targetAddress = DetermineTargetAddress( stock.LocationDetails?.RoadwayNo ?? "", targetAddressMap); var task = new Dt_Task { WarehouseId = stock.WarehouseId, PalletCode = stock.PalletCode, PalletType = stock.PalletType, SourceAddress = stock.LocationCode, CurrentAddress = stock.LocationCode, NextAddress = targetAddress, TargetAddress = targetAddress, Roadway = stock.LocationDetails?.RoadwayNo ?? "", TaskType = TaskTypeEnum.Outbound.GetHashCode(), TaskStatus = TaskStatusEnum.New.GetHashCode(), Grade = 1, TaskNum = 0, // 使用 0 让数据库自动生成任务号 Creater = "system_auto" }; taskList.Add(task); } var addResult = await BaseDal.AddDataAsync(taskList) > 0; if (!addResult) { return WebResponseContent.Instance.Error($"批量创建任务失败,共 {taskList.Count} 个任务"); } // 6. 通知 WCS(异步,不影响主流程) _ = Task.Run(() => { foreach (var task in taskList) { try { var wmstaskDto = _mapper.Map(task); _httpClientHelper.Post( "http://logistics-service/api/logistics/notifyoutbound", JsonSerializer.Serialize(wmstaskDto)); } catch (Exception ex) { // WCS 通知失败不影响任务创建,记录日志即可 Console.WriteLine($"WCS 通知失败,任务编号: {task.TaskNum}, 错误: {ex.Message}"); } } }); return WebResponseContent.Instance.OK($"成功创建 {taskList.Count} 个出库任务", taskList.Count); } catch (Exception ex) { return WebResponseContent.Instance.Error($"自动创建出库任务失败: {ex.Message}"); } } #endregion WCS逻辑处理 #region 分容柜接口 /// /// 堆垛机取放货完成后物流通知化成分容柜完成信号 /// public async Task InOrOutCompletedAsync(InputDto input) { WebResponseContent content = new WebResponseContent(); if (string.IsNullOrWhiteSpace(input.PalletCode) || string.IsNullOrWhiteSpace(input.LocationCode)) { return content.Error($"托盘号或者货位编号不能为空"); } try { var stockInfo = await _stockInfoService.GetStockInfoAsync(input.PalletCode, input.LocationCode); if (stockInfo == null) { var location = await _locationInfoService.GetLocationInfoAsync(input.LocationCode); OutPutDto outPutDto = new OutPutDto() { LocationCode = input.LocationCode, PalletCode = input.PalletCode, IsNormalProcedure = 1, LocationStatus = location.LocationStatus }; content.OK(data: outPutDto); } else { OutPutDto outPutDto = new OutPutDto() { LocationCode = input.LocationCode, PalletCode = input.PalletCode, IsNormalProcedure = 1, LocationStatus = stockInfo.LocationDetails.LocationStatus }; content.OK(data: outPutDto); } } catch (Exception ex) { content.Error(ex.Message); } return content; } /// /// 化成分容柜定时向物流更新分容柜状态信息 /// /// /// public async Task SendLocationStatusAsync(InputDto input) { WebResponseContent content = new WebResponseContent(); if (string.IsNullOrWhiteSpace(input.LocationCode)) { return content.Error($"货位编号不能为空"); } try { var result = await _locationInfoService.Db.Updateable() .SetColumns(s => new Dt_LocationInfo { LocationStatus = input.LocationStatus }).Where(s => s.LocationCode == input.LocationCode).ExecuteCommandAsync() > 0; if (result) { content.OK("更新成功"); } else { content.Error("更新失败"); } } catch (Exception ex) { content.Error(ex.Message); } return content; } /// /// 分容柜工作完成后调用此接口通知物流出库 /// /// /// public async Task RequestOutboundAsync(InputDto input) { WebResponseContent content = new WebResponseContent(); if (string.IsNullOrWhiteSpace(input.LocationCode) || string.IsNullOrWhiteSpace(input.PalletCode)) { return content.Error($"托盘号或者货位编号不能为空"); } try { var stock = await _stockInfoService.GetStockInfoAsync(input.PalletCode, input.LocationCode); if (stock == null) { content.Error("未找到对应的托盘"); } else { var taskList = new Dt_Task { WarehouseId = stock.WarehouseId, PalletCode = stock.PalletCode, PalletType = stock.PalletType, SourceAddress = stock.LocationCode, CurrentAddress = stock.LocationCode, NextAddress = "10080", TargetAddress = "10081", Roadway = stock.LocationDetails.RoadwayNo, TaskType = TaskTypeEnum.Outbound.GetHashCode(), TaskStatus = TaskStatusEnum.New.GetHashCode(), Grade = 1, TaskNum = await BaseDal.GetTaskNo(), Creater = "system", }; var result = await BaseDal.AddDataAsync(taskList) > 0; var wmstaskDto = result ? _mapper.Map(taskList) : null; var httpResponse = _httpClientHelper.Post("http://logistics-service/api/logistics/notifyoutbound", JsonSerializer.Serialize(wmstaskDto)).Data; if (result && httpResponse != null) { content.OK("出库请求成功"); } else { content.Error("出库请求失败"); } } } catch (Exception ex) { content.Error(ex.Message); } return content; } /// /// 入库完成分容调用获取托盘上每个通道电芯 /// /// /// public async Task GetPalletCodeCellAsync(InputDto input) { WebResponseContent content = new WebResponseContent(); if (string.IsNullOrWhiteSpace(input.PalletCode) || string.IsNullOrWhiteSpace(input.LocationCode)) { return content.Error($"托盘号或者货位编号不能为空"); } try { var stockInfo = await _stockInfoService.GetStockInfoAsync(input.PalletCode, input.LocationCode); if (stockInfo == null) { return content.Error("未找到对应的托盘"); } var outPutDtos = stockInfo.Details.Select(x => new OutPutDto() { LocationCode = input.LocationCode, PalletCode = input.PalletCode, IsNormalProcedure = 1, LocationStatus = stockInfo.LocationDetails.LocationStatus, CellCode = x.SerialNumber, Channel = x.InboundOrderRowNo.ToString() }).ToList(); return content.OK(data: outPutDtos); } catch (Exception ex) { return content.Error(ex.Message); } } #endregion 分容柜接口 } }