# 手动创建任务功能实施计划
> **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 后端:起点为普通点位时,走原有路由逻辑