# 手动创建任务功能实施计划 > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** 在WMS界面上添加手动创建任务功能,支持入库/出库/移库三种类型,任务发送至WCS后,WCS判断起点为线体点位(11068/11010/11001)时写入输送线任务。 **Architecture:** WMS前端Vue页面 → WMS后端API → WCS ReceiveTask → WCS FlowService判断并写入输送线PLC **Tech Stack:** .NET 6 (WMS/WCS Backend), Vue3 (WMS Frontend), MapsterMapper, SqlSugar --- ## 文件结构 | 文件 | 职责 | |------|------| | `WMS/WIDESEA_WMSClient/src/extension/taskinfo/task.js` | 添加手动创建任务按钮和处理逻辑 | | `WMS/WIDESEA_WMSServer/WIDESEA_DTO/Task/CreateManualTaskDto.cs` | **新建** - 手动创建任务请求DTO | | `WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/TaskInfo/TaskController.cs` | 添加 CreateManualTask 接口 | | `WMS/WIDESEA_WMSServer/WIDESEA_TaskInfoService/TaskService.cs` | 添加 CreateManualTaskAsync 方法 | | `WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoService/TaskService.cs` | ReceiveWMSTask 已有分发逻辑,确认入口正确 | | `WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoService/Flows/InboundTaskFlowService.cs` | 添加线体点位判断和输送线任务写入逻辑 | --- ## WMS 前端 ### Task 1: 添加手动创建任务按钮 **文件:** - Modify: `WMS/WIDESEA_WMSClient/src/extension/taskinfo/task.js:16` - [ ] **Step 1: 在 buttons.box 添加手动创建按钮** 在 `task.js` 的 `buttons: { view: [], box: [], detail: [] }` 中添加: ```javascript buttons: { view: [], box: [ { value: 'CreateManualTask', label: '手动创建任务', onClick: function () { // 弹出手动创建任务对话框 this.$refs.grid.openModel('Add'); } } ], detail: [] }, ``` - [ ] **Step 2: 在 modelFooter 添加自定义弹窗** 在 `components.modelFooter` 添加 Vue 模板(如果框架支持自定义弹窗内容),或使用框架内置的 `openModel` 配合 `addBefore` 处理。 > **注意:** 具体的弹窗实现取决于当前前端框架的扩展机制。若当前页面不支持自定义字段,则需要在 `task.vue` 中添加新的页面组件,或通过 `modelBody` 扩展自定义表单。 - [ ] **Step 3: 提交** ```bash git add WMS/WIDESEA_WMSClient/src/extension/taskinfo/task.js git commit -m "feat(WMS): 添加手动创建任务按钮" ``` --- ## WMS 后端 ### Task 2: 创建 DTO **文件:** - Create: `WMS/WIDESEA_WMSServer/WIDESEA_DTO/Task/CreateManualTaskDto.cs` - [ ] **Step 1: 编写 CreateManualTaskDto** ```csharp using System.ComponentModel.DataAnnotations; using System.Text.Json.Serialization; using WIDESEA_Common.TaskEnum; namespace WIDESEA_DTO.Task { /// /// 手动创建任务请求DTO /// public class CreateManualTaskDto { /// /// 任务类型:1=入库, 2=出库, 3=移库 /// [JsonPropertyName("taskType")] [Required(ErrorMessage = "任务类型不能为空")] public TaskTypeEnum TaskType { get; set; } /// /// 起点地址 /// [JsonPropertyName("sourceAddress")] [Required(ErrorMessage = "起点地址不能为空")] public string SourceAddress { get; set; } /// /// 终点地址 /// [JsonPropertyName("targetAddress")] [Required(ErrorMessage = "终点地址不能为空")] public string TargetAddress { get; set; } /// /// 条码 /// [JsonPropertyName("barcode")] [Required(ErrorMessage = "条码不能为空")] public string Barcode { get; set; } /// /// 仓库ID /// [JsonPropertyName("warehouseId")] [Required(ErrorMessage = "仓库ID不能为空")] public int WarehouseId { get; set; } /// /// 优先级,默认1 /// [JsonPropertyName("grade")] public int Grade { get; set; } = 1; } } ``` - [ ] **Step 2: 提交** ```bash git add WMS/WIDESEA_WMSServer/WIDESEA_DTO/Task/CreateManualTaskDto.cs git commit -m "feat(WMS): 新增CreateManualTaskDto" ``` --- ### Task 3: 添加 Controller 接口 **文件:** - Modify: `WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/TaskInfo/TaskController.cs` - [ ] **Step 1: 添加 CreateManualTask 接口** 在 `TaskController` 类中添加: ```csharp /// /// 手动创建任务 /// /// 手动创建任务参数 /// [HttpGet, HttpPost, Route("CreateManualTask"), AllowAnonymous] public async Task CreateManualTaskAsync([FromBody] CreateManualTaskDto dto) { return await Service.CreateManualTaskAsync(dto); } ``` - [ ] **Step 2: 提交** ```bash git add WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/TaskInfo/TaskController.cs git commit -m "feat(WMS): 添加手动创建任务接口 CreateManualTask" ``` --- ### Task 4: 添加 TaskService 方法 **文件:** - Modify: `WMS/WIDESEA_WMSServer/WIDESEA_TaskInfoService/TaskService.cs` 首先查看现有 `TaskService.cs` 结构,确认方法签名格式。 - [ ] **Step 1: 添加 CreateManualTaskAsync 方法** 在 `TaskService` 类中添加(参考 `CreateAutoOutboundTasksAsync` 的模式): ```csharp /// /// 手动创建任务 /// /// 手动创建任务参数 /// public async Task CreateManualTaskAsync(CreateManualTaskDto dto) { try { // 1. 生成任务号 int taskNum = await BaseDal.GetTaskNo(); // 2. 根据任务类型确定状态值 int taskStatus = dto.TaskType switch { TaskTypeEnum.Inbound => TaskInStatusEnum.InNew.GetHashCode(), // 200 TaskTypeEnum.Outbound => TaskOutStatusEnum.OutNew.GetHashCode(), // 100 TaskTypeEnum.Relocation => TaskRelocationStatusEnum.New.GetHashCode(), // 300 _ => TaskStatusEnum.New.GetHashCode() }; // 3. 构建任务实体 var task = new Dt_Task { TaskNum = taskNum, PalletCode = dto.Barcode, SourceAddress = dto.SourceAddress, TargetAddress = dto.TargetAddress, TaskType = dto.TaskType.GetHashCode(), TaskStatus = taskStatus, Grade = dto.Grade, WarehouseId = dto.WarehouseId, Creater = "manual", CreateDate = DateTime.Now, ModifyDate = DateTime.Now }; // 4. 保存到数据库 var result = await BaseDal.Add(task); if (!result) return WebResponseContent.Instance.Error("创建任务失败"); // 5. 发送任务到WCS var wmsTaskDto = new WMSTaskDTO { TaskNum = task.TaskNum, PalletCode = task.PalletCode, SourceAddress = task.SourceAddress, TargetAddress = task.TargetAddress, TaskType = task.TaskType, TaskStatus = task.TaskStatus, WarehouseId = task.WarehouseId ?? 0 }; var wcsResult = await _httpClientHelper.PostAsync( "http://localhost:9292/api/Task/ReceiveTask", wmsTaskDto.ToJson()); if (!wcsResult.IsSuccess || !wcsResult.Data.Status) return WebResponseContent.Instance.Error($"任务已创建但发送给WCS失败: {wcsResult.Data?.Message}"); return WebResponseContent.Instance.OK($"手动创建任务成功,任务号: {taskNum}"); } catch (Exception ex) { return WebResponseContent.Instance.Error($"手动创建任务异常: {ex.Message}"); } } ``` > **注意:** 需要确认 `TaskRelocationStatusEnum` 是否存在,如不存在则使用 `TaskStatusEnum.New`。 - [ ] **Step 2: 添加必要的 using** ```csharp using WIDESEA_DTO.Task; using WIDESEA_Common.TaskEnum; using WIDESEAWCS_DTO; ``` - [ ] **Step 3: 提交** ```bash git add WMS/WIDESEA_WMSServer/WIDESEA_TaskInfoService/TaskService.cs git commit -m "feat(WMS): 添加手动创建任务 CreateManualTaskAsync 方法" ``` --- ## WCS 后端 ### Task 5: 确认 ReceiveWMSTask 入口 **文件:** - Modify: `WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoService/TaskService.cs` - [ ] **Step 1: 确认 ReceiveWMSTask 方法存在** 确认 `ReceiveWMSTask` 接收 `WMSTaskDTO` 列表后路由到对应 FlowService。 ```csharp public WebResponseContent ReceiveWMSTask([NotNull] List taskDTOs) { // 遍历任务,根据 TaskType 分发到不同 FlowService foreach (var item in taskDTOs) { // 根据 item.TaskType 判断路由到 Inbound/Outbound/Relocation } } ``` - [ ] **Step 2: 提交** ```bash git add WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoService/TaskService.cs git commit -m "feat(WCS): 确认 ReceiveWMSTask 入口正确" ``` --- ### Task 6: 在 InboundTaskFlowService 添加线体点位处理 **文件:** - Modify: `WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoService/Flows/InboundTaskFlowService.cs` 首先查看现有的 `InitializeOnReceive` 方法完整实现。 - [ ] **Step 1: 添加线体点位常量** 在类中添加: ```csharp /// /// 线体入库点位 /// private static readonly string[] LINE_IN_POINTS = { "11068", "11010", "11001" }; ``` - [ ] **Step 2: 修改 InitializeOnReceive 方法** 在 `InitializeOnReceive` 方法中,判断如果 `SourceAddress` 是线体点位,则写入输送线任务: ```csharp public void InitializeOnReceive([NotNull] Dt_Task task, [NotNull] WMSTaskDTO source) { // 先执行原有的路由逻辑 Dt_Router routers = _routerService.QueryNextRoute(source.SourceAddress); if (routers.IsNullOrEmpty()) { return; } task.TaskStatus = (int)TaskInStatusEnum.InNew; task.CurrentAddress = source.SourceAddress; task.NextAddress = routers.ChildPosi; // 如果起点是线体点位(11068/11010/11001),直接写入输送线任务 if (LINE_IN_POINTS.Contains(source.SourceAddress)) { WriteConveyorLineTask(task, source.SourceAddress); } } /// /// 写入输送线任务到PLC /// private void WriteConveyorLineTask(Dt_Task task, string sourceAddress) { // 1. 通过 Storage.Devices 查找对应的输送线设备 IDevice? device = Storage.Devices .FirstOrDefault(x => x.DeviceProDTOs.Any(d => d.DeviceChildCode == sourceAddress)); if (device == null) { // 记录日志:未找到对应的输送线设备 QuartzLogger.Error($"手动创建任务:未找到源地址【{sourceAddress}】对应的输送线设备", "InboundTaskFlowService"); return; } // 2. 转换为 CommonConveyorLine 类型 if (device is not CommonConveyorLine conveyorLine) { QuartzLogger.Error($"设备【{device.DeviceCode}】不是输送线类型", "InboundTaskFlowService"); return; } // 3. 获取子设备编码 string? childDeviceCode = device.DeviceProDTOs .FirstOrDefault(d => d.DeviceChildCode == sourceAddress)?.DeviceChildCode; if (string.IsNullOrEmpty(childDeviceCode)) { QuartzLogger.Error($"源地址【{sourceAddress}】未找到对应的子设备编码", "InboundTaskFlowService"); return; } // 4. 构造 ConveyorLineTaskCommandNew ConveyorLineTaskCommandNew command = new ConveyorLineTaskCommandNew { TaskNo = (short)task.TaskNum, Source = short.Parse(sourceAddress), Target = short.Parse(task.TargetAddress ?? "0"), Barcode = task.PalletCode ?? string.Empty, WCS_STB = 1, // WCS已发送标志 WCS_ACK = 0, PLC_STB = 0, PLC_ACK = 0 }; // 5. 写入PLC bool success = conveyorLine.SendCommand(command, childDeviceCode); if (success) { QuartzLogger.Info($"手动创建入库任务已写入输送线:任务号【{task.TaskNum}】,源地址【{sourceAddress}】", conveyorLine.DeviceCode); } else { QuartzLogger.Error($"手动创建入库任务写入输送线失败:任务号【{task.TaskNum}】,源地址【{sourceAddress}】", conveyorLine.DeviceCode); } } ``` - [ ] **Step 3: 添加必要的 using** ```csharp using WIDESEAWCS_QuartzJob; using WIDESEAWCS_QuartzJob.ConveyorLine; using WIDESEAWCS_Tasks; using WIDESEAWCS_Core.LogHelper; ``` - [ ] **Step 4: 提交** ```bash git add WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoService/Flows/InboundTaskFlowService.cs git commit -m "feat(WCS): 入库任务线体点位时写入输送线任务" ``` --- ## 验证清单 - [ ] WMS 前端:点击"手动创建任务"按钮弹出对话框 - [ ] WMS 前端:填写表单后提交,返回成功 - [ ] WMS 后端:创建任务写入数据库 - [ ] WMS 后端:任务成功发送给 WCS - [ ] WCS 后端:ReceiveTask 收到任务 - [ ] WCS 后端:起点为线体点位时,任务写入 PLC 成功 - [ ] WCS 后端:起点为普通点位时,走原有路由逻辑