wanshenmean
5 天以前 b0327633d7d0c19693a4e577d1e17b3b22e8274e
refactor(WCS&WMS): 新增手动下发输送线任务

- 移除command参数,直接使用conveyorLine和task写入PLC

fix(WCS): 更新任务状态失败时不写入WCS_ACK

检查UpdateTaskStatusToNext返回的Status字段,
只有在成功时才写入WCS_ACK=1

feat(WCS): 手动任务写入PLC前先更新任务状态

- ManualInboundTaskHandler增加ITaskService依赖
- 写入PLC前调用UpdateTaskStatusToNext更新任务状态
- WCS_ACK写入改为1

fix(WCS): 手动任务写入PLC时WCS_ACK设为0

根据用户最终要求,WCS_ACK标志应设为0而非1,
并使用SetValue逐字段写入而非SendCommand整体写入。

refactor(WCS): 内联手动任务处理逻辑

- 移除ProcessManualInboundTasks方法,直接在调用处处理

fix(WCS): 根据childDeviceCode查找手动任务

- QueryManualInboundTask根据sourceAddress查找单个任务
- ProcessManualInboundTasks先查找任务再写入PLC

refactor(WCS): 优化手动任务处理直接使用PLC命令

- ProcessManualInboundTasks直接接收conveyorLine/command/childDeviceCode参数
- ManualInboundTaskHandler直接写入PLC,无需Storage.Devices查找

refactor(WCS): 重构手动任务处理架构

- TaskInfoService层:ReceiveManualTask只创建任务,QueryManualInboundTasks查询手动入库任务
- ConveyorLineNewJob层:新建ManualInboundTaskHandler处理写入PLC
- CommonConveyorLineNewJob定时轮询检测并处理手动任务

feat(WMS): 添加手动创建任务接口 CreateManualTask

feat(WCS): 添加手动任务接收接口ReceiveManualTask

- 新增ReceiveManualTask方法,调用ReceiveWMSTask后写入PLC
- 新增WriteConveyorLineTask方法写入输送线任务到PLC
- 新增ReceiveManualTask API接口
- 恢复InboundTaskFlowService原始代码
- WMS调用改为ReceiveManualTask接口

feat(WCS): 添加线体入库点位处理逻辑

当入库任务起点为线体入库点位(11068/11010/11001)时,跳过路由查询,
直接通过 Storage.Devices 查找输送线设备并写入 PLC 任务。

feat(WMS): 添加 CreateManualTaskAsync 方法

feat(WMS): 新增 CreateManualTaskDto

fix(WMS): 修复手动创建任务字段名与后端DTO对齐

feat(WMS): 添加手动创建任务按钮

在任务管理页面添加"手动创建任务"按钮,点击后弹出手动创建任务对话框,包含任务类型(入库/出库/移库)、起点地址、终点地址、条码、仓库ID、优先级等字段,点击确定后调用 POST /api/Task/CreateManualTask 接口提交数据。

docs: 添加手动创建任务功能实施计划

docs: 更新手动创建任务设计(修复评审问题)

- 修正WCS调用为ReceiveTask
- 明确线体点位(11068/11010/11001)均为入库点位
- 新增写入输送线任务逻辑说明
- 添加WCS相关源文件

