# 订单-计划-产品-记录 全链路 & 无计划拦截 实施方案 > 关联提交:`a7167fc fix: 打通生产订单-生产计划-工件关联链路`(已完成 1/3:订单→计划→产品) > 本次做 2/3:**工位结果层面的无计划拦截** + **按订单/计划查询** --- ## 一、现状分析 ### 1.1 已有的链路(上次提交已完成) ``` mes_product_order (订单) --order_id--> mes_product_plan (计划: order_id/day/shift_id/nums) | plan_id v mes_product (产品/工件) ``` | 环节 | 代码位置 | 说明 | |---|---|---| | 排产 | `MesProductPlanService.save()` | 校验订单已下发(`state=1`)、BOM 的 `product_cate_id`、同日期+班次+产品唯一、累计不超订单数量 | | 创建工件时匹配计划 | `MesProductPlanService.getCurrentPlan(cateSn)` | 按 **当前班次的开始日期 + 班次 + 工件类型** 唯一匹配;`c.state='1'`(订单已下发) | | 工件绑定计划 | `MesProductRecordService.resolvePlan()` → `mes_product.plan_id` | 创建工件时无计划直接抛 `当前班次没有有效的生产计划,请先排产!` | | 超产校验 | `MesProductPlanService.validatePlanCapacity()` | 按 `mes_product.plan_id` 计数 | ### 1.2 缺口(本次要补的) | # | 缺口 | 现状 | |---|---|---| | 1 | `mes_product_record`(生产记录/工位结果)**没有计划字段** | 记录层面看不到属于哪个计划,履历无法按订单/计划追溯 | | 2 | **提交工位结果不校验计划** | `updateRecordResult()` / `updateRecord()` 等直接写库;历史件、返修件、非正常创建路径产生的"无计划件"照样能过站 | | 3 | **查询侧没有订单/计划维度** | 生产记录列表/导出只能按工件码、工位号查;看不出哪些件"无计划";计划页只能按 orderId 精确查 | | 4 | 计划实际产出统计不完整 | `MesProductPlanController.listData` 只按 `mes_product` 计数,返修件/无计划件统计不到 | --- ## 二、拦截规则(业务口径) **拦截主体 = 工件(sn)+ 计划(plan)**,在 **提交工位结果** 时校验。 ### 2.1 计划来源(三级兜底,逐级回写) ``` 1) mes_product_record.plan_id ← 新增字段,本次加 2) mes_product.plan_id ← 已有字段 3) getCurrentPlan(cateSn) ← 按 当前班次开始日期+班次+工件类型 自动匹配(自愈),匹配到即回写 1)/2) ``` 三级都取不到 → **拦截**。 ### 2.2 拦截条件(任一命中即拒绝) | 条件 | 提示语 | |---|---| | 找不到有效计划 | `该工件未关联生产计划(订单-计划-工件),当前班次无有效生产计划,不能作业!` | | 计划已删除/停用 (`status != '0'`) | `该工件关联的生产计划已失效,不能作业!` | | 计划对应订单未下发 (`order.state != '1'`) | `该工件关联的生产订单未下发,不能作业!` | ### 2.3 放行例外 | 例外 | 处理 | |---|---| | 返修提交 (`isRepair = '1'`) | 放行,沿用原记录 plan(返修件不占用计划产量) | | 全局开关 `mes.plan.intercept = '0'` | 全量关闭拦截(应急/灰度),拦截代码保留 | | 计划超产 | **提交工位结果时不再校验数量**(数量在创建工件时已扣),只校验计划有效性 | --- ## 三、数据库改动 新增脚本:`db/mes_product_record_plan.sql` ```sql -- 1) 生产记录表增加生产计划字段 ALTER TABLE `mes_product_record` ADD COLUMN `plan_id` VARCHAR(64) NULL COMMENT '生产计划' AFTER `cate_sn`; -- 2) 索引(按计划/订单查记录) CREATE INDEX `idx_mpr_plan_id` ON `mes_product_record` (`plan_id`); -- 3) 历史数据回填:工件有计划 -> 回填到记录 UPDATE `mes_product_record` r JOIN `mes_product` p ON p.`sn` = r.`sn` SET r.`plan_id` = p.`plan_id` WHERE (r.`plan_id` IS NULL OR r.`plan_id` = '') AND p.`plan_id` IS NOT NULL AND p.`plan_id` <> ''; -- 4) 拦截总开关(0=不拦截,1=拦截) DELETE FROM `js_sys_config` WHERE `config_key` = 'mes.plan.intercept'; INSERT INTO `js_sys_config` (id, config_name, config_key, config_value, is_sys, create_by, create_date, update_by, update_date, remarks) VALUES ('2026102018000001','生产计划无计划拦截','mes.plan.intercept','1','0','system',NOW(),'system',NOW(), '提交工位结果时校验工件是否关联有效生产计划;0=关闭拦截'); ``` > 注:`js_sys_config` 直插数据库后需重启服务或在"系统配置"页面重新保存一次触发缓存刷新(沿用 `cz060_cz061_routing_align.sql` 的做法)。 --- ## 四、代码改动清单 ### 4.1 实体 `MesProductRecord.java` 1. 列定义新增: ```java @Column(name="plan_id", attrName="planId", label="生产计划"), ``` 2. 字段新增:`planId`、`planDay`(计划日期)、`planOrderId`(计划所属订单)、`orderSn`(生产订单号)、`shiftTitle`(班次) 3. 恢复并启用 `joinTable`(当前是注释状态),做三级左连接: ```java joinTable={ @JoinTable(type=JoinTable.Type.LEFT_JOIN, entity=MesProductPlan.class, attrName="this", alias="b", on="b.id = a.plan_id", columns={ @Column(name="day", attrName="planDay", label="计划日期", queryType=QueryType.EQ) }), @JoinTable(type=JoinTable.Type.LEFT_JOIN, entity=MesProductOrder.class, attrName="this", alias="c", on="c.id = b.order_id", columns={ @Column(name="sn", attrName="orderSn", label="生产订单号", queryType=QueryType.LIKE) }), @JoinTable(type=JoinTable.Type.LEFT_JOIN, entity=MesShift.class, attrName="this", alias="d", on="d.id = b.shift_id", columns={ @Column(name="title", attrName="shiftTitle", label="班次", queryType=QueryType.LIKE) }) } ``` > 注意:JeeSite 的 join 不会带出被 join 实体自身的 join 字段,所以 `orderSn` / `shiftTitle` 必须在 `MesProductRecord` 上重新声明(别名 b/c/d 之前被注释占用,现已释放)。 4. `ExcelField` 增列:`生产订单号`、`计划日期`(导出用) ### 4.2 `MesProductPlanService.java` 新增方法 ```java // 按 id 取计划并校验有效性(计划存在 + status='0' + 订单已下发) public MesProductPlan getValidPlan(String planId) // 取工件的 plan_id(来自 mes_product) public String getPlanIdByProductSn(String sn) // 把计划回写到工件 public void bindPlanToProduct(String sn, String planId) ``` ### 4.3 `MesProductRecordService.java`(核心) **新增统一校验方法**(放在 service 层,controller 只做参数解析,所有通道自动覆盖): ```java /** * 提交工位结果前校验:工件必须关联有效生产计划,否则拦截。 * @return 命中的计划;开关关闭时返回 null */ private MesProductPlan requirePlan(String sn, String cateSn, String isRepair) ``` 执行顺序: 1. 开关:`"0".equals(Global.getConfig("mes.plan.intercept"))` → 直接返回 null(不拦截) 2. 返修:`"1".equals(isRepair)` → 跳过拦截(只做查询,不强制) 3. 依次尝试 2.1 的三级来源;三级都取不到 → 抛 `ServiceException` 4. 校验计划有效性(2.2 的 2、3 条)→ 不合法抛 `ServiceException` 5. 命中第 3 级(自动匹配)时,回写 `mes_product.plan_id` 和 `mes_product_record.plan_id`(自愈) **接入点**(覆盖所有工位结果提交通道): | 接入方法 | 说明 | |---|---| | `updateRecordResult()` | **主路径**(PC 结果上传 / PDA / CCD 质量上传 / 返修 / `apiUpdateRecordResult` / `updateMultipleRecords` 内部都走这里)→ 在方法最前面校验 | | `updateRecord()` | 单工位结果回写(`update` 接口、解绑、终检等) | | `updateRecordParam()` | 带参数的结果提交 | | `updateUnbindRecord()` | 解绑回写 | **记录写入时回填 plan_id**: - `pcadd()` / `add()` / `sdadd()`:创建 `mpr1` 时 `mpr1.setPlanId(mesProductPlan.getId())`(这 3 处已调用 `resolvePlan()`,直接复用) - `save()` 兜底:`planId` 为空时按 `mes_product.plan_id` 补齐 ### 4.4 `MesProductRecordController.java` - `listData` / `listData2` / `exportData`:支持按 **生产订单号**、**计划日期** 过滤 ```java if (StringUtils.isNotBlank(mesProductRecord.getOrderSn())) { mesProductRecord.getSqlMap().getWhere().and("c.sn", QueryType.LIKE, mesProductRecord.getOrderSn()); } ``` - `listData`:批量回填「无计划」标识(`planId` 为空的记录标红) ### 4.5 视图 | 文件 | 改动 | |---|---| | `mesProductRecordList.html` | 搜索项加「生产订单号」「计划日期」;列表加列「生产订单」「计划日期」「班次」「计划状态」(无计划标红) | | `mesProductPlanList.html` | 搜索项「生产订单」由 orderId 精确下拉改为 **订单号模糊查询**;列表加「完成率」(nums2/nums) | | `mesProductOrderList.html` | 订单列表加「已排产 / 未排产」数量列(`mes_product_order_plan.html` 已有排产入口,可复用) | --- ## 五、实施步骤(按序执行,每步可独立验证) | 步骤 | 内容 | 产出 | |---|---|---| | 1 | 执行 `db/mes_product_record_plan.sql`(加字段+索引+回填+开关) | DB 就绪 | | 2 | `MesProductRecord` 实体加 `plan_id` + 三级 join + 导出列 | 可查订单/计划 | | 3 | `MesProductPlanService` 加 `getValidPlan` / `getPlanIdByProductSn` / `bindPlanToProduct` | 复用能力 | | 4 | `MesProductRecordService` 加 `requirePlan()` + 4 个接入点 + 记录回填 | **拦截生效** | | 5 | 列表/导出/计划页/订单页加订单、计划维度 | 查询闭环 | | 6 | 回归测试(见下) | 验收 | --- ## 六、验证用例 | # | 场景 | 预期 | |---|---|---| | 1 | 白班排了计划(订单已下发、BOM 维护了工件类型),创建 1 件 | 工件 `plan_id` 有值;记录 `plan_id` 有值 | | 2 | 当前班次**无计划**,创建工件 | `当前班次没有有效的生产计划,请先排产!`(已有逻辑保持) | | 3 | 当前班次无计划,用历史件(`mes_product.plan_id` 为空)提交工位结果 | 拦截:`该工件未关联生产计划...不能作业!` | | 4 | 工件有计划但计划被删除/停用 | 拦截:`该工件关联的生产计划已失效,不能作业!` | | 5 | 工件有计划但订单改成未下发 | 拦截:`该工件关联的生产订单未下发,不能作业!` | | 6 | 返修件提交(isRepair=1) | 放行,不影响计划产量 | | 7 | `mes.plan.intercept='0'` | 全部放行(应急) | | 8 | 生产记录列表按订单号过滤 | 只返回该订单下的记录;无计划件标红 | | 9 | 计划页「生产数量」统计 | 与 `mes_product` 中该计划下的件数一致 | **核查 SQL** ```sql -- 无计划仍在生产的工件 SELECT p.sn, p.cate, p.plan_id FROM mes_product p WHERE (p.plan_id IS NULL OR p.plan_id='') AND EXISTS (SELECT 1 FROM mes_product_record r WHERE r.sn = p.sn AND r.content <> 'UDF'); -- 记录与工件计划不一致(应为 0) SELECT r.id, r.sn, r.plan_id, p.plan_id FROM mes_product_record r JOIN mes_product p ON p.sn = r.sn WHERE IFNULL(r.plan_id,'') <> IFNULL(p.plan_id,''); -- 计划未关联订单 SELECT id, order_id, day, shift_id, nums FROM mes_product_plan WHERE order_id IS NULL OR order_id = ''; ``` --- ## 七、风险与兼容 | 风险 | 处理 | |---|---| | 存量工件无 `plan_id`,上线后立即被拦 | 步骤 1 的回填 SQL 解决大部分;剩余用开关 `mes.plan.intercept='0'` 过渡,排产后逐步收紧 | | 返修/补录/换件等非标准流程被误拦 | `isRepair='1'` 放行;必要时再加 `mes.plan.intercept.exempt.oprno` 白名单 | | join 增加列表查询成本 | 走 `plan_id` 索引 + 左连接;列表页本身已分页,影响可控;必要时改为按需批量回填(现有 listData 已是批量 Map 回填模式) | | `js_sys_config` 缓存 | 直插后需重启服务或后台重新保存一次 | | 多产线/多班次 | 计划取自**工件创建时**的班次,跨零点夜班沿用"班次开始日期"口径(`getCurrentPlan` 已处理) | --- ## 八、待确认事项 1. **拦截范围**:只在"提交工位结果"拦截,还是"创建工件"也要再次强校验(现在创建已强校验)?建议两处都保留。 2. **返修是否放行**:建议放行(返修不占产量),若管理上要求返修也必须挂在有效计划下,去掉例外即可。 3. **无计划件是否允许补录**:建议提供"按 日期+班次+工件类型 批量补关联计划"的后台工具/SQL,用于历史数据归集。 4. **计划超产是否在提交时拦截**:建议否(数量在创建时已扣),如需则按 `mes_product.plan_id` 计数再校验一次。 --- ## 九、实施结果(已落地) ### 9.1 改动清单 | 文件 | 改动 | |---|---| | `db/mes_product_record_plan.sql`(新增) | `mes_product_record.plan_id` 字段 + `plan_id`/`sn` 索引 + 存量回填 + `mes.plan.intercept` 开关 + 验证 SQL | | `entity/MesProductRecord.java` | 新增 `plan_id` 列;启用 joinTable(b=计划 / c=订单 / d=班次);新增 `planId`/`planDay`/`orderSn`/`shiftTitle` 属性;导出加「生产订单号」「计划日期」 | | `entity/MesProductOrder.java` | 新增 `planNums`(已排产)/`unPlanNums`(未排产)展示字段 | | `dao/MesProductRecordDao.java` + `MesProductRecordDao.xml` | 新增 `findPlanIdBySn`(取工件已关联计划)、`updatePlanIdBySn`(回填记录计划);NG 查询 join 计划/订单/班次并支持订单号过滤;`updateDate` 去掉 `sqlMap.table.toSql()` | | `service/MesProductPlanService.java` | 新增 `getValidPlan`(计划有效+订单已下发校验)、`getPlanIdByProductSn`、`bindPlanToProduct` | | `service/MesProductRecordService.java` | 新增 `requirePlan()`(三级计划来源 + 拦截 + 自愈回写)、`isPlanInterceptEnabled()`(开关);接入 `updateRecordResult`/`updateRecord`/`updateRecordParam`/`updateUnbindRecord`;`pcadd`/`add`/`sdadd` 写入记录计划;`save`/`insertBatch` 兜底补计划 | | `web/MesProductRecordController.java` | 新增 `applyPlanFilter()`(订单号/计划日期过滤),应用于 `listData`/`listData2`/`exportData`;后面板新记录继承计划 | | `web/MesProductPlanController.java` | 计划列表支持按订单号模糊查询 | | `web/MesProductOrderController.java` | 订单列表统计已排产/未排产数量 | | 视图 `mesProductRecordList/List2/ListNg.html` | 搜索项加「生产订单号」,列表加「生产订单号」「计划日期」「班次」「计划状态(无计划标红)」 | | 视图 `mesProductPlanList.html` | 搜索项加「订单号」,列表加「完成率」 | | 视图 `mesProductOrderList.html` | 列表加「已排产」「未排产」 | ### 9.2 顺带修复的两个问题 | 问题 | 原因 | 处理 | |---|---|---| | `pcresult` 接口返修标记判断错误 | `isRepair = StringUtils.isEmpty(isRepair)?"0":"1"` → 客户端传 `isRepair=0`(正常)被当成返修,导致拦截被绕过 | 改为 `StringUtils.isEmpty(isRepair)?"0":isRepair`;服务层 `isRepair.equals("1")` 改为 `"1".equals(isRepair)` 防 NPE | | `updateDate` 原始 SQL 在有 join 的实体上报错 | `update ${sqlMap.table.toSql()}` 会带上 LEFT JOIN,`update_date`/`id` 出现二义性 | 改为写死表名 `update mes_product_record ...` / `update mes_product ...` | ### 9.3 发布步骤 1. **先执行** `db/mes_product_record_plan.sql`(必须先加字段,否则生产记录相关查询直接报错) 2. 再发布应用 3. `js_sys_config` 直插后需重启服务,或在"系统配置"页面重新保存一次触发缓存刷新 4. 灰度建议:先把 `mes.plan.intercept` 置为 `0` 上线,确认存量数据回填正常后再置 `1` 开启拦截 **本地库已执行验证**(localhost:3307 / mes_cloud_xz,MySQL 5.7): | 检查项 | 结果 | |---|---| | `mes_product_record.plan_id` 字段 | 已创建 `varchar(64)` | | 索引 `idx_mpr_plan_id` | 已创建;`sn` 索引原本就有,脚本自动跳过 | | 存量回填 | 47 条记录(对应唯一有计划的工件) | | 记录与工件计划不一致 | 0 行 ✓ | | 未关联订单的计划 | 0 行 ✓ | | 三级 join 查询 / 订单号+计划日期过滤 / 回退查询 / 回填语句 / NG 查询 | 全部实跑通过 | > 本地库现状:工件 6361 个,仅 1 个有计划;`mes.plan.intercept` 当前为 `1`(拦截已开)。 > 本地调试提交工位结果前请先排产,否则会被"无计划"拦截;生产环境同样建议先置 `0` 灰度。 > 库中另有 2 条历史脏计划(2026-04-16/17,nums=400,`shift_id='1'` 占位值,关联订单已不存在), > 因无工件引用、也匹配不到真实班次,不影响拦截,建议按 `mes_product_plan_link_fix.sql` 的说明人工清理。 ### 9.4 关键实现说明 - **热路径无额外查询**:新建工件的记录在创建时就写入 `plan_id`,提交工位结果时直接从已加载的记录上取计划,不产生额外 SQL;只有历史无计划件才会走回退查询并自动回写(自愈)。 - **关联字段查询**:JeeSite 会把 join 上的字段自动绑定为查询条件(`orderSn` 声明为 LIKE、`planDay` 声明为 EQ),控制器里另加了兜底条件,值一致不会冲突。 - **拦截范围**:返修(`isRepair=1`)放行;开关关闭时全部放行;提交时**不再**校验超产数量(数量在创建工件时已扣)。 --- ## 十、下单选择产品类型 + 当前计划/当前产品接口(已落地) ### 10.1 目标 把 **订单 → 计划 → 产品** 三段链路显式化: ``` 生产订单.product_cate_id → 生产计划.product_cate_id → 工件(mes_product.cate) (下单时选择) (排产时继承订单) ``` - 下单时可直接选择产品类型(工件类型),不再强依赖 BOM 是否维护了工件类型; - 选择 BOM 时自动带出其工件类型;新建订单时按**当前计划的产品类型**默认带出; - 提供接口获取当前班次的计划与当前产品,客户端/下单页面据此判断"现在在生产什么"。 ### 10.2 数据模型(脚本 `db/mes_product_cate_link.sql`,已执行) | 表 | 新增字段 | 说明 | |---|---|---| | `mes_product_order` | `product_cate_id` | 产品类型(存 `mes_product_cate.sn`) | | `mes_product_plan` | `product_cate_id` | 产品类型(存 `mes_product_cate.sn`) | 存量回填:订单按 BOM 的工件类型补齐;计划按订单的产品类型补齐(订单为空时用 BOM 兜底)。 > 约定:全系统"工件类型"都用 `mes_product_cate.sn`(如 `CZ060`),与 `mes_line.product_cate`、 > `wms_bom.product_cate_id` 保持一致(`wms_bom` 实体里按 id 关联的那处是历史遗留,不影响本链路)。 ### 10.3 下单(生产订单) | 位置 | 改动 | |---|---| | `MesProductOrderService.checkProductCate()` | 保存时校验:未选择产品类型 → 按 BOM 的工件类型自动补齐;所选类型必须存在且启用;与 BOM 的工件类型不一致 → 报错「所选产品类型与BOM的工件类型(CZ060)不一致,请核对后重试!」 | | `MesProductOrderController.form()` | 下发产品类型下拉(`mes_product_cate`,status=0)、BOM→工件类型映射、当前计划的产品类型 | | `mesProductOrderForm.html` | 新增「产品类型」下拉(必填);选择 BOM 自动带出工件类型;新建订单按当前计划的产品类型默认带出 | | `mesProductOrderList.html` | 新增「产品类型」「产品类型编号」列 | ### 10.4 排产(生产计划) | 位置 | 改动 | |---|---| | `MesProductPlanService.save()` | 产品类型优先取订单选择的产品类型,订单未维护时用 BOM 兜底;写入 `plan.product_cate_id`;"同日期+班次+产品唯一"校验改用 `plan.product_cate_id`;编辑计划时产品类型跟随订单同步 | | `MesProductPlanService.getCurrentPlan(cateSn)` | 匹配条件由 BOM 关联改为 `plan.product_cate_id = cateSn`(不再依赖 BOM 是否维护工件类型) | | `mesProductPlanList.html` | 新增「产品类型」列 | ### 10.5 接口:获取当前计划 / 当前产品 ``` POST /mes/mesProductPlan/currentPlan ``` 返回示例: ```json { "result": "true", "message": "成功", "data": { "shiftId": "1700688350585237504", "shiftTitle": "白班", "shiftStart": "2026-10-21 08:00", "shiftEnd": "2026-10-21 20:00", "day": "2026-10-21", "cateSn": "CZ060", "cateTitle": "CZ060", "planNums": 3, "producedNums": 0, "plans": [{ "planId": "2112728390803562496", "orderId": "2112728164835434496", "orderSn": "202610216998016", "bomTitle": "初版AD02-BOM", "cateSn": "CZ060", "cateTitle": "CZ060", "day": "2026-10-21", "shiftTitle": "白班", "nums": 3, "produced": 0 }] } } ``` - `cateSn`/`cateTitle`:**当前产品**(当前班次只有一个产品类型时给出;存在多个产品类型时为 null,用 `plans` 自行判断) - `plans`:**当前计划**明细(含订单号、计划数量、已生产数量) - 其它实现:`MesProductPlanService.getCurrentShiftPlans()` / `getCurrentProductCate()` / `getCurrentPlanData()` ### 10.6 本地库执行验证(localhost:3307 / mes_cloud_xz) | 检查项 | 结果 | |---|---| | `mes_product_order.product_cate_id` | 已创建,4 个订单全部回填为 `CZ060` | | `mes_product_plan.product_cate_id` | 已创建,5/7 条计划回填为 `CZ060` | | 计划与订单产品类型不一致 | 0 行 ✓ | | 当前计划查询(白班 2026-10-21) | 返回 1 条:订单 `202610216998016`,CZ060,3 件 ✓ | | 订单列表 join 产品类型 | 正常 ✓ | > 剩余 2 条未回填的计划是历史脏数据(2026-04-16/17,关联订单已不存在、`shift_id='1'` 占位值), > 产品类型无从推导,且匹配不到真实班次,不影响排产与拦截,建议人工清理。 --- ## 十一、产品创建 195 / 196 / 198(已落地) ### 11.1 目标 在工件/产品类型主数据 `mes_product_cate` 中创建 `195`、`196`、`198` 三种产品, 使下单页「产品类型」下拉可以直接选择,订单-计划-工件链路按产品区分。 ### 11.2 脚本 `db/mes_product_create_195_196_198.sql`(可重复执行): - 默认模板取 `CZ060` 的 `template_id`,取不到时用任一启用模板兜底; - 按 `sn` 幂等创建 `195`/`196`/`198`,`title` 与 `sn` 一致、`status='0'`、`cate=0`(产品); - 已存在时自动校正名称、模板、状态,不会产生重复数据。 ### 11.3 验证 ```sql SELECT id, title, sn, status, template_id, cate FROM mes_product_cate WHERE sn IN ('195', '196', '198') ORDER BY sn; ``` 本地库(localhost:3307 / mes_cloud_xz)执行结果: | sn | title | status | template_id | cate | |---|---|---|---|---| | 195 | 195 | 0 | 2102266346164490240(CZ060 模板) | 0 | | 196 | 196 | 0 | 2102266346164490240(CZ060 模板) | 0 | | 198 | 198 | 0 | 2102266346164490240(CZ060 模板) | 0 | > 执行后刷新下单页即可看到三种产品;如需为三种产品配置独立生产模板,可在「工件类型管理」中编辑 `template_id`。