using System.ComponentModel; using System.Reflection; using Newtonsoft.Json; using SqlSugar; using WIDESEA_Common.Constants; using WIDESEA_Common.StockEnum; using WIDESEA_Common.TaskEnum; using WIDESEA_Core; using WIDESEA_Core.Helper; using WIDESEA_DTO.MES; using WIDESEA_DTO.Task; using WIDESEA_Model.Models; namespace WIDESEA_TaskInfoService { public partial class TaskService { #region 手动任务 /// /// 手动创建任务 /// /// 手动创建任务参数 /// public async Task CreateManualTaskAsync(CreateManualTaskDto dto) { try { int taskType; int taskStatus; switch (dto.TaskType) { case "入库": taskType = TaskInboundTypeEnum.Inbound.GetHashCode(); taskStatus = TaskInStatusEnum.InNew.GetHashCode(); break; case "出库": taskType = TaskOutboundTypeEnum.Outbound.GetHashCode(); taskStatus = TaskOutStatusEnum.OutNew.GetHashCode(); break; case "移库": taskType = TaskRelocationTypeEnum.Relocation.GetHashCode(); taskStatus = TaskRelocationStatusEnum.RelocationNew.GetHashCode(); break; case "空箱入库": taskType = TaskInboundTypeEnum.InEmpty.GetHashCode(); taskStatus = TaskInStatusEnum.InNew.GetHashCode(); break; default: return WebResponseContent.Instance.Error($"不支持的任务类型: {dto.TaskType}"); } int taskNum = await BaseDal.GetTaskNo(); var task = new Dt_Task { TaskNum = taskNum, PalletCode = dto.Barcode, SourceAddress = dto.SourceAddress, TargetAddress = dto.TargetAddress, TaskType = taskType, TaskStatus = taskStatus, Grade = dto.Grade, Roadway = dto.TargetAddress, WarehouseId = dto.WarehouseId, CurrentAddress = dto.SourceAddress, NextAddress = dto.TargetAddress, Creater = "manual", CreateDate = DateTime.Now, ModifyDate = DateTime.Now }; var wmsTaskDtos = new List() { new() { TaskNum = task.TaskNum, PalletCode = task.PalletCode, SourceAddress = task.SourceAddress, TargetAddress = task.TargetAddress, TaskType = task.TaskType, Roadway = task.Roadway, TaskStatus = task.TaskStatus, WarehouseId = task.WarehouseId } }; return await _unitOfWorkManage.BeginTranAsync(async () => { // 保存到数据库并同步发送给WCS var result = await BaseDal.AddDataAsync(task) > 0; if (!result) return WebResponseContent.Instance.Error("创建任务失败"); var wcsResult = _httpClientHelper.Post( "http://localhost:9292/api/Task/ReceiveManualTask", wmsTaskDtos.ToJson()); if (!wcsResult.IsSuccess || !wcsResult.Data.Status) return WebResponseContent.Instance.Error($"任务已创建但发送给WCS失败:{wcsResult.ErrorMessage}\r\n {wcsResult.Data?.Message}"); return WebResponseContent.Instance.OK($"手动创建任务成功,任务号: {taskNum}"); }); } catch (Exception ex) { return WebResponseContent.Instance.Error($"手动创建任务异常: {ex.Message}"); } } /// /// 手动下发任务到WCS(批量处理) /// /// 下发任务参数列表 /// 批量下发结果 public async Task DispatchTasksToWCSAsync(List dtos) { try { if (dtos == null || !dtos.Any()) return WebResponseContent.Instance.Error("请选择要下发的任务"); var resultDto = new DispatchResultDto { SuccessCount = 0, FailCount = 0, FailResults = new List() }; // 第一步:查询并校验所有任务状态,收集有效任务 var validTasks = new List<(DispatchTaskDto Dto, Dt_Task Task)>(); foreach (var dto in dtos) { var task = await BaseDal.QueryFirstAsync(t => t.TaskId == dto.TaskId); if (task == null) { resultDto.FailResults.Add(new DispatchTaskResultDto { TaskId = dto.TaskId, TaskNum = 0, Success = false, ErrorMessage = "任务不存在" }); resultDto.FailCount++; continue; } // 校验任务状态:仅入库新单/出库新单/移库新单可下发 bool canDispatch = false; if (task.TaskType == TaskInboundTypeEnum.Inbound.GetHashCode() && task.TaskStatus == TaskInStatusEnum.InNew.GetHashCode()) canDispatch = true; else if (task.TaskType == TaskOutboundTypeEnum.Outbound.GetHashCode() && task.TaskStatus == TaskOutStatusEnum.OutNew.GetHashCode()) canDispatch = true; else if (task.TaskType == TaskRelocationTypeEnum.Relocation.GetHashCode() && task.TaskStatus == TaskRelocationStatusEnum.RelocationNew.GetHashCode()) canDispatch = true; if (!canDispatch) { var statusName = GetTaskStatusName(task.TaskType, task.TaskStatus); resultDto.FailResults.Add(new DispatchTaskResultDto { TaskId = dto.TaskId, TaskNum = task.TaskNum, Success = false, ErrorMessage = $"任务状态[{statusName}]不允许下发" }); resultDto.FailCount++; continue; } validTasks.Add((dto, task)); } // 如果全部校验失败,直接返回 if (validTasks.Count == 0) return WebResponseContent.Instance.Error($"下发失败,共{resultDto.FailCount}个任务"); // 第二步:构造所有WMSTaskDTO,一次性调用WCS var wmsTaskDtos = validTasks.Select(vt => new WMSTaskDTO { Id = vt.Task.TaskId, TaskNum = vt.Task.TaskNum, PalletCode = vt.Dto.PalletCode, SourceAddress = vt.Dto.SourceAddress, TargetAddress = vt.Dto.TargetAddress, CurrentAddress = vt.Dto.SourceAddress, NextAddress = vt.Dto.TargetAddress, TaskType = vt.Task.TaskType, TaskStatus = vt.Task.TaskStatus, Roadway = vt.Task.Roadway, Grade = vt.Dto.Grade, WarehouseId = vt.Task.WarehouseId, PalletType = vt.Task.PalletType }).ToList(); // 一次性调用WCS批量接口 var wcsResult = _httpClientHelper.Post( "http://localhost:9292/api/Task/ReceiveManualTask", wmsTaskDtos.ToJson()); if (wcsResult == null || !wcsResult.IsSuccess) { // WCS调用失败,所有任务都算失败 // 尝试从WCS响应中解析错误详情 string errorDetail = ""; if (wcsResult?.Data != null) { // 尝试将Data转换为错误信息 try { var errorData = wcsResult.Data.ToString(); errorDetail = $"WCS错误: {errorData}"; } catch { errorDetail = wcsResult?.ErrorMessage ?? "WCS响应异常"; } } else { errorDetail = wcsResult?.ErrorMessage ?? "WCS响应异常"; } foreach (var vt in validTasks) { resultDto.FailResults.Add(new DispatchTaskResultDto { TaskId = vt.Dto.TaskId, TaskNum = vt.Task.TaskNum, Success = false, ErrorMessage = $"{errorDetail} (任务号:{vt.Task.TaskNum}, 托盘:{vt.Dto.PalletCode})" }); resultDto.FailCount++; } resultDto.SuccessCount = 0; return WebResponseContent.Instance.Error($"WCS批量下发失败,共{resultDto.FailCount}个任务"); } // WCS调用成功,解析返回的结构化数据 ReceiveTaskResultDto wcsResultData = null; try { if (wcsResult.Data?.Data != null) { var jsonStr = wcsResult.Data.Data.ToString(); if (!string.IsNullOrEmpty(jsonStr) && jsonStr.Contains("duplicateTasks")) { wcsResultData = JsonConvert.DeserializeObject(jsonStr); } } } catch (Exception ex) { // 解析WCS返回数据失败,记录日志但继续处理 Console.WriteLine($"解析WCS返回数据异常: {ex.Message}"); } // 如果有重复任务,记录到失败结果中 if (wcsResultData?.DuplicateTasks != null && wcsResultData.DuplicateTasks.Count > 0) { foreach (var dup in wcsResultData.DuplicateTasks) { var statusName = GetTaskStatusName(dup.TaskType, dup.TaskStatus); resultDto.FailResults.Add(new DispatchTaskResultDto { TaskId = 0, // 重复任务可能没有WMS的TaskId TaskNum = dup.TaskNum, Success = false, ErrorMessage = $"WCS中已存在该任务(托盘:{dup.PalletCode}, 状态:{statusName})" }); resultDto.FailCount++; } } // 第三步:WCS调用成功后,批量更新DB foreach (var vt in validTasks) { vt.Task.PalletCode = vt.Dto.PalletCode; vt.Task.SourceAddress = vt.Dto.SourceAddress; vt.Task.TargetAddress = vt.Dto.TargetAddress; vt.Task.CurrentAddress = vt.Dto.SourceAddress; vt.Task.NextAddress = vt.Dto.TargetAddress; vt.Task.Grade = vt.Dto.Grade; vt.Task.Dispatchertime = DateTime.Now; } // 批量更新DB var tasksToUpdate = validTasks.Select(vt => vt.Task).ToList(); await BaseDal.UpdateDataAsync(tasksToUpdate); resultDto.SuccessCount = validTasks.Count - (wcsResultData?.DuplicateTasks?.Count ?? 0); if (resultDto.FailCount == 0) return WebResponseContent.Instance.OK($"成功下发{resultDto.SuccessCount}个任务", resultDto); var errorResponse = WebResponseContent.Instance.Error($"部分下发成功{resultDto.SuccessCount}个,失败{resultDto.FailCount}个"); errorResponse.Data = resultDto; return errorResponse; } catch (Exception ex) { return WebResponseContent.Instance.Error($"下发任务异常: {ex.Message}"); } } /// /// 获取任务状态名称 /// /// 任务类型 /// 任务状态 /// 状态的中文描述 private string GetTaskStatusName(int taskType, int taskStatus) { FieldInfo? fieldInfo = taskType switch { _ when taskType == TaskInboundTypeEnum.Inbound.GetHashCode() => typeof(TaskInStatusEnum).GetField(((TaskInStatusEnum)taskStatus).ToString()), _ when taskType == TaskOutboundTypeEnum.Outbound.GetHashCode() => typeof(TaskOutStatusEnum).GetField(((TaskOutStatusEnum)taskStatus).ToString()), _ when taskType == TaskRelocationTypeEnum.Relocation.GetHashCode() => typeof(TaskRelocationStatusEnum).GetField(((TaskRelocationStatusEnum)taskStatus).ToString()), _ => null }; var descAttr = fieldInfo?.GetCustomAttributes(typeof(DescriptionAttribute), false) .FirstOrDefault() as DescriptionAttribute; return descAttr?.Description ?? taskStatus.ToString(); } /// /// 组盘/拆盘操作并创建入库任务,完成后下发WCS /// /// 组盘/拆盘操作参数 /// 操作结果 public async Task PalletOperationAndCreateTaskAsync(PalletOperationTaskDto dto) { try { // 1. 参数校验 if (string.IsNullOrWhiteSpace(dto.PalletCode)) return WebResponseContent.Instance.Error("托盘号不能为空"); if (string.IsNullOrWhiteSpace(dto.Action) || (dto.Action != "组盘" && dto.Action != "拆盘")) return WebResponseContent.Instance.Error("执行动作必须是'组盘'或'拆盘'"); if (string.IsNullOrWhiteSpace(dto.LineId)) return WebResponseContent.Instance.Error("线体编号不能为空"); if (string.IsNullOrWhiteSpace(dto.WarehouseCode)) return WebResponseContent.Instance.Error("仓库编号不能为空"); if (string.IsNullOrWhiteSpace(dto.RobotName)) return WebResponseContent.Instance.Error("机械手名称不能为空"); // 组盘时电芯列表必传 if (dto.Action == "组盘" && (dto.Cells == null || !dto.Cells.Any())) return WebResponseContent.Instance.Error("组盘时电芯列表不能为空"); // 2. 根据仓库编号查询仓库信息,获取 WarehouseId、targetAddress 和 roadway var warehouse = _sqlSugarClient.Queryable() .First(w => w.WarehouseCode == dto.WarehouseCode); if (warehouse == null) return WebResponseContent.Instance.Error($"未找到仓库编号为[{dto.WarehouseCode}]的仓库"); int warehouseId = warehouse.WarehouseId; string targetAddress = warehouse.WarehouseCode; string roadway = warehouse.WarehouseCode; // 3. 根据动作类型执行不同流程 if (dto.Action == "组盘") return await ExecuteGroupPalletAsync(dto, warehouseId, targetAddress, roadway); return await ExecuteSplitPalletAsync(dto, warehouseId, targetAddress, roadway); } catch (Exception ex) { return WebResponseContent.Instance.Error($"组盘/拆盘操作异常: {ex.Message}"); } } /// /// 执行组盘操作:添加库存 → 直接调用MES绑定 → 创建入库任务 → 下发WCS /// private async Task ExecuteGroupPalletAsync( PalletOperationTaskDto dto, int warehouseId, string targetAddress, string roadway) { return await _unitOfWorkManage.BeginTranAsync(async () => { // 1. 创建库存主记录(不绑定货位) var stockInfo = new Dt_StockInfo { PalletCode = dto.PalletCode, PalletType = 0, WarehouseId = warehouseId, LocationId = 0, StockStatus = 0, MesUploadStatus = 0 }; var stockId = await _stockInfoService.Repository.AddDataAsync(stockInfo); if (stockId <= 0) return WebResponseContent.Instance.Error("创建库存记录失败"); // 2. 批量创建库存明细 var details = dto.Cells.Select((cell, i) => new Dt_StockInfoDetail { StockId = (int)stockId, MaterielCode = cell.SfcCode, MaterielName = "", OrderNo = "", ProductionDate = DateTime.Now.ToString("yyyy-MM-dd"), EffectiveDate = "", SerialNumber = cell.SfcCode, StockQuantity = 1, OutboundQuantity = 0, Status = 0, InboundOrderRowNo = Convert.ToInt32(cell.Channel), Remark = cell.Channel }).ToList(); if (await _sqlSugarClient.Insertable(details).ExecuteCommandAsync() <= 0) return WebResponseContent.Instance.Error("创建库存明细失败"); // 3. 调用MES绑定 var (equipmentCode, resourceCode, token) = ResolveMesConfig(dto.RobotName); var bindRequest = new BindContainerRequest { ContainerCode = dto.PalletCode, EquipmentCode = equipmentCode, ResourceCode = resourceCode, LocalTime = DateTime.Now, OperationType = StockConstants.MES_BIND_OPERATION_TYPE, ContainerSfcList = dto.Cells.Select((cell, i) => new ContainerSfcItem { Sfc = cell.SfcCode, Location = cell.Channel }).ToList() }; var mesResult = string.IsNullOrWhiteSpace(token) ? _mesService.BindContainer(bindRequest) : _mesService.BindContainer(bindRequest, token); if (!CheckMesResult(mesResult, out var mesError)) return WebResponseContent.Instance.Error($"MES组盘绑定失败: {mesError}"); // 4. 创建入库任务并下发WCS return await CreateTaskAndDispatchAsync(dto, warehouseId, targetAddress, roadway, TaskInboundTypeEnum.Inbound, "组盘成功并创建入库任务"); }); } /// /// 执行拆盘操作:查询库存 → 直接调用MES解绑 → 清除库存货位 → 创建空托盘入库任务 → 下发WCS /// private async Task ExecuteSplitPalletAsync( PalletOperationTaskDto dto, int warehouseId, string targetAddress, string roadway) { return await _unitOfWorkManage.BeginTranAsync(async () => { // 1. 导航查询库存及明细 var stockInfo = await _stockInfoService.Repository .QueryDataNavFirstAsync(s => s.PalletCode == dto.PalletCode); if (stockInfo == null) return WebResponseContent.Instance.Error($"未找到托盘[{dto.PalletCode}]的库存记录"); var sfcList = stockInfo.Details?.Select(d => d.SerialNumber).ToList() ?? new List(); if (!sfcList.Any()) return WebResponseContent.Instance.Error($"托盘[{dto.PalletCode}]下无电芯数据"); // 2. 调用MES解绑 var (equipmentCode, resourceCode, token) = ResolveMesConfig(dto.RobotName); var unbindRequest = new UnBindContainerRequest { EquipmentCode = equipmentCode, ResourceCode = resourceCode, LocalTime = DateTime.Now, ContainCode = dto.PalletCode, SfcList = sfcList }; var mesResult = string.IsNullOrWhiteSpace(token) ? _mesService.UnBindContainer(unbindRequest) : _mesService.UnBindContainer(unbindRequest, token); if (!CheckMesResult(mesResult, out var mesError)) return WebResponseContent.Instance.Error($"MES拆盘解绑失败: {mesError}"); // 3. 清除库存绑定的货位 stockInfo.LocationId = 0; stockInfo.LocationCode = null; await _sqlSugarClient.Updateable(stockInfo) .UpdateColumns(s => new { s.LocationId, s.LocationCode }) .ExecuteCommandAsync(); // 4. 创建空托盘入库任务并下发WCS return await CreateTaskAndDispatchAsync(dto, warehouseId, targetAddress, roadway, TaskInboundTypeEnum.InEmpty, "拆盘成功并创建空托盘入库任务"); }); } /// /// 解析MES设备配置,返回 (equipmentCode, resourceCode, token) /// private (string equipmentCode, string resourceCode, string token) ResolveMesConfig(string deviceName) { var config = _mesDeviceConfigService.GetByDeviceName(deviceName); return ( config?.EquipmentCode ?? StockConstants.MES_EQUIPMENT_CODE, config?.ResourceCode ?? StockConstants.MES_RESOURCE_CODE, config?.Token ); } /// /// 检查MES返回结果,失败时通过 error 输出错误信息 /// private bool CheckMesResult(HttpResponseResult result, out string error) { if (result?.Data != null && result.Data.IsSuccess) { error = null; return true; } error = result?.Data?.Msg ?? result?.ErrorMessage ?? "未知错误"; return false; } /// /// 创建入库任务并下发WCS /// private async Task CreateTaskAndDispatchAsync( PalletOperationTaskDto dto, int warehouseId, string targetAddress, string roadway, TaskInboundTypeEnum taskType, string successMessage) { int taskNum = await BaseDal.GetTaskNo(); var task = new Dt_Task { TaskNum = taskNum, PalletCode = dto.PalletCode, SourceAddress = dto.LineId, TargetAddress = targetAddress, TaskType = taskType.GetHashCode(), TaskStatus = TaskInStatusEnum.InNew.GetHashCode(), Grade = dto.Grade, Roadway = roadway, WarehouseId = warehouseId, CurrentAddress = dto.LineId, NextAddress = targetAddress, Creater = "manual", CreateDate = DateTime.Now, ModifyDate = DateTime.Now }; if (await BaseDal.AddDataAsync(task) <= 0) return WebResponseContent.Instance.Error("创建任务失败"); var wmsTaskDto = new WMSTaskDTO { TaskNum = task.TaskNum, PalletCode = task.PalletCode, SourceAddress = task.SourceAddress, TargetAddress = task.TargetAddress, TaskType = task.TaskType, Roadway = task.Roadway, TaskStatus = task.TaskStatus, WarehouseId = task.WarehouseId }; var wcsResult = _httpClientHelper.Post( "http://localhost:9292/api/Task/ReceiveManualTask", new List { wmsTaskDto }.ToJson()); if (!wcsResult.IsSuccess || !wcsResult.Data.Status) return WebResponseContent.Instance.Error( $"任务已创建但发送给WCS失败: {wcsResult.ErrorMessage}\r\n {wcsResult.Data?.Message}"); return WebResponseContent.Instance.OK($"{successMessage},任务号: {taskNum}"); } #endregion 手动任务 } }