docs: 添加手动创建任务功能设计文档
已添加5个文件
已修改9个文件
993 ■■■■■ 文件已修改
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_ITaskInfoService/ITaskService.cs 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Server/Controllers/Task/TaskController.cs 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoService/TaskService.cs 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/ConveyorLineNewJob/CommonConveyorLineNewJob.cs 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/ConveyorLineNewJob/ConveyorLineDispatchHandler.cs 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/ConveyorLineNewJob/ManualInbound/ManualInboundTaskHandler.cs 73 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSClient/src/extension/taskinfo/extend/addManualTask.vue 93 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSClient/src/extension/taskinfo/task.js 15 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_DTO/Task/CreateManualTaskDto.cs 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_ITaskInfoService/ITaskService.cs 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_TaskInfoService/TaskService_WCS.cs 83 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/TaskInfo/TaskController.cs 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/docs/superpowers/plans/2026-04-11-manual-task-creation-plan.md 434 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/docs/superpowers/specs/2026-04-11-manual-task-creation-design.md 151 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_ITaskInfoService/ITaskService.cs
@@ -61,6 +61,20 @@
        WebResponseContent ReceiveWMSTask([NotNull] List<WMSTaskDTO> taskDTOs);
        /// <summary>
        /// æŽ¥æ”¶WMS手动创建的任务,创建WCS任务
        /// </summary>
        /// <param name="taskDTOs">WMS任务对象集合</param>
        /// <returns>返回处理结果</returns>
        WebResponseContent ReceiveManualTask([NotNull] List<WMSTaskDTO> taskDTOs);
        /// <summary>
        /// æŸ¥è¯¢æŒ‡å®šèµ·ç‚¹åœ°å€çš„æ–°å»ºæ‰‹åŠ¨å…¥åº“ä»»åŠ¡
        /// </summary>
        /// <param name="sourceAddress">起点地址</param>
        /// <returns>任务列表</returns>
        Dt_Task QueryManualInboundTask(string sourceAddress);
        /// <summary>
        /// æ ¹æ®æ‰˜ç›˜å·ã€èµ·å§‹åœ°å€å‘WMS请求任务
        /// </summary>
        /// <param name="palletCode">托盘号</param>
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Server/Controllers/Task/TaskController.cs
@@ -25,6 +25,12 @@
            return Service.ReceiveWMSTask(taskDTOs);
        }
        [HttpPost, Route("ReceiveManualTask"), AllowAnonymous]
        public WebResponseContent ReceiveManualTask([FromBody] List<WMSTaskDTO> taskDTOs)
        {
            return Service.ReceiveManualTask(taskDTOs);
        }
        [HttpPost, HttpGet(), Route("UpdateTaskExceptionMessage")]
        public WebResponseContent UpdateTaskExceptionMessage(int taskNum, string message)
        {
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoService/TaskService.cs
@@ -644,6 +644,41 @@
        {
            return BaseDal.QueryFirst(x => x.TaskNum == taskNum);
        }
        /// <summary>
        /// æŽ¥æ”¶WMS手动创建的任务,创建WCS任务
        /// </summary>
        /// <param name="taskDTOs">WMS任务对象集合</param>
        /// <returns>返回处理结果</returns>
        public WebResponseContent ReceiveManualTask([NotNull] List<WMSTaskDTO> taskDTOs)
        {
            WebResponseContent content = new WebResponseContent();
            try
            {
                // è°ƒç”¨ ReceiveWMSTask åˆ›å»º WCS ä»»åŠ¡
                content = ReceiveWMSTask(taskDTOs);
                return content;
            }
            catch (Exception ex)
            {
                content = WebResponseContent.Instance.Error($"手动任务接收错误,错误信息:{ex.Message}");
                return content;
            }
        }
        /// <summary>
        /// æŸ¥è¯¢æŒ‡å®šèµ·ç‚¹åœ°å€çš„æ–°å»ºæ‰‹åŠ¨å…¥åº“ä»»åŠ¡
        /// </summary>
        /// <param name="sourceAddress">起点地址</param>
        /// <returns>任务实体</returns>
        public Dt_Task QueryManualInboundTask(string sourceAddress)
        {
            return BaseDal.QueryFirst(x =>
                x.TaskType == (int)TaskInboundTypeEnum.Inbound &&
                x.TaskStatus == (int)TaskInStatusEnum.InNew &&
                x.SourceAddress == sourceAddress &&
                x.Creater == "WMS");
        }
    }
    public enum ConveyorLineDBNameNew
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/ConveyorLineNewJob/CommonConveyorLineNewJob.cs
@@ -14,6 +14,7 @@
using WIDESEAWCS_Model.Models;
using WIDESEAWCS_QuartzJob;
using WIDESEAWCS_QuartzJob.Service;
using ManualInboundTaskHandler = WIDESEAWCS_Tasks.ConveyorLineNewJob.ManualInbound.ManualInboundTaskHandler;
namespace WIDESEAWCS_Tasks
{
@@ -212,6 +213,22 @@
                                // å¦‚æžœ WCS_ACK ä¸º 1,先清除(表示处理过上一次请求)
                                if (command.WCS_ACK == 1)
                                    conveyorLine.SetValue(ConveyorLineDBNameNew.WCS_ACK, (short)0, childDeviceCode);
                                // å¤„理手动入库任务(起点为线体点位的任务)
                                try
                                {
                                    var task = _taskService.QueryManualInboundTask(childDeviceCode);
                                    if (task != null)
                                    {
                                        var handler = new ManualInboundTaskHandler(_taskService);
                                        handler.WriteTaskToPlc(conveyorLine, childDeviceCode, task);
                                    }
                                }
                                catch (Exception ex)
                                {
                                    _logger.LogError(ex, "处理手动入库任务异常");
                                    QuartzLogger.Error($"处理手动入库任务异常: {ex.Message}", "CommonConveyorLineNewJob", ex);
                                }
                                continue;
                            }
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/ConveyorLineNewJob/ConveyorLineDispatchHandler.cs
@@ -210,15 +210,14 @@
                // æ›´æ–°ä»»åŠ¡çŠ¶æ€åˆ°ä¸‹ä¸€é˜¶æ®µï¼ˆé€šå¸¸æ˜¯å®Œæˆï¼‰
                if (_taskService.UpdateTaskStatusToNext(task).Status)
                {
                }
                // å›žå¤ ACK ç¡®è®¤
                conveyorLine.SetValue(ConveyorLineDBNameNew.WCS_ACK, (short)1, childDeviceCode);
                _logger.LogInformation("ConveyorLineInFinish:入库完成,任务号: {TaskNum},子设备: {ChildDeviceCode}", task.TaskNum, childDeviceCode);
                QuartzLogger.Info($"入库完成,任务号: {task.TaskNum}", conveyorLine.DeviceCode);
            }
            }
        }
        /// <summary>
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/ConveyorLineNewJob/ManualInbound/ManualInboundTaskHandler.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,73 @@
using WIDESEAWCS_Core.LogHelper;
using WIDESEAWCS_ITaskInfoService;
using WIDESEAWCS_Model.Models;
using WIDESEAWCS_QuartzJob;
using WIDESEAWCS_QuartzJob.ConveyorLine;
namespace WIDESEAWCS_Tasks.ConveyorLineNewJob.ManualInbound
{
    /// <summary>
    /// æ‰‹åŠ¨å…¥åº“ä»»åŠ¡å¤„ç†å™¨
    /// </summary>
    /// <remarks>
    /// è´Ÿè´£å¤„理手动创建的入库任务,当PLC请求入库时,根据childDeviceCode查找任务并写入PLC。
    /// </remarks>
    public class ManualInboundTaskHandler
    {
        /// <summary>
        /// ä»»åŠ¡æœåŠ¡
        /// </summary>
        private readonly ITaskService _taskService;
        /// <summary>
        /// æž„造函数
        /// </summary>
        /// <param name="taskService">任务服务</param>
        public ManualInboundTaskHandler(ITaskService taskService)
        {
            _taskService = taskService;
        }
        /// <summary>
        /// å†™å…¥æ‰‹åŠ¨å…¥åº“ä»»åŠ¡åˆ°PLC
        /// </summary>
        /// <param name="conveyorLine">输送线设备对象</param>
        /// <param name="childDeviceCode">子设备编码</param>
        /// <param name="task">任务实体</param>
        public void WriteTaskToPlc(CommonConveyorLine conveyorLine, string childDeviceCode, Dt_Task task)
        {
            if (conveyorLine == null || string.IsNullOrEmpty(childDeviceCode) || task == null)
            {
                QuartzLogger.Error("ManualInboundTaskHandler.WriteTaskToPlc: å‚数为空", "ManualInbound");
                return;
            }
            try
            {
                // å†™å…¥ä»»åŠ¡å·
                conveyorLine.SetValue(ConveyorLineDBNameNew.TaskNo, (short)task.TaskNum, childDeviceCode);
                // å†™å…¥èµ·å§‹åœ°å€
                conveyorLine.SetValue(ConveyorLineDBNameNew.Source, short.Parse(task.SourceAddress ?? "0"), childDeviceCode);
                // å†™å…¥ç›®æ ‡åœ°å€
                conveyorLine.SetValue(ConveyorLineDBNameNew.Target, short.Parse(task.TargetAddress ?? "0"), childDeviceCode);
                // æ›´æ–°ä»»åŠ¡çŠ¶æ€åˆ°ä¸‹ä¸€é˜¶æ®µ
                var updateResult = _taskService.UpdateTaskStatusToNext(task);
                if (!updateResult.Status)
                {
                    QuartzLogger.Error($"ManualInboundTaskHandler: æ›´æ–°ä»»åŠ¡çŠ¶æ€å¤±è´¥ï¼Œä»»åŠ¡å·ã€{task.TaskNum}】,错误信息:{updateResult.Message}", conveyorLine.DeviceCode);
                    return;
                }
                // å†™å…¥ACK标志
                conveyorLine.SetValue(ConveyorLineDBNameNew.WCS_ACK, (short)1, childDeviceCode);
                QuartzLogger.Info($"ManualInboundTaskHandler: æ‰‹åŠ¨ä»»åŠ¡å†™å…¥PLC成功,任务号【{task.TaskNum}】,源地址【{task.SourceAddress}】", conveyorLine.DeviceCode);
            }
            catch (Exception ex)
            {
                QuartzLogger.Error($"ManualInboundTaskHandler: å†™å…¥è¾“送线任务异常,错误信息:{ex.Message}", "ManualInbound");
            }
        }
    }
}
Code/WMS/WIDESEA_WMSClient/src/extension/taskinfo/extend/addManualTask.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,93 @@
<template>
  <div>
    <vol-box
      v-model="showBox"
      :lazy="true"
      width="500px"
      :padding="15"
      title="手动创建任务"
    >
      <el-form :model="formData" ref="form" label-width="100px">
        <el-form-item label="任务类型" prop="taskType" required>
          <el-select v-model="formData.taskType" placeholder="请选择任务类型">
            <el-option label="入库" value="入库"></el-option>
            <el-option label="出库" value="出库"></el-option>
            <el-option label="移库" value="移库"></el-option>
          </el-select>
        </el-form-item>
        <el-form-item label="起点地址" prop="sourceAddress" required>
          <el-input v-model="formData.sourceAddress" placeholder="请输入起点地址"></el-input>
        </el-form-item>
        <el-form-item label="终点地址" prop="targetAddress" required>
          <el-input v-model="formData.targetAddress" placeholder="请输入终点地址"></el-input>
        </el-form-item>
        <el-form-item label="条码" prop="barcode" required>
          <el-input v-model="formData.barcode" placeholder="请输入条码"></el-input>
        </el-form-item>
        <el-form-item label="仓库ID" prop="warehouseId" required>
          <el-input v-model="formData.warehouseId" placeholder="请输入仓库ID"></el-input>
        </el-form-item>
        <el-form-item label="优先级">
          <el-input v-model="formData.grade" readonly></el-input>
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button type="primary" size="small" @click="submit">确定</el-button>
        <el-button type="danger" size="small" @click="showBox = false">关闭</el-button>
      </template>
    </vol-box>
  </div>
</template>
<script>
import VolBox from "@/components/basic/VolBox.vue";
export default {
  components: { VolBox },
  data() {
    return {
      showBox: false,
      formData: {
        taskType: "",
        sourceAddress: "",
        targetAddress: "",
        barcode: "",
        warehouseId: "",
        grade: 1,
      },
    };
  },
  methods: {
    open() {
      this.showBox = true;
      this.resetForm();
    },
    resetForm() {
      this.formData = {
        taskType: "",
        sourceAddress: "",
        targetAddress: "",
        barcode: "",
        warehouseId: "",
        grade: 1,
      };
    },
    submit() {
      if (!this.formData.taskType) return this.$message.error("请选择任务类型");
      if (!this.formData.sourceAddress) return this.$message.error("请输入起点地址");
      if (!this.formData.targetAddress) return this.$message.error("请输入终点地址");
      if (!this.formData.barcode) return this.$message.error("请输入条码");
      if (!this.formData.warehouseId) return this.$message.error("请输入仓库ID");
      this.http
        .post("/api/Task/CreateManualTask", this.formData, "数据处理中...")
        .then((res) => {
          if (!res.status) return this.$message.error(res.message);
          this.$message.success("任务创建成功");
          this.showBox = false;
          this.$emit("parentCall", ($vue) => {
            $vue.refresh();
          });
        });
    },
  },
};
</script>
Code/WMS/WIDESEA_WMSClient/src/extension/taskinfo/task.js
@@ -1,11 +1,12 @@
//此js文件是用来自定义扩展业务代码,可以扩展一些自定义页面或者重新配置生成的代码
import addManualTask from './extend/addManualTask.vue'
let extension = {
    components: {
      //查询界面扩展组件
      gridHeader: '',
      gridBody: '',
      gridBody: addManualTask,
      gridFooter: '',
      //新建、编辑弹出框扩展组件
      modelHeader: '',
@@ -17,6 +18,17 @@
    methods: {
       //下面这些方法可以保留也可以删除
      onInit() {  
        //添加"手动创建任务"按钮
        this.buttons.push({
          name: '手动创建任务',
          icon: 'el-icon-plus',
          type: 'primary',
          value: 'ManualCreateTask',
          onClick: () => {
            this.$refs.gridBody.open();
          }
        });
        let TaskHandCancelBtn = this.buttons.find(x => x.value == 'TaskHandCancel');
      if (TaskHandCancelBtn) {
        TaskHandCancelBtn.onClick = function () {
@@ -93,4 +105,3 @@
    }
  };
  export default extension;
Code/WMS/WIDESEA_WMSServer/WIDESEA_DTO/Task/CreateManualTaskDto.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,47 @@
using System.Text.Json.Serialization;
using WIDESEA_Common.TaskEnum;
namespace WIDESEA_DTO.Task
{
    /// <summary>
    /// æ‰‹åŠ¨åˆ›å»ºä»»åŠ¡Dto
    /// </summary>
    public class CreateManualTaskDto
    {
        /// <summary>
        /// ä»»åŠ¡ç±»åž‹ï¼šå…¥åº“/出库/移库
        /// </summary>
        [JsonPropertyName("taskType")]
        public string TaskType { get; set; }
        /// <summary>
        /// èµ·ç‚¹åœ°å€
        /// </summary>
        [JsonPropertyName("sourceAddress")]
        public string SourceAddress { get; set; }
        /// <summary>
        /// ç»ˆç‚¹åœ°å€
        /// </summary>
        [JsonPropertyName("targetAddress")]
        public string TargetAddress { get; set; }
        /// <summary>
        /// æ¡ç 
        /// </summary>
        [JsonPropertyName("barcode")]
        public string Barcode { get; set; }
        /// <summary>
        /// ä»“库ID
        /// </summary>
        [JsonPropertyName("warehouseId")]
        public int WarehouseId { get; set; }
        /// <summary>
        /// ä¼˜å…ˆçº§ï¼Œé»˜è®¤1
        /// </summary>
        [JsonPropertyName("grade")]
        public int Grade { get; set; } = 1;
    }
}
Code/WMS/WIDESEA_WMSServer/WIDESEA_ITaskInfoService/ITaskService.cs
@@ -160,6 +160,13 @@
        /// <returns></returns>
        Task<WebResponseContent> CreateRobotChangePalletTaskAsync(StockDTO stock);
        /// <summary>
        /// æ‰‹åŠ¨åˆ›å»ºä»»åŠ¡
        /// </summary>
        /// <param name="dto">手动创建任务参数</param>
        /// <returns></returns>
        Task<WebResponseContent> CreateManualTaskAsync(CreateManualTaskDto dto);
        #region æžå·åº“任务模块
        /// <summary>
Code/WMS/WIDESEA_WMSServer/WIDESEA_TaskInfoService/TaskService_WCS.cs
@@ -876,6 +876,89 @@
            }
        }
        /// <summary>
        /// æ‰‹åŠ¨åˆ›å»ºä»»åŠ¡
        /// </summary>
        /// <param name="dto">手动创建任务参数</param>
        /// <returns></returns>
        public async Task<WebResponseContent> CreateManualTaskAsync(CreateManualTaskDto dto)
        {
            try
            {
                // 1. æ ¹æ®ä»»åŠ¡ç±»åž‹å­—ç¬¦ä¸²ç¡®å®š TaskType å’Œ TaskStatus
                int taskType;
                int taskStatus;
                switch (dto.TaskType)
                {
                    case "入库":
                        taskType = TaskTypeEnum.Inbound.GetHashCode();
                        taskStatus = TaskInStatusEnum.InNew.GetHashCode();
                        break;
                    case "出库":
                        taskType = TaskTypeEnum.Outbound.GetHashCode();
                        taskStatus = TaskOutStatusEnum.OutNew.GetHashCode();
                        break;
                    case "移库":
                        taskType = TaskTypeEnum.Relocation.GetHashCode();
                        taskStatus = TaskRelocationStatusEnum.RelocationNew.GetHashCode();
                        break;
                    default:
                        return WebResponseContent.Instance.Error($"不支持的任务类型: {dto.TaskType}");
                }
                // 2. ç”Ÿæˆä»»åŠ¡å·
                int taskNum = await BaseDal.GetTaskNo();
                // 3. æž„建任务实体
                var task = new Dt_Task
                {
                    TaskNum = taskNum,
                    PalletCode = dto.Barcode,
                    SourceAddress = dto.SourceAddress,
                    TargetAddress = dto.TargetAddress,
                    TaskType = taskType,
                    TaskStatus = taskStatus,
                    Grade = dto.Grade,
                    WarehouseId = dto.WarehouseId,
                    CurrentAddress = dto.SourceAddress,
                    NextAddress = dto.TargetAddress,
                    Creater = "manual",
                    CreateDate = DateTime.Now,
                    ModifyDate = DateTime.Now
                };
                // 4. ä¿å­˜åˆ°æ•°æ®åº“
                var result = await BaseDal.AddDataAsync(task) > 0;
                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
                };
                var wcsResult = _httpClientHelper.Post<WebResponseContent>(
                    "http://localhost:9292/api/Task/ReceiveManualTask",
                    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}");
            }
        }
        #endregion WCS逻辑处理
    }
}
Code/WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/TaskInfo/TaskController.cs
@@ -47,6 +47,17 @@
        }
        /// <summary>
        /// æ‰‹åŠ¨åˆ›å»ºä»»åŠ¡
        /// </summary>
        /// <param name="dto">手动创建任务参数</param>
        /// <returns></returns>
        [HttpGet, HttpPost, Route("CreateManualTask"), AllowAnonymous]
        public async Task<WebResponseContent?> CreateManualTaskAsync([FromBody] CreateManualTaskDto dto)
        {
            return await Service.CreateManualTaskAsync(dto);
        }
        /// <summary>
        /// èŽ·å–å¯å…¥åº“è´§ä½
        /// </summary>
        /// <param name="taskDto"></param>
Code/docs/superpowers/plans/2026-04-11-manual-task-creation-plan.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,434 @@
# æ‰‹åŠ¨åˆ›å»ºä»»åŠ¡åŠŸèƒ½å®žæ–½è®¡åˆ’
> **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
{
    /// <summary>
    /// æ‰‹åŠ¨åˆ›å»ºä»»åŠ¡è¯·æ±‚DTO
    /// </summary>
    public class CreateManualTaskDto
    {
        /// <summary>
        /// ä»»åŠ¡ç±»åž‹ï¼š1=入库, 2=出库, 3=移库
        /// </summary>
        [JsonPropertyName("taskType")]
        [Required(ErrorMessage = "任务类型不能为空")]
        public TaskTypeEnum TaskType { get; set; }
        /// <summary>
        /// èµ·ç‚¹åœ°å€
        /// </summary>
        [JsonPropertyName("sourceAddress")]
        [Required(ErrorMessage = "起点地址不能为空")]
        public string SourceAddress { get; set; }
        /// <summary>
        /// ç»ˆç‚¹åœ°å€
        /// </summary>
        [JsonPropertyName("targetAddress")]
        [Required(ErrorMessage = "终点地址不能为空")]
        public string TargetAddress { get; set; }
        /// <summary>
        /// æ¡ç 
        /// </summary>
        [JsonPropertyName("barcode")]
        [Required(ErrorMessage = "条码不能为空")]
        public string Barcode { get; set; }
        /// <summary>
        /// ä»“库ID
        /// </summary>
        [JsonPropertyName("warehouseId")]
        [Required(ErrorMessage = "仓库ID不能为空")]
        public int WarehouseId { get; set; }
        /// <summary>
        /// ä¼˜å…ˆçº§ï¼Œé»˜è®¤1
        /// </summary>
        [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
/// <summary>
/// æ‰‹åŠ¨åˆ›å»ºä»»åŠ¡
/// </summary>
/// <param name="dto">手动创建任务参数</param>
/// <returns></returns>
[HttpGet, HttpPost, Route("CreateManualTask"), AllowAnonymous]
public async Task<WebResponseContent?> 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
/// <summary>
/// æ‰‹åŠ¨åˆ›å»ºä»»åŠ¡
/// </summary>
/// <param name="dto">手动创建任务参数</param>
/// <returns></returns>
public async Task<WebResponseContent> 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<WebResponseContent>(
            "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<WMSTaskDTO> 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
/// <summary>
/// çº¿ä½“入库点位
/// </summary>
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);
    }
}
/// <summary>
/// å†™å…¥è¾“送线任务到PLC
/// </summary>
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 åŽç«¯ï¼šèµ·ç‚¹ä¸ºæ™®é€šç‚¹ä½æ—¶ï¼Œèµ°åŽŸæœ‰è·¯ç”±é€»è¾‘
Code/docs/superpowers/specs/2026-04-11-manual-task-creation-design.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,151 @@
# æ‰‹åŠ¨åˆ›å»ºä»»åŠ¡åŠŸèƒ½è®¾è®¡
## 1. éœ€æ±‚概述
在 WMS ç•Œé¢ä¸Šæ·»åŠ æ‰‹åŠ¨åˆ›å»ºä»»åŠ¡åŠŸèƒ½ã€‚ç”¨æˆ·è¾“å…¥èµ·ç‚¹ã€ç»ˆç‚¹ã€æ¡ç ã€ä»“åº“ID,系统自动生成任务号等字段,发送给 WCS。WCS æ ¹æ®èµ·ç‚¹æ˜¯å¦ä¸ºçº¿ä½“点位(11068/11010/11001)判断是否需要写入输送线任务。
## 2. æ•´ä½“流程
```
用户(WMS界面)
    â†“ è¾“入:任务类型/起点/终点/条码/仓库ID
WMS后端
    â†“ è°ƒç”¨ BaseDal.GetTaskNo() ç”Ÿæˆ TaskNum
    â†“ æ ¹æ®ä»»åŠ¡ç±»åž‹è®¾ç½®çŠ¶æ€
    â†“ è°ƒç”¨ WCS ReceiveTask
WCS后端
    â†“ åˆ¤æ–­èµ·ç‚¹æ˜¯å¦ä¸ºçº¿ä½“点位(11068/11010/11001)
    â†“ æ˜¯ â†’ æŸ¥ Storage.Devices èŽ·å–è¾“é€çº¿å®žä¾‹
    â†“ å†™å…¥è¾“送线任务(起点/终点/任务号等)
    â†“ è¿”回成功
```
## 3. WMS å‰ç«¯æ”¹åЍ
### 3.1 ä»»åŠ¡é¡µé¢ (`task.vue`)
在现有任务管理页面添加**手动创建任务**按钮,弹出对话框。
### 3.2 å¯¹è¯æ¡†å­—段
| å­—段 | ç±»åž‹ | è¯´æ˜Ž |
|------|------|------|
| ä»»åŠ¡ç±»åž‹ | ä¸‹æ‹‰æ¡† | å…¥åº“ / å‡ºåº“ / ç§»åº“ |
| èµ·ç‚¹åœ°å€ | è¾“入框 | ç”¨æˆ·è¾“入,如 11068 |
| ç»ˆç‚¹åœ°å€ | è¾“入框 | ç”¨æˆ·è¾“å…¥ |
| æ¡ç  | è¾“入框 | ç”¨æˆ·è¾“å…¥ |
| ä»“库ID | è¾“入框 | ç”¨æˆ·è¾“å…¥ |
| ä¼˜å…ˆçº§ | åªè¯» | é»˜è®¤å€¼ 1 |
## 4. WMS åŽç«¯æ”¹åЍ
### 4.1 æ–°å¢žæŽ¥å£
**Controller:** `TaskController.cs`
```
POST /api/Task/CreateManualTask
```
### 4.2 Service æ–¹æ³•
**TaskService.cs** æ–°å¢ž `CreateManualTaskAsync` æ–¹æ³•:
```csharp
public async Task<WebResponseContent> CreateManualTaskAsync(CreateManualTaskDto dto)
{
    // 1. è°ƒç”¨ BaseDal.GetTaskNo() ç”Ÿæˆ TaskNum
    // 2. æ ¹æ®ä»»åŠ¡ç±»åž‹è®¾ç½®çŠ¶æ€
    //    - å…¥åº“ â†’ InNew (200)
    //    - å‡ºåº“ â†’ OutNew (100)
    //    - ç§»åº“ â†’ RelocationNew (300)
    // 3. æž„建 Dt_Task å®žä½“
    // 4. è°ƒç”¨ WCS ReceiveTask (POST /api/Task/ReceiveTask)
    // 5. è¿”回结果
}
```
### 4.3 DTO å®šä¹‰
```csharp
public class CreateManualTaskDto
{
    public int TaskType { get; set; }      // 1=入库, 2=出库, 3=移库
    public string SourceAddress { get; set; }
    public string TargetAddress { get; set; }
    public string Barcode { get; set; }
    public int WarehouseId { get; set; }
    public int Grade { get; set; } = 1;    // é»˜è®¤ä¼˜å…ˆçº§1
}
```
## 5. WCS åŽç«¯æ”¹åЍ
### 5.1 å·²æœ‰é€»è¾‘复用
`ReceiveWMSTask` æ–¹æ³•已支持接收 WMS ä»»åŠ¡å¹¶åˆ†å‘åˆ°å¯¹åº” FlowService:
- å…¥åº“任务 â†’ `InboundTaskFlowService.InitializeOnReceive()`
- å‡ºåº“任务 â†’ `OutboundTaskFlowService.InitializeOnReceive()`
- ç§»åº“任务 â†’ `RelocationTaskFlowService.InitializeOnReceive()`
### 5.2 çº¿ä½“点位判断
在 `InboundTaskFlowService.InitializeOnReceive()` ä¸­ï¼Œåˆ¤æ–­ `SourceAddress` æ˜¯å¦ä¸ºçº¿ä½“点位(11068/11010/11001)。这三个点位均为**入库线体点位**。
### 5.3 èŽ·å–è¾“é€çº¿å®žä¾‹
```csharp
var conveyorLine = Storage.Devices
    .FirstOrDefault(x => x.DeviceProDTOs.Any(d => d.DeviceChildCode == sourceAddress));
```
### 5.4 æ–°å¢žï¼šå†™å…¥è¾“送线任务
**此逻辑为新增**,目前 FlowService ä¸­ä¸å­˜åœ¨å†™å…¥è¾“送线的逻辑,需要在判断为线体点位后新增:
1. èŽ·å–è¾“é€çº¿å®žä¾‹æˆåŠŸåŽï¼Œè¯»å–æºçº¿ä½“å·
2. æž„造 `ConveyorLineTaskCommandNew` å¯¹è±¡
3. è°ƒç”¨ `conveyorLine.WriteCustomer(sourceLineNo, command)` å†™å…¥ PLC
写入字段:
- `TaskNo` = WCS åˆ†é…çš„任务号
- `Source` = èµ·ç‚¹åœ°å€
- `Target` = ç»ˆç‚¹åœ°å€
- `Barcode` = æ¡ç 
- `WCS_STB` = 1 (标记WCS已发送)
### 5.5 é‡å¤ä»»åŠ¡å¤„ç†
`ReceiveWMSTask` ä¸­å·²æœ‰é‡å¤ä»»åŠ¡æ£€æŸ¥é€»è¾‘ï¼ˆæ ¹æ® TaskNum æˆ– PalletCode),手动创建时如遇重复返回错误。
## 6. ä»»åŠ¡çŠ¶æ€æžšä¸¾
| ç±»åž‹ | æ–°å»ºçŠ¶æ€ | çŠ¶æ€å€¼ |
|------|----------|--------|
| å…¥åº“ | InNew | 200 |
| å‡ºåº“ | OutNew | 100 |
| ç§»åº“ | RelocationNew | 300 |
## 7. é”™è¯¯å¤„理
- ä»»åŠ¡å·èŽ·å–å¤±è´¥ â†’ è¿”回错误
- WCS æŸ¥æ‰¾è¾“送线实例失败 â†’ è¿”回错误给 WMS
- å†™å…¥ PLC å¤±è´¥ â†’ è¿”回错误,WMS ä»»åŠ¡çŠ¶æ€ä¿æŒæ–°å»º
- WCS è¿”回重复任务 â†’ è¿”回错误给 WMS
## 8. æ¶‰åŠçš„æºæ–‡ä»¶
### WMS å‰ç«¯
- `WMS/WIDESEA_WMSClient/src/views/taskinfo/task.vue`
- `WMS/WIDESEA_WMSClient/src/extension/taskinfo/task.js`
### WMS åŽç«¯
- `WMS/WIDESEA_WMSServer/WIDESEA_TaskInfoService/TaskService.cs`
- `WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/TaskInfo/TaskController.cs`
### WCS åŽç«¯
- `WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoService/TaskService.cs`
- `WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoService/Flows/InboundTaskFlowService.cs`
- `WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoService/Flows/OutboundTaskFlowService.cs`
- `WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoService/Flows/RelocationTaskFlowService.cs`
- `WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/ConveyorLineNewJob/ConveyorLineDispatchHandler.cs` (参考写入模式)
- `WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/ConveyorLineNewJob/ConveyorLine/ConveyorLineTaskCommandNew.cs` (任务命令结构)