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 手动任务
|
|
/// <summary>
|
/// 手动创建任务
|
/// </summary>
|
/// <param name="dto">手动创建任务参数</param>
|
/// <returns></returns>
|
public async Task<WebResponseContent> 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<WMSTaskDTO>()
|
{
|
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<WebResponseContent>(
|
"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}");
|
}
|
}
|
|
/// <summary>
|
/// 手动下发任务到WCS(批量处理)
|
/// </summary>
|
/// <param name="dtos">下发任务参数列表</param>
|
/// <returns>批量下发结果</returns>
|
public async Task<WebResponseContent> DispatchTasksToWCSAsync(List<DispatchTaskDto> dtos)
|
{
|
try
|
{
|
if (dtos == null || !dtos.Any())
|
return WebResponseContent.Instance.Error("请选择要下发的任务");
|
|
var resultDto = new DispatchResultDto
|
{
|
SuccessCount = 0,
|
FailCount = 0,
|
FailResults = new List<DispatchTaskResultDto>()
|
};
|
|
// 第一步:查询并校验所有任务状态,收集有效任务
|
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<WebResponseContent>(
|
"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<ReceiveTaskResultDto>(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}");
|
}
|
}
|
|
/// <summary>
|
/// 获取任务状态名称
|
/// </summary>
|
/// <param name="taskType">任务类型</param>
|
/// <param name="taskStatus">任务状态</param>
|
/// <returns>状态的中文描述</returns>
|
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();
|
}
|
|
/// <summary>
|
/// 组盘/拆盘操作并创建入库任务,完成后下发WCS
|
/// </summary>
|
/// <param name="dto">组盘/拆盘操作参数</param>
|
/// <returns>操作结果</returns>
|
public async Task<WebResponseContent> 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<Dt_Warehouse>()
|
.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}");
|
}
|
}
|
|
/// <summary>
|
/// 执行组盘操作:添加库存 → 直接调用MES绑定 → 创建入库任务 → 下发WCS
|
/// </summary>
|
private async Task<WebResponseContent> 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, "组盘成功并创建入库任务");
|
});
|
}
|
|
/// <summary>
|
/// 执行拆盘操作:查询库存 → 直接调用MES解绑 → 清除库存货位 → 创建空托盘入库任务 → 下发WCS
|
/// </summary>
|
private async Task<WebResponseContent> 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<string>();
|
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, "拆盘成功并创建空托盘入库任务");
|
});
|
}
|
|
/// <summary>
|
/// 解析MES设备配置,返回 (equipmentCode, resourceCode, token)
|
/// </summary>
|
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
|
);
|
}
|
|
/// <summary>
|
/// 检查MES返回结果,失败时通过 error 输出错误信息
|
/// </summary>
|
private bool CheckMesResult(HttpResponseResult<MesResponse> result, out string error)
|
{
|
if (result?.Data != null && result.Data.IsSuccess)
|
{
|
error = null;
|
return true;
|
}
|
|
error = result?.Data?.Msg ?? result?.ErrorMessage ?? "未知错误";
|
return false;
|
}
|
|
/// <summary>
|
/// 创建入库任务并下发WCS
|
/// </summary>
|
private async Task<WebResponseContent> 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<WebResponseContent>(
|
"http://localhost:9292/api/Task/ReceiveManualTask",
|
new List<WMSTaskDTO> { 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 手动任务
|
}
|
}
|