云
7 小时以前 651cd10dc5a7c02411fd74cfe4b3b17560495c1e
docs(agent): 添加智能体相关开发文档

- 新增智能体物料导入开发进度文档,记录端到端链路测试完成情况
- 新增AI模型管理启用与分类联调方案文档,完善模型启停用流程说明
- 新增工业智能体授权设计方案,明确菜单按钮数据权限关系
- 新增工业智能体文档入职技术方案,设计文件解析与数据补录流程
- 新增工业智能体LangGraph技术设计方案,确定Python协调主图架构
- 完善智能体开发相关配置与接口说明,提供详细的API参数与响应示例
已添加15个文件
2384 ■■■■■ 文件已修改
docs/agent-material-import-开发进度.md 109 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/ai_model_capability_integration.md 107 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/industrial-agent-authorization-design.md 110 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/industrial-agent-document-onboarding-design.md 192 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/industrial-agent-langgraph-technical-design.md 285 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/industrial-agent-python-design.md 162 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/industrial-agent-scenario-blueprint.md 189 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/mdm_item_category_unification.md 73 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/mes_collect_design.md 289 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/mes_collect_frontend_integration.md 179 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/mes_workbench_workstation_pending_count.md 93 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/route_process_output_attribution.md 112 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/sale_order_production_cancel_integration.md 84 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/work_order_process_warehouse_integration.md 110 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/mysql/20261028_sp_generic_shift_dates.sql 290 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/agent-material-import-¿ª·¢½ø¶È.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,109 @@
# æ™ºèƒ½ä½“物料导入 - å¼€å‘进度
> æ›´æ–°æ—¶é—´ï¼š2026-10-22
> å…³è”项目:`D:\牛马\南通\后端\mom-industrial-agent`(Python æ™ºèƒ½ä½“)+ æœ¬é¡¹ç›®ï¼ˆJava Bridge)
> æµ‹è¯•库:本地 `ruoyi-vue-pro`(Docker å®¹å™¨ `mysql8`,MySQL 8.4.8)
## æ€»ä½“状态
端到端链路已打通并完成负例验收:
```
创建补录会话 â†’ ä¸Šä¼  CSV â†’ ç»‘定附件 â†’ Python åˆ›å»ºä»»åŠ¡
→ Java åªè¯»é¢„检 â†’ äººå·¥å®¡æ ¸ç¡®è®¤ â†’ å¹‚等提交 â†’ é€è¡Œå›žæ‰§
```
阶段 1~6 å…¨éƒ¨å®Œæˆï¼ŒJava ä¸Ž Python æµ‹è¯•全绿,测试数据已逻辑删除。
## å„阶段完成情况
| é˜¶æ®µ | å†…容 | çŠ¶æ€ |
|------|------|------|
| 1 | æ¡¥æŽ¥å‰ç½®ä¿®å¤ï¼šçœŸå®ž HTTP çŠ¶æ€ç ã€ç§Ÿæˆ·å…œåº•ã€æœåŠ¡ä»¤ç‰Œå®ˆå«ã€é™„ä»¶è¯»å–å¤ç”¨ã€403 æƒé™æ˜ å°„ | å®Œæˆ |
| 2 | æŠ½å–物料导入解析(`buildImportContext/resolveImportRow`,原导入行为不变) | å®Œæˆ |
| 3 | Java åªè¯»é¢„检(分类/单位解析、编码存在性、CREATE/SKIP/BLOCKED、确定性 preflight_hash) | å®Œæˆ |
| 4 | Java å¹‚等提交(command_id/idempotency_key、PLAN_STALE、逐行独立事务、查询回执) | å®Œæˆ |
| 5 | Python æ”¹é€ ï¼ˆpreflight/review/confirm/commit å›¾ã€CommonResult è§£æžã€è¶…时对账、token ä¸å…¥ checkpoint) | å®Œæˆ |
| 6 | çœŸå®žç«¯åˆ°ç«¯è”调、负例、数据库核对、清理、文档 | å®Œæˆ |
## é˜¶æ®µ 6 å®žæµ‹è®°å½•
正向链路:
- é¢„检结果 2 è¡Œ `CREATE`;提交返回 `SUCCEEDED`,`targetItemId` = 1994 / 1995。
- `mdm_item` 1053 â†’ 1055,中文名称落库正确;`mes_md_item` æ— å˜åŒ–(未同步 MES)。
- é‡å¤æäº¤åŒä¸€è¯·æ±‚返回原回执,不新增物料。
- æ¸…理:按 `target_item_id` å°†ä¸¤æ¡æµ‹è¯•物料置 `deleted=1`,数量回到 1053。
负例验收:
| åœºæ™¯ | ç»“æžœ |
|------|------|
| æ—  Bearer token | 401 |
| é”™è¯¯æœåŠ¡ä»¤ç‰Œ | 403 |
| ç”¨æˆ·æ—  `mdm:item:import` æƒé™ | 403 `FORBIDDEN` |
| è·¨ç”¨æˆ·/跨会话附件 | 404 / æ‹’绝 |
| source_sha256 ç¯¡æ”¹ | `SOURCE_DIGEST_MISMATCH` |
| åŒ commandId æ”¹å†…容 | 409 `COMMAND_ID_CONFLICT` |
| åŒå¹‚等键改内容 | 409 `IDEMPOTENCY_CONFLICT` |
| é¢„检输入变化后用旧 hash æäº¤ | 409 `PLAN_STALE`(body å¸¦æœ€æ–°é¢„检) |
| å…³é—­ Java å†™å¼€å…³ | 503 `WRITE_DISABLED` |
| å…³é—­ Python æäº¤å¼€å…³ | 409 `COMMIT_DISABLED` |
| ä¸Šä¼ è¶…过 8 MiB | æ‹’绝 |
测试结果:
- Java:13 ä¸ªé’ˆå¯¹æ€§å•测通过;`mvn -pl yudao-server -am -DskipTests package` é€šè¿‡ã€‚
- Python:`uv run pytest -q` 14 é¡¹é€šè¿‡ï¼Œ`uv run ruff check .` æ— å‘Šè­¦ã€‚
## è¿è¡Œé…ç½®
| å¼€å…³ | ä½ç½® | é»˜è®¤ |
|------|------|------|
| Java å†™å¼€å…³ | `yudao.agent.bridge.write-enabled` | `false` |
| Python æäº¤å¼€å…³ | `MOM_COMMIT_ENABLED` | `false` |
| æœåŠ¡ä»¤ç‰Œ | `yudao.agent.bridge-service-token`(gitignored `application-my.yaml`) | å¿…填,未配置则拒绝 |
| MES åŒæ­¥ | å›ºå®š `syncMes=false` | å…³é—­ |
联调时需同时打开两个写开关;任一关闭即停止写入物料。
## æ•°æ®åº“变更
| è„šæœ¬ | å†…容 |
|------|------|
| `sql/mysql/system_agent_import_session_20261022.sql` | `system_agent_import_session` è¡¥å½•会话 |
| `sql/mysql/mdm_agent_import_command_20261022.sql` | `mdm_agent_import_command`、`mdm_agent_import_command_row` |
| `docs/sql/config_export_all_20261022.sql` | åˆå¹¶åˆå§‹åŒ–脚本,阶段一表数 416 â†’ 419(新增上述 3 å¼ è¡¨ï¼‰ |
以上脚本已在本地容器 `mysql8` çš„ `ruoyi-vue-pro` æ‰§è¡Œå¹¶å›žè¯»éªŒè¯ã€‚
## å·²çŸ¥é™åˆ¶
- åªæ–°å»ºç‰©æ–™ï¼Œä¸æ›´æ–°å·²æœ‰ç‰©æ–™ï¼›ç¼–码已存在时预检 `SKIP`、提交时跳过。
- ä¸åŒæ­¥ MES;请求 `sync_mes=true` ç›´æŽ¥é˜»æ–­ã€‚
- `mdm_item.code` æ— å”¯ä¸€ç´¢å¼•,并发写入窗口依赖命令幂等键与提交前重查;非桥接并发写入仍可能重复。
- åŒä¸€ä¼šè¯é‡å¤ç»‘定附件会替换旧附件,旧 `attachmentId` å¤±æ•ˆã€‚
- æäº¤ä¸ºåŒæ­¥è¯·æ±‚(建议前端超时 120 ç§’),超时后应查询命令回执,不要重试提交。
- `@PreAuthorize` å…œåº•拒绝返回 HTTP 200 + body 403,Python ä¾§å·²å…¼å®¹è§£æžï¼›æ¡¥æŽ¥ä¸šåŠ¡å¼‚å¸¸è¿”å›žçœŸå®žçŠ¶æ€ç ã€‚
## å‰ç«¯è”调(2026-10-22)
| é¡¹ | å†…容 |
|------|------|
| ä¾èµ–代理 | `vite.config.ts` æ–°å¢ž `/agent-api` â†’ `127.0.0.1:8091`(去前缀) |
| API å®¢æˆ·ç«¯ | `src/api/mdm/item/agent.ts`,独立 `RequestClient`(body æ¨¡å¼ã€120 ç§’超时、自动带用户 token) |
| å¯¼å…¥å¼¹çª— | `src/views/basicData/mdm/modules/agent-import.vue`,三步:上传 â†’ å®¡æ ¸ä¿®è®¢/确认 â†’ å›žæ‰§ |
| å…¥å£ | ç‰©æ–™åˆ—表工具栏「智能体导入」,权限 `mdm:item:import`,受 AI æ€»å¼€å…³æŽ§åˆ¶ |
| æ ¡éªŒ | `pnpm typecheck` å¯¹æœ¬è½®æ”¹åŠ¨æ–‡ä»¶æ— æ–°å¢žé”™è¯¯ï¼ˆ`mdm/index.vue` Switch çš„ `CheckedType` ä¸ºæ—¢æœ‰é—®é¢˜ï¼‰ |
### AI æ€»å¼€å…³
- `.env` æ–°å¢ž `VITE_APP_AI_ENABLE`;`@vben/hooks` æä¾› `isAiEnable()`。
- å…³é—­åŽéšè—ï¼š`/ai` é™æ€è·¯ç”±ã€ç‰©æ–™ã€Œæ™ºèƒ½ä½“导入」、CRM æŠ¥ä»·ä¸Ž ERP æ¥ç¥¨ã€Œè¯†åˆ«å½•入」、工单四类 AI é¢„测、质检 AI åˆ¤å®šã€è´¨æ£€åŠ©æ‰‹ã€‚
- æœªå®žé™…启动浏览器验证:本地 Java(48080)与 Python(8091)均未运行,登录依赖后端;启动两端后按联调方案复测。
## åŽç»­å¯é€‰äº‹é¡¹
- å¢žåŠ å¹¶å‘æäº¤åŽ‹æµ‹ä¸Ž `mdm_item.code` å”¯ä¸€æ€§æ–¹æ¡ˆè¯„估。
- è¯„估「更新已有物料」的专用导入方法(禁止复用会删除 MES ç‰©æ–™çš„ `updateItem`)。
- å¯åЍ Java/Python åŽæŒ‰ `docs/agent_material_import_integration.md` åšä¸€æ¬¡æµè§ˆå™¨ç«¯åˆ°ç«¯å¤æµ‹ã€‚
docs/ai_model_capability_integration.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,107 @@
# AI æ¨¡åž‹ç®¡ç† - å¯ç”¨ä¸Žåˆ†ç±»è”调方案
## æ¶‰åŠé¡µé¢
- AI å¤§æ¨¡åž‹ â†’ æ¨¡åž‹ç®¡ç†ï¼ˆ`ai/model/model/index`)
- ä¾èµ–模型下拉的业务页面(对话、图像、知识库、工作流)
## ä¸šåŠ¡æµç¨‹ä¸Žæ•°æ®å¸¦å…¥
1. æ¨¡åž‹ç®¡ç†åˆ—表按「模型类型」分组展示:同一类型的多行在类型列纵向合并成一格。
2. æ¯è¡ŒçŠ¶æ€åˆ—æ”¹ä¸ºã€Œå¯ç”¨ã€å¼€å…³ï¼Œç›´æŽ¥åˆ‡æ¢è¯¥æ¨¡åž‹çš„å¯ç”¨/停用。
3. å¯ç”¨æŸä¸ªæ¨¡åž‹æ—¶ï¼ŒåŽç«¯è‡ªåЍ关闭**同类型 + åŒèƒ½åŠ›**下已启用的其他模型,前端只需整页刷新。
4. åœç”¨æ¨¡åž‹ä¸ä¼šè‡ªåŠ¨å¯ç”¨å…¶ä»–æ¨¡åž‹ï¼Œè¯¥ç±»åž‹è¯¥èƒ½åŠ›å¯èƒ½å› æ­¤æ²¡æœ‰å¯ç”¨æ¨¡åž‹ã€‚
## API
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| PUT | `/ai/model/update-status` | æ›´æ–°æ¨¡åž‹å¯ç”¨çŠ¶æ€ï¼ˆæœ¬æ¬¡æ–°å¢žï¼‰ |
| GET | `/ai/model/page` | æ¨¡åž‹åˆ†é¡µï¼Œæ–°å¢ž `capability` æŸ¥è¯¢å‚æ•° |
| GET | `/ai/model/simple-list` | æ¨¡åž‹ä¸‹æ‹‰ï¼Œæ–°å¢ž `capability` æŸ¥è¯¢å‚æ•° |
**请求参数(update-status):**
| å‚æ•° | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|------|------|------|------|
| id | Long | æ˜¯ | æ¨¡åž‹ç¼–号 |
| status | Integer | æ˜¯ | 0-启用,1-停用 |
**请求参数(page):**
| å‚æ•° | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|------|------|------|------|
| pageNo | Integer | æ˜¯ | é¡µç  |
| pageSize | Integer | æ˜¯ | æ¯é¡µæ¡æ•° |
| name | String | å¦ | æ¨¡åž‹åå­—,模糊匹配 |
| model | String | å¦ | æ¨¡åž‹æ ‡è¯†ï¼Œæ¨¡ç³ŠåŒ¹é… |
| type | Integer | å¦ | æ¨¡åž‹ç±»åž‹ |
| capability | String | å¦ | æ¨¡åž‹èƒ½åŠ›ï¼štext-文本,vision-视觉 |
| platform | String | å¦ | å¹³å° |
| status | Integer | å¦ | çŠ¶æ€ |
**响应字段新增(page / get / simple-list):**
| å­—段 | ç±»åž‹ | è¯´æ˜Ž |
|------|------|------|
| capability | String | æ¨¡åž‹èƒ½åŠ›ï¼štext-文本,vision-视觉 |
**响应示例:**
```json
{
  "code": 0,
  "data": {
    "list": [
      {
        "id": 2,
        "name": "通义千问对话模型",
        "model": "qwen-max",
        "platform": "TongYi",
        "type": 1,
        "capability": "text",
        "status": 0
      },
      {
        "id": 4,
        "name": "通义千问视觉模型",
        "model": "qwen-vl-plus",
        "platform": "TongYi",
        "type": 1,
        "capability": "vision",
        "status": 0
      }
    ],
    "total": 4
  }
}
```
## å­—段展示规则
| å­—段 | å±•示位置 | è¯´æ˜Ž |
|------|----------|------|
| æ¨¡åž‹ç±»åž‹ | åˆ—表首列 | åŒç±»åž‹å¤šè¡Œçºµå‘合并,形成分类分组 |
| æ¨¡åž‹èƒ½åŠ› | åˆ—表、表单 | æ–‡æœ¬/视觉;仅对话类型需要区分 |
| å¯ç”¨ | åˆ—表状态列 | å¼€å…³æŽ§ä»¶ï¼Œåˆ‡æ¢å³è°ƒç”¨ update-status |
| æ¨¡åž‹æ ‡è¯† | æœç´¢è¡¨å• | æ”¯æŒæ¨¡ç³Šæœç´¢ |
## ä¸šåŠ¡è§„åˆ™è¯´æ˜Ž
| åœºæ™¯ | è§„则 |
|------|------|
| å¯ç”¨æ¨¡åž‹ | è‡ªåŠ¨å…³é—­åŒ `type + capability` çš„其他启用模型,保证每组只有一个启用 |
| åœç”¨æ¨¡åž‹ | åªå…³é—­è‡ªèº«ï¼Œä¸è‡ªåŠ¨å¯ç”¨å…¶ä»–æ¨¡åž‹ |
| æ–°å»ºæ¨¡åž‹ | è‹¥çŠ¶æ€ä¸ºå¯ç”¨ï¼ŒåŒæ ·ä¼šå…³é—­åŒç»„å…¶ä»–æ¨¡åž‹ |
| ç¼–辑模型 | æ¨¡åž‹ç±»åž‹ã€èƒ½åŠ›ä¸å¯ä¿®æ”¹ï¼ˆå‰ç«¯å·²ç¦ç”¨ï¼‰ï¼›ä»…æ–°å»ºæ—¶å¯é€‰ |
| å¯¹è¯ç±»åž‹ | æ–‡æœ¬èƒ½åŠ›ä¸Žè§†è§‰èƒ½åŠ›å„å¯å¯ç”¨ä¸€ä¸ªï¼Œäº’ä¸è¦†ç›– |
| éžå¯¹è¯ç±»åž‹ | èƒ½åŠ›å›ºå®šä¸ºæ–‡æœ¬ï¼Œæ¯ç»„åªèƒ½å¯ç”¨ä¸€ä¸ª |
| å›¾ç‰‡è¯†åˆ« | åªä½¿ç”¨è§†è§‰èƒ½åŠ›çš„å¯ç”¨æ¨¡åž‹ï¼Œæœªå¯ç”¨æ—¶æŽ¥å£ç›´æŽ¥æŠ¥é”™æç¤ºåŽ»å¯ç”¨ |
## æ³¨æ„äº‹é¡¹
- æ¨¡åž‹ç±»åž‹ã€èƒ½åŠ›åœ¨ç¼–è¾‘æ—¶ä¸å¯ä¿®æ”¹ã€‚è‹¥è¦æ”¹å˜æ¨¡åž‹å½’ç±»ï¼Œéœ€åˆ é™¤åŽé‡æ–°åˆ›å»ºã€‚
- å¯ç”¨å¼€å…³åˆ‡æ¢åŽå¿…须整页刷新,因为同组其他行的状态已被后端改写。
- å›¾ç‰‡è¯†åˆ«ï¼ˆå‘票、报价单、质检报告)依赖视觉能力模型;如果停用了唯一的视觉模型,这些功能会报错并提示去启用。
- èƒ½åŠ›å­—æ®µåœ¨åŽ†å²æ•°æ®ä¸Šç”±åŽç«¯æŒ‰æ¨¡åž‹æ ‡è¯†è‡ªåŠ¨åˆ¤å®šï¼ˆå« `vl` çš„对话模型归为视觉),前端无需迁移。
- éžå¯¹è¯ç±»åž‹ä¸éœ€è¦å±•示能力字段,提交时可不传,后端默认按 `text` å¤„理。
docs/industrial-agent-authorization-design.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,110 @@
# å·¥ä¸šæ™ºèƒ½ä½“授权设计:菜单、按钮与数据范围
> 2026-10-21,设计稿。适用于运营场景、工作文件补录、定时任务、审批恢复和结果导出。模型依据服务端提供的可用能力规划任务;最终授权由 Java æœåŠ¡ç«¯æ‰§è¡Œã€‚
## 1. æŽˆæƒåŽŸåˆ™ä¸Žé¡¹ç›®ä¾æ®
用户通过智能体访问业务时,拥有的能力不得超过其通过正常业务系统获准使用的能力。工具授权综合判断模块/菜单授权、操作权限码、对象数据范围、字段策略、业务状态和动作审批要求。任意一项不满足,禁止相应调用。
已核对的代码基础:
- `AuthController.getPermissionInfo()` èŽ·å–å½“å‰ç”¨æˆ·ã€å¯ç”¨è§’è‰²å’Œè¿‡æ»¤ç¦ç”¨é¡¹åŽçš„èœå•åˆ—è¡¨ã€‚
- `PermissionServiceImpl.hasAnyPermissions()` ä¸Ž `getDeptDataPermission()` æä¾›åŠŸèƒ½æƒé™ä¸Žéƒ¨é—¨èŒƒå›´åŸºç¡€èƒ½åŠ›ã€‚
- ä¸šåŠ¡ Controller ä½¿ç”¨ `@PreAuthorize` æ£€æŸ¥æƒé™ç ï¼Œä¾‹å¦‚合同的 `crm:contract:query/create/update/delete/import/export`。
- æ•°æ®æƒé™æ¡†æž¶åœ¨æŸ¥è¯¢ä¾§åº”用规则;CRM è¿˜æœ‰ `CrmPermissionAspect` çš„对象级权限与公海等领域规则;售后有独立 `AfterSalePermissionService`。
这些规则组合方式由现有 Java æƒé™æœåŠ¡å’Œä¸šåŠ¡æœåŠ¡å†³å®šï¼ŒPython ä¸è‡ªè¡Œç”¨è§’色名称或部门 ID æŽ¨ç®—“应该有权限”。上面的代码核对不代表每个模块均已完成数据权限审计,接入前需验证列表、详情、聚合与写入的一致性。
## 2. èœå•、按钮、数据与字段的关系
| å±‚次 | ä½œç”¨ | Agent çš„行为 |
| --- | --- | --- |
| èœå•/模块授权 | ç”¨æˆ·èŽ·å‡†è¿›å…¥çš„ä¸šåŠ¡åŸŸ | æœåŠ¡ç«¯èƒ½åŠ›ç›®å½•å…³è” menu_id/module;无授权不开放该域工具 |
| æŒ‰é’®/操作权限 | æŸ¥è¯¢ã€æ–°å¢žã€ä¿®æ”¹ã€åˆ é™¤ã€å¯¼å…¥ã€å¯¼å‡ºã€æäº¤ç­‰ | ç²¾ç¡®æ˜ å°„工具到所需权限表达式,不用“有菜单”代替操作权限 |
| æ•°æ®èŒƒå›´ | æœ¬äººã€éƒ¨é—¨ã€ä¸‹çº§éƒ¨é—¨ã€è‡ªå®šä¹‰åŠé¢†åŸŸå¯¹è±¡æŽˆæƒ | æŸ¥è¯¢æ—¶åœ¨æœåŠ¡ç«¯çº¦æŸï¼›ç»™å®š ID ä¹Ÿè¦å¯¹è±¡æ ¡éªŒï¼Œä¸èƒ½åªè¿‡æ»¤åˆ—表 |
| å­—段权限 | æˆæœ¬ã€åº•价、薪酬、账号、联系方式等敏感字段 | åœ¨æ•°æ®è¿›å…¥æ¨¡åž‹ä¹‹å‰è¿‡æ»¤ï¼›è‹¥çŽ°æœ‰ç³»ç»Ÿæœªç»Ÿä¸€æä¾›ï¼Œæ–°å¢žå­—æ®µç­–ç•¥ |
| çŠ¶æ€/审批 | å·²å…³é—­ã€å®¡æ‰¹ä¸­ã€å·²æ‰§è¡Œç­‰ä¸šåŠ¡é™åˆ¶ | æœ‰æŒ‰é’®æƒé™ä»éœ€æ ¡éªŒçŠ¶æ€ï¼›å®¡æ‰¹èµ„æ ¼ä¸Žæ‰§è¡Œèµ„æ ¼åˆ†åˆ«æ ¸éªŒ |
菜单的前端隐藏属性、页面路由可见性和服务端授权不同。隐藏详情页不应一律视为未授权;以服务端实际授权关系和能力映射为准。菜单授权不完整、无对应菜单的跨模块服务能力,必须显式登记,不能从前端传来的菜单树判断。
多角色的功能权限与数据范围合并沿用现有系统语义;不能对全部角色简单求交集导致误拒,也不能把不同业务对象上的授权随意求并导致扩大范围。Agent åŠ¨ä½œç­–ç•¥å¯ä»¥æ”¶ç´§ä¸šåŠ¡æƒé™ï¼Œä¸å¾—å°†å…¶æ”¾å®½ã€‚
## 3. æœåŠ¡ç«¯èƒ½åŠ›ç›®å½•ä¸Ž LangGraph è·¯å¾„
拟新增 Java `AgentAuthorizationService`:生成能力目录,执行工具/对象/字段判定,并记录授权决策。拟提供 `capabilities` å’Œ `check-action` å†…部接口;前者用于规划,后者及业务服务用于实际调用。
```text
验证当前用户/受控委托
→ Java è®¡ç®—可用能力与 policy_revision
→ LangGraph åªç»‘定当前场景的已授权工具
→ æ¨¡åž‹æå‡ºç»“构化计划
→ ä»£ç æ£€æŸ¥è®¡åˆ’工具与参数范围
→ Java æ¯æ¬¡é‡æ–°é‰´æƒå¹¶æ‰§è¡Œå¸¦èŒƒå›´çš„业务查询/命令
→ è¿‡æ»¤å­—段后交给模型
→ ç»“果查看、附件下载、导出与发送时再次检查
```
能力目录示意:
```json
{
  "policy_revision": "server-generated-version",
  "capabilities": [{
    "tool": "crm.contract.read",
    "module": "crm",
    "operation": "query",
    "scope_summary": "仅当前用户可见合同",
    "allowed_fields": ["code", "customer_name", "delivery_date"],
    "can_execute": true
  }]
}
```
目录是短期规划提示,不是可长期使用的授权凭证。服务端保存真实权限表达式与数据规则,不必把完整角色配置、可访问 ID åˆ—表或内部 SQL å‘给模型。字段白名单按具体对象与用户策略进一步收紧。
LangGraph åœ¨å…¥å£å¢žåŠ  `load_capabilities`,在计划后增加 `authorize_plan`,工具封装统一 `authorize_and_invoke`。权限不足走 `access_limited` åˆ†æ”¯ï¼Œå‘用户说明可执行部分和缺少的操作权限,不泄露无权对象的名称、数量或内容。
## 4. Java Bridge çš„强制检查
Bridge ç›´æŽ¥è°ƒç”¨ Service æ—¶ï¼ŒåŽŸ Controller ä¸Šçš„ `@PreAuthorize` ä¸ä¼šå› æ­¤è‡ªåŠ¨æ‰§è¡Œï¼Œå¿…é¡»æ˜¾å¼æ£€æŸ¥å¯¹åº”åŠŸèƒ½æƒé™ï¼›å¯¹è±¡è§„åˆ™ä¹Ÿå¿…é¡»ç»è¿‡å®žé™…ç”Ÿæ•ˆçš„ Service/AOP/查询路径。不得为跨模块方便使用全局管理员身份、`executeIgnore` æˆ–关闭数据过滤来代查。
源身份由 Java é‰´åˆ«å¹¶ç­¾å‘短时委托上下文,包含用户、租户、audience、有效期与允许用途。工具参数中的 user_id/dept_id/owner_id ä¸å…·æœ‰æŽˆæƒæ•ˆåŠ›ã€‚åŽå°çº¿ç¨‹æ‰§è¡Œæ—¶å»ºç«‹æ­£ç¡®çš„æœåŠ¡ç«¯èº«ä»½ä¸Šä¸‹æ–‡å¹¶åœ¨ç»“æŸæ—¶æ¸…ç†ï¼Œé˜²æ­¢çº¿ç¨‹å¤ç”¨ä¸²èº«ä»½ã€‚
查询、详情、关联查询、聚合、搜索建议、文件检索都要执行相同范围规则;禁止“先查全量,模型再筛选”。更新/删除除了权限码,还校验目标对象;新增校验可指定的部门、负责人、仓库、客户等关联对象,不能借新增把数据挂到无权部门。
## 5. è·¨æ¨¡å—分析与输出控制
例如销售有合同查询权限但没有采购价格权限:可以在已授权范围内分析合同;不能把采购单明细和价格提供给模型。如果企业希望销售了解交付能力,新增经过授权的“交期评估摘要”能力,由服务端限制输出字段和来源范围,而非自动继承采购明细权限。
跨模块任务逐节点授权,必需数据无权限时标注“当前授权范围不足以完成评估”,不得把不可见部分当作不存在或 0。结果注明“当前可见范围”,不得宣称是全企业统计。若提供跨范围汇总,需有独立聚合授权及输出策略,不能借汇总推断隐藏客户或敏感金额。
权限过滤同样覆盖证据链接、引用标题、附件预览、SSE、报告下载、分享与通知。导出需要对应导出授权;查询权限不自动包含导出权限。发送结果同时检查发起人发送权限及接收人的可见范围。原始来源的权限不会因为做成摘要而消失。
## 6. æ–‡ä»¶è¡¥å½•的权限规则
上传权限、查看文件权限、匹配系统数据权限、建立主数据权限、导入业务权限和更新既有数据权限分别判断。用户上传了文件,不代表获准查询文件中提及的所有系统记录,也不代表获准导入其中全部数据。
- å®žä½“匹配只返回可见候选;发现全局唯一键冲突但目标不可见时,返回通用冲突和受控处理入口,不透露目标记录信息。
- å¯¼å…¥åŠ¨ä½œé‡‡ç”¨ç»è¿‡è¯„å®¡çš„æƒé™ç»„åˆï¼šä¾‹å¦‚éœ€æ¨¡å—å¯¼å…¥æƒé™åŠç›¸åº”åˆ›å»º/更新能力,具体与现有导入 Service è¯­ä¹‰å¯¹é½ï¼›ä¸èƒ½æ“…自假设一个 import æƒé™æ¶µç›–全部操作。
- æ‰¹æ¬¡ä¸­æœ‰æƒå’Œæ— æƒè®°å½•分别标识;无权行阻断其依赖链。明确审批计划后才允许独立有权分组继续执行。
- è¡¥å½•主数据缺失时,无新增权限的用户只能提出申请或暂存候选,不自动新建客户、物料、仓库。
- è´Ÿè´£äºº/部门归属、迁移历史状态、期初余额等涉及额外权限,逐字段/动作校验。dry-run、提交和回读都重新授权。
- åŽŸå§‹æ–‡ä»¶ã€å€™é€‰ã€é—®é¢˜æ¸…å•ã€äººå·¥ä¿®è®¢å’Œå¯¼å…¥æŠ¥å‘Šè®¾æ‰¹æ¬¡ ACL;协作人员看到的内容按其权限裁剪,不能因参与批次就获准查看所有薪酬或成本。
## 7. å®¡æ‰¹æ¢å¤ã€å®šæ—¶ä»»åŠ¡ä¸Žæƒé™å˜åŒ–
审批人有审批权,不代表发起人自动获得执行权。默认执行要求发起人的当前授权仍有效;如业务允许由独立执行主体接管,必须记录明确的主体切换、委托范围和审批依据,不能默默使用审批人或管理员身份。
角色撤销、账号禁用、离职交接、对象归属变化、菜单禁用时,更新授权版本并失效能力缓存。挂起图恢复时重新加载权限;准备好的方案不保留旧授权。执行时仍检查最新权限,避免只依赖失效通知。
个人定时任务使用明确的受控委托,用户停用/撤权后暂停。企业巡检使用专门服务主体和批准的数据范围;向个人推送前按接收人权限裁剪。个人对话不可借企业巡检主体扩大查询范围。
缓存按租户、主体、权限版本、对象/字段范围隔离;检查点和产物不直接面向前端。权限收紧后,再次读取旧结果、恢复会话或重建模型上下文时重新授权,必要时遮蔽或重新生成。已向用户展示过的信息无法撤回,但系统不能继续返回已撤权内容。
## 8. å®¡è®¡ä¸ŽéªŒæ”¶
每次授权决策记录主体、委托主体、tool/action、对象引用、权限版本、决策、原因码、task_id、approval_id(适用时)。拒绝日志避免记录完整敏感字段。前端原因区分无操作权限、数据范围不足、状态不允许、审批缺失与授权过期;不得泄露无权对象存在性。
验收必须包含:有菜单无查询按钮;可查不可改;可查不可导出;本人数据越界详情 ID;跨部门聚合;CRM åªè¯»/读写/负责人;售后对象权限;新增时指定无权负责人;导入含无权关联对象;审批等待中撤权;旧检查点与缓存泄漏;服务账号巡检结果推送;字段脱敏;Bridge ç»•过 Controller çš„防护。
本次为方案补充,未改变现有权限代码。实施时逐模块登记“菜单绑定、工具权限表达式、对象规则、字段策略、状态规则、审批门槛”,未登记或核验失败的能力默认关闭。
docs/industrial-agent-document-onboarding-design.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,192 @@
# æ ¸å¿ƒèƒ½åŠ›ï¼šä¼ä¸šå·¥ä½œæ–‡ä»¶ç†è§£ã€æ ¸å¯¹ä¸Žæ•°æ®è¡¥å½•
> æŠ€æœ¯æ–¹æ¡ˆ v1,2026-10-21。作为 LangGraph å¹³å°çš„第二条核心主线,与九个运营场景并列。面向企业初次上线及日常增量补录;本轮仅设计,未处理真实文件或导入业务数据。
授权规则参见 [统一权限方案](industrial-agent-authorization-design.md):上传、实体匹配、创建、更新、导入、导出分别授权,批次中的每条记录及其关联对象都需要验证数据范围。
## 1. ç›®æ ‡ä¸Žäº¤ä»˜ç»“æžœ
企业可按文件夹/批次提交日常 WPS å·¥ä½œæ–‡ä»¶ï¼Œç³»ç»Ÿè¯†åˆ«å…¶ä¸­çš„客户、供应商、物料、设备、合同、订单、库存、报工、质检等内容,经过多轮检查与人员补充,形成可预览、可追溯、可恢复的导入方案。
每次导入交付五项产物:原始文件清单与版本关系、字段映射和来源证据、待补充/冲突清单、带依赖顺序的入库计划、逐项执行与核对报告。文件内容存在不确定性时保留原值和候选值;未确认的历史事实不能通过模型推测补齐。
## 2. æ–‡ä»¶èŒƒå›´ä¸Žè§£æžç­–ç•¥
| æ–‡ä»¶ç±»åž‹ | è§£æžç›®æ ‡ | é™çº§æˆ–限制 |
| --- | --- | --- |
| WPS ç¼–辑的 xlsx/xls/csv | å·¥ä½œè¡¨ã€åŒºåŸŸã€è¡¨å¤´ã€å•元格、公式与可用缓存值、备注、隐藏行列 | ç¼“存缺失/外链断开标为待核实;不得把空缓存当 0 |
| WPS ç¼–辑的 docx/doc | æ®µè½ã€è¡¨æ ¼ã€é¡µçœ‰é¡µè„šã€æ‰¹æ³¨/修订、内嵌图片 | åŽ†å²äºŒè¿›åˆ¶æ ¼å¼é€šè¿‡å—æ”¯æŒè½¬æ¢é€‚é…å™¨å¤„ç†å¹¶æ ¸å¯¹å‰åŽå†…å®¹ |
| pptx/ppt åŠæ±‡æŠ¥ææ–™ | è¡¨æ ¼ã€æ–‡æœ¬ã€å›¾ç¤ºä¸­çš„æŒ‡æ ‡å’Œå¯¹è±¡çº¿ç´¢ | æ±‡æ€»æŒ‡æ ‡ä¸é»˜è®¤æ‹†æˆæ˜Žç»†äº¤æ˜“;缺少原始台账时只归档或生成核实任务 |
| PDF、扫描件、照片 | æ–‡æœ¬å±‚优先,扫描区域 OCR,页码/坐标及版面证据 | å°æ•°ç‚¹ã€è´Ÿå·ã€ç¼–码、签章和跨页表格进入重点复核 |
| WPS åŽŸç”Ÿ et/wps/dps | æ–‡ä»¶è¯†åˆ«ã€è½¬æ¢åŽæŒ‰æ ‡å‡†æ ¼å¼å¤„理 | ä¸æ‰¿è¯ºæ‰€æœ‰ç‰ˆæœ¬æ— æŸï¼›è½¬æ¢å¤±è´¥è¿›å…¥å¾…处理,可要求另存标准格式 |
| æ–‡ä»¶å¤¹ã€åŽ‹ç¼©åŒ… | ä¿ç•™è·¯å¾„、文件间引用和相对关系,逐文件解析 | é™åˆ¶è§£åŽ‹æ•°é‡/深度/大小;异常文件隔离,不阻塞无依赖文件 |
解析采用“格式结构解析 â†’ ç‰ˆé¢/区域识别 â†’ å±€éƒ¨ OCR â†’ è¯­ä¹‰æ˜ å°„”。具体解析/转换组件通过真实样本兼容测试后选型,不在设计阶段宣称已兼容所有 WPS ç‰ˆæœ¬ã€‚转换服务独立隔离,禁止自动执行宏、脚本或外部数据连接。
保留原始文件不可变副本、文件 hash、原格式、解析器版本及转换产物。文件密码由安全输入流程处理,不写进对话、检查点或日志。失败原因必须具体到“加密”“损坏”“格式不支持”“OCR ä¸ç¡®å®šâ€ç­‰ã€‚
## 3. è¡¥å½•对象与边界
| æ•°æ®å±‚ | å¯¹åº”项目 | å¤„理要求 |
| --- | --- | --- |
| åŸºç¡€ä¸»æ•°æ® | MDM ç‰©æ–™/SKU、分类、单位、仓库;CRM å®¢æˆ·è”系人;SRM ä¾›åº”商;设备/班组 | ä¼˜å…ˆåŒ¹é…çŽ°æœ‰è®°å½•ï¼Œå†æå‡ºæ–°å¢žï¼›åˆ«åä¸Žæ­£å¼åç§°åˆ†åˆ«ç®¡ç† |
| ä¸šåŠ¡ç»“æž„ | BOM、工艺路线、检验标准、客户产品对照 | ä¿å­˜é€‚用范围、生效版本、数量单位;验证循环依赖与替代关系 |
| è¿›è¡Œä¸­å•据 | åˆåŒã€é‡‡è´­/销售单、MPS/工单、待收待发、未结售后 | å¯¼å…¥å‰©ä½™è´£ä»»ã€å·²æ‰§è¡Œé‡ä¸Žæ¥æºï¼›ä¸é‡æ–°æ‰§è¡Œå·²å‘生的发货/付款 |
| åŽ†å²äº‹å®ž | æŠ¥å·¥ã€æ£€éªŒã€ç»´ä¿®ã€æ”¶å‘货、历史回款 | ä½¿ç”¨ä¸“门历史迁移语义和真实业务日期,保存补录时间与操作者 |
| æœŸåˆçŠ¶æ€ | åº“存、应收应付、在制、未结数量 | ä»¥åˆ‡æ¢æ—¶ç‚¹ç»Ÿä¸€å¯¹è´¦ï¼ŒæŒ‰ä¸šåŠ¡è®¤å¯çš„æœŸåˆæŽ¥å£å…¥è´¦ |
| æ–‡æ¡£çŸ¥è¯† | SOP、图纸、纪要、制度、报告 | è¿›å…¥é™„ä»¶/知识库;不因文档存在就虚构交易或审批记录 |
以上是目标范围,不表示现有项目已经提供所有历史迁移接口。每类对象通过能力登记返回“可导入 / ä»…草稿 / ä»…å½’æ¡£ / éœ€æ–°å¢žä¸šåŠ¡æŽ¥å£â€ã€‚HR è–ªé…¬ã€ä¸ªäººè¯ä»¶ç­‰å­—段仅向授权岗位开放,映射和预览同样受数据权限控制。
## 4. LangGraph å›¾è®¾è®¡
在主路由新增 `document_onboarding`,业务案件以 `import_batch_id` ä¸ºæ ¸å¿ƒï¼Œå­ä»»åŠ¡æŒ‰æ–‡ä»¶/区域解析,随后在批次层统一消歧和核对。
```mermaid
flowchart TD
    A[建立导入批次与迁移范围] --> B[文件清单 / åŽ»é‡ / ç‰ˆæœ¬è¯†åˆ«]
    B --> C[并行解析文件与表格区域]
    C --> D[识别业务类型与字段映射]
    D --> E[规范化候选记录与字段证据]
    E --> F[主数据匹配 / è·¨æ–‡ä»¶å…³è”]
    F --> G[结构 / ä¸šåŠ¡ / æ•°é‡é‡‘额 / æ—¶åºæ ¡éªŒ]
    G --> H{存在阻断问题?}
    H -->|是| I[聚合同类问题与建议]
    I --> J[interrupt ç­‰å¾…确认 / è¡¥æ–‡ä»¶ / æ”¹å€¼]
    J --> K[应用修订,标记受影响依赖失效]
    K --> F
    H -->|否| L[生成依赖 DAG ä¸Ž Java dry-run]
    L --> M[冻结方案版本、影响预览与审批]
    M --> N[interrupt ç­‰å¾…授权执行]
    N --> O[重新核验权限 / æ–¹æ¡ˆ hash / ç›®æ ‡ç‰ˆæœ¬]
    O -->|变化| F
    O -->|有效| P[按业务聚合分批幂等导入]
    P --> Q[回读 / å¯¹è´¦ / å®Œæ•´æ€§éªŒè¯]
    Q --> R{通过?}
    R -->|否| S[暂停相关链,修复或业务补偿]
    R -->|是| T[归档报告 / å…³é—­æ‰¹æ¬¡]
```
解析子图按文件独立运行,可并行;匹配、去重和供应/金额汇总在批次范围完成。审批和入库集中在命令执行图,解析节点不直接写业务数据。大文件按稳定分区处理,记录分区进度;检查点保存引用,不保存整本表格或图片。
问题反馈只触发受影响的记录、校验器和下游依赖重新运行。变更单位映射要重算相关数量与金额;变更客户归属要重做权限和关联检查;上传新文件要判断是补充证据还是替代版本。迭代轮数有预算,未解决事项转人工处理;不能为了结束循环自动把警告改成通过。
## 5. å¤šè½®æ£€æŸ¥çš„具体分工
| è½®æ¬¡ | æ£€æŸ¥å†…容 | äº§ç‰© / è°è´Ÿè´£ |
| --- | --- | --- |
| ç¬¬ä¸€è½®ï¼šè¯»æ‡‚文件 | æ–‡ä»¶ç±»åž‹ã€å¤šä¸ªè¡¨æ ¼åŒºåŸŸã€è¡¨å¤´å±‚级、汇总/明细区、单位与期间 | è§£æžç»“构图、无法识别区域;解析器 + æ¨¡åž‹è¾…助 |
| ç¬¬äºŒè½®ï¼šå¯¹é½å¯¹è±¡ | å­—段与业务对象映射、客户/供应商/物料候选、单据分组 | æ˜ å°„草稿、实体候选、需要确认的简称 |
| ç¬¬ä¸‰è½®ï¼šäº¤å‰æ ¸å¯¹ | å¤šæ–‡ä»¶ç‰ˆæœ¬å†²çªã€è®¢å•与发货/回款关联、行小计与总计、库存/期初关系 | è§„则化差异报告;确定性计算器 |
| ç¬¬å››è½®ï¼šä¸šåŠ¡æ ¸éªŒ | å¿…填、状态机、单位换算、审批要求、数据范围、目标记录冲突 | Java dry-run çš„逐记录结果与副作用清单 |
| ç¬¬äº”轮:人工补充 | ç¼ºå°‘的责任人/仓库/税率、歧义匹配、历史日期、采用版本 | å¸¦äººå‘˜ã€æ—¶é—´ã€é€‚用范围的决策补丁 |
| ç¬¬å…­è½®ï¼šæäº¤å‰å¤æŸ¥ | æ–¹æ¡ˆ hash、目标版本、依赖齐备、总量/金额与重复记录 | å¯æ‰§è¡Œæ–¹æ¡ˆä¸Žé˜»æ–­é¡¹ï¼›ç¡®è®¤åŽæ‰§è¡Œ |
| ç¬¬ä¸ƒè½®ï¼šå…¥åº“后核对 | é€è®°å½•回读、总量金额、关联完整、意外副作用 | æˆåŠŸ/失败/结果未知/未尝试分项报告 |
模型复核可以提出遗漏或冲突,但不能替代数量金额核算与 Java ä¸šåŠ¡æ ¡éªŒã€‚å®¡æ ¸ä»¥ç‹¬ç«‹è§„åˆ™ã€æºæ–‡ä»¶è¯æ®å’Œäººå·¥å†³ç­–ä¸ºä¾æ®ï¼›å¤šæ¬¡æ¨¡åž‹ç»™å‡ºç›¸åŒç­”æ¡ˆä¸ä»£è¡¨é€šè¿‡ã€‚
## 6. å­—段级证据与暂存模型
每个候选字段保存 `raw_value`、`normalized_value`、类型、来源定位、变换规则、提取可靠度、匹配依据与确认状态。表格定位为文件版本/工作表/单元格或区域;文档定位为页码/段落/表格;扫描件保存页码/框坐标。排序或重解析后仍保留原始定位。
候选记录有稳定 `candidate_id`,与业务 ID åˆ†å¼€ï¼›åŽŸå§‹è¡Œã€ä¸šåŠ¡å•æ®å¤´ã€å•æ®è¡Œé‡‡ç”¨æ˜¾å¼åˆ†ç»„å…³ç³»ã€‚ä¸€ä¸ªæºè¡Œå¯å¯¹åº”å¤šæ¡ä¸šåŠ¡è®°å½•ï¼Œå¤šä¸ªæºè¡Œä¹Ÿå¯ç»„æˆä¸€å¼ å•æ®ï¼Œæ¯æ¬¡æ‹†åˆ†/合并保留转换链。
新增暂存表:
- `import_batch`:导入模式、切换时点、范围、负责人、当前 revision ä¸ŽçŠ¶æ€ã€‚
- `import_file_version / import_region`:文件血缘、hash、版本关系、解析区域与缓存。
- `import_candidate / import_field_evidence`:候选对象、字段、来源与变换过程。
- `import_entity_match`:系统候选、匹配特征、决策与别名适用范围。
- `import_issue / import_resolution`:问题类型、阻断级别、影响对象、人工补丁与失效状态。
- `import_plan / import_plan_item / import_dependency`:不可变方案、目标版本、依赖 DAG。
- `import_execution / import_record_link`:幂等键、业务 ID、源到目标映射、回读结果。
候选记录状态为 `待解析 â†’ å¾…匹配 â†’ å¾…补充 â†’ å¯æ ¡éªŒ â†’ å¯æäº¤ â†’ å·²æäº¤ â†’ å·²æ ¸å®ž`,另有 `跳过 / å†²çª / å¤±è´¥ / ç»“果未知`。文件解析成功不等于候选记录可提交。界面分别统计文件数、源行数、候选对象数、业务单据数和明细行数,避免不同计数混在一起。
## 7. å¤æ‚情况处理清单
| æƒ…况 | è¯†åˆ«ä¸Žå¤„理规则 |
| --- | --- |
| åˆå¹¶è¡¨å¤´ã€æ— æ ‡é¢˜è¡¨ã€æ¨ªå‘月份、多表同页、跨页续表 | åˆ†åŒºåŸŸè¯†åˆ«ï¼›ä¿ç•™å±‚级;重复页眉与合计不当作明细;先展示映射样本 |
| åˆå¹¶å•元格表示“同上”、空白表示未知或 0 | ä»…在确认的区域规则内向下填充;未知、空、0 åˆ†åˆ«ç¼–码 |
| éšè—è¡Œåˆ—、筛选行、备注、删除线、修订 | æç¤ºå­˜åœ¨å¹¶ç¡®å®šçº³å…¥æ”¿ç­–;不能默认隐藏项作废或默认修订已接受 |
| å…¬å¼ç¼“存旧、外链断开、手填合计不等于行合计 | åŒæ—¶ä¿ç•™å…¬å¼/显示值/独立核算值;冲突阻断相关金额数量提交 |
| ç¼–码前导 0、长编号科学计数法、日期序列、千分位 | åŽŸå§‹ç±»åž‹ä¼˜å…ˆï¼›ç¼–å·æŒ‰æ–‡æœ¬ï¼›ä¸å¯æ¢å¤çš„ç²¾åº¦æŸå¤±è¦æ±‚åŽŸä»¶æ ¸å®ž |
| æ—¥æœŸç¼ºå¹´ã€æœˆ/日歧义、农历、自然语言日期 | ä½¿ç”¨å·²ç¡®è®¤çš„æ–‡ä»¶æœŸé—´è§£é‡Šï¼›ä¸èƒ½ç”¨ä¸Šä¼ æ—¥æœŸçŒœåŽ†å²ä¸šåŠ¡æ—¥æœŸ |
| åŒåä¸åŒå®¢æˆ·ã€å®¢æˆ·ç®€ç§°ã€ä¾›åº”商曾用名 | å¤šç‰¹å¾åŒ¹é…ã€æ˜¾ç¤ºå€™é€‰è¯æ®ï¼›ä¸è·¨ä¼ä¸š/部门权限范围自动合并 |
| åŒåç‰©æ–™ä¸åŒè§„格、客户料号与内部料号、单位不同 | è§„æ ¼/型号/SKU/单位一起匹配;客户料号映射按客户隔离;换算需确认 |
| åŒæ–‡ä»¶é‡å¤ä¸Šä¼ ã€æ”¹åä¸Šä¼ ã€æ”¹ä¸€æ ¼å†ä¼  | hash é˜²æ–‡ä»¶çº§é‡å¤ï¼›ä¸šåС键/内容指纹防记录重复;修改版进行逐字段 diff |
| â€œæœ€ç»ˆç‰ˆâ€â€œæœ€ç»ˆç‰ˆ2”与日期/内容矛盾 | å»ºç«‹ç‰ˆæœ¬å…³ç³»ï¼Œç”¨æˆ·ç¡®è®¤æƒå¨æ¥æºï¼›ä¸æŒ‰æ–‡ä»¶åæˆ–修改时间自动覆盖 |
| æ±‡æ€»è¡¨ä¸Žæ˜Žç»†è¡¨ã€å¯¹è´¦å•与发货单重复描述同一交易 | åŒºåˆ†äº‹å®žæ¥æºä¸Žæ ¸å¯¹æ¥æºï¼›åªå»ºä¸€æ¬¡äº¤æ˜“,汇总用于控制总额 |
| ä¸€å•多次发货、多笔回款抵多单、合并开票、退补货 | å»ºç«‹åˆ†é…å…³ç³»åŠæœªåˆ†é…ä½™é¢ï¼›æ²¡æœ‰è¯æ®æ—¶ä¸éšæ„å¹³å‡åˆ†æ‘Š |
| ç¨Žçއ/含税价/折扣/运费/币种口径不同 | æ˜Žç¡®å‡€ä»·/税额/含税额、舍入位数与汇率日期;使用 Decimal æ ¸ç®— |
| é‡‡è´­ä¸Žé”€å”®ã€å¤–协发料与成品回收数量不等 | æŒ‰ä¸šåŠ¡ç±»åž‹ã€BOM/计量换算和合同允许损耗校验;不以数量不同直接判错 |
| æ‰¹æ¬¡/SN ç¼ºå¤±ã€åº“存只有总数、仓库别名 | å¯å½¢æˆå¾…分配期初草稿;不可编造批次/SN、库位或供应商归属 |
| æ—§ç³»ç»Ÿå·²æ‰¹å‡†/已支付/已发货,当前系统无流程 | ä¿å­˜åŽ†å²çŠ¶æ€æ¥æºï¼›èµ°è¿ç§»æŽ¥å£ï¼Œä¸ä¼ªé€ å®¡æ‰¹äººæˆ–é‡æ”¾å®žæ—¶ä¸šåŠ¡åŠ¨ä½œ |
| åŽŸè¡¨é—æ¼è´Ÿæ•°ã€çº¢å­—å†²é”€ã€å–æ¶ˆå•/作废单 | ä¿ç•™ç¬¦å·ä¸Žå†²é”€å…³ç³»ï¼›ä¸å¯ç»Ÿä¸€å–绝对值或全部按有效单导入 |
| OCR å°† 0/O、1/I、8/B、小数点/负号混淆 | ç”¨ç¼–码字典、算术与原图交叉验证;关键字段不确定则人工复核 |
| éƒ¨åˆ†æ–‡ä»¶æŸå/加密/缺附件 | ç»™å‡ºå—影响对象;无依赖分支可继续,依赖分支保持阻断 |
| å¯¼å…¥æœŸé—´ç³»ç»Ÿè¢«å…¶å®ƒäººä¿®æ”¹ | ç›®æ ‡ç‰ˆæœ¬/内容 hash ä¹è§‚校验,冲突回到预览,已审批旧方案失效 |
| å¤§æ–‡ä»¶ã€å¤šæ–‡ä»¶ã€å¤šç”¨æˆ·åŒæ—¶æ”¹æ˜ å°„ | åˆ†åŒºè§£æžã€æ‰¹æ¬¡ revision ä¹è§‚锁、影响范围重算;保留修改记录 |
| æ–‡ä»¶å†…容包含指令、宏、外部链接 | ä½œä¸ºå¾…解析数据,不改变工具权限;不自动执行宏或访问外链 |
不存在可靠匹配的实体可提议新建,但默认“待新增确认”,不是自动制造物料或客户。缺失法定/财务/追溯关键字段时阻断相关对象;非必填字段可保留空并列入补充清单,不能塞入“默认客户”“默认仓库”掩盖问题。
## 8. äººå·¥è¡¥å……与多轮交互
导入工作台采用“源文件证据—候选业务数据—问题与处理”三栏。选择某字段时定位原表单元格或原文区域;批量选择同类问题后可一次确认映射,例如“这些文件里的‘件’均映射成系统单位 PCS”。确认必须限定文件/区域/物料类别等范围,不能无条件扩散到全企业。
提问按影响面优先:先确认导入模式、期间、主体、权威文件,再解决影响大量记录的单位/实体映射,最后处理个别缺失。用户可补文件、修正字段、选择候选、暂缓、跳过或要求人工处理。批量应用前显示影响数量和示例,应用后重跑相关检查。
每次回答成为版本化 `ResolutionPatch`,包含目标字段/问题、原值、新值、依据、操作者、时间、作用范围与基线 revision。问题可以重新打开;新证据推翻旧决策时标记旧决策过期。金额、数量、历史状态等必须能解释采用哪个来源。
## 9. å‰æœŸä¸Šçº¿è¡¥å½•:切换时点与防止重复记账
批次必须选择模式:①主数据初始化;②历史资料归档;③未结业务迁移;④期初余额/库存;⑤日常新增/修订。不同模式有不同允许动作和接口,不共用一个“全量导入”开关。
先确定切换时点 T、组织/仓库范围、业务期间、权威台账和控制总额。T ä¹‹å‰çš„累计历史与 T æ—¶ç‚¹ä½™é¢é‡‡ç”¨æ˜Žç¡®è¿ç§»ç­–略:若历史收发只作归档,期初库存入账一次;若重建完整交易链,则不得再把相同余额作为额外库存入账。应收应付、在制与未结订单同样处理。
例如历史合同已生成订单且已发部分货,补录合同时不能再次触发“审批通过自动生成销售订单”,也不能重跑销售单生成 MPS、报工倒冲、质检回调和通知。Java éœ€è¦æ˜Žç¡®çš„迁移入口、迁移权限与受控副作用策略;不支持时仅导入草稿/归档并标记后续待建设,不绕过服务写库。
业务发生时间、原系统创建/审批时间(有证据时)、本次补录时间分别保存。历史审批证据作为附件/来源记录,当前系统操作日志记录真实补录操作者,不能冒充历史人员执行审批。
## 10. Java å¯¼å…¥åè®®ä¸Žæ‰§è¡Œé¡ºåº
现有 `MdmItemController` ç‰©æ–™å¯¼å…¥ã€CRM åˆåŒå¯¼å…¥ã€ERP é‡‡è´­ç”³è¯·/采购单导入可作为领域校验与映射参考。它们的模板导入语义不等于通用历史迁移语义;逐项审查 Service çš„默认值、自动编号、更新策略、状态推进与自动生成下游单据后再复用。
拟增加内部接口:`capabilities` è¿”回支持对象/字段/模式/限制;`validate` åšæ— å‰¯ä½œç”¨æ ¡éªŒå¹¶è¿”回引用冲突与副作用预览;`plan` å†»ç»“执行计划;`commit` æŒ‰æ–¹æ¡ˆç‰ˆæœ¬ä¸ŽæŽˆæƒä»¤ç‰Œæ‰§è¡Œï¼›`status` æŒ‰å‘½ä»¤å·æŸ¥è¯¢ï¼›`reconcile` å›žè¯»å¯¹è´¦ã€‚LLM ä»…生成候选 DTO,不能直接调用 `commit`。
依赖通常为单位/分类/仓库 â†’ å®¢æˆ·/供应商/物料 â†’ äº§å“ BOM/工艺 â†’ åˆåŒ/订单 â†’ å·¥å•/收发/质检等,但最终以对象依赖 DAG å†³å®šï¼ŒçŽ¯ä¾èµ–å•ç‹¬æ‹†è§£ã€‚æš‚å­˜å€™é€‰é€šè¿‡ `candidate_id` å¼•用,入库后生成候选到真实 ID æ˜ å°„供下游使用。
以完整业务聚合(单据头+全部行)为原子边界,不逐行提交半张单据。默认一个依赖组失败则阻止下游;无依赖组可选择继续,并在审批方案里明确策略。外部接口返回成功后必须回读验证。
记录幂等键由企业/迁移范围、稳定源记录标识、目标对象、方案版本和动作组成;另设跨批次业务唯一键/内容指纹冲突检测,避免新建批次绕过防重。源记录修改使用显式更新策略与期望目标版本;不能默默覆盖已有人工修改。
提交超时先查询结果。部分成功后只补失败/未尝试项;重试不重新导入成功记录。撤销按业务能力执行:未被引用的草稿可以撤销;已发生库存/资金/下游引用的记录走冲销或受控更正,不能承诺整批硬删除回滚。
## 11. å¯¹è´¦ã€éªŒæ”¶ä¸Žå®žæ–½ä¼˜å…ˆçº§
提交前后控制总额至少包括对象数、行数、物料数量(按单位分组)、金额(按币种与税口径分组)、库存(按仓库/物料/批次口径)、应收应付/未结数量,以及引用完整率。跨单位/币种不能直接求总和。报告明确可核对、不可核对、人工确认差异及原因。
样本集覆盖:标准表、复杂合并表、扫描件、跨文件冲突、同名实体、重复版本、公式错误、日期/金额歧义、缺少批次、历史已执行单据、同时期初与历史交易、并发修改、执行后断网、部分失败和更正重导。关键事实不得由模型凭空补全;阻断问题未解决不得提交。
交付顺序提升为:A å›¾è¿è¡Œä¸Žæš‚å­˜/证据底座;B ä¸»æ•°æ®æ–‡ä»¶è¡¥å½•闭环;C åˆåŒ/采购/销售等业务草稿与未结迁移;D æœŸåˆåº“存及复杂历史迁移;E ä¸Žè¿è¥ä¹åœºæ™¯è”动。复杂历史迁移按实际业务接口成熟度开放,不能为了补录速度绕过审批和库存规则。
上线前需要企业提供脱敏的常用样本与对应正确结果,建立格式兼容集和业务基准集;当前方案不要求先上传文件才能继续技术设计。最终验收看字段准确、实体关联、重复防护、控制总额、人工工作量与恢复能力,分别统计自动确认与人工确认结果。
## 12. Python æ¨¡å—增量
```text
graphs/scenarios/document_onboarding.py
graphs/documents/parse.py, resolve.py, review.py, import.py
documents/adapters/       # æ ¼å¼è§£æžã€è½¬æ¢ã€OCR çš„可替换接口
documents/layout/         # åŒºåŸŸã€è¡¨å¤´ã€å•据分组与来源定位
documents/normalization/  # æ—¥æœŸã€é‡‘额、单位、编码,保留原始值
documents/matching/       # å®žä½“匹配、跨文件版本和业务关联
documents/validation/     # ç»“构、业务、算术、跨文件、迁移规则
documents/staging/        # å€™é€‰ã€è¯æ®ã€é—®é¢˜ã€ä¿®è®¢ã€æ–¹æ¡ˆç‰ˆæœ¬
documents/planning/       # ä¾èµ– DAG、dry-run、影响预览
documents/reconciliation/ # å…¥åº“回读、控制总额、异常恢复
```
该能力共享主平台的权限、检查点、命令幂等、审批与事件系统,并独立保存文件解析和候选数据。运营图只有在相应数据已核实入库后,才把它作为实时业务事实使用;未确认候选仅用于补录工作台。
docs/industrial-agent-langgraph-technical-design.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,285 @@
# MOM å·¥ä¸šæ™ºèƒ½ä½“:LangGraph å®šåˆ¶æŠ€æœ¯æ–¹æ¡ˆ
> è®¾è®¡ç‰ˆæœ¬ï¼šv1,2026-10-21。阶段:技术方案评审,尚未实施。
> ä¸šåŠ¡èŒƒå›´æ‰¿æŽ¥ [全模块设计](industrial-agent-python-design.md) ä¸Ž [九场景蓝图](industrial-agent-scenario-blueprint.md)。本方案确定使用 Python + LangGraph;具体依赖版本在实施时完成兼容验证后写入锁文件。
新增并列核心能力:[企业工作文件理解与多轮补录](industrial-agent-document-onboarding-design.md)。协调主图增加 `document_onboarding` è·¯ç”±ï¼Œè¡¥å½•暂存、字段证据、多轮人工修订、依赖导入与对账作为专用子图。实施顺序调整为先完成主数据补录闭环,再逐步联动运营场景;期初与历史迁移采用专门业务接口。
统一授权详见 [菜单、按钮与数据权限方案](industrial-agent-authorization-design.md):主图增加能力加载与计划授权节点,所有查询/操作复用 Java å½“前用户权限;模型可用工具、字段、产物与恢复过程均受约束。
## 1. æ ¸å¿ƒæŠ€æœ¯å†³ç­–
采用独立 Python é¡¹ç›® `mom-industrial-agent`,以 LangGraph `StateGraph` ç¼–排业务流程。设计为**协调主图 + ä¹ä¸ªåœºæ™¯å­å›¾ + å…¨æ¨¡å—领域工具 + ç»Ÿä¸€å‘½ä»¤æ‰§è¡Œé€šé“**。查询、计算、建议与执行有明确节点和数据契约。
LLM è´Ÿè´£è‡ªç„¶è¯­è¨€æ„å›¾ã€ä¸šåŠ¡å¯¹è±¡æ¶ˆæ­§ã€æ–‡æ¡£è§£é‡Šã€è¯æ®æ€»ç»“å’Œå€™é€‰è¡ŒåŠ¨ï¼›åº“å­˜å¯ç”¨é‡ã€é‡‘é¢ã€å·¥åºçº¦æŸã€æ£€éªŒè§„åˆ™ã€æƒé™ä¸Žå®¡æ‰¹ç»“è®ºç”±ç¡®å®šæ€§ä»£ç åŠ Java ä¸šåŠ¡æœåŠ¡è´Ÿè´£ã€‚æ¨¡åž‹è¾“å‡ºå¿…é¡»ç»è¿‡ç»“æž„æ ¡éªŒå’Œé¢†åŸŸç­–ç•¥æ ¡éªŒï¼Œæ‰èƒ½æˆä¸ºå·¥å…·è¾“å…¥ã€‚
主图采用明确路由;场景子图以预定义流程为主。仅在知识检索或原因分析节点中允许有预算的动态工具循环。框架支持用状态、节点和边表达这种流程,节点可以执行普通计算或模型调用。[LangGraph Graph API](https://docs.langchain.com/oss/python/langgraph/graph-api)
## 2. éƒ¨ç½²ä¸Žç³»ç»ŸèŒè´£
```mermaid
flowchart TB
    UI[现有 Vue / ç§»åŠ¨ç«¯] --> GW[Java Agent ç½‘关:身份与数据范围]
    GW --> API[Python FastAPI:任务入口 / SSE]
    API --> DB[(Agent PostgreSQL:任务与检查点)]
    API --> Q[Redis Streams:任务分发]
    Q --> W[LangGraph Worker]
    W --> GRAPH[协调图与场景子图]
    GRAPH --> MODEL[模型适配层]
    GRAPH --> KNOW[知识检索适配层]
    GRAPH --> BRIDGE[Java Agent Bridge:领域查询与命令]
    BRIDGE --> SVC[CRM / ERP / SRM / MES / HRM / BI / System]
    BRIDGE --> BPM[现有 BPM å®¡æ‰¹]
    SVC --> MYSQL[(现有业务 MySQL)]
    SVC --> OUT[业务 Outbox]
    BPM --> OUT
    OUT --> EVENTS[事件接入 / åŽ»é‡ / è·¯ç”±]
    EVENTS --> Q
    GRAPH --> DB
```
| ç»„ä»¶ | é€‰åž‹ä¸Žè´£ä»» |
| --- | --- |
| Python API | FastAPI + Pydantic,校验输入、登记任务、读取结果、输出业务事件流 |
| å›¾è¿è¡Œ | LangGraph,自定义状态与节点;异步模型和 HTTP è°ƒç”¨ |
| ä¸šåŠ¡è¿žæŽ¥ | httpx è®¿é—® Java å†…部 Bridge;有限连接池、明确超时、只读重试 |
| æ£€æŸ¥ç‚¹ | `langgraph-checkpoint-postgres` çš„异步持久化实现;初始化/迁移在发布任务执行 |
| ä»»åŠ¡è®°å½• | ç‹¬ç«‹ PostgreSQL ä¿å­˜ä»»åŠ¡ã€æ–¹æ¡ˆã€äº‹ä»¶ã€æ‰§è¡Œè®°å½•ï¼›ä¸šåŠ¡åº“ç»§ç»­ä½¿ç”¨çŽ°æœ‰ MySQL |
| é˜Ÿåˆ— | Redis Streams + consumer group;PostgreSQL outbox ä¿è¯æäº¤ä»»åŠ¡åŽå¯è¡¥å‘ï¼ŒRedis ä¸ä½œä¸ºå”¯ä¸€ä»»åŠ¡äº‹å®žæº |
| å®šæ—¶è§¦å‘ | ä¼˜å…ˆå¤ç”¨é¡¹ç›®çŽ°æœ‰ Quartz,发送场景事件;企业班次/节假日配置决定时间 |
| æ–‡æ¡£ | å¤ç”¨é™„件与知识库;大文件在对象存储,检查点仅存引用与摘要 |
| å¯è§‚测 | ç»“构化日志、trace_id、节点耗时、工具调用审计、指标;外部追踪平台按部署要求选配 |
这修订了早期“全部沿用 MySQL/Redis”的建议:LangGraph ç”Ÿäº§æ£€æŸ¥ç‚¹ä¼˜å…ˆé‡‡ç”¨å®˜æ–¹æ–‡æ¡£åˆ—出的 PostgreSQL å®žçŽ°ï¼Œå‡å°‘è‡ªç ”æ£€æŸ¥ç‚¹é€‚é…å·¥ä½œï¼›å¼€å‘å¯ç”¨ SQLite,内存检查点仅供单元测试。[Persistence](https://docs.langchain.com/oss/python/langgraph/persistence)
API ä¸Ž Worker å¯ç‹¬ç«‹æ‰©å®¹ã€‚同一个业务任务只允许一个 Worker æŒæœ‰æ‰§è¡Œç§Ÿçº¦ï¼›ç»­ç§Ÿã€fencing token å’Œå‘½ä»¤å¹‚等共同处理进程失联。Python æœåŠ¡æ— éœ€è¿ç§» Java ä¸šåŠ¡æ•°æ®ï¼Œä¹Ÿä¸æŒæœ‰ä¸šåŠ¡æ•°æ®åº“å†™æƒé™ã€‚
## 3. ä¸»å›¾è®¾è®¡
```mermaid
flowchart TD
    START([请求 / ä¸šåŠ¡äº‹ä»¶]) --> AUTH[validate_context:核验身份与对象范围]
    AUTH --> INTENT[resolve_request:结构化意图与对象]
    INTENT --> CLEAR{对象和范围明确?}
    CLEAR -->|否| CLARIFY[clarify:interrupt ç­‰å¾…补充]
    CLARIFY --> INTENT
    CLEAR -->|是| ROUTE[route_scenario:白名单场景路由]
    ROUTE --> SUB[场景子图:取证 / è®¡ç®— / æ ¡éªŒ]
    SUB --> CHECK[validate_evidence:完整度与新鲜度]
    CHECK -->|不足| PARTIAL[生成缺口报告]
    CHECK -->|充分| PROPOSE[构建结果与动作方案]
    PROPOSE --> POLICY{动作策略判定}
    POLICY -->|只读结果| PUBLISH[保存并发布结果]
    POLICY -->|需审批| SUBMIT[幂等登记方案与 BPM ç”³è¯·]
    SUBMIT --> WAIT[await_approval:interrupt]
    WAIT --> RECHECK[重新核验审批 / æƒé™ / æ•°æ®ç‰ˆæœ¬]
    RECHECK -->|通过| EXEC[执行明确授权的命令]
    RECHECK -->|过期| SUB
    RECHECK -->|拒绝| REJECT[记录拒绝 / å…³é—­åŠ¨ä½œ]
    POLICY -->|规则授权的内部待办| EXEC
    EXEC --> VERIFY[按命令号查询并验证结果]
    VERIFY --> PUBLISH
    PARTIAL --> PUBLISH
    REJECT --> PUBLISH
    PUBLISH --> END([结束本轮 / ç™»è®°åŽç»­äº‹ä»¶è®¢é˜…])
```
节点职责:
- `validate_context`:接受 Java ç­¾å‘的限时用户委托或系统巡检身份;业务对象必须在当前范围内。身份与访问令牌不放入可持久化图状态。
- `resolve_request`:输出 `scenario_id + business_refs + time_window + requested_action`。对象歧义时产生待补充项,不能自行从同名客户中选择。
- `route_scenario`:按配置允许的场景与权限路由,模型不生成任意 Python å‡½æ•°åæˆ– URL。
- `validate_evidence`:判断数据缺失、来源冲突、单位不一致和快照过期;失败时可输出部分结果,但阻止相关写操作。
- `POLICY`:由代码按动作类型与用户授权判定审批要求;文本中出现“已批准”不能改变该结果。
- `VERIFY`:区分“命令已接收”“审批中”“单据已创建”“业务完成”;前端据此展示状态。
## 4. ä¹ä¸ªä¸šåŠ¡å­å›¾ä¸Žå¤ç”¨èŠ‚ç‚¹
| åœºæ™¯å­å›¾ | æ ¸å¿ƒèŠ‚ç‚¹é¡ºåº | Java é¢†åŸŸä¾èµ– | ä¸»è¦æ–°å¢žå†…容 |
| --- | --- | --- | --- |
| `order_review` | è®¢å•上下文 â†’ é½å¥— â†’ äº§èƒ½æ¨¡æ‹Ÿ â†’ äº¤æœŸæ¯”较 â†’ è¯„审方案 | CRM åˆåŒã€ERP é”€å”®å•、MPS/工单、库存、排程 | è®¢å•行关联聚合、承诺日计算、影响订单列表 |
| `material_readiness` | å·¥å•范围 â†’ BOM ä¸Žç‰©æ–™æ˜ å°„ â†’ å¯ç”¨åº“å­˜/在途 â†’ ç¡®å®šæ€§åˆ†é… â†’ ç¼ºå£åˆå¹¶ | MDM、MES BOM/WMS、ERP é‡‡è´­ã€SRM | è·¨å·¥å•分配策略、催交回执与净缺口快照 |
| `progress_patrol` | è®¡åˆ’基线 â†’ æœ‰æ•ˆæŠ¥å·¥ â†’ æ—¥åކ/暂停记录 â†’ é€¾æœŸè®¡ç®— â†’ è´£ä»»å½’口 | å·¥å•/任务/报工、排班、HRM | è¿žç»­è¶…期检测、待办去重与升级 |
| `reschedule` | èµ„源快照 â†’ å½“前方案 â†’ æ’单预览 â†’ å½±å“å·®å¼‚ â†’ æäº¤å€™é€‰æ–¹æ¡ˆ | MES æŽ’程、设备、班组、ERP/CRM | å¤šæ–¹æ¡ˆå¿«ç…§ã€å®¡æ‰¹ä¸Žç‰ˆæœ¬å†éªŒè¯ |
| `shipment_check` | é”€å”®è¡Œ â†’ å‡ºåº“/库存/装箱 â†’ OQC/冻结 â†’ å¯å‘量 â†’ é˜»æ–­æŠ¥å‘Š | ERP、MES-WM、MES-QC | å‘货前聚合视图 |
| `quality_containment` | æ£€éªŒè§„则版本 â†’ è¶Šé™åˆ¤å®š â†’ æ‰¹æ¬¡æµè½¬ â†’ å½±å“é¢ â†’ NCR/复验方案 | IQC/IPQC/OQC/RQC、NCR、库存 | æ£€éªŒè®¾å¤‡æŽ¥å…¥ã€éš”离及停复线审批流程 |
| `equipment_patrol` | è®¡åˆ’窗口 â†’ æœ‰æ•ˆè®°å½• â†’ ç»´ä¿®åŽ»é‡ â†’ å·¥å•影响 â†’ è¡¥æ£€/保养方案 | è®¾å¤‡ã€ç‚¹æ£€/保养、维修、排班 | å¯éªŒè¯çš„æœºæ—¶é‡‡é›†ã€å…³é”®è®¾å¤‡æŽ’序 |
| `outsource_reconciliation` | å¤–协单 â†’ å‘收退料/IQC â†’ åˆåŒç‰ˆæœ¬ â†’ æ•°é‡ä»·å·® â†’ æ ¸å®žæ¸…单 | å¤–协仓储、ERP、SRM | åˆåŒæŸè€—率、加工费口径与差异处理 |
| `complaint_trace` | å”®åŽ/批号解析 â†’ åå‘追溯 â†’ æ­£å‘影响 â†’ æŠ¥å‘Šé™„ä»¶ â†’ å›žå¤è‰ç¨¿ | å”®åŽã€CRM、ERP、批次/SN、质检报告 | è·¨æ¨¡å—关系补全与证据断点任务 |
子图用独立 input/output schema,通过包装节点转换父子状态。默认采用每次调用独立的子图状态,继承父图检查点;不让不同订单共享领域消息历史。批量巡检可用 `Send` åˆ†å‘独立工单,结果按对象键聚合,明确分页和并发上限。子图持久化模式及父图关系见 [Subgraphs](https://docs.langchain.com/oss/python/langgraph/use-subgraphs)。
全模块以领域工具包覆盖:`mdm / crm / srm / erp / mes_pro / mes_wm / mes_qc / mes_dv / mes_cal / mes_energy / qcreport / aftersales / hrm / bpm / bi / system / infra / ai_knowledge`。Archive、Finance、Research、UMS、WXCP ç­‰å…ˆç™»è®°èƒ½åŠ›æ¸…å•ï¼Œæ ¸å®žå®žé™…æŽ¥å£åŽå¯ç”¨ï¼›IM/Member é»˜è®¤å…³é—­ã€‚经营问答、成本分析作为后续跨域图,复用同一工具层。
## 5. çŠ¶æ€ä¸Žèº«ä»½æ¨¡åž‹
不要把所有业务数据塞进 `messages`。图状态采用明确的业务字段,边界 DTO ä½¿ç”¨ Pydantic æ ¡éªŒï¼›æ¶ˆæ¯åªä¿å­˜å¿…要的人机往来。
| å­—段 | ç±»åž‹/含义 | åˆå¹¶ç­–ç•¥ |
| --- | --- | --- |
| `task_id / case_id / graph_version` | æ‰§è¡Œä»»åŠ¡ã€ä¸šåŠ¡æ¡ˆä»¶ã€å›¾ç‰ˆæœ¬ | å•一协调节点写 |
| `scenario_id / business_refs` | åœºæ™¯ä¸Žå¯¹è±¡å¼•用 | éªŒè¯åŽå›ºå®šï¼Œä¿®æ”¹éœ€æ–°æ–¹æ¡ˆç‰ˆæœ¬ |
| `time_window / rule_versions` | æœ¬æ¬¡è®¡ç®—范围与规则版本 | å•一写入 |
| `snapshot_refs` | å„工具结果的存储引用、业务版本、采集时间 | æŒ‰ source_key åˆå¹¶ |
| `evidence` | è¯æ® ID、来源对象、字段摘要、可信程度 | æŒ‰ evidence_id åŽ»é‡åˆå¹¶ï¼›åŒ ID å†²çªæŠ¥é”™ |
| `findings` | ç¡®å®šäº‹å®žã€æŽ¨æ–­ã€ç¼ºå£ä¸Žé£Žé™© | æŒ‰ finding_id åˆå¹¶ |
| `proposal_ref` | ä¸å¯å˜åŠ¨ä½œæ–¹æ¡ˆ ID、版本、hash | å•一节点写入 |
| `approval_ref / command_refs` | BPM å®žä¾‹ä¸Žå‘½ä»¤è®°å½•引用 | æŒ‰ä¸šåŠ¡é”®åŽ»é‡ |
| `result_ref / status / errors` | è¾“出产物与任务状态 | çŠ¶æ€æœºæŽ§åˆ¶ |
并行节点仅返回自己负责的局部更新;共享结果字段配置 reducer,避免并行覆盖。同一条证据重放后不重复计数。状态保存 JSON å¯åºåˆ—化值与引用,禁止把连接池、HTTP å®¢æˆ·ç«¯ã€å‡­æ®ã€å®Œæ•´é™„件放进检查点。
`conversation_id` æ˜¯ç”¨æˆ·å¯¹è¯ï¼›`case_id` æ˜¯ä¸šåŠ¡é—®é¢˜ç”Ÿå‘½å‘¨æœŸï¼›`task_id` å¯¹åº”一次完整图执行,作为 `thread_id` çš„æœåŠ¡ç«¯æ˜ å°„ï¼›`run_id` æ˜¯ä¸€æ¬¡è¿è¡Œæˆ–恢复尝试。一个对话可有多个案件和任务。审批恢复使用原 thread;定时复查建立新任务并关联原 case,避免旧图状态无限增长。
请求时的权限快照仅用于审计。每次查询、审批恢复和写入都由 Java æ£€æŸ¥æœ€æ–°æƒé™ã€‚恢复任务重新取得受控访问上下文,不依赖几天前已过期的登录 token。
## 6. é‡ç‚¹å­å›¾ï¼šé½å¥—分析的真实计算口径
```text
load_workorders
  â†’ resolve_bom_and_units
  â†’ fetch_material_snapshot(库存、冻结、占用、采购在途)
  â†’ validate_snapshot
  â†’ allocate_supply_across_orders
  â†’ calculate_shortages
  â†’ aggregate_by_material_and_supplier
  â†’ explain_with_evidence
  â†’ propose_expedite_tasks
```
一个批次巡检先统一加载物料供应快照,再按确定的工单优先级分配供应。逐工单独立读取同一库存会重复承诺同一批物料,因此不能简单并行计算后相加。优先级、替代料规则、计量单位和允许损耗必须版本化。
库存服务提供权威的可分配量及其口径。冻结与占用可能重叠,不能未经验证就重复扣减;也要区分本工单占用与其它工单占用、已领物料和在途预计到货。前期蓝图的简化净可用公式以本节为实施依据。
跨模块读取未必是同一个数据库事务快照,因此结果记录每个来源的版本与采集时间。仅将结果称作“截至某时点的评估”;执行领料或采购等动作时,由原业务事务重新校验,不把分析快照当库存锁。
## 7. å·¥å…·åè®®ä¸Ž Java å¯¹æŽ¥
内部接口建议放在 `yudao-module-ai` çš„ `bridge` å…¥å£ï¼›è°ƒç”¨å„模块已有 Service/API,并补足缺失的只读聚合 API。公共 DTO æ”¾åœ¨ç›¸åº” `*-api` æ¨¡å—,遵循现有 Maven ä¾èµ–方向,防止 AI ä¸Ž MES ç›¸äº’依赖。大型接入可独立为 `yudao-module-agent`,需要先核对编译依赖图。
```json
{
  "tool": "mes.material.readiness_snapshot",
  "schema_version": "1",
  "request_id": "uuid",
  "task_id": "uuid",
  "arguments": {"work_order_ids": [101, 102]},
  "response": {
    "data_ref": "snapshot-id",
    "source_versions": {"workorder:101": "revision"},
    "observed_at": "ISO-8601",
    "completeness": "partial",
    "missing": ["purchase_delivery_confirmation"]
  }
}
```
上例是契约形状示意,实际请求和响应分别传输。用户/租户身份来自服务端签发的委托上下文,不接受模型生成的身份字段。工具登记包括领域、读写级别、权限码、schema、超时、分页上限、可重试错误和敏感字段策略。
现有代码可利用的具体入口包括:
- `MesProTaskController`:`POST /mes/pro/task/auto-schedule/preview` ä¸Ž `/auto-schedule`。正式执行说明为重新校验预览并整批原子提交,接入时继续调用该业务规则。当前预览说明有 09–18 ç‚¹åŠå‘¨æœ«/节假日约束;夜班、多班与额外换型约束需扩展并验证。
- `MesProAiController`:四类生产预测,作为既有建议能力对接,确定性计算结果优先。
- `MesProWorkOrderController`:工单进度与计划查询;`MesWmBatchController`:前后向及工序追溯。
- ç‚¹æ£€ã€ç»´ä¿®ã€NCR、外协发料/入库、销售出库等控制器已有对象入口,需通过 Bridge æš´éœ²æœ€å°å­—段集。
这些 HTTP è·¯å¾„是源码中的 Controller ç›¸å¯¹è·¯å¾„;部署的 `/admin-api` ç­‰å‰ç¼€ã€é‰´æƒå’Œæ•°æ®èŒƒå›´é¡»åœ¨è”调中确认。Python ä¸é€šè¿‡è¶…级管理员账号转发全部业务请求。
## 8. å®¡æ‰¹æ¢å¤ä¸Žä¸šåŠ¡å¹‚ç­‰
LangGraph `interrupt()` ç”¨äºŽæš‚停等待,恢复使用同一 thread ä¸‹çš„ `Command(resume=...)`。恢复会从中断节点起点重跑,因此审批节点只读取已登记方案并发出中断,建单、发通知等副作用放在独立的幂等节点。不能用泛化异常捕获吞掉中断。[Interrupts](https://docs.langchain.com/oss/python/langgraph/interrupts)
完整流程:
1. `prepare_proposal` å†»ç»“参数、业务版本、证据引用、有效期,生成 `proposal_id + version + hash`。
2. `register_approval` é€šè¿‡ Java å¹‚等创建审批记录;事务提交后由 outbox é€šçŸ¥ã€‚
3. `await_approval` ä¸­æ–­å¹¶é‡Šæ”¾ Worker,不保持 HTTP æˆ–数据库连接等待。
4. BPM å›žè°ƒå…ˆè½ inbox åŽ»é‡ï¼ŒéªŒè¯å®¡æ‰¹å®žä¾‹ã€åŠ¨ä½œ hash ä¸Žç­¾ç½²æƒé™ï¼›è‹¥å›¾å°šæœªåˆ°ä¸­æ–­ç‚¹ï¼Œäº‹ä»¶ä¿ç•™ç­‰å¾…,不能丢弃。
5. Worker æŒæœ‰ä»»åŠ¡ç§Ÿçº¦ï¼Œè¯»å–å¯¹åº” interrupt;用经过验证的审批记录引用恢复,禁止前端直接提交任意 `resume` å€¼ã€‚
6. `revalidate` å†æŸ¥æƒé™ã€å®¡æ‰¹æ’¤é”€çŠ¶æ€ã€å‚æ•° hash、对象版本和有效期。数据变更需重算,动作参数变更须重新审批。
7. `execute_command` å¸¦ç¨³å®š `command_id`、幂等键和期望版本交给 Java。幂等键由场景、方案版本、动作类型及业务对象构成,同一批准动作重放时保持不变。
8. Java åœ¨åŒä¸€ä¸šåŠ¡äº‹åŠ¡å†…ç™»è®°å‘½ä»¤ç»“æžœä¸Žä¸šåŠ¡å˜æ›´ï¼Œå”¯ä¸€çº¦æŸæ‹¦æˆªé‡å¤è¯·æ±‚ï¼›é‡è¯•è¿”å›žåŽŸä¸šåŠ¡å•å·ã€‚
9. ç½‘络超时属于“结果未知”,按命令 ID æŸ¥è¯¢ç»“果,不能直接当失败再建新单。回读成功后才向前端显示“已执行”。
检查点不提供外部系统的 exactly-once ä¿è¯ã€‚业务一致性依赖 Java äº‹åŠ¡ã€å‘½ä»¤åŽ»é‡ã€ç‰ˆæœ¬æ ¡éªŒä¸Žå¯¹è´¦è¡¥å¿ã€‚è·¨æ¨¡å—é•¿æµç¨‹æ‹†æˆå¯æ ¸å¯¹çš„å‘½ä»¤åºåˆ—ï¼›ä¸­é€”å¤±è´¥åœæ­¢åŽç»­åŠ¨ä½œï¼Œè¡¥å¿ä»…è°ƒç”¨ä¸šåŠ¡å…è®¸çš„æ’¤é”€æŽ¥å£ï¼Œå¹¶ä¿ç•™äººå·¥å¤„ç†å…¥å£ã€‚
## 9. äº‹ä»¶ã€ä¸»åŠ¨å·¡æ£€ä¸Žæ¢å¤
建议事件类型:`sales.delivery_changed`、`purchase.delivery_confirmed`、`production.feedback_approved`、`quality.inspection_completed`、`equipment.repair_changed`、`warehouse.receipt_completed`、`bpm.approval_decided`。名称为拟定契约,需要新增发布适配。
事件至少包含 `event_id`、`event_type`、`aggregate_ref`、`aggregate_version`、`occurred_at`、`schema_version` å’Œç§Ÿæˆ·ã€‚业务事务写 outbox,异步发布;Python inbox ç”¨äº‹ä»¶ ID åŽ»é‡ï¼ŒæŒ‰å¯¹è±¡ç‰ˆæœ¬å¤„ç†ä¹±åºï¼ŒæŒ‰äº‹ä»¶å…³è”å”¤é†’æ¡ˆä»¶æˆ–å¯åŠ¨å¤æŸ¥ã€‚
定时器与事件接入负责发起/恢复图,LangGraph æœ¬èº«ä¸å……当生产定时调度服务。等待期间不使用长时间 sleep。跟进记录含下次检查时间、责任人、截止时间、升级策略与停止条件;通知记录保存渠道回执,未变化的巡检结果不反复提醒。
## 10. æ•°æ®è¡¨ä¸ŽæŒä¹…化边界
| Agent è‡ªæœ‰è¡¨ | æ ¸å¿ƒç”¨é€”/约束 |
| --- | --- |
| `agent_case` | ä¸šåŠ¡æ¡ˆä»¶åŠå…³è”å¯¹è±¡ã€å½“å‰å¤„ç†é˜¶æ®µ |
| `agent_task` | case/thread æ˜ å°„、图版本、租约、状态;同任务单执行者 |
| `agent_run` | æ¯æ¬¡æ‰§è¡Œ/恢复的输入摘要、起止时间、错误与成本 |
| `agent_snapshot` | æ•°æ®æ¥æºã€ç‰ˆæœ¬ã€é‡‡é›†æ—¶é—´ã€å®Œæ•´åº¦ã€å¯¹è±¡å­˜å‚¨å¼•用 |
| `agent_proposal` | ä¸å¯å˜æ–¹æ¡ˆç‰ˆæœ¬ã€å‚æ•° hash、有效期 |
| `agent_command` | å‘½ä»¤ ID、幂等键、请求 hash、远端结果;执行权威结果在 Java |
| `agent_inbox / agent_outbox` | å¯é äº‹ä»¶æŽ¥æ”¶ä¸Žå‘送;事件 ID å”¯ä¸€ |
| `agent_followup` | ä¸‹æ¬¡å¤æŸ¥ä¸Žå‡çº§æ¡ä»¶ã€å…³é—­åŽŸå›  |
| `agent_ui_event` | æœ‰åºç•Œé¢äº‹ä»¶ã€SSE é‡è¿žæ¸¸æ ‡ |
| æ¡†æž¶æ£€æŸ¥ç‚¹è¡¨ | ç”±å›ºå®šç‰ˆæœ¬ saver ç®¡ç†ï¼Œä¸æ‰‹å·¥ä¼ªé€ æˆ–修改状态 |
Checkpointer ä¿å­˜çº¿ç¨‹æ‰§è¡ŒçŠ¶æ€ï¼›è·¨ä»»åŠ¡çš„åå¥½ä¸Žå—å®¡æ ¸çŸ¥è¯†å¯ä»¥ä½¿ç”¨ Store æˆ–应用数据表,并按租户/用户命名空间隔离。实时库存、已变更审批、客户信用等事实每次重新查询,不从聊天记忆当作当前真值。[Checkpointers](https://docs.langchain.com/oss/python/langgraph/checkpointers)
图版本固定到任务,升级保留旧版本 Worker ç›´è‡³æŒ‚起任务结束,或做经过测试的状态迁移。部署时不能随意重命名等待中的节点/删除状态字段。配置检查点与产物保留期,业务审计按企业要求单独留存。
## 11. æ¨¡åž‹ã€çŸ¥è¯†ä¸Žç•Œé¢åè®®
模型网关按能力选择文本、结构化输出和视觉模型,密钥保留服务端。短期用现有 `AiChatApi` é€‚配自然语言说明;若其仅返回文本,则 Python ä½¿ç”¨ JSON schema æ ¡éªŒå¹¶æœ‰é™ä¿®å¤ã€‚需要原生工具调用时新增协议适配,不假设现有字符串接口已提供该能力。
知识检索传入领域、物料/产品、标准版本、生效日期和权限过滤。证据记录文档 ID、版本、页码/段落、附件引用;工艺变更按业务发生时间选有效版本。检索文档作为内容数据,不能改变工具权限或业务规则。
前端采用稳定的业务 SSE åè®®ï¼š`task_started`、`step_completed`、`evidence_added`、`result_ready`、`approval_required`、`command_submitted`、`command_verified`、`task_failed`。前端不消费原始 checkpoint、内部 prompt æˆ–完整工具返回。服务端将 LangGraph æµäº‹ä»¶è½¬æ¢æˆä¸Šè¿°åè®®å¹¶è¿‡æ»¤æƒé™ï¼›æ–­çº¿åŽæŒ‰ event_id è¡¥å‘。官方支持状态更新、消息及自定义流,选定版本后固定适配器并覆盖协议测试。[Streaming](https://docs.langchain.com/oss/python/langgraph/streaming)
## 12. Python å·¥ç¨‹ç›®å½•与依赖策略
```text
mom-industrial-agent/
  pyproject.toml / uv.lock
  src/mom_agent/
    api/                  # tasks、cases、results、SSE;内部事件接入
    runtime/              # Worker、租约、恢复、图版本注册
    graphs/
      coordinator.py
      scenarios/          # ä¹ä¸ªåœºæ™¯ StateGraph
      shared/             # è¯æ®æ ¡éªŒã€æ–¹æ¡ˆã€å®¡æ‰¹ã€å‘½ä»¤å›žè¯»
    state/                # çˆ¶å­å›¾çŠ¶æ€ã€reducer、schema è¿ç§»
    domains/              # è¦†ç›–所有实际业务模块的工具契约
    calculators/          # é½å¥—分配、工时、差异等可测试计算
    integrations/         # Java、模型、知识库、通知渠道
    policies/             # æƒé™éœ€æ±‚、动作等级、预算、数据有效期
    persistence/          # PostgreSQL æ£€æŸ¥ç‚¹ä¸Žåº”用表
    events/               # inbox/outbox、事件关联、跟进任务
    observability/        # æ—¥å¿—、指标、trace
  tests/unit/ contracts/ graph/ recovery/ scenarios/
  evaluations/            # è„±æ•çœŸå®žæ¡ˆä¾‹ã€åŸºå‡†ç»“果与标注
  migrations/
  deploy/                 # å®¹å™¨ã€é…ç½®ç¤ºä¾‹ã€è¿è¡Œæ‰‹å†Œ
```
Python 3.11+;依赖锁定 LangGraph、checkpoint-postgres、Pydantic、FastAPI、httpx、数据库驱动与模型适配包的兼容组合。只采用已验证所需 API;不自动跟随最新版本,不为初期方案引入额外多智能体框架。
## 13. éªŒæ”¶ä¸Žå®žæ–½æ‹†åˆ†
第一包:图运行底座、Java å§”托身份、只读工具、检查点、SSE、任务/事件表。通过“挂起后重启仍能恢复、同任务并发受控、越权隔离、事件不丢不重复”的验收。
第二包:齐套、工单巡检和交期评审三图。用脱敏真实工单对比人工基准;检验同批库存不被重复分配、冻结占用不被重复扣减、来源缺失不生成确定性承诺。
第三包:审批与写命令。覆盖回调先到/重复到、审批后业务变化、执行后网络断开、写成功但检查点未保存、审批撤销等故障注入;重跑不重复建单。
第四包:质量/追溯、设备、发货、委外;补充全模块查询图、经营问答与成本口径。场景开放前逐项核对真实数据与业务接口,不以存在 Controller ä½œä¸ºå®Œæ•´å¯ç”¨çš„证据。
完成标准:九场景各有节点图、输入输出契约、权限清单、人工边界、失败路径和真实样本;每个结果可追溯来源,每个动作可关联审批和业务单号。时延、模型成本和并发目标在真实数据规模压测后设定,不沿用宣传图中的耗时或交付周期。
## 14. æœ¬è½®å·²ç¡®å®šä¸ŽåŽç»­è¯„审点
已确定:Python ç‹¬ç«‹é¡¹ç›®ã€LangGraph è‡ªå®šä¹‰ç¼–排、主图与九场景子图、Java ä¿ç•™ä¸šåŠ¡æ‰§è¡Œä¸Žå®¡æ‰¹ã€ç”Ÿäº§æŒä¹…åŒ–æ£€æŸ¥ç‚¹ã€äº‹ä»¶é©±åŠ¨è·Ÿè¿›ã€è¯æ®å¡è¾“å‡ºã€‚
后续评审:新增 PostgreSQL çš„部署资源;首批模型与数据出域要求;现有 BPM æ˜¯å¦è¦†ç›–催交/停复线等新流程;委外价格损耗与机时来源;各岗位动作授权。以上影响接口与上线范围,不妨碍先完成图、工具和数据契约设计。
docs/industrial-agent-python-design.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,162 @@
# å…¨ä¸šåŠ¡å·¥ä¸šæ™ºèƒ½ä½“ Python é¡¹ç›®è®¾è®¡è‰æ¡ˆ
> çŠ¶æ€ï¼šæ–¹æ¡ˆè®¨è®ºç¨¿ï¼›ä»…è®¾è®¡ï¼Œå°šæœªå®žæ–½ã€‚ç›®æ ‡æ˜¯ä¸ºæœ¬ä»“åº“å»ºç«‹ç‹¬ç«‹çš„ Python æ™ºèƒ½ä½“服务,并通过受控接口覆盖实际业务链路。
九个业务场景的触发、取证、计算、结果、审批边界与仓库接入核对见 [场景蓝图](industrial-agent-scenario-blueprint.md)。
框架已确定为 LangGraph;以 [LangGraph æŠ€æœ¯æ–¹æ¡ˆ](industrial-agent-langgraph-technical-design.md) ä¸ºå·¥ç¨‹å®žçŽ°ä¾æ®ï¼Œå…¶ä¸­æ˜Žç¡®äº†æ£€æŸ¥ç‚¹å­˜å‚¨ã€å›¾çŠ¶æ€ä¸Žå®¡æ‰¹æ¢å¤ç»†èŠ‚ï¼Œå¹¶ä¿®è®¢æ—©æœŸå­˜å‚¨å»ºè®®ã€‚
企业初次上线与日常补录新增核心主线:[WPS ç­‰å·¥ä½œæ–‡ä»¶åˆ†æžã€å¤šè½®æ ¸å¯¹ä¸Žä¸šåŠ¡å…¥åº“](industrial-agent-document-onboarding-design.md),与运营场景并列建设。
## 1. é¡¹ç›®å®šä½ä¸Žè¾¹ç•Œ
本仓库以 Spring Boot æ¨¡å—承载业务规则、权限、审批和数据,Python é¡¹ç›®è´Ÿè´£è·¨æ¨¡å—理解、检索、分析、规划与建议。用户从现有前端或业务单据发起任务;Java ç½‘关鉴权并提供工具接口;Python æ™ºèƒ½ä½“生成带证据的建议或待审批操作;所有业务写入仍经原有 Java æœåŠ¡å®Œæˆã€‚
设计目标是“一个智能体平台 + å¤šä¸ªé¢†åŸŸèƒ½åŠ›åŒ… + è·¨é¢†åŸŸæµç¨‹â€ï¼Œè€Œä¸æ˜¯ä¸ºæ¯ä¸ªæ¨¡å—复制一个聊天机器人。平台先接入全部业务对象的**目录和只读能力**,再按风险逐步开放可审批的动作。目录覆盖不等于第一版就有完整的自动执行能力。
### ä»“库依据
- `docs/project-business/data/modules.json` ç»™å‡ºäº† MDM、CRM、SRM、ERP、生产、仓储、质量、质检报告、设备、售后、HRM、BPM、AI çš„业务对象与上下游。
- `yudao-module-ai` å·²æœ‰æ¨¡åž‹é…ç½®ã€çŸ¥è¯†åº“å’Œ `AiChatApi`;CRM æŠ¥ä»· OCR、ERP æ¥ç¥¨ OCR、MES è´¨æ£€å»ºè®®ä¸Žå››ç±»ç”Ÿäº§é¢„测已有调用点。新 Python æœåŠ¡åº”å¯¹æŽ¥æˆ–é€æ­¥è¿ç§»è¿™äº›èƒ½åŠ›ï¼Œé¿å…åŒå¥—ç­”æ¡ˆä¸Žé…ç½®ã€‚
- `yudao-module-mes` æœ‰è®¾å¤‡ã€ç‚¹æ£€ã€ç»´ä¿®ã€å·¥å•、库存、检验等领域;`yudao-module-bi` æœ‰ä»ªè¡¨ç›˜ä¸Žå›¾è¡¨é…ç½®ã€‚`archive`、`finance`、`research`、`ums`、`wxcp` åœ¨å½“前源码中缺少可直接证明的管理端控制器,先放入能力目录,待逐一核实接口后开放。
- `im` ä¸Ž `member` æ¨¡å—虽有代码,现有业务地图将其列为主链路未消费或遗留能力。默认不把它们作为首批生产工具。
## 2. æ€»ä½“æž¶æž„
```mermaid
flowchart LR
    U[Vue / ç§»åŠ¨ç«¯ / å•据按钮] --> J[Java æ™ºèƒ½ä½“网关]
    J --> P[Python FastAPI æ™ºèƒ½ä½“服务]
    P --> O[任务编排与状态机]
    O --> R[领域工具目录]
    O --> K[知识检索]
    O --> M[模型与规则 / é¢„测器]
    R --> J
    J --> B[现有 Java ä¸šåŠ¡ Service / API]
    B --> D[(业务库)]
    O --> A[(智能体会话 / è¯æ® / å®¡è®¡)]
    O --> H[人工审批队列]
    H --> J
```
**责任划分**
| å±‚ | èŒè´£ | ä¸æ‰¿æ‹… |
| --- | --- | --- |
| Java æ™ºèƒ½ä½“网关 | çŽ°æœ‰ç™»å½•æ€ã€RBAC、数据范围、业务 API å°è£…、审批与幂等写入 | æ¨¡åž‹æŽ¨ç†ä¸Žè·¨åŸŸè§„划 |
| Python æ™ºèƒ½ä½“服务 | æ„å›¾è·¯ç”±ã€å·¥å…·è®¡åˆ’、证据汇总、约束校验、建议生成、会话审计 | ç›´è¿žå¹¶ä¿®æ”¹ä¸šåŠ¡è¡¨ã€ç»•è¿‡ BPM |
| é¢†åŸŸå·¥å…· | ç»“构化查询、计算、模拟与受控命令;每个工具声明权限、字段和版本 | è¿”回不受控的整库数据 |
| çŸ¥è¯†å±‚ | SOP、工艺、质检规范、产品手册、制度等文档检索 | æŠŠçŸ¥è¯†æ–‡æ¡£å½“作实时库存或审批状态 |
| è§„则与模型层 | ç¡®å®šæ€§æ ¡éªŒã€é¢„测模型、LLM è¯­è¨€è§£é‡Š | è®© LLM å†³å®šç¡¬æ€§ä¸šåŠ¡è§„åˆ™ |
关键请求上下文:`user_id`、部门/数据范围、`tenant_id`(即使当前关闭也保留字段)、`task_id`、`trace_id`、场景、业务对象引用和权限快照。Python ä¸å¯ä¿¡ä»»å‰ç«¯ä¼ å…¥çš„æƒé™å£°æ˜Žï¼›æ¯æ¬¡å·¥å…·è°ƒç”¨ç”± Java æœåŠ¡ç«¯æŒ‰å½“å‰ç”¨æˆ·é‡æ–°é‰´æƒã€‚
## 3. é¢†åŸŸèƒ½åŠ›å›¾è°±
下表是**目标工具目录**,不是对现有接口均已可用的声明。每行先交付只读工具和引用链接,写动作另行进行接口与权限核实。
| é¢†åŸŸ | é¦–批只读能力 | åŽç»­å»ºè®®æˆ–审批动作 |
| --- | --- | --- |
| MDM ä¸»æ•°æ® | ç‰©æ–™/SKU、计量单位、仓库、批次属性查询;编码消歧 | ä¸»æ•°æ®é‡å¤é¡¹ä¿®è®¢å»ºè®® |
| CRM é”€å”® | å®¢æˆ·ã€å•†æœºã€æŠ¥ä»·ã€åˆåŒã€å›žæ¬¾è®¡åˆ’、销售目标摘要 | è·Ÿè¿›å»ºè®®ã€åˆåŒé£Žé™©æç¤ºã€æŠ¥ä»·è‰ç¨¿ |
| SRM ä¾›åº”商 | ä¾›åº”商资质、招投标、交付与绩效查询 | èµ„质到期提醒、候选供应商比较 |
| ERP é‡‡è´­/销售/资金 | é‡‡è´­ç”³è¯·ä¸Žè®¢å•、来票付款、销售订单、收款状态 | ç¼ºè´§é‡‡è´­å»ºè®®ã€è®¢å•异常解释、对账候选项 |
| MES ç”Ÿäº§ | MPS、工单、工序任务、报工、工艺路线、工单 BOM | ç¼ºæ–™/工时/风险/交付预测整合,排程方案模拟 |
| MES ä»“储 | åº“存维度、占用、冻结、批次/SN、出入库事务 | è°ƒæ‹¨/补货建议,追溯链生成 |
| MES è´¨é‡ | IQC/IPQC/OQC/RQC、指标、缺陷、NCR | æ£€éªŒåˆ¤å®šå»ºè®®ã€å¼‚常归因、NCR å¤„置草稿 |
| è´¨æ£€æŠ¥å‘Š | æ¨¡æ¿ç‰ˆæœ¬ã€æŠ¥å‘Šå®žä¾‹ã€å†»ç»“快照引用 | æŠ¥å‘Šç¼ºå£è§£é‡Šä¸Žç”Ÿæˆå‰æ£€æŸ¥ |
| è®¾å¤‡/工装/排班/能耗 | è®¾å¤‡çŠ¶æ€ã€ç‚¹æ£€/维修、工装、班组、能耗记录 | ç»´ä¿®ä¼˜å…ˆçº§ã€è®¾å¤‡é£Žé™©ã€æŽ’班冲突建议 |
| å”®åŽ | å·¥å•、维修、退货、质检与退款回调状态 | å®¢è¯‰å½’因、退货进度解释、处理草稿 |
| HRM | äººå‘˜å¯ç”¨æ€§ã€æŽ’班、考勤与交接状态的最小必要摘要 | äººåŠ›ç¼ºå£å»ºè®®ï¼›è–ªé…¬æ•°æ®é»˜è®¤ä¸è¿›å…¥é€šç”¨ä¼šè¯ |
| BPM | æµç¨‹å®žä¾‹ã€å½“前任务、审批轨迹、业务回调状态 | å¾…办解释、审批材料缺口提示;审批决定仍由审批人执行 |
| BI | æŒ‡æ ‡å®šä¹‰ã€ä»ªè¡¨ç›˜èšåˆç»“æžœ | è·¨åŸŸç»è¥è§£é‡Šã€å¼‚常钻取、指标口径引用 |
| AI/知识 | çŽ°æœ‰æ¨¡åž‹ä¸ŽçŸ¥è¯†åº“ã€OCR/预测调用登记 | ç»Ÿä¸€æ¨¡åž‹ç½‘关、提示词版本与评估 |
| System/Infra | ç”¨æˆ·å¯è§èŒƒå›´ã€å­—典、附件引用、任务/审计日志 | ç®¡ç†é…ç½®å»ºè®®ï¼›é«˜æƒé™å·¥å…·å•独审批 |
| Archive/Finance/Research/UMS/WXCP | å»ºç«‹æ¨¡å—清单、核实是否有业务 API | æœ‰å®žé™…接口与责任人后加入工具目录 |
| IM/Member | ä¿ç•™å…¼å®¹å…¥å£ï¼Œä¸åœ¨é»˜è®¤ä¸šåŠ¡åŠ©æ‰‹æœç´¢èŒƒå›´å†… | ä¸šåŠ¡ç¡®è®¤å¯ç”¨åŽå†æŽ¥å…¥ |
**跨域语义层**统一使用 `BusinessRef(module, object_type, id, code)` å’Œä¸šåŠ¡äº‹ä»¶å…³ç³»ï¼Œé¿å…åœ¨æç¤ºè¯ä¸­æ‹¼æŽ¥è¡¨åã€‚ä¼˜å…ˆè¿žæŽ¥â€œé”€å”®è®¢å•â†’MPS→工单→领料/报工→质检→入库/出库→回款/售后”等真实单据引用;没有确定关联时明确显示“待核实”,不靠名称猜测。
## 4. ä¸‰ä¸ªé¦–要跨模块场景
### A. è®¢å•交付风险
输入销售订单或合同编号。读取 CRM åˆåŒã€ERP é”€å”®è®¢å•、MPS/工单/报工、库存占用、采购到货、设备停机、质检阻断与历史交付。确定性计算缺口和预计完成范围;模型说明主要风险、证据、下一步与责任岗位。输出是“风险卡 + è¯æ®å¼•用 + å¯é€‰å¤„理方案”,排程或采购仅生成草稿。
### B. è´¨é‡å¼‚常追溯
输入 IQC/IPQC/OQC/RQC/NCR æˆ–批次号。沿供应商、来料、库存批次、工单工序、设备、检验指标、销售出库和售后链路追溯。输出影响批次/订单清单、时间线、已知与未知环节。任何库存冻结、返工、退货和报废动作进入原有审批流。
### C. ç»è¥ä¸ŽçŽ°åœºå¼‚å¸¸é—®ç­”
用户提问“本周交付为何下降?”之类问题。先识别可访问的 BI æŒ‡æ ‡å£å¾„,再查聚合数据和相关单据,引用具体时间范围与来源。区分事实、计算结果与推测;没有足够数据时给出缺失清单。该入口可覆盖全部已接入模块,但仅返回用户可见的数据。
## 5. æ™ºèƒ½ä½“工作机制
```text
接收任务 â†’ é‰´æƒ/识别业务对象 â†’ é€‰ç”¨åœºæ™¯æ¨¡æ¿ â†’ è§„划只读工具
→ å¹¶è¡Œå–证 â†’ æ ¡éªŒæ—¶é—´ã€å•位、口径与关联 â†’ è§„则/预测计算
→ ç”Ÿæˆå¸¦æ¥æºçš„答案/方案 â†’ å¦‚需写入则形成待审批命令
→ Java å†é‰´æƒä¸Žä¸šåŠ¡æ ¡éªŒ â†’ BPM/人工确认 â†’ æ‰§è¡Œå¹¶å›žè¯»ç»“æžœ
```
工具调用使用 JSON Schema/Pydantic è¾“入输出。每个工具登记:`name`、`domain`、`read_or_write`、`permission`、`data_scope`、`timeout`、`schema_version`、`idempotency_key`。查询默认分页与限时;跨域任务限制最大步骤、最大工具调用次数和成本。失败时返回已核实部分与缺失证据,不伪造结论。
输出统一为 `answer`、`facts[]`、`inferences[]`、`unknowns[]`、`citations[]`、`risk_level`、`proposed_actions[]`、`approval_required`。每条来源包含业务对象、接口版本、采集时间和可在原系统打开的链接。对于金额、库存、合格判定、审批权限等硬约束,以业务服务返回的规则和状态为准。
## 6. Python é¡¹ç›®ç»“构(拟)
```text
industrial-agent/
  pyproject.toml
  src/industrial_agent/
    api/                 # FastAPI è·¯ç”±ï¼šä¼šè¯ã€ä»»åŠ¡ã€å®¡æ‰¹è‰ç¨¿ã€å¥åº·æ£€æŸ¥
    core/                # çŠ¶æ€æœºã€è®¡åˆ’å™¨ã€å·¥å…·è°ƒåº¦ã€ä¸Šä¸‹æ–‡ã€å®¡è®¡
    contracts/           # Pydantic DTO ä¸Žç‰ˆæœ¬åŒ–工具契约
    domains/             # mdm/crm/srm/erp/mes/qc/aftersales/hrm/bpm/bi ...
    workflows/           # äº¤ä»˜é£Žé™©ã€è´¨é‡è¿½æº¯ã€è·¨åŸŸé—®ç­”
    integrations/        # Java ç½‘关、模型、知识库、事件总线适配器
    policies/            # æƒé™èŒƒå›´ã€æ•æ„Ÿå­—段、审批门槛、预算
    evaluation/          # åœºæ™¯æ ·æœ¬ã€å›žå½’指标、结果标注
  tests/
  docs/
```
建议选用 Python 3.11+、FastAPI、Pydantic、httpx;任务状态与审计存储采用仓库现有 MySQL/Redis ä½“系,但使用独立 schema/表和服务账号,不直接修改业务表。模型适配层兼容已有 AI æ¨¡åž‹é…ç½®ï¼Œå…·ä½“复用方式要在实施前核对认证与 API。RAG å¯ä»¥å…ˆæ¶ˆè´¹çŽ°æœ‰ AI çŸ¥è¯†åº“能力,不预设再次部署向量库。
### Java â†” Python å¥‘约草案
- `POST /agent/tasks`:带业务对象引用与用户意图,返回 `task_id`。
- `GET /agent/tasks/{id}`:流式或轮询获取状态、证据和结果。
- `POST /agent/tasks/{id}/proposals`:生成结构化待审批命令;不直接写业务单据。
- `POST /agent/tasks/{id}/approve`:由 Java ç½‘关与 BPM å¤„理审批,不接受 Python è‡ªæŠ¥å®¡æ‰¹é€šè¿‡ã€‚
- é¢†åŸŸå·¥å…·ç”± Java ç½‘关暴露内部 API,返回最小字段集、权限过滤结果及 `source_ref`;内部通信使用服务认证,用户上下文由网关签发并限时。
## 7. æ–¹æ¡ˆé€‰æ‹©
| æ–¹æ¡ˆ | å½¢æ€ | ä¼˜ç‚¹ | ä»£ä»·/适用情况 |
| --- | --- | --- | --- |
| A:统一问答层 | Python æœåŠ¡åªè°ƒåªè¯»å·¥å…·å’ŒçŸ¥è¯†åº“ | æŽ¥å…¥å¿«ã€é£Žé™©ä½Žï¼Œé€‚合先做统一入口 | å¾ˆéš¾è¦†ç›–审批与执行闭环 |
| **B:平台 + é¢†åŸŸèƒ½åŠ›åŒ…ï¼ˆæŽ¨èï¼‰** | ç»Ÿä¸€ä»»åŠ¡ç¼–æŽ’ã€é¢†åŸŸå·¥å…·ã€è·¨åŸŸå·¥ä½œæµã€åˆ†çº§å®¡æ‰¹ | èƒ½é€æ­¥è¦†ç›–所有业务,又保留各模块规则 | éœ€è¦å»ºè®¾ Java ç½‘关和工具契约 |
| C:多自治智能体群 | æ¯ä¸ªé¢†åŸŸç‹¬ç«‹è§„划与协商 | é¢†åŸŸå›¢é˜Ÿå¯ç‹¬ç«‹æ¼”è¿› | åè°ƒã€å®¡è®¡ã€æƒé™å’Œç»“果一致性复杂,当前不宜直接作为首版 |
推荐 B çš„第一阶段只读运行,保留未来从单一编排器发展为领域专长智能体的接口。选择方案时重点比较:想先解决的跨域任务、现有 Java API çš„可复用程度、是否允许智能体提交审批草稿,以及上线期限。
## 8. åˆ†é˜¶æ®µè½åœ°ä¸ŽéªŒæ”¶
1. **业务和接口盘点**:逐模块标记“已验证 API / éœ€æ–°å¢ž API / æš‚无业务入口”;绘制对象关系与权限边界。验收:每个目标工具都有业务负责人、来源接口、字段、数据范围和测试样例。
2. **平台骨架**:Python API、工具注册、任务状态机、审计、模型适配、只读 Java ç½‘关。验收:越权请求被拒绝;故障可追踪到 `trace_id`;答案有来源链接。
3. **横向覆盖**:接入上述所有实际业务模块的只读目录与至少一个关键查询,未实现模块明确标记不可用。验收:模块能力清单可自动检查,不将空模块展示成已接入。
4. **深度场景**:优先交付风险与质量追溯,其次经营问答、采购协同、设备维护、售后闭环。验收:与人工基准结果比对事实正确率、关联准确率、漏报率和响应时延。
5. **受控执行**:只对经过业务评审的命令开放草稿/提交,经 Java å†é‰´æƒã€å¹‚等校验与 BPM å®¡æ‰¹åŽæ‰§è¡Œã€‚验收:重复请求不重复建单;拒绝审批不产生业务写入;可按单据回放审计链。
首批评估集应包含缺料、重复库存占用、质检不合格、已存在维修单、跨部门越权、已撤销审批、数据过期、接口超时、关联断链等案例。上线采用观察模式,先比对建议与人工处理,再扩大可执行范围。
## 9. å¾…选择与润色的问题
1. é¦–个可演示闭环选“订单交付风险”、“质量异常追溯”还是“跨域经营问答”?
2. ç¬¬ä¸€ç‰ˆæ˜¯å¦åªè¯»ï¼Œæˆ–允许创建**待审批草稿**?
3. ç”¨æˆ·å…¥å£ä¼˜å…ˆæ”¾å…¨å±€åŠ©æ‰‹ã€ä¸šåŠ¡å•æ®ä¾§æ ï¼Œè¿˜æ˜¯ä¸¤è€…å¹¶è¡Œï¼Ÿ
4. æ¨¡åž‹éƒ¨ç½²ä¼˜å…ˆæ²¿ç”¨çŽ°æœ‰ `yudao-module-ai` é…ç½®ï¼Œè¿˜æ˜¯ç”± Python ç‹¬ç«‹ç®¡ç†ï¼Ÿè¿™éœ€è¦æ ¸å¯¹å½“前模型服务的可调用接口与运维方式。
以上选定后,再收敛接口清单、交互流程、数据库表、部署图和验收样例,形成可实施版本。
docs/industrial-agent-scenario-blueprint.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,189 @@
# å·¥ä¸šæ™ºèƒ½ä½“场景蓝图:从订单到交付
> æ–¹æ¡ˆæ·±åŒ–稿。基于用户给出的场景示意图,面向本仓库的实际模块和接口重新定义。图片中的数字、客户、设备和执行状态仅作交互示例,不代表本系统真实数据或已实现功能。本文只设计,不改动业务代码。
工程实现参见 [LangGraph å®šåˆ¶æŠ€æœ¯æ–¹æ¡ˆ](industrial-agent-langgraph-technical-design.md),其中齐套分配与库存口径是本蓝图简化公式的实施细化。
前期数据准备与日常增量补录由 [工作文件补录核心能力](industrial-agent-document-onboarding-design.md) æ‰¿æ‹…;未核实的文件候选数据不能直接作为九场景中的当前业务事实。
## ä¸€ã€äº§å“ç›®æ ‡
用户要的不是“能回答制造业问题的聊天框”,而是一个在现有 CRM、ERP、SRM、MES、WMS、质检、设备、售后、BPM å’Œ BI ä¸Šå·¥ä½œçš„**业务执行助理**:按计划主动巡检;用户提问时能沿订单与批次链找证据;给出可核算的结论;把需要处理的事项交给正确岗位;持续跟进到业务系统回写完成。
一个场景必须同时具备五部分才算交付:**触发条件 â†’ ä¸šåŠ¡å–è¯ â†’ ç¡®å®šæ€§è®¡ç®—/判断 â†’ å¯æ ¸å¯¹çš„结果 â†’ äººæœºè¾¹ç•Œä¸Žå›žå†™**。界面上的“已执行”仅在 Java ä¸šåŠ¡æŽ¥å£è¿”å›žçœŸå®žä¸šåŠ¡å•å·åŽå‡ºçŽ°ã€‚â€œé¢„æµ‹â€â€œå»ºè®®â€â€œè‰ç¨¿â€â€œå®¡æ‰¹ä¸­â€â€œå·²æ‰§è¡Œâ€â€œå·²å…³é—­â€æ˜¯ä¸åŒçŠ¶æ€ï¼Œä¸èƒ½æ··ç”¨ã€‚
## äºŒã€ä¹ä¸ªåœºæ™¯è·¯çº¿
| ç¼–号 | åœºæ™¯ | è§¦å‘ | ä¸»è¦ç³»ç»Ÿ | ç¬¬ä¸€ç‰ˆäº§ç‰© | åŽç»­é—­çޝ |
| --- | --- | --- | --- | --- | --- |
| 01 | æŽ¥å•评审与交期答复 | åˆåŒ/销售订单创建或交期变更 | CRM、ERP、MES、WMS | æœ€æ—©å¯æ‰¿è¯ºæ—¥ä¸Žå†²çªæ¸…单 | è¯„审单、答复草稿、人工发送 |
| 02 | å¼€å·¥é½å¥—与缺料催交 | æ¬¡æ—¥å·¥å•定时巡检/工单下发 | MES、WMS、ERP、SRM | é½å¥—率、净缺口、采购关联 | å‚¬äº¤å¾…办、回执与再次计算 |
| 03 | å·¥å•超期巡检 | æ¯ç­/每日巡检和报工变化 | MES、排班、设备 | çœŸè¶…期工单、卡点与责任组 | æŒ‡æ´¾å¾…办、超时升级 |
| 04 | æ’单与排程重算 | æ€¥å•申请/资源或设备变化 | CRM、ERP、MES、设备、排班 | ä¸¤å¥—排程方案与受影响订单 | äººå®¡åŽè°ƒç”¨çŽ°æœ‰æŽ’ç¨‹æœåŠ¡ |
| 05 | å‘货与仓储拦截 | å‘货通知/出库前检查 | WMS、ERP、OQC、批次/SN | å¯å‘数量、阻断原因、位置 | æ‹£è´§/发货建议,单据照原审批 |
| 06 | è´¨é‡å¼‚常与停线复验 | IPQC è¶Šé™/NCR/设备检验事件 | MES-QC、工单、仓储、设备 | å½±å“æ‰¹æ¬¡å’Œå·²æµè½¬èŒƒå›´ | éš”离建议、复验待办、停线申请待签 |
| 07 | è®¾å¤‡æ¼æ£€ä¸Žä¿å…» | ç‚¹æ£€æ—¶çª—/运行时长到期 | è®¾å¤‡ã€å·¥å•、排班 | æ¼æ£€æ¸…单和关键设备排序 | ä¿å…»/补检待办与维修回执 |
| 08 | å§”外加工对账 | æœˆç»“或外协单完结 | MES-WM、ERP、SRM | å‘收耗损与价格差异清单 | é‡‡è´­æ ¸å®žã€è´¢åŠ¡å®¡æ‰¹åŽç»“ç®— |
| 09 | å®¢è¯‰æ‰¹æ¬¡è¿½æº¯ | å”®åŽå·¥å•/客户批号提问 | å”®åŽã€ERP、WMS、MES-QC | æ­£åå‘追溯图、受影响客户 | å›žå¤è‰ç¨¿ã€ç¼ºè¯æ®ä»»åŠ¡ |
横向能力包括**工单成本归集**、**经营异常问答**、**日/周报生成**。这些可消费九个场景的可信结果,但成本核算须先确认材料价、工时费率、委外费用、期间口径和财务责任人,不能直接把报工时数乘一个猜测费率。
## ä¸‰ã€åœºæ™¯ç»†åŒ–
### 01 æŽ¥å•评审与交期答复
- **典型问句**:“客户要把这单提前到 15 å·ï¼Œèƒ½ç­”应吗?”
- **取证**:CRM åˆåŒ/客户要求、ERP é”€å”®è®¢å•行、MES MPS/工单与工艺路线、工单 BOM、WMS å¯ç”¨åº“存和占用、在途采购与设备/班次能力。全部以同一订单行、产品 SKU、数量和单位为基准。
- **计算**:`最早物料齐套时间` ä¸Žå„工序可用产能窗口结合,模拟最早完工与 OQC/出库时间;承诺日要加上配置的检验与物流提前期。现有生产 AI äº¤ä»˜é¢„测可作参考,不能替代排程与库存硬约束。
- **结果**:可承诺日范围、相对客户要求的差额、受影响工单、关键路径和证据时间。若计算不可行,列出“加班/调设备/挪单”方案及每个方案挤占哪些订单。
- **动作边界**:生成接单评审草稿和客户回复草稿;销售/计划员选择方案,合同变更及客户消息必须由授权人提交。系统记录采用了哪个版本的排程数据。
- **缺口**:需要统一的订单到工单关联查询、库存可用量口径、排程模拟结果的版本快照、交期回复审批与发送入口。
### 02 å¼€å·¥é½å¥—与缺料催交
- **典型问句**:“明天要开的工单,哪些料齐了?”
- **取证**:已批准工单 BOM ç‰ˆæœ¬ã€å·¥å•需求量、WMS åº“存的仓库/库位/批次/冻结/占用、领料记录、采购订单未到数量、供应商交期。
- **计算**:逐物料计算 `净可用 = åˆæ ¼çŽ°å­˜ âˆ’ å·²å ç”¨ âˆ’ å†»ç»“ âˆ’ ä¸å¯ç”¨æ‰¹æ¬¡`,`净缺口 = max(需求 âˆ’ å·²é¢† âˆ’ å‡€å¯ç”¨, 0)`;按相同物料、单位、采购来源合并缺口,同时保留工单分摊明细。不能把“在途”直接计入现存。
- **结果**:工单级齐套/欠料分类、按物料合并的采购催交清单、供应商承诺日与开工时间差、无采购单或交期不明项目。
- **动作边界**:可自动生成采购内部待办并提醒责任人;采购改交期、下单、替料、特采须走原业务审批。每次采购回执/入库后重算并显示变化。
- **缺口**:现有库存与 BOM æŽ¥å£éœ€èšåˆæˆä¸€æ¬¡å¯å¤ç”¨é½å¥—快照;催交待办和供应商回执需要明确系统内承载方式。
### 03 å·¥å•超期巡检
- **典型问句**:“今天有哪些工单跟不上计划?”
- **取证**:工单工序计划节点、报工审批后的实际完成量、设备停机、班次日历、工单状态、责任班组。
- **计算**:排除已完成/取消/改期并重新基线的工单;仅在计划节点到期且实际进度不足时判超期。连续两班无有效报工是单独风险,不能自动等同“没干活”,还要核对报工审批中与离线补报。
- **结果**:超期清单、预计延误量、卡住的工序、连续未进展时长、责任组、下游交付影响。
- **动作边界**:生成班组长待办、截止时间、未回复升级规则;任何班组绩效判断由主管确认。状态由待办回执及新报工自动更新。
- **缺口**:需要统一计划节点与实际进度的时间口径、逾期基线版本,以及与现有通知/任务系统的对接。
### 04 æ’单与排程重算
- **典型问句**:“把这个急单插进去,会影响谁?”
- **取证**:订单优先级、已承诺交期、在制工单、工艺路线与设备效率、设备维护窗口、班组排班、物料齐套情况。
- **计算**:使用现有排程预览能力生成“保持现状”与“插单方案”两份快照,逐工单比较开始/结束时间、设备占用、改期天数和已承诺客户。
- **结果**:受益订单、受损订单、不可用资源、需同意的岗位、排程变化图;无法满足约束时返回原因,不输出“已安排”。
- **动作边界**:计划员确认、必要时销售确认客户交期后,Java ä¸šåŠ¡æœåŠ¡æ‰§è¡Œæ­£å¼æŽ’ç¨‹ã€‚Agent ä¸èƒ½è‡ªè¡Œè¦†ç›–已批准的工单计划。
- **仓库依据**:`MesProTaskController` å·²æœ‰ `auto-schedule/preview` å’Œæ­£å¼æŽ’程入口;跨订单影响比较与审批门槛需补充。
### 05 å‘货与仓储拦截
- **典型问句**:“这单明天能发吗?缺在哪一格?”
- **取证**:销售订单、发货通知、成品库存位置/批次/SN、库存冻结与占用、OQC ç»“论、拣货/装箱与出库状态。
- **计算**:按订单行与批次核算可发数量;列出质量未放行、冻结、已占用、库位不明或数量不足的阻断项。应使用仓储业务服务确认可用量,不复刻库存事务规则。
- **结果**:可发/不可发数量、阻断原因、预计解除时间、库位清单、同批次关联订单。
- **动作边界**:可生成拣货与补证据建议;库存调整、解冻、正式出库、发货通知仍走原系统权限及审批。
- **缺口**:需汇总 ERP é”€å”®è®¢å•与 WMS å‡ºåº“/库存占用/OQC çš„æŸ¥è¯¢è§†å›¾ã€‚
### 06 è´¨é‡å¼‚常与停线复验
- **典型问句**:“这批的缺陷是不是有点多?已经流到哪里?”
- **取证**:IPQC æŒ‡æ ‡å€¼ä¸Žæ£€éªŒæ ‡å‡†ç‰ˆæœ¬ã€ç¼ºé™·è®°å½•、工单批次、已产出量、下道工序流转、库存批次、设备或检验设备日志。
- **计算**:按指标规则判越限,统计连续次数、样本数量、影响批次/工件、已流转范围。区分“规则判越限”“建议隔离”“停线已批准”“实机停线已执行”。
- **结果**:异常单/NCR è‰ç¨¿ã€å½±å“é¢æ¸…单、复验任务、停线申请草稿;全部附原始检验记录与标准版本。
- **动作边界**:越限可自动告警与生成复验待办;隔离/库存冻结需仓储或质量授权;停线和复线分别由有权限岗位签字,Agent ä¸ä¸‹å‘控制指令。
- **缺口**:本仓库有 IPQC、NCR ä¸Žå·¥å•关联,但图片中的检验设备数据采集、停线双签和控制系统回读需要新增集成与流程。
### 07 è®¾å¤‡æ¼æ£€ä¸Žä¿å…»
- **典型问句**:“昨天的点检有没有没做的?哪些会影响关键工序?”
- **取证**:设备台账、点检计划/记录、保养计划/记录、维修单、工单设备分配、班次与设备运行时长(若有可信采集源)。
- **计算**:按设备适用计划、班次窗口和有效提交记录找漏检;排除停用、维护中和计划暂停的设备。按照未来生产占用和工序关键性排序。
- **结果**:漏检设备、最后有效点检时间、预计影响工单、今日到期保养、已存在维修单。
- **动作边界**:生成补检与保养待办,避免重复建单;维修人完成并回写记录后关闭。是否继续使用设备由设备负责人依据现场检查决定。
- **缺口**:运行时长触发需可靠 IoT/设备采集源;仅有手工台账时先按计划周期触发。
### 08 å§”外加工对账
- **典型问句**:“这个月委外的账差在哪几笔?”
- **取证**:外协工单、外协发料/入库、IQC åˆæ ¼é‡ã€ä¾›åº”商、采购或合同约定损耗率与单价、来票与付款状态。
- **计算**:逐单逐物料比对发料、收回、报废/退料、合同允许损耗,计算超耗数量;逐合同版本比较结算价与约定价,列出数量差和价差。单位换算、税率、币种、退补料要明确处理。
- **结果**:差异台账,含单号、依据、数量、金额、责任待确认项;不能把未验收的收货当作合格入库。
- **动作边界**:采购核实并记录供应商答复,财务复核后才生成应付或扣款。Agent å¯ä»¥è·Ÿè¸ªé€¾æœŸæœªæ ¸å®žé¡¹ï¼Œä¸èƒ½è‡ªè¡Œåˆ¤å®šæŸè€—归属。
- **缺口**:MES å·²æœ‰å¤–协发料/入库对象;合同约定损耗、加工价和结算口径是否在当前系统完整可查需进一步盘点。
### 09 å®¢è¯‰æ‰¹æ¬¡è¿½æº¯
- **典型问句**:“客户投诉的这个批号,是哪批料做的?还发给了谁?”
- **取证**:售后工单、ERP é”€å”®è®¢å•、WMS é”€å”®å‡ºåº“/SN/批次、MES å·¥å•领料、来料批次、IQC/IPQC/OQC ä¸Žè´¨æ£€æŠ¥å‘Šç‰ˆæœ¬ã€‚
- **计算**:沿“出货批次→工单产出→领料批次→到货/供应商”反向追溯,再从可疑来料批次正向寻找其它工单、库存和客户。每条边记录来源单据、数量、时间和可信度。
- **结果**:可展开的追溯图、受影响客户/库存/在制清单、证据断点、客户回复草稿。纸质报告或未电子归档资料明确列为断点,不能给出完整追溯结论。
- **动作边界**:客服与质量负责人确认外部回复;是否通知其他客户、召回、冻结库存等由对应负责人决定。
- **仓库依据**:现有批次前向/后向及工序追溯入口可复用;售后与出库的跨域关联、报告附件完整性还需验证。
## å››ã€æ”¯æ’‘上述场景的统一能力
### 4.1 è®¢å•与批次关系图
统一键为业务对象引用 `module + object_type + id + code`,边来自业务外键或回调记录。主线为:
```mermaid
flowchart LR
  CRM[CRM åˆåŒ] --> SO[ERP é”€å”®è®¢å•]
  SO --> MPS[MES MPS]
  MPS --> WO[生产工单]
  WO --> BOM[工单 BOM]
  WO --> FB[工序任务/报工]
  BOM --> ST[库存/占用/领料批次]
  FB --> QC[IPQC/OQC]
  FB --> FG[成品批次/SN]
  FG --> SHIP[出库/发货]
  SHIP --> AFTER[售后/客诉]
  ST --> SUP[采购/供应商/IQC]
```
每条关联分为**直接外键、业务回调、规则推导、人工确认**四种。仅前两种可作为“已确认事实”。业务编号相似、时间接近不能自动认作同一单据。
### 4.2 ä¸»åŠ¨è¿è¡Œä¸ŽæŒç»­è·Ÿè¿›
- å®šæ—¶ä»»åŠ¡ï¼šæ¬¡æ—¥é½å¥—ï¼ˆå»ºè®®æ—©ç­å‰ï¼‰ã€å·¥å•è¶…æœŸï¼ˆæ¯ç­ï¼‰ã€è®¾å¤‡ç‚¹æ£€ï¼ˆè®¡åˆ’çª—å£åŽï¼‰ã€å§”å¤–å¯¹è´¦ï¼ˆæœˆç»“ï¼‰ã€‚å…·ä½“æ—¶é—´å–ä¼ä¸šç­æ¬¡/节假日设置,不硬编码图片里的 07:30、08:00、06:30。
- äº‹ä»¶è§¦å‘:订单交期变更、采购交期更新、报工审批、质检越限、维修状态变化、出库完成、售后工单创建。
- å·¡æ£€ç»“果要去重:`场景 + ä¸šåŠ¡å¯¹è±¡ + åŸºçº¿ç‰ˆæœ¬ + æ—¶é—´çª—口` å½¢æˆäº‹ä»¶é”®ï¼›ä»…关键事实变化时重新通知。待办有负责人、到期时间、回执状态、升级路径和关闭条件。
- å·¥ä½œæµçŠ¶æ€ï¼š`待取证 â†’ è®¡ç®—中 â†’ å¾…人工判断/待审批 â†’ å·²æäº¤ä¸šåŠ¡ç³»ç»Ÿ â†’ ä¸šåŠ¡å¤„ç†ä¸­ â†’ å·²éªŒè¯å…³é—­`,另有 `证据不足`、`过期需重算`、`执行失败`。Agent çš„任务完成不代表业务单据完成。
### 4.3 å›žç­”与操作卡片
对话是入口,卡片是可操作的工作台。每个结果卡至少显示:**结论、统计口径/时间、关键数字、证据单据、做过的三步、建议动作、当前状态、责任人、下一次检查时间**。点击数字可展开明细,点击证据跳原业务页面;“确认发送/审批/改排程”按钮只在有权限且数据未过期时出现。
采用与当前 Vue ç®¡ç†ç«¯ä¸€è‡´çš„æ“ä½œåž‹å¸ƒå±€ï¼šå·¦ä¾§ä»»åŠ¡/场景导航,中间结论与证据,右侧待办与审批状态。移动端优先显示结论、待办和证据摘要,复杂追溯图提供表格替代。所有状态同时用文字和颜色表达,按钮有明确的加载、失败、无权限和数据过期反馈。图片里的样式可作视觉参考,真实界面必须显示真实单号及执行结果。
**场景 02 çš„界面样例(纯示例数据)**:用户问“明天计划开工的 10 å¼ å·¥å•,哪些能开?”卡片先答“6 å¼ é½å¥—,4 å¼ ç¼ºæ–™â€ï¼Œæ˜¾ç¤ºâ€œæ£€æŸ¥äº† 10 ä»½å·¥å• BOM、库存/占用、7 å¼ å…³è”采购单”,再列出每张欠料工单的物料、净缺口、采购单及供应商交期。下一张卡展示“采购催交待办草稿:2 åè´Ÿè´£äººã€æˆªæ­¢ä»Šæ—¥ 16:00”。用户确认后,Java æœåŠ¡è¿”å›žå¾…åŠžç¼–å·ï¼Œç•Œé¢æ‰æ”¹ä¸ºâ€œå·²æ´¾å‘â€ï¼›é‡‡è´­å›žæ‰§æˆ–åº“å­˜å…¥åº“åŽé‡æ–°è®¡ç®—ï¼Œæ—§ç»“è®ºæ˜¾ç¤ºâ€œæ•°æ®å·²å˜åŒ–ï¼Œè¯·æŸ¥çœ‹æ–°ç‰ˆâ€ã€‚æ•°å­—ã€åŠ¨ä½œå’Œè¯æ®å§‹ç»ˆå¯å±•å¼€æ ¸å¯¹ã€‚
### 4.4 äººæœºè¾¹ç•ŒçŸ©é˜µ
| çº§åˆ« | ä¾‹å­ | é»˜è®¤è¡Œä¸º |
| --- | --- | --- |
| L0 è‡ªåŠ¨å–è¯ | æŸ¥å·¥å•、库存、批次、检验、审批状态 | æœ‰æƒé™å³å¯è¿è¡Œå¹¶ç•™ç—• |
| L1 è‡ªåŠ¨è®¡ç®—/提醒 | é½å¥—、逾期、漏检、差异、内部提醒 | è§„则版本化、可撤回提醒、可复算 |
| L2 å¾…办/草稿 | å‚¬äº¤å¾…办、复验待办、回复稿、评审稿 | æŒ‰å²—位授权,回显草稿与业务单号 |
| L3 å®¡æ‰¹åŽå†™å…¥ | æŽ’程变更、库存隔离/解冻、采购单、财务结算 | Java æ ¡éªŒã€BPM å®¡æ‰¹ã€å¹‚等执行、回读验证 |
| L4 çŽ°åœºå®‰å…¨å†³å®š | åœçº¿/复线、设备继续使用、特采/放行、召回 | å¿…须由有权人员决定;Agent åªæä¾›è¯æ®ä¸Žç”³è¯· |
### 4.5 æŠ€æœ¯è½ç‚¹
Python ç‹¬ç«‹æœåŠ¡é‡‡ç”¨â€œåœºæ™¯ç¼–æŽ’å™¨ + é¢†åŸŸå·¥å…·æ³¨å†Œè¡¨ + ç¡®å®šæ€§è®¡ç®—器 + çŸ¥è¯†æ£€ç´¢ + æ¨¡åž‹è¯´æ˜Žå™¨â€ã€‚Java å¢žåŠ é¢å‘æ™ºèƒ½ä½“çš„å†…éƒ¨èšåˆ API,负责现有用户身份、数据范围、业务校验和审批。Python ä¸ç›´æŽ¥æ“ä½œä¸šåŠ¡æ•°æ®åº“ã€‚é¦–æ‰¹å·¥å…·åŒ…æ‹¬ `get_order_context`、`calculate_material_readiness`、`preview_schedule`、`inspect_workorder_progress`、`trace_batch`、`get_quality_context`、`get_equipment_due`、`reconcile_outsource`;每个工具有输入/输出 schema、权限、版本和测试基准。
## äº”、与当前仓库的接入核对
| èƒ½åŠ› | ä»£ç ä¸­å·²çœ‹åˆ°çš„入口 | ä»éœ€å»ºè®¾æˆ–核实 |
| --- | --- | --- |
| ç”Ÿäº§é¢„测 | `MesProAiController` å››ç±»é¢„测 | ä¸Žç¡®å®šæ€§æŽ’程/库存口径融合、结果校准 |
| ç”Ÿäº§æŽ’程 | `MesProTaskController` é¢„览和执行 | å¤šæ–¹æ¡ˆæ¯”较、受影响订单、审批门槛 |
| ä»“储 | ç‰©æ–™åº“存、占用、出库、批次追溯控制器 | è·¨ä»“/批次的净可用聚合和源数据时点 |
| è´¨æ£€ | IQC/IPQC/OQC/RQC、NCR、质检报告 | æ£€éªŒè®¾å¤‡é‡‡é›†ã€åœçº¿/复线签批、报告附件补全 |
| è®¾å¤‡ | ç‚¹æ£€è®¡åˆ’/记录、保养/维修控制器 | è¿è¡Œæ—¶é•¿é‡‡é›†ã€å…³é”®å·¥åºå½±å“æŽ’序 |
| å§”外 | å¤–协发料与入库控制器 | åˆåŒä»·/损耗率、完整月结与归责流程 |
| é”€å”®ä¸Žå”®åŽ | CRM åˆåŒã€ERP é”€å”®è®¢å•、售后工单 | ç»Ÿä¸€è®¢å•到批次链与跨系统客户回复流程 |
| å®¡æ‰¹/通知 | BPM ä¸Žç³»ç»Ÿé€šçŸ¥åŸºç¡€èƒ½åŠ› | Agent ä¸“用待办、提醒回执、审计视图 |
这些“已看到的入口”只证明对象和部分操作存在;上线前仍须检查参数、权限注解、数据范围、单据状态机和真实数据完整度。
## å…­ã€å»ºè®®çš„首批交付组合
推荐以 **01 æŽ¥å•评审 + 02 å¼€å·¥é½å¥— + 03 è¶…期巡检** ä¸ºç¬¬ä¸€æ¡çºµå‘链:销售能回答能否接,计划知道能否开工,车间知道哪里卡住。这条链能复用现有生产预测、库存和排程入口,并为其余场景打下订单关系图、主动巡检、待办回执和证据卡基础。
第二条链做 **06 è´¨é‡å¼‚常 + 09 æ‰¹æ¬¡è¿½æº¯**,重点验证跨批次关联与“证据断点”表达。第三条链做 **07 è®¾å¤‡ + 08 å§”外 + 05 å‘è´§**;04 æŽ’程正式执行待审批链完善后开放。
验收不以演示对话流畅为准,而以业务结果为准:结论能复算、受影响单据不漏不串、权限不穿透、待办真的到人、审批与业务单号能回读、数据变化后能撤销过期结论。每个场景至少准备正常、数据缺失、跨权限、单据变更和动作失败样本。
**待最终选择**:九场景路线与推荐交付顺序是否符合企业优先级;是否把“工单成本归集”和“经营问答”提升为第一批。确认后再将本蓝图收敛为接口清单、数据契约、页面流程和实施任务。
docs/mdm_item_category_unification.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,73 @@
# ç‰©æ–™åˆ†ç±»ç»Ÿä¸€åˆ° mdm_item_category æ–¹æ¡ˆ
## èƒŒæ™¯
基础数据 â†’ ç‰©æ–™ç®¡ç†åœ¨ä¿å­˜ç‰©æ–™å¹¶å‹¾é€‰ã€ŒåŒæ­¥åˆ° MES」时提示「物料分类不存在」。
根因是物料分类存在两套表且未同步:
- ã€ŒåŸºç¡€æ•°æ® â†’ ç‰©æ–™åˆ†ç±»ã€é¡µé¢çš„æ–°å¢ž/修改/删除走 MES æŽ¥å£ `MesMdItemTypeServiceImpl`,只写 `mes_md_item_type`;
- ç‰©æ–™è¡¨å•的分类下拉走 `/mes/md/item-type/simple-list`,读的也是 `mes_md_item_type`;
- ä½† MDM ç‰©æ–™åŒæ­¥åˆ° MES æ—¶ï¼ˆ`MdmItemServiceImpl#syncItemToMes`)按分类 ID æŸ¥ `mdm_item_category`,查不到即抛 `MDM_ITEM_CATEGORY_NOT_EXISTS`。
因此老分类可用(历史数据两表都有),新分类必报错。
## ç›®æ ‡
物料分类统一以 `mdm_item_category` ä¸ºå”¯ä¸€æ•°æ®æºï¼š
- MES åˆ†ç±»æŽ¥å£ `/mes/md/item-type/*` ä¿ç•™ï¼Œå‰ç«¯è·¯å¾„与出入参不变,但底层读写全部改为 `mdm_item_category`;
- MDM ä¸Ž MES çš„分类下拉、物料保存、同步、导入全部基于同一张表;
- `mes_md_item_type` ä¸å†å†™å…¥ï¼Œä»…作为历史备份保留。
## æ”¹åŠ¨èŒƒå›´
### åŽç«¯
| æ¨¡å— | æ–‡ä»¶ | æ”¹åЍ |
|------|------|------|
| mdm | `MdmItemCategoryApi` | å¢žåŠ  create / update / delete / å…¨é‡åˆ—表 RPC æ–¹æ³• |
| mdm | `MdmItemCategoryApiImpl` | å®žçŽ°ä¸Šè¿°æ–¹æ³• |
| mdm | `MdmItemCategorySaveReqDTO`(新增) | RPC å†™å…¥å‚æ•° |
| mdm | `MdmItemCategoryRespDTO` | è¡¥ `remark`、`createTime` |
| mdm | `MdmItemCategoryService(Impl)` | å¢žåŠ å…¨é‡åˆ—è¡¨æ–¹æ³• |
| mes | `MesMdItemTypeServiceImpl` | å¢žåˆ æ”¹æŸ¥ã€å­åˆ†ç±»ã€å¯¼å…¥å…¨éƒ¨æ”¹ä¸ºè°ƒç”¨ MDM åˆ†ç±» API |
### å‰ç«¯
**无需改动。** é¡µé¢è°ƒç”¨çš„仍是对外路径:
- åˆ†ç±»ç®¡ç†é¡µï¼š`/mes/md/item-type/list|get|create|update|delete|import`
- åˆ†ç±»ä¸‹æ‹‰ï¼š`/mes/md/item-type/simple-list`
- MDM ç‰©æ–™é¡µåˆ†ç±»ä¸‹æ‹‰ï¼šåŒæ ·è°ƒç”¨ `/mes/md/item-type/simple-list`
响应字段保持 `id / parentId / code / name / itemOrProduct / sort / status / remark / createTime`。
## æ•°æ®è¿ç§»
上线前执行 `docs/sql/mdm_item_category_migration.sql`,把 `mes_md_item_type` å…¨é‡ï¼ˆå«å·²é€»è¾‘删除记录)按**原 ID** è¿ç§»åˆ° `mdm_item_category`,保证:
- `mes_md_item.item_type_id`、`mdm_item.category_id` ç­‰åŽ†å²å¤–é”®ç»§ç»­æœ‰æ•ˆï¼›
- `item_type` ç”± `item_or_product` æŽ¨å¯¼ï¼š`PRODUCT â†’ 2(产品)`,其余 `→ 1(物料)`;
- è„šæœ¬å¯é‡å¤æ‰§è¡Œï¼ˆæŒ‰ä¸»é”® upsert)。
迁移后新分类由 `mdm_item_category` è‡ªå¢ž ID ç”Ÿæˆï¼Œä¸¤å¥— ID ä½“系自然收敛为一套。
## ä¸šåŠ¡è§„åˆ™
| åœºæ™¯ | è§„则 |
|------|------|
| æ–°å¢žåˆ†ç±» | å†™ `mdm_item_category`,编码全局唯一、同一父级下名称唯一(由 MDM æ ¡éªŒï¼‰ |
| ä¿®æ”¹åˆ†ç±» | æŒ‰åŽŸ ID æ›´æ–° `mdm_item_category` |
| åˆ é™¤åˆ†ç±» | å…ˆæ ¡éªŒ MES ç‰©æ–™å¼•用,再由 MDM æ ¡éªŒå­åˆ†ç±»ä¸Ž MDM ç‰©æ–™å¼•用 |
| ç‰©æ–™ä¿å­˜ + åŒæ­¥ MES | æŒ‰ `categoryId` æŸ¥ `mdm_item_category` å– code,正常同步 |
| ç‰©æ–™å¯¼å…¥ | æŒ‰åˆ†ç±»åç§°åœ¨ `mdm_item_category` ä¸­è§£æž |
| è®¾å¤‡è”动物料 | `EQUIPMENT` åˆ†ç±»è¿ç§»åŽ MDM ä¾§å¯æ­£å¸¸æŒ‰ code è§£æž |
## éªŒè¯
1. æ‰§è¡Œè¿ç§» SQL,核对 `mes_md_item_type` ä¸Ž `mdm_item_category` è®°å½•数、ID ä¸€ä¸€å¯¹åº”。
2. ã€ŒåŸºç¡€æ•°æ® â†’ ç‰©æ–™åˆ†ç±»ã€æ–°å¢žã€Œæµ‹è¯•」分类,确认 `mdm_item_category` å‡ºçŽ°è®°å½•ã€`mes_md_item_type` ä¸æ–°å¢žã€‚
3. ã€ŒåŸºç¡€æ•°æ® â†’ ç‰©æ–™ç®¡ç†ã€ç¼–辑物料,选择「测试」,勾选「同步到 MES」保存,应成功。
4. åˆ†ç±»ä¸‹æ‹‰ã€ç‰©æ–™åˆ—表分类名称展示、分类编辑回显(含备注)正常。
5. åˆ†ç±»å¯¼å…¥ã€ç‰©æ–™æŒ‰åˆ†ç±»åç§°å¯¼å…¥æ­£å¸¸ã€‚
docs/mes_collect_design.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,289 @@
# MES æ³¨å¡‘机数采对接需求与设计
> çŠ¶æ€ï¼šå¾…è¯„å®¡ï¼ˆè¯„å®¡é€šè¿‡åŽå¼€å§‹ç¼–ç ï¼‰
> ç¼–写日期:2026-10-20
> é€‚用数据库:`mom-kyzn`
> æ¶‰åŠæ¨¡å—:`yudao-module-mes`
## 1. èƒŒæ™¯ä¸Žç›®æ ‡
客户注塑机通过弘讯采集网关,将运行状态、周期过程、工艺参数、参数变更和报警以 MQTT æŠ¥æ–‡æŽ¨é€åˆ° EMQX。当前 MES æ²¡æœ‰ä»»ä½• MQTT æŽ¥å…¥ä»£ç ï¼Œéœ€è¦å®Œæˆâ€œè®¢é˜… â†’ è§£æž â†’ è½åº“ â†’ æŸ¥è¯¢/展示”的全链路对接,并把采集数据与 MES è®¾å¤‡å°è´¦ã€ç”Ÿäº§ä»»åŠ¡/工单关联起来。
本期目标(已与用户确认):
1. **全量接入**:realtime(实时状态)、spc(周期过程)、tech(工艺参数)、opLog(参数变更)、报警五类数据全部落库。
2. **设备绑定**:在 `mes_dv_machinery` è®¾å¤‡å°è´¦ä¸Šå¢žåŠ â€œæ•°é‡‡è®¾å¤‡ç¼–å·â€ï¼Œ`devId`(如 `C01`)直接匹配台账。
3. **工单联动**:采集数据按“设备所属工作站上正在运行的生产任务”自动关联 `taskId / workOrderId`,关联不到时留空。
4. **分级保留**:原始报文 7 å¤©ã€å®žæ—¶åŽ†å² 30 å¤©ã€å‘¨æœŸ/工艺/变更/报警长期保留。
不在本期范围:前端页面代码(按项目规则只输出联调方案)、设备反向控制(下行指令)、多实例共享订阅。
## 2. æŽ¥å…¥ä¿¡æ¯
| é¡¹ç›® | å€¼ |
|------|----|
| EMQX åœ°å€ | `36.213.90.123:1883`(MQTT),`18083`(Dashboard/HTTP API) |
| è´¦å· | `admin / public`(Dashboard ä¸Ž MQTT å…±ç”¨ï¼‰ |
| åè®® | MQTT 3.1.1,QoS 0 |
| ä¸»é¢˜å‰ç¼€ | `/YLCY/IMM`(可配置) |
| è®¾å¤‡ç¼–号 | `C01` ~ `C08`(`devId`) |
| è®¢é˜…通配 | `{prefix}/+/+`,即 `/YLCY/IMM/+/+` |
| å®¢æˆ·ç«¯ ID ç¤ºä¾‹ | `/YLCY/IMM/C03/id` |
主题约定(来自采集文档):
| æ•°æ®ç±»åž‹ | ä¸»é¢˜ | è¯´æ˜Ž |
|----------|------|------|
| realtime | `{prefix}/{devId}/realtime` | å®žæ—¶è¿è¡ŒçŠ¶æ€ã€æ¸©åº¦ã€æ²¹æ¸© |
| spc | `{prefix}/{devId}/spc` | æ¯ä¸ªæ³¨å¡‘周期的过程数据 |
| tech | `{prefix}/{devId}/tech` | å·¥è‰ºè®¾å®šå‚数全量快照 |
| opLog | `{prefix}/{devId}/opLog` | å•个参数修改前后值 |
| æŠ¥è­¦ | æ–‡æ¡£æœªç»™å‡ºä¸»é¢˜å | é€šè¿‡æŠ¥æ–‡ `topic` å­—段或 `Data.wmId` è¯†åˆ«ï¼›è®¢é˜…通配兜底 |
> è¯´æ˜Žï¼šæŠ¥è­¦ä¸»é¢˜ååœ¨æ–‡æ¡£æˆªå›¾ä¸­ç¼ºå¤±ï¼Œå› æŠ¥æ–‡è‡ªå¸¦ `topic` å­—段且 `Data` ä¸­ `wmId/wmMsg/wmTime` ç‰¹å¾å”¯ä¸€ï¼Œä»£ç æŒ‰æŠ¥æ–‡ç‰¹å¾è¯†åˆ«ï¼Œä¸»é¢˜åŽç¼€ä»…作为配置项兜底,不阻塞接入。
## 3. æŠ¥æ–‡è§„范
### 3.1 å…¬å…±ä¿¡å°
```json
{
  "clientId": "/YLCY/IMM/C03/id",
  "devId": "C03",
  "topic": "realtime",
  "sendTime": "2026-09-24 11:37:00",
  "sendStamp": 1790221000000,
  "time": "2026-09-24 11:37:00",
  "timestamp": 1790221000000,
  "Data": { }
}
```
- æ•°æ®æ—¶é—´ä¼˜å…ˆå– `time`(`yyyy-MM-dd HH:mm:ss`),缺失时回退 `timestamp`。
- `Data` å¯èƒ½ä¸ºå¯¹è±¡ï¼›è‹¥ä¸ºå­—符串,二次解析为对象。
- æ•°æ®ç±»åž‹ä¼˜å…ˆå– `topic` å­—段,其次按主题后缀,最后按 `Data` ç‰¹å¾è¯†åˆ«ã€‚
### 3.2 realtime(实时状态)
`Data` ç¤ºä¾‹å­—段:
| ç¼–码 | å«ä¹‰ | å¤‡æ³¨ |
|------|------|------|
| `STS` | çŠ¶æ€ | 1 å¾…机,2 ç”Ÿäº§ |
| `OPM` | è¿è¡Œæ¨¡å¼ | 0 æ‰‹åŠ¨ï¼Œ1 åŠè‡ªåŠ¨ï¼Œ2 ç”µçœ¼è‡ªåŠ¨ï¼Œ3 æ—¶é—´è‡ªåŠ¨ï¼Œ4 è°ƒæ¨¡ä½¿ç”¨ |
| `OT` | æ²¹æ¸© | |
| `T1..Tn` | å„段温度 | æ®µæ•°æŒ‰å®žé™…为准,动态解析 |
| `ASTS` | é™„加状态 | åŽŸæ ·ä¿å­˜ |
### 3.3 spc(周期过程数据)
| ç¼–码 | å«ä¹‰ |
|------|------|
| `CYCN` | å‘¨æœŸåºå·ï¼ˆéƒ¨åˆ†ç³»åˆ—独立计数,增量一致) |
| `ECYCT` | å‘¨æœŸæ—¶é—´ |
| `EISS` | å°„出起点 |
| `EIVM` | æœ€å¤§å°„速 |
| `EIPM` | æœ€å¤§å°„压 |
| `ESIPT` | è½¬ä¿åŽ‹æ—¶é—´ |
| `ESIPP` | è½¬ä¿åŽ‹åŽ‹åŠ› |
| `ESIPS` | è½¬ä¿åŽ‹ä½ç½® |
| `EIPT` | å°„出时间 |
| `EIPSE` | å°„出终点位置 |
| `EPLST` | å‚¨æ–™æ—¶é—´ |
| `EPLSSE` | å‚¨æ–™ç»ˆç‚¹ |
| `EPLSPM` | æœ€å¤§å‚¨æ–™åŽ‹åŠ› |
| `ET1..ETn` | ç”Ÿäº§æ¸©åº¦ï¼ˆå„段) |
### 3.4 tech(工艺参数)
按文档第 3 èŠ‚å…¨é‡å­—æ®µä¿å­˜ï¼ˆTS/IP/IV/IS/IT/ITUSE/ISUSE/SIPT/SIPS/SIPP/PP/PV/PT/SBS/SBT/PLLT/PLV/PLP/PLBP/PLS/SBV/SBP/SBS/SBT/CT/MCV/MCP/MCS/MOV/MOP/MOS/EFDT/EFV/EFP/EFS/EBDT/EBV/EBP/EBS/CP1M~CP4M/CPI1S~CPI4S/CPI1P~CPI4P/CPI1V~CPI4V/CPI1T~CPI4T/CPO1S~CPO4S/CPO1P~CPO4P/CPO1V~CPO4V/CPO1T~CPO4T/CFP/CFV/CFS/CFT/CBP/CBV/CBS/CBT/CBDT)。字段数量多且可能扩展,**不建宽表**,整包 JSON å¿«ç…§ä¿å­˜ï¼Œå‰ç«¯æŒ‰ JSON æ¸²æŸ“。
### 3.5 opLog(参数变更)
```json
{
  "devId": "5",
  "time": "2022-12-09 12:09:48",
  "Data": {
    "varId": "SIPS",
    "value": 37.5,
    "lastValue": 37,
    "modifyTime": "2022-12-09 12:09:46"
  }
}
```
### 3.6 æŠ¥è­¦
```json
{
  "devId": "6",
  "Data": {
    "wmId": 2,
    "wmMsg": "安全门未关",
    "wmTime": "2022-12-09 16:42:22"
  }
}
```
- `wmId = 2`:报警产生 â†’ æ–°å¢žä¸€æ¡â€œæœªæ¶ˆé™¤â€è®°å½•。
- `wmId = 0`:报警消失 â†’ æŠŠè¯¥è®¾å¤‡æ‰€æœ‰â€œæœªæ¶ˆé™¤â€è®°å½•置为“已消除”,写入 `clearTime`。
## 4. æ•°æ®æ¨¡åž‹
> æ‰€æœ‰æ–°è¡¨æ²¿ç”¨é¡¹ç›® `BaseDO` å­—段:`creator/create_time/updater/update_time/deleted`。高频日志表按保留策略物理清理。
### 4.1 è®¾å¤‡å°è´¦æ‰©å±•
```sql
ALTER TABLE mes_dv_machinery
  ADD COLUMN collect_device_code VARCHAR(64) NULL COMMENT '数采设备编号(MQTT devId,如 C01)' AFTER workstation_id,
  ADD COLUMN collect_enabled BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否启用数采' AFTER collect_device_code;
ALTER TABLE mes_dv_machinery
  ADD UNIQUE INDEX uk_collect_device_code (collect_device_code);
```
- `collect_device_code` ä¸ºç©ºè¡¨ç¤ºæœªç»‘定数采。
- å”¯ä¸€ç´¢å¼•允许多个 `NULL`,不会影响存量设备。
- å…³è”任务时使用台账已有的 `workstation_id`。
### 4.2 æ–°å»ºè¡¨
| è¡¨å | ç”¨é€” | ä¿ç•™ |
|------|------|------|
| `mes_collect_realtime` | æ¯è®¾å¤‡æœ€æ–°å®žæ—¶çŠ¶æ€ï¼ˆUPSERT) | å¸¸é©» |
| `mes_collect_realtime_history` | å®žæ—¶çŠ¶æ€åŽ†å² | 30 å¤© |
| `mes_collect_cycle` | å‘¨æœŸè¿‡ç¨‹æ•°æ® | é•¿æœŸ |
| `mes_collect_tech` | å·¥è‰ºå‚数最新快照(UPSERT) | å¸¸é©» |
| `mes_collect_operate_log` | å‚数变更日志 | é•¿æœŸ |
| `mes_collect_alarm` | æŠ¥è­¦è®°å½• | é•¿æœŸ |
| `mes_collect_message` | åŽŸå§‹æŠ¥æ–‡ï¼ˆæŽ’éšœã€è¡¥æ•°ä¾æ®ï¼‰ | 7 å¤© |
关键字段:
- `mes_collect_realtime`:`device_code`(唯一)、`machinery_id`、`task_id`、`work_order_id`、`sts`、`opm`、`asts`、`ot`、`temps` JSON、`data_json`、`collect_time`。
- `mes_collect_realtime_history`:字段同上,唯一键 `(device_code, collect_time)`,索引 `(device_code, collect_time)`、`(work_order_id, collect_time)`。
- `mes_collect_cycle`:`device_code`、`cycle_no`、`collect_time`、`ecyct/eiss/eivm/eipm/esipt/esipp/esips/eipt/eipse/eplst/eplsse/eplspm`、`et_temps` JSON、`data_json`;唯一键 `(device_code, cycle_no, collect_time)`。
- `mes_collect_tech`:`device_code`(唯一)、`task_id`、`work_order_id`、`collect_time`、`data_json`。
- `mes_collect_operate_log`:`device_code`、`var_id`、`old_value`、`new_value`、`modify_time`、`collect_time`、`data_json`;索引 `(device_code, modify_time)`。
- `mes_collect_alarm`:`device_code`、`wm_id`、`wm_msg`、`wm_time`、`status`(0 æœªæ¶ˆé™¤/1 å·²æ¶ˆé™¤ï¼‰ã€`clear_time`、`data_json`;索引 `(device_code, status)`、`(wm_time)`。
- `mes_collect_message`:`device_code`、`machinery_id`、`mqtt_topic`、`msg_type`、`event_time`、`raw_payload` LONGTEXT、`process_status`(0 æˆåŠŸ/1 å¤±è´¥ï¼‰ã€`error_msg`、`receive_time`。
### 4.3 è½åº“规则
- `realtime`:更新 `mes_collect_realtime` å¿«ç…§ + å†™ `mes_collect_realtime_history`(唯一键去重)。
- `spc`:写 `mes_collect_cycle`(唯一键去重,`INSERT IGNORE`)。
- `tech`:整包 JSON UPSERT `mes_collect_tech`;不重复生成变更日志,变更以设备上报的 `opLog` ä¸ºå‡†ã€‚
- `opLog`:逐条写 `mes_collect_operate_log`。
- æŠ¥è­¦ï¼šæŒ‰ `wmId` è§„则新增/清除。
- æ‰€æœ‰æŠ¥æ–‡å…ˆå†™ `mes_collect_message`,解析失败也保留原文并记录 `error_msg`。
- æœªç»‘定台账的 `devId`:结构化数据照常入库(`machinery_id/task_id/work_order_id` ä¸ºç©ºï¼‰ï¼ŒåŽŸå§‹æŠ¥æ–‡ä¿ç•™ï¼Œå¹¶ä»¥é™æµæ—¥å¿—æç¤ºè¡¥ç»‘å°è´¦ï¼Œé¿å…è®¾å¤‡å·²ä¸Šçº¿ä½†å°è´¦æœªç»´æŠ¤æ—¶ä¸¢æ•°æ®ã€‚
## 5. å·¥å•自动关联
```
devId â†’ mes_dv_machinery.collect_device_code â†’ machinery.workstation_id
      â†’ mes_pro_task(workstation_id åŒ¹é…ã€deleted=0、status IN (1 ç”Ÿäº§ä¸­, 2 éƒ¨åˆ†å®Œæˆ, 9 å·²åœå·¥))
      â†’ å– last_feedback_time / id æœ€æ–°çš„一条 â†’ task_id + work_order_id
```
- ç»“果缓存 30 ç§’(按 `workstationId`),避免每条报文查库。
- å…³è”不到时 `task_id / work_order_id` ä¸ºç©ºï¼Œä¸å½±å“è½åº“。
- ä»»åŠ¡åˆ‡æ¢åŽæœ€å¤š 30 ç§’延迟生效。
## 6. åŽç«¯å®žçŽ°è®¾è®¡
### 6.1 ä»£ç ä½ç½®
```
yudao-module-mes/
├── controller/admin/collect/                  # æŸ¥è¯¢æŽ¥å£ + VO
├── service/collect/                           # æŸ¥è¯¢ã€è§£æžã€è½åº“、关联
│   â”œâ”€â”€ MesCollectQueryService
│   â”œâ”€â”€ MesCollectDataService
│   â”œâ”€â”€ MesCollectMessageDispatcher
│   â”œâ”€â”€ MesCollectDataParser
│   â”œâ”€â”€ MesCollectTaskResolver
│   â””── MesCollectCleanupJob(@Scheduled)
├── framework/collect/mqtt/MesCollectMqttClient # Paho è¿žæŽ¥ã€è®¢é˜…、重连
├── dal/dataobject/collect/                    # DO
└── dal/mysql/collect/                         # Mapper
```
- MQTT å®¢æˆ·ç«¯ä½¿ç”¨é¡¹ç›®ä¾èµ–管理里已有的 `org.eclipse.paho.client.mqttv3:1.2.5`,在 `yudao-module-mes/pom.xml` å¼•入即可。
- Paho å›žè°ƒåªåšè§£æžå…¥é˜Ÿï¼Œè½åº“放到单线程有界队列执行,避免阻塞心跳。
- å¼€å¯ `automaticReconnect`,客户端 ID ä½¿ç”¨ `{prefix}-{hostname}-{random}`;**生产单实例部署**,多实例会重复消费(后续可改共享订阅)。
- é€šè¿‡ `yudao.mes.collect.mqtt.enabled` æŽ§åˆ¶æ˜¯å¦è¿žæŽ¥ï¼Œé»˜è®¤å…³é—­ï¼ŒæŒ‰çŽ¯å¢ƒå¼€å¯ã€‚
### 6.2 é…ç½®é¡¹ï¼ˆ`application-kyzn.yaml`,并同步 `local/test`)
```yaml
yudao:
  mes:
    collect:
      mqtt:
        enabled: true
        url: tcp://36.213.90.123:1883
        username: admin
        password: public
        client-id-prefix: mes-collect
        topic-prefix: /YLCY/IMM
        qos: 0
        keep-alive: 65
        connection-timeout: 10
        alarm-topic-suffix: wm
      retention:
        raw-days: 7
        realtime-days: 30
```
### 6.3 æ¸…理任务
- æ¯æ—¥ 03:30 æ‰§è¡Œã€‚
- ç‰©ç†åˆ é™¤ `mes_collect_message` ä¸­ `receive_time < now - 7 å¤©`、`mes_collect_realtime_history` ä¸­ `collect_time < now - 30 å¤©` çš„æ•°æ®ã€‚
- å•次删除限制批量,避免长事务锁表。
## 7. åŽç«¯æŽ¥å£
统一前缀 `/mes/collect`,全部返回项目标准 `CommonResult`。
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž | æƒé™ |
|------|------|------|------|
| GET | `/device/list` | è®¾å¤‡æ•°é‡‡çŠ¶æ€åˆ—è¡¨ï¼ˆå°è´¦ + æœ€æ–°çŠ¶æ€ + å½“前任务) | `mes:collect:query` |
| GET | `/realtime/get` | æŒ‡å®šè®¾å¤‡æœ€æ–°å®žæ—¶çŠ¶æ€ | `mes:collect:query` |
| GET | `/realtime/history-page` | å®žæ—¶çŠ¶æ€åŽ†å²åˆ†é¡µ | `mes:collect:query` |
| GET | `/cycle/page` | å‘¨æœŸè¿‡ç¨‹æ•°æ®åˆ†é¡µ | `mes:collect:query` |
| GET | `/cycle/get` | å‘¨æœŸæ˜Žç»† | `mes:collect:query` |
| GET | `/tech/get` | æŒ‡å®šè®¾å¤‡æœ€æ–°å·¥è‰ºå‚æ•°å¿«ç…§ | `mes:collect:query` |
| GET | `/operate-log/page` | å‚数变更分页 | `mes:collect:query` |
| GET | `/alarm/page` | æŠ¥è­¦åˆ†é¡µ | `mes:collect:query` |
| PUT | `/alarm/ack` | æ‰‹å·¥ç¡®è®¤æŠ¥è­¦ | `mes:collect-alarm:ack` |
设备绑定复用现有设备台账新增/更新接口,新增字段 `collectDeviceCode`、`collectEnabled`;台账分页支持按 `collectDeviceCode` è¿‡æ»¤ã€‚
## 8. æƒé™ä¸Žèœå•
- æ–°å¢žæƒé™ç ï¼š`mes:collect:query`、`mes:collect-alarm:ack`。
- èœå•:设备管理(5300)下新增“数采监控”(`collect/monitor`)。
- åŒæ­¥å†™å…¥ `docs/sql/config_export_all_*.sql` ä¸Žæ•°æ®åº“初始化脚本,避免新库缺菜单/权限。
- æŒ‰â€œæƒé™ç ä¸‰æ–¹å¯¹é½â€è§„则,菜单、角色授权、后端注解三处同时落地。
## 9. éƒ¨ç½²ä¸ŽéªŒè¯
1. æ‰§è¡Œ `docs/sql/mes_collect_20261020.sql`(本地 `mom-kyzn` å…ˆæ‰§è¡ŒéªŒè¯ï¼›ç”Ÿäº§ç”±è¿ç»´æ‰§è¡Œï¼‰ã€‚
2. åœ¨è®¾å¤‡å°è´¦å¡«å†™ `collect_device_code` å¹¶å¯ç”¨æ•°é‡‡ã€‚
3. å¯åŠ¨åŽç«¯ï¼Œç¡®è®¤æ—¥å¿—å‡ºçŽ° MQTT è¿žæŽ¥æˆåŠŸã€è®¢é˜…æˆåŠŸã€‚
4. è§‚察 `mes_collect_message` æ˜¯å¦æœ‰å„类型报文入库。
5. æ ¡éªŒ `mes_collect_realtime / cycle / tech / operate_log / alarm` æ•°æ®ä¸Ž EMQX æŠ¥æ–‡ä¸€è‡´ã€‚
6. è§¦å‘一次参数修改与报警,确认变更日志和报警状态流转正确。
7. éªŒè¯å·¥å•关联:工作站存在生产中任务时,新数据的 `task_id/work_order_id` ä¸ä¸ºç©ºã€‚
## 10. é£Žé™©ä¸Žè¯´æ˜Ž
- **报警主题名缺失**:按报文特征识别,已兜底;如后续确认主题名只需改配置。
- **CYCN é‡ç½®**:周期序号可能随设备重启重置,去重键包含采集时间,避免误合并。
- **多实例部署**:当前为单实例消费,多实例会产生重复数据;后续可切换 EMQX å…±äº«è®¢é˜…。
- **时钟偏差**:设备时间与服务器时间可能不一致,事件时间以报文 `time` ä¸ºå‡†ï¼Œå¦å­˜ `receive_time`。
- **数据量**:按 8 å°è®¾å¤‡ã€30 ç§’一帧估算,实时历史约 70 ä¸‡è¡Œ/月;如后续提高频率,需评估分区或改时序库。
- **不修改前端代码**:按项目规则另行输出前端联调方案。
docs/mes_collect_frontend_integration.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,179 @@
# æ³¨å¡‘机数采监控 - å‰ç«¯è”调方案
## æ¶‰åŠé¡µé¢
- è®¾å¤‡ç®¡ç† - è®¾å¤‡å°è´¦ï¼ˆæ–°å¢žæ•°é‡‡å­—段、导入模板)
- è®¾å¤‡ç®¡ç† - æ•°é‡‡ç›‘控(新增)
- è®¾å¤‡ç®¡ç† - æ•°é‡‡ç›‘控 - è®¾å¤‡è¯¦æƒ…/历史(新增)
- è®¾å¤‡ç®¡ç† - æ•°é‡‡æŠ¥è­¦ï¼ˆæ–°å¢žï¼Œæˆ–作为数采监控页签)
## ä¸šåŠ¡æµç¨‹ä¸Žæ•°æ®å¸¦å…¥
1. è®¾å¤‡å°è´¦ç»´æŠ¤ `collectDeviceCode`(如 `C01`)并启用 `collectEnabled` åŽï¼Œæ•°é‡‡æŠ¥æ–‡æ‰èƒ½è‡ªåŠ¨å…³è”åˆ°è¯¥è®¾å¤‡ã€‚
2. æ•°é‡‡ç›‘控列表以设备台账为基准展示,`collectDeviceCode` æ˜¯åˆ—表与各明细接口之间的唯一关联键,选中设备后需把该值作为 `deviceCode` å¸¦å…¥å®žæ—¶çŠ¶æ€ã€å·¥è‰ºå‚æ•°ã€å‘¨æœŸæ•°æ®ã€å‚æ•°å˜æ›´ã€æŠ¥è­¦æŸ¥è¯¢ã€‚
3. åˆ—表中的 `taskId / workOrderId` ç”±åŽç«¯æŒ‰è®¾å¤‡æ‰€åœ¨å·¥ä½œç«™è‡ªåŠ¨è§£æžå½“å‰è¿è¡Œä»»åŠ¡ï¼Œå‰ç«¯åªè¯»å±•ç¤ºï¼Œä¸éœ€è¦ä¼ å‚ã€‚
4. ä»Žåˆ—表进入报警时,建议默认带入当前 `deviceCode` ä¸Ž `status=0`(未消除),保证用户先看到当前设备的待处理报警。
## API
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| GET | `/mes/collect/device/list` | æ•°é‡‡è®¾å¤‡çŠ¶æ€åˆ—è¡¨ |
| GET | `/mes/collect/realtime/get` | è®¾å¤‡æœ€æ–°å®žæ—¶çŠ¶æ€ |
| GET | `/mes/collect/realtime/history-page` | å®žæ—¶çŠ¶æ€åŽ†å²åˆ†é¡µ |
| GET | `/mes/collect/cycle/page` | å‘¨æœŸè¿‡ç¨‹æ•°æ®åˆ†é¡µ |
| GET | `/mes/collect/cycle/get` | å‘¨æœŸè¿‡ç¨‹æ•°æ®æ˜Žç»† |
| GET | `/mes/collect/tech/get` | è®¾å¤‡æœ€æ–°å·¥è‰ºå‚æ•°å¿«ç…§ |
| GET | `/mes/collect/operate-log/page` | å‚数变更日志分页 |
| GET | `/mes/collect/alarm/page` | æŠ¥è­¦è®°å½•分页 |
| PUT | `/mes/collect/alarm/ack` | äººå·¥ç¡®è®¤æ¶ˆé™¤æŠ¥è­¦ |
### è®¾å¤‡å°è´¦å­—段新增
新增/更新、分页查询、Excel å¯¼å…¥å¯¼å‡ºå‡éœ€æ”¯æŒä»¥ä¸‹å­—段:
| å­—段 | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|------|------|------|------|
| collectDeviceCode | String | å¦ | æ•°é‡‡è®¾å¤‡ç¼–号(MQTT devId),全局唯一,如 `C01` |
| collectEnabled | Boolean | å¦ | æ˜¯å¦å¯ç”¨æ•°é‡‡ï¼Œé»˜è®¤å¦ |
- è®¾å¤‡å°è´¦åˆ†é¡µæŸ¥è¯¢æ”¯æŒæŒ‰ `collectDeviceCode`、`collectEnabled` è¿‡æ»¤ã€‚
- Excel å¯¼å…¥æ¨¡æ¿æ–°å¢žâ€œæ•°é‡‡è®¾å¤‡ç¼–号”列;同一次导入内重复的编号会整行失败并返回具体行号与编号。
### è®¾å¤‡çŠ¶æ€åˆ—è¡¨
`GET /mes/collect/device/list`
无请求参数,返回已配置 `collectDeviceCode` çš„全部设备。
响应字段:
| å­—段 | ç±»åž‹ | è¯´æ˜Ž |
|------|------|------|
| machineryId | Long | è®¾å¤‡å°è´¦ç¼–号 |
| machineryCode | String | è®¾å¤‡ç¼–码 |
| machineryName | String | è®¾å¤‡åç§° |
| workstationId | Long | å·¥ä½œç«™ç¼–号 |
| workstationName | String | å·¥ä½œç«™åç§° |
| collectDeviceCode | String | æ•°é‡‡è®¾å¤‡ç¼–号 |
| collectEnabled | Boolean | æ˜¯å¦å¯ç”¨æ•°é‡‡ |
| lastCollectTime | DateTime | æœ€è¿‘采集时间 |
| online | Boolean | æ˜¯å¦åœ¨çº¿ï¼ˆ5 åˆ†é’Ÿå†…有数据) |
| sts / stsName | Integer / String | è¿è¡ŒçŠ¶æ€ï¼š1 å¾…机、2 ç”Ÿäº§ |
| opm / opmName | Integer / String | è¿è¡Œæ¨¡å¼ï¼š0 æ‰‹åŠ¨ã€1 åŠè‡ªåŠ¨ã€2 ç”µçœ¼è‡ªåŠ¨ã€3 æ—¶é—´è‡ªåŠ¨ã€4 è°ƒæ¨¡ä½¿ç”¨ |
| ot | Decimal | æ²¹æ¸© |
| temps | String | å„段温度 JSON å­—符串 |
| taskId / taskCode / taskName | Long / String / String | å½“前生产任务 |
| workOrderId / workOrderCode | Long / String | å½“前生产工单 |
| activeAlarmCount | Long | æœªæ¶ˆé™¤æŠ¥è­¦æ•°é‡ |
### å®žæ—¶çŠ¶æ€
`GET /mes/collect/realtime/get`,参数 `deviceCode`(必填)。
响应字段:`id`、`machineryId`、`deviceCode`、`taskId`、`workOrderId`、`collectTime`、`sts/stsName`、`opm/opmName`、`asts`、`ot`、`temps`、`dataJson`。设备暂无数据时 `data` ä¸º `null`。
`GET /mes/collect/realtime/history-page`
| å‚æ•° | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|------|------|------|------|
| pageNo / pageSize | Integer | æ˜¯ | åˆ†é¡µå‚æ•° |
| deviceCode | String | å¦ | æ•°é‡‡è®¾å¤‡ç¼–号 |
| machineryId | Long | å¦ | è®¾å¤‡å°è´¦ç¼–号 |
| taskId | Long | å¦ | ç”Ÿäº§ä»»åŠ¡ç¼–å· |
| workOrderId | Long | å¦ | ç”Ÿäº§å·¥å•编号 |
| collectTime | DateTime[] | å¦ | é‡‡é›†æ—¶é—´èŒƒå›´ï¼Œä¼ å¼€å§‹ã€ç»“束两个值 |
响应结构同实时状态,按采集时间倒序。
### å‘¨æœŸè¿‡ç¨‹æ•°æ®
`GET /mes/collect/cycle/page`
| å‚æ•° | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|------|------|------|------|
| pageNo / pageSize | Integer | æ˜¯ | åˆ†é¡µå‚æ•° |
| deviceCode | String | å¦ | æ•°é‡‡è®¾å¤‡ç¼–号 |
| machineryId | Long | å¦ | è®¾å¤‡å°è´¦ç¼–号 |
| taskId | Long | å¦ | ç”Ÿäº§ä»»åŠ¡ç¼–å· |
| workOrderId | Long | å¦ | ç”Ÿäº§å·¥å•编号 |
| collectTime | DateTime[] | å¦ | é‡‡é›†æ—¶é—´èŒƒå›´ |
响应字段:`id`、`machineryId`、`deviceCode`、`taskId`、`workOrderId`、`cycleNo`、`collectTime`、`ecyct`、`eiss`、`eivm`、`eipm`、`esipt`、`esipp`、`esips`、`eipt`、`eipse`、`eplst`、`eplsse`、`eplspm`、`etTemps`(JSON å­—符串,键为 ET1..ETn)、`dataJson`。
`GET /mes/collect/cycle/get`,参数 `id`(必填),返回单条明细,字段同上。
### å·¥è‰ºå‚æ•°å¿«ç…§
`GET /mes/collect/tech/get`,参数 `deviceCode`(必填)。
响应字段:`id`、`machineryId`、`deviceCode`、`taskId`、`workOrderId`、`collectTime`、`dataJson`。`dataJson` ä¸ºè®¾å¤‡ä¸ŠæŠ¥çš„完整工艺参数键值对,约 150 ä¸ªå˜é‡ï¼›æ— æ•°æ®æ—¶ `data` ä¸º `null`。
### å‚数变更日志
`GET /mes/collect/operate-log/page`
| å‚æ•° | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|------|------|------|------|
| pageNo / pageSize | Integer | æ˜¯ | åˆ†é¡µå‚æ•° |
| deviceCode | String | å¦ | æ•°é‡‡è®¾å¤‡ç¼–号 |
| machineryId | Long | å¦ | è®¾å¤‡å°è´¦ç¼–号 |
| workOrderId | Long | å¦ | ç”Ÿäº§å·¥å•编号 |
| varId | String | å¦ | å˜é‡ç¼–码,如 `SIPS` |
| modifyTime | DateTime[] | å¦ | å‚数修改时间范围 |
响应字段:`id`、`machineryId`、`deviceCode`、`taskId`、`workOrderId`、`varId`、`oldValue`、`newValue`、`modifyTime`、`collectTime`、`dataJson`。
### æŠ¥è­¦è®°å½•
`GET /mes/collect/alarm/page`
| å‚æ•° | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|------|------|------|------|
| pageNo / pageSize | Integer | æ˜¯ | åˆ†é¡µå‚æ•° |
| deviceCode | String | å¦ | æ•°é‡‡è®¾å¤‡ç¼–号 |
| machineryId | Long | å¦ | è®¾å¤‡å°è´¦ç¼–号 |
| workOrderId | Long | å¦ | ç”Ÿäº§å·¥å•编号 |
| status | Integer | å¦ | 0 æœªæ¶ˆé™¤ã€1 å·²æ¶ˆé™¤ |
| wmTime | DateTime[] | å¦ | æŠ¥è­¦æ—¶é—´èŒƒå›´ |
响应字段:`id`、`machineryId`、`deviceCode`、`taskId`、`workOrderId`、`wmId`(2 äº§ç”Ÿã€0 æ¶ˆå¤±ï¼‰ã€`wmMsg`、`wmTime`、`status/statusName`、`clearTime`、`collectTime`、`dataJson`。
`PUT /mes/collect/alarm/ack`,参数 `id`(必填),无请求体,成功返回 `true`。
## å­—段展示规则
| å­—段 | å±•示位置 | è¯´æ˜Ž |
|------|----------|------|
| collectDeviceCode | å°è´¦åˆ—表、台账详情、数采监控列表 | å°è´¦åˆ—表可直接展示,便于现场核对 |
| collectEnabled | å°è´¦åˆ—表、台账表单 | æœªå¯ç”¨çš„设备仍展示在监控列表,便于发现配置错误 |
| online | ç›‘控列表 | åœ¨çº¿ä¸ºç»¿è‰²æ ‡è¯†ï¼Œç¦»çº¿ä¸ºç°è‰²ï¼›ä¾æ® 5 åˆ†é’Ÿé‡‡é›†çª—口 |
| sts/stsName | ç›‘控列表、设备详情 | ç”Ÿäº§/待机两态 |
| opm/opmName | ç›‘控列表、设备详情 | äº”态枚举,直接展示中文名称 |
| ot、temps | ç›‘控列表、设备详情 | `temps` ä¸º JSON å­—符串,前端解析后按 T1..Tn å±•示 |
| taskCode/taskName、workOrderCode | ç›‘控列表、设备详情 | åªè¯»ï¼›æ— è¿è¡Œä»»åŠ¡æ—¶æ˜¾ç¤ºä¸ºç©º |
| activeAlarmCount | ç›‘控列表 | å¤§äºŽ 0 æ—¶é«˜äº®ï¼Œå¯ç‚¹å‡»è·³è½¬æŠ¥è­¦é¡µç­¾å¹¶å¸¦å…¥è®¾å¤‡ |
| status/statusName | æŠ¥è­¦åˆ—表 | æœªæ¶ˆé™¤/已消除 |
| dataJson | å„详情抽屉 | ä½œä¸ºâ€œåŽŸå§‹æ•°æ®â€æŠ˜å å±•ç¤ºï¼Œä¾¿äºŽæŽ’éšœ |
## ä¸šåŠ¡è§„åˆ™è¯´æ˜Ž
| åœºæ™¯ | è§„则 |
|------|------|
| è®¾å¤‡æœªé…ç½®æ•°é‡‡ç¼–号 | ä¸å‡ºçŽ°åœ¨ `/device/list`,但后端仍会接收并保存其报文,补绑台账后新数据自动关联 |
| è®¾å¤‡ç¦»çº¿åˆ¤å®š | æœ€è¿‘采集时间早于当前时间 5 åˆ†é’Ÿå³ç¦»çº¿ï¼Œä¸Žè®¾å¤‡å¯åœæ— å…³ |
| å·¥å•带入 | åŽç«¯æŒ‰è®¾å¤‡å·¥ä½œç«™è‡ªåŠ¨åŒ¹é…ç”Ÿäº§ä¸­/部分完成/已停工任务,任务切换最长 30 ç§’生效 |
| æŠ¥è­¦äº§ç”Ÿ | è®¾å¤‡ä¸ŠæŠ¥ `wmId=2` æ–°å¢žä¸€æ¡æœªæ¶ˆé™¤è®°å½•,同一报警重复上报不重复生成 |
| æŠ¥è­¦æ¶ˆå¤± | è®¾å¤‡ä¸ŠæŠ¥ `wmId=0` è‡ªåŠ¨æŠŠè¯¥è®¾å¤‡å…¨éƒ¨æœªæ¶ˆé™¤è®°å½•ç½®ä¸ºå·²æ¶ˆé™¤å¹¶å†™æ¶ˆé™¤æ—¶é—´ |
| äººå·¥ç¡®è®¤ | ä»…未消除记录可确认;已消除记录再次确认会提示“该报警已消除,无需重复确认” |
| å‘¨æœŸæ•°æ®åŽ»é‡ | åŒä¸€è®¾å¤‡ã€åŒä¸€å‘¨æœŸåºå·ã€åŒä¸€é‡‡é›†æ—¶é—´åªä¿ç•™ä¸€æ¡ |
| æ•°æ®ä¿ç•™ | åŽŸå§‹æŠ¥æ–‡ 7 å¤©ã€å®žæ—¶åŽ†å² 30 å¤©ï¼Œå‘¨æœŸ/工艺/参数变更/报警长期保留 |
## æ³¨æ„äº‹é¡¹
- æ‰€æœ‰æ•°é‡‡æŸ¥è¯¢æŽ¥å£éœ€æƒé™ `mes:collect:query`,人工确认报警需 `mes:collect-alarm:ack`。
- `deviceCode` ç»Ÿä¸€ä½¿ç”¨å°è´¦ `collectDeviceCode`,不要使用设备编码 `machineryCode`。
- `collectTime`、`modifyTime`、`wmTime` æ—¶é—´èŒƒå›´ä¸ºä¸¤ä¸ªå€¼ï¼šç¬¬ä¸€ä¸ªä¸ºå¼€å§‹æ—¶é—´ï¼Œç¬¬äºŒä¸ªä¸ºç»“束时间。
- `temps`、`etTemps`、`dataJson` å‡ä¸º JSON å­—符串,需要前端二次解析;解析失败时按原文本展示,避免页面报错。
- è®¾å¤‡åˆ—表接口一次返回全部已配置数采的设备(当前 8 å°ï¼‰ï¼Œä¸éœ€è¦åˆ†é¡µã€‚
- å°è´¦ä¿å­˜æ—¶æ•°é‡‡ç¼–号重复会返回“数采设备编号已被其他设备占用”,需提示用户更换编号。
docs/mes_workbench_workstation_pending_count.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,93 @@
# ç”Ÿäº§å·¥ä½œå° - å·¥ä½œç«™å¾…完成数量联调方案
## æ¶‰åŠé¡µé¢
- ç”Ÿäº§å·¥ä½œå°ï¼ˆ`/mes/workbench`)→「选择工作站」弹窗
## éœ€æ±‚说明
工作站卡片需要展示该工作站的**待完成数量**,口径与工作台左侧「工单列表」(待报工台账)一致:
- ä»»åŠ¡çŠ¶æ€ä¸ºè¿›è¡Œä¸­ï¼šè‰ç¨¿(0)、生产中(1)、部分完成(2)、已停工(9)
- ä¸”仍有待报工量:`排产数量 - å·²ç”Ÿäº§æ•°é‡ > 0`
- å¡ç‰‡æ˜¾ç¤ºçš„æ˜¯æ»¡è¶³ä¸Šè¿°æ¡ä»¶çš„**任务条数**,不是数量总和
## API
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| GET | `/mes/pro/task/pending-count-by-workstation` | æŒ‰å·¥ä½œç«™ç»Ÿè®¡å¾…报工任务条数 |
**请求参数:** æ— 
**响应字段:**
| å­—段 | ç±»åž‹ | è¯´æ˜Ž |
|------|------|------|
| data | Map<Long, Long> | å·¥ä½œç«™ç¼–号 â†’ å¾…报工任务条数;无待办的工作站不返回 |
**响应示例:**
```json
{
  "code": 0,
  "data": {
    "9": 1,
    "10": 1,
    "11": 1,
    "12": 1,
    "13": 1,
    "14": 1,
    "15": 1
  },
  "msg": ""
}
```
**权限:** `mes:pro-task:query`(与待报工台账接口一致,工作台页面已具备该权限)
## æŽ¥å£è°ƒç”¨æ—¶åº
1. æ‰“开「选择工作站」弹窗
2. å¹¶è¡Œè¯·æ±‚:
   - `GET /mes/md-workstation/page?pageNo=1&pageSize=200` èŽ·å–å·¥ä½œç«™åˆ—è¡¨
   - `GET /mes/pro/task/pending-count-by-workstation` èŽ·å–å„å·¥ä½œç«™å¾…åŠžæ•°
3. å¡ç‰‡æ¸²æŸ“时用 `工作站 id` æŸ¥è¯¢æ˜ å°„,未命中显示 0
## å­—段展示规则
| å­—段 | å±•示位置 | è¯´æ˜Ž |
|------|----------|------|
| å¾…完成数量 | å¡ç‰‡åç§°å³ä¾§å¾½æ ‡ | æœ‰ä»»åŠ¡æ—¶æ©™è‰²å¾½æ ‡"待完成 N";N=0 æ—¶ç°è‰²å¾½æ ‡"待完成 0" |
| å¾…完成数量 | å¾½æ ‡ hover æç¤º | "该工作站待报工任务数:N" |
## ä¸šåŠ¡è§„åˆ™è¯´æ˜Ž
| åœºæ™¯ | è§„则 |
|------|------|
| ç»Ÿè®¡å£å¾„ | ä¸Žã€Œå¾…报工台账」完全一致,避免卡片数量与左侧工单列表对不上 |
| ç»Ÿè®¡æŽ¥å£å¤±è´¥ | éšè—å…¨éƒ¨å¾½æ ‡ï¼Œä¸å½±å“å·¥ä½œç«™åˆ—表加载与选择(统计为辅助信息) |
| å·¥ä½œç«™æš‚无待办 | æ˜¾ç¤ºç°è‰²"待完成 0",保持卡片布局一致 |
| æ•°é‡å«ä¹‰ | ä»»åŠ¡æ¡æ•°ï¼›åŒä¸€å·¥ä½œç«™å¤šæ¡ä»»åŠ¡æ—¶æŒ‰æ¡ç´¯åŠ ï¼Œä¸åˆå¹¶ä¸ºæ•°é‡åˆè®¡ |
## æ³¨æ„äº‹é¡¹
- ç»Ÿè®¡æŽ¥å£è¿”回的 Map key æ˜¯å­—符串形式的工作站编号,前端按 `String(工作站id)` å–值
- å¼¹çª—每次打开都会重新请求统计,报工后重新打开即可看到最新数量
- è¯¥æŽ¥å£è¿”回全量工作站统计,数据量很小(每工作站一行),无需按工作站 id è¿‡æ»¤
- è‹¥åŽç»­éœ€è¦"待完成数量总和"(剩余可报工量合计),可复用同一查询增加 `SUM(quantity - produced_quantity)` å­—段,无需新增接口
## åŽç«¯æ”¹åЍ
| æ–‡ä»¶ | æ”¹åЍ |
|------|------|
| `MesProTaskMapper` | æ–°å¢ž `selectPendingFeedbackCountByWorkstation()`,按 `workstation_id` åˆ†ç»„统计 |
| `MesProTaskService` / `Impl` | æ–°å¢ž `getPendingFeedbackCountByWorkstation()`,返回 Map |
| `MesProTaskController` | æ–°å¢ž `GET /pending-count-by-workstation` æŽ¥å£ |
## å‰ç«¯æ”¹åЍ
| æ–‡ä»¶ | æ”¹åЍ |
|------|------|
| `src/api/mes/pro/task/index.ts` | æ–°å¢ž `getPendingFeedbackCountByWorkstation()` |
| `src/views/mes/workbench/components/WorkstationSelectDialog.vue` | å¹¶è¡ŒåŠ è½½ç»Ÿè®¡ï¼Œå¡ç‰‡åç§°è¡Œå¢žåŠ å¾…å®Œæˆå¾½æ ‡ |
docs/route_process_output_attribution.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,112 @@
# å·¥è‰ºè·¯çº¿å·¥åºäº§å‡ºå½’属 - å‰ç«¯è”调方案
## æ¶‰åŠé¡µé¢
- å·¥è‰ºè·¯çº¿ - å·¥åºä¸Žäº§å‡ºé¡µç­¾ - å·¥åºæ–°å¢ž/编辑弹窗(process-form.vue)
- å·¥è‰ºè·¯çº¿ - å¯ç”¨å‰æ£€æŸ¥ï¼ˆroute-configurator.vue)
- ç”Ÿäº§å·¥å• - æ–°å¢ž/编辑弹窗 - å·¥åºåˆ—表(受归属过滤影响,无需新增字段)
- ç”Ÿäº§ä»»åŠ¡ - è‡ªåŠ¨ç”Ÿæˆä»»åŠ¡ï¼ˆäº§å‡ºç‰©æ–™æŒ‰å½’å±žå¸¦å…¥ï¼‰
## ä¸šåŠ¡æµç¨‹ä¸Žæ•°æ®å¸¦å…¥
1. å·¥è‰ºè·¯çº¿å·¥åºçš„产出物料新增「归属」概念:
   - å½’属为空 = å¤šäº§å“é€šç”¨ï¼ˆè¯¥è·¯çº¿ä¸‹æ‰€æœ‰äº§å“éƒ½ä¼šäº§å‡ºè¯¥ç‰©æ–™ï¼Œé€‚合同线共用的半成品);
   - å½’属为某个产品 = äº§å“ä¸“属(只有生产该产品时才会产出)。
2. é€‰æ‹©äº§å‡ºç‰©æ–™æ—¶é»˜è®¤å½’属到物料自身;多产品共用的半成品需手动改为「通用」。
3. ä¸‹å•(新建工单)时,按工单产品过滤工艺路线工序产出:只带入「通用」产出和「该产品专属」产出。
4. è‡ªåŠ¨ç”Ÿæˆä»»åŠ¡æ—¶ï¼Œä»»åŠ¡äº§å‡ºç‰©æ–™åŒæ ·æŒ‰å½’å±žè¿‡æ»¤ï¼Œä¼˜å…ˆå–è¿‡æ»¤åŽçš„ç¬¬ä¸€ä¸ªäº§å‡ºï¼›æ²¡æœ‰äº§å‡ºæ—¶å–å·¥å•æˆå“ã€‚
5. æœ«é“工序的成品产出必须归属到自身产品(不能为「通用」),否则启用路线时会报错。
## API
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| POST | /mes/pro/route-process/create | æ–°å¢žå·¥åºï¼ˆè¯·æ±‚新增 outputs) |
| PUT | /mes/pro/route-process/update | ä¿®æ”¹å·¥åºï¼ˆè¯·æ±‚新增 outputs) |
| GET | /mes/pro/route-process/get | å·¥åºè¯¦æƒ…(响应新增 outputs) |
| GET | /mes/pro/route-process/list-by-route | æŒ‰è·¯çº¿èŽ·å–å·¥åºï¼ˆå“åº”æ–°å¢ž outputs) |
| GET | /mes/pro/route-process/list-by-product | æŒ‰äº§å“èŽ·å–å·¥åºï¼ˆäº§å‡ºå·²æŒ‰å½’å±žè¿‡æ»¤ï¼‰ |
**请求参数新增(create / update):**
| å‚æ•° | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|------|------|------|------|
| outputs | Array | å¦ | äº§å‡ºç‰©æ–™å½’属配置,传入后优先于 outputItemIds |
| outputs[].itemId | Long | æ˜¯ | äº§å‡ºç‰©æ–™ç¼–号,必须是本路线绑定的产品 |
| outputs[].productId | Long | å¦ | å½’属产品编号;不传或传空表示多产品通用 |
| outputs[].sort | Integer | å¦ | æŽ’序,不传按数组顺序 |
| outputs[].remark | String | å¦ | å¤‡æ³¨ |
> å…¼å®¹è¯´æ˜Žï¼šä»å¯åªä¼  outputItemIds(老页面/工艺设计页),后端会自动判定归属——产出是路线绑定产品时归属到自身,否则视为通用。
**响应字段新增(get / list-by-route / list-by-product):**
| å­—段 | ç±»åž‹ | è¯´æ˜Ž |
|------|------|------|
| outputs | Array | äº§å‡ºå½’属列表 |
| outputs[].id | Long | å½’属记录编号 |
| outputs[].routeProcessId | Long | å·¥åºç¼–号 |
| outputs[].productId | Long | å½’属产品编号,空表示通用 |
| outputs[].productName | String | å½’属产品名称 |
| outputs[].itemId | Long | äº§å‡ºç‰©æ–™ç¼–号 |
| outputs[].itemCode | String | äº§å‡ºç‰©æ–™ç¼–码 |
| outputs[].itemName | String | äº§å‡ºç‰©æ–™åç§° |
| outputs[].sort | Integer | æŽ’序 |
| outputs[].remark | String | å¤‡æ³¨ |
**响应示例:**
```json
{
  "code": 0,
  "data": {
    "id": 123,
    "processName": "包装入库",
    "outputs": [
      {
        "productId": 201,
        "productName": "油浸式配电变压器",
        "itemId": 201,
        "itemName": "油浸式配电变压器",
        "sort": 1
      },
      {
        "productId": null,
        "productName": null,
        "itemId": 300,
        "itemName": "通用包装箱",
        "sort": 2
      }
    ]
  }
}
```
## å­—段展示规则
| å­—段 | å±•示位置 | è¯´æ˜Ž |
|------|----------|------|
| outputs | å·¥åºæ–°å¢ž/编辑弹窗「本工序产出」 | æ¯è¡Œä¸¤ä¸ªä¸‹æ‹‰ï¼šäº§å‡ºç‰©æ–™ã€å½’属;提供「添加产出」「删除」 |
| å½’属下拉 | å·¥åºæ–°å¢ž/编辑弹窗 | é€‰é¡¹ä¸ºã€Œé€šç”¨ï¼ˆæ‰€æœ‰äº§å“ï¼‰ã€+ è·¯çº¿äº§å“åˆ—表 |
| äº§å‡ºç‰©æ–™ä¸‹æ‹‰ | å·¥åºæ–°å¢ž/编辑弹窗 | é€‰é¡¹é™å®šä¸ºè·¯çº¿ç»‘定的产品 |
| outputs | å·¥åºåˆ—表「本工序产出」列 | å±•示为「物料名(归属产品名)」或「物料名(通用)」 |
## ä¸šåŠ¡è§„åˆ™è¯´æ˜Ž
| åœºæ™¯ | è§„则 |
|------|------|
| é€‰æ‹©äº§å‡ºç‰©æ–™ | é»˜è®¤å½’属到物料自身,可手动改为「通用」 |
| å¤šäº§å“å…±ç”¨åŠæˆå“ | å½’属选择「通用」,所有产品下单时都会带入该产出 |
| äº§å“ä¸“属产出 | å½’属选择对应产品,只有该产品的工单/任务会带入 |
| æœ«é“工序成品 | æ¯ä¸ªæˆå“å¿…须归属到自身产品,不能使用「通用」 |
| å­˜é‡æ•°æ® | åŽŸ output_item_id è‡ªåŠ¨æ‹†åˆ†ï¼šæ˜¯è·¯çº¿äº§å“çš„å½’å±žåˆ°è‡ªèº«ï¼Œå¦åˆ™è§†ä¸ºé€šç”¨ |
| åªä¼  outputItemIds | åŽç«¯è‡ªåŠ¨æŒ‰ã€Œè·¯çº¿äº§å“â†’ä¸“å±žã€å…¶å®ƒâ†’é€šç”¨ã€åˆ¤å®šå½’å±ž |
| äº§å‡ºèšåˆå­—段 | output_item_id ä»ä¿ç•™å¹¶è‡ªåŠ¨èšåˆï¼Œä¾›ä»»åŠ¡ã€ç‰©æ–™éœ€æ±‚ç­‰æ—§é€»è¾‘ä½¿ç”¨ |
## æ³¨æ„äº‹é¡¹
- å½’属产品下拉的选项就是当前工艺路线绑定的产品列表,未绑定产品不能作为归属。
- äº§å‡ºç‰©æ–™å¿…须属于路线绑定产品,否则启用路线时会报「产出未绑定到当前工艺路线」。
- åŒä¸€é“工序可以同时存在通用产出和多个产品专属产出。
- å·¥åºäº§å‡ºå½’属保存后,已创建的工单工序快照不受影响(快照在下单时按当时的归属过滤)。
- å·¥è‰ºè·¯çº¿å¯ç”¨æ—¶ï¼Œæœ«é“工序若存在「通用」成品产出会校验失败,需要在工序编辑中改为产品专属。
docs/sale_order_production_cancel_integration.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,84 @@
# é”€å”®è®¢å•生产状态“已取消” - å‰ç«¯è”调方案
## æ¶‰åŠé¡µé¢
- ERP é”€å”®è®¢å•列表页
- ERP é”€å”®è®¢å•详情页
## ä¸šåŠ¡èƒŒæ™¯
生产计划(MPS)取消时,会同步更新来源销售订单的生产状态。此前取消后订单只会回到“未完成”,无法区分“从未生产”和“生产计划已取消”。现新增生产状态 `2-已取消`。
状态取值:
| productionStatus | å«ä¹‰ | è§¦å‘时机 |
|------------------|------|----------|
| 0 | æœªå®Œæˆ | è®¢å•审核通过生成生产计划;或取消后重新审核生成了新的生产计划 |
| 1 | å·²å®Œæˆ | ç”Ÿäº§å®Œæˆäº‹ä»¶ï¼ˆProductionFinishedEvent) |
| 2 | å·²å–消 | æ¥æºç”Ÿäº§è®¡åˆ’被取消(ProductionCanceledEvent) |
## API
接口路径与参数无变化,仅响应字段 `productionStatus` å¢žåŠ å–å€¼ `2`。
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| GET | /erp/sale-order/page | é”€å”®è®¢å•分页 |
| GET | /erp/sale-order/get | é”€å”®è®¢å•详情 |
**响应字段:**
| å­—段 | ç±»åž‹ | è¯´æ˜Ž |
|------|------|------|
| productionStatus | Integer | ç”Ÿäº§çŠ¶æ€ï¼š0-未完成,1-已完成,2-已取消 |
| productionFinishTime | DateTime | ç”Ÿäº§å®Œæˆæ—¶é—´ï¼Œä»… productionStatus=1 æ—¶æœ‰å€¼ |
**响应示例:**
```json
{
  "code": 0,
  "data": {
    "id": 1310,
    "no": "XSDD20261026000002",
    "needProduction": 1,
    "productionStatus": 2,
    "productionFinishTime": null
  }
}
```
## å­—段展示规则
| å­—段 | å±•示位置 | è¯´æ˜Ž |
|------|----------|------|
| productionStatus | åˆ—表“生产状态”列 | éžâ€œæ— éœ€ç”Ÿäº§â€æ—¶æŒ‰ 0/1/2 åˆ†åˆ«å±•示未完成 / å·²å®Œæˆ / å·²å–消;建议“已取消”使用区别于“已完成”的醒目颜色 |
| productionStatus | è¯¦æƒ…页“生产状态” | åŒä¸Šï¼Œä»…在明细需要生产时展示 |
| productionFinishTime | åˆ—表、详情 | ä»…在 productionStatus=1 æ—¶å±•示,状态为 2 æ—¶ä¸å±•示 |
## æŸ¥è¯¢ç­›é€‰è§„则
列表筛选“生产状态”下拉需新增选项:
| é€‰é¡¹ | ä¼ å‚ |
|------|------|
| æ— éœ€ç”Ÿäº§ | needProduction=0(沿用现有特殊处理) |
| æœªå®Œæˆ | productionStatus=0 |
| å·²å®Œæˆ | productionStatus=1 |
| å·²å–消 | productionStatus=2 |
## ä¸šåŠ¡è§„åˆ™è¯´æ˜Ž
| åœºæ™¯ | è§„则 |
|------|------|
| å–消生产计划 | é”€å”®è®¢å• productionStatus ç½®ä¸º 2,productionFinishTime æ¸…空 |
| å–消后重新审核订单 | ç³»ç»Ÿæ£€æµ‹åˆ°åŽŸç”Ÿäº§è®¡åˆ’å·²å–æ¶ˆï¼Œä¼šé‡æ–°ç”Ÿæˆæ–°çš„ç”Ÿäº§è®¡åˆ’ï¼Œå¹¶æŠŠè®¢å• productionStatus é‡ç½®ä¸º 0 |
| è®¢å•存在有效生产计划时再次审核 | å¤ç”¨å·²æœ‰ç”Ÿäº§è®¡åˆ’,不改变生产状态 |
| ä¸€å•多明细 | ç”Ÿäº§çŠ¶æ€ä¸ºè®¢å•çº§ï¼›ä»»ä¸€æ˜Žç»†å–æ¶ˆç”Ÿäº§è®¡åˆ’ï¼Œæ•´å•æ˜¾ç¤ºâ€œå·²å–æ¶ˆâ€ |
## æ³¨æ„äº‹é¡¹
- è®¢å•状态 `status`(未审核/已审核/已作废)不受生产计划取消影响,仍保持“已审核”。
- productionStatus=2 æ—¶è®¢å•仍可执行反审核、编辑,重新审核后会自动生成新生产计划。
- åŽ†å²å·²å–æ¶ˆçš„ç”Ÿäº§è®¡åˆ’å¯¹åº”çš„è®¢å•ä¸ä¼šè¿½æº¯åˆ·æ–°ï¼Œä»æ˜¾ç¤ºåŽŸçŠ¶æ€ï¼›å¦‚éœ€ä¿®æ­£è¯·å•ç‹¬å¤„ç†å­˜é‡æ•°æ®ã€‚
- å‰ç«¯ç­›é€‰ä¸‹æ‹‰æ–°å¢žâ€œå·²å–消”后,历史上此前取消且状态已被置为 0 çš„订单仍会落在“未完成”筛选项下。
docs/work_order_process_warehouse_integration.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,110 @@
# ç”Ÿäº§å·¥å•工序报工仓库 - å‰ç«¯è”调方案
## æ¶‰åŠé¡µé¢
- ç”Ÿäº§å·¥å• - æ–°å¢ž/编辑弹窗 - å·¥åºåˆ—表(process-list.vue)
- ç”Ÿäº§å·¥å• - å·¥åºæ–°å¢ž/编辑弹窗(process-form.vue)
- å·¥è‰ºè·¯çº¿ - äº§å“é¡µç­¾ï¼ˆå·¥è‰ºè·¯çº¿äº§å“æŠ¥å·¥ä»“库配置,作为工序默认值来源)
## ä¸šåŠ¡æµç¨‹ä¸Žæ•°æ®å¸¦å…¥
1. å·¥è‰ºè·¯çº¿äº§å“é…ç½®æŠ¥å·¥ä»“库(`mes_pro_route_product` çš„ `warehouseId/locationId/areaId`),该配置是工序的**默认值来源**。
2. ç”Ÿäº§è®¢å•下单(新建工单)时,工序列表从工艺路线带入,工序的报工仓库/库区/库位默认取工艺路线产品的配置。
3. ä¸‹å•时可对每道工序单独修改报工仓库/库区/库位,修改结果随工单工序一起保存(工单工序为快照,不受后续工艺路线修改影响)。
4. ç”Ÿäº§æŠ¥å·¥å®¡æ‰¹é€šè¿‡åŽï¼Œäº§å‡ºç‰©æ–™æŒ‰ä»¥ä¸‹ä¼˜å…ˆçº§å†³å®šå…¥åº“位置:
   - â‘  å·¥å•工序上配置的报工仓库/库区/库位;
   - â‘¡ å·¥è‰ºè·¯çº¿äº§å“ä¸Šé…ç½®çš„æŠ¥å·¥ä»“库/库区/库位;
   - â‘¢ è™šæ‹Ÿçº¿è¾¹ä»“(历史数据兜底)。
5. å·¥å•已存在但工序未配置仓库(本次改造前的历史工单)时,自动走 â‘¡ã€â‘¢ï¼Œæ— éœ€è¡¥æ•°æ®ã€‚
## API
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| GET | /mes/pro/route-process/list-by-product | æŒ‰äº§å“èŽ·å–å·¥è‰ºè·¯çº¿å·¥åºåˆ—è¡¨ï¼ˆæ–°å¢žï¼šé»˜è®¤æŠ¥å·¥ä»“åº“ï¼‰ |
| GET | /mes/pro/work-order-process/list-by-product-id | æŒ‰äº§å“èŽ·å–å·¥å•å·¥åºé¢„å¡«åˆ—è¡¨ï¼ˆæ–°å¢žï¼šé»˜è®¤æŠ¥å·¥ä»“åº“ï¼‰ |
| GET | /mes/pro/work-order-process/list-by-work-order-id | èŽ·å–å·¥å•å·¥åºåˆ—è¡¨ï¼ˆæ–°å¢žï¼šæŠ¥å·¥ä»“åº“åŠåç§°ï¼‰ |
| GET | /mes/pro/work-order-process/get | èŽ·å–å·¥å•å·¥åºè¯¦æƒ…ï¼ˆæ–°å¢žï¼šæŠ¥å·¥ä»“åº“åŠåç§°ï¼‰ |
| POST | /mes/pro/work-order-process/batch-save | æ‰¹é‡ä¿å­˜å·¥å•工序(请求新增仓库字段) |
| POST/PUT | /mes/pro/work-order-process/create、/update | å•条新增/修改(请求新增仓库字段) |
**请求参数(batch-save / create / update):**
| å‚æ•° | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|------|------|------|------|
| warehouseId | Long | å¦ | æŠ¥å·¥ä»“库编号,不填则按工艺路线产品兜底 |
| locationId | Long | å¦ | æŠ¥å·¥åº“区编号,需与 warehouseId åŒ¹é… |
| areaId | Long | å¦ | æŠ¥å·¥åº“位编号,需与 locationId åŒ¹é… |
> ä»“库/库区/库位三者同时填写时,后端校验层级归属关系;只填部分时,报工产出按工艺路线产品兜底。
**响应字段新增(list-by-product、list-by-work-order-id、get ç­‰å·¥åºæŸ¥è¯¢æŽ¥å£ï¼‰ï¼š**
| å­—段 | ç±»åž‹ | è¯´æ˜Ž |
|------|------|------|
| warehouseId | Long | æŠ¥å·¥ä»“库编号 |
| warehouseName | String | æŠ¥å·¥ä»“库名称 |
| locationId | Long | æŠ¥å·¥åº“区编号 |
| locationName | String | æŠ¥å·¥åº“区名称 |
| areaId | Long | æŠ¥å·¥åº“位编号 |
| areaName | String | æŠ¥å·¥åº“位名称 |
**响应示例:**
```json
{
  "code": 0,
  "data": [
    {
      "processId": 1,
      "processName": "焊接",
      "sort": 1,
      "warehouseId": 5,
      "warehouseName": "半成品仓",
      "locationId": 12,
      "locationName": "通用库区",
      "areaId": 31,
      "areaName": "默认库位"
    }
  ]
}
```
## å­—段展示规则
| å­—段 | å±•示位置 | è¯´æ˜Ž |
|------|----------|------|
| warehouseName | å·¥å•工序列表「报工仓库」列 | æœ‰åº“位时拼接显示「仓库/库区/库位」,未配置显示「-」 |
| warehouseId | å·¥åºæ–°å¢ž/编辑弹窗 | ä¸‹æ‹‰å•选,选项来自仓库精简列表,允许清空 |
| locationId | å·¥åºæ–°å¢ž/编辑弹窗 | ä¸‹æ‹‰å•选,依赖 warehouseId,仓库变更时清空 |
| areaId | å·¥åºæ–°å¢ž/编辑弹窗 | ä¸‹æ‹‰å•选,依赖 locationId,库区变更时清空 |
## äº§å‡ºç‰©æ–™è¿‡æ»¤
同一工艺路线可以关联多个产品(工艺路线产品)。下单/排产按产品获取工艺路线工序时,工序的产出物料会自动过滤,只保留「通用产出」和「当前产品专属产出」,其它产品的专属产出不会带入。
产出归属在工艺路线工序弹窗中配置:产出物料可归属到某个产品(专属),也可留空表示多产品通用(适合同线共用的半成品)。详见 `docs/route_process_output_attribution.md`。
示例:路线同时关联「油浸式配电变压器」和「光伏固定支架」,包装入库工序分别为两个产品配置专属产出。下单「光伏固定支架」时,该工序只带入「光伏固定支架」;若该工序还有一个「通用」半成品产出,也会一并带入。
> å·¥è‰ºè·¯çº¿é…ç½®é¡µï¼ˆæŒ‰ routeId æŸ¥è¯¢ï¼‰ä¸å—影响,仍展示全部已配置的产出物料及其归属。
工单工序新增/编辑弹窗中的「产出物料」选择器同样按此规则收敛:候选范围 = è¯¥äº§å“å·¥è‰ºè·¯çº¿ä¸­å¸¦å…¥çš„产出(通用 + æœ¬äº§å“ä¸“属)+ è¯¥å·¥åºå·²æœ‰äº§å‡º + å·¥å•产品本身。产品未关联工艺路线时不做限制,保持原有可选范围。
## ä¸šåŠ¡è§„åˆ™è¯´æ˜Ž
| åœºæ™¯ | è§„则 |
|------|------|
| æ–°å»ºå·¥å•带入工序 | ä»“库/库区/库位默认取工艺路线产品的报工仓库配置,可逐工序修改 |
| ä¿®æ”¹å·¥åºä»“库 | ä¿®æ”¹åŽéšå·¥åºä¿å­˜åˆ°å·¥å•工序快照,报工按快照执行 |
| å·¥è‰ºè·¯çº¿åŽç»­ä¿®æ”¹ | ä¸å½±å“å·²åˆ›å»ºçš„工单工序快照 |
| å·¥åºæœªé…ç½®ä»“库 | æŠ¥å·¥äº§å‡ºå›žé€€åˆ°å·¥è‰ºè·¯çº¿äº§å“é…ç½®ï¼Œå†å›žé€€è™šæ‹Ÿçº¿è¾¹ä»“ |
| ä»“库层级不匹配 | åŽç«¯æ ¡éªŒ `warehouse â†’ location â†’ area` å½’属关系,不匹配时报错 |
| å€’冲扣料 | ä»å›ºå®šä»Žè™šæ‹Ÿçº¿è¾¹ä»“扣料,与产出仓库无关 |
## æ³¨æ„äº‹é¡¹
- å·¥åºä»“库三级字段为可选:历史工单工序没有仓库数据时,报工自动回退,不会报错。
- å‰ç½®ä»“库、库区、库位下拉数据分别来自 `/mes/wm/warehouse/simple-list`、`/mes/wm/warehouse-location/simple-list?warehouseId=`、`/mes/wm/warehouse-area/simple-list?locationId=`。
- åªæ¸…空仓库而不清空库区/库位会产生脏数据,前端在上级字段变更时需级联清空下级字段。
- äº§å‡ºä»“库改为实际仓库后,虚拟线边仓的库存不再随产出增加(倒冲扣料仍从虚拟仓扣减),属于预期行为。
sql/mysql/20261028_sp_generic_shift_dates.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,290 @@
-- sp_generic_shift_dates:按指定时间窗口平移库内日期类字段
-- ä¿®å¤ç‚¹ï¼š
-- 1) å”¯ä¸€ç´¢å¼•包含日期列的表(如 hrm_attendance_record.uk_user_date)改为整表统一平移,
--    å¸¸é‡åç§»ä¸ä¼šç ´åå”¯ä¸€é”®ï¼Œé¿å… 1062 Duplicate entry
-- 2) å•表执行失败不再中断整轮,错误记入预览表后继续处理后续表
DROP PROCEDURE IF EXISTS `sp_generic_shift_dates`;
DELIMITER $$
CREATE DEFINER = `root`@`%` PROCEDURE `sp_generic_shift_dates`(
    IN p_schema VARCHAR(64),
    IN p_start DATETIME,
    IN p_end DATETIME,
    IN p_execute BOOLEAN,
    IN p_exclude_regex VARCHAR(1000),
    IN p_include_regex VARCHAR(1000)
)
BEGIN
    DECLARE v_done BOOL DEFAULT FALSE;
    DECLARE v_table VARCHAR(64);
    DECLARE v_pk VARCHAR(64);
    DECLARE v_pk_count INT;
    DECLARE v_cols LONGTEXT;
    DECLARE v_anchor VARCHAR(64);
    DECLARE v_sql LONGTEXT;
    DECLARE v_seconds BIGINT;
    DECLARE v_count BIGINT DEFAULT 0;
    DECLARE v_tables BIGINT DEFAULT 0;
    DECLARE v_fields INT;
    DECLARE v_uniq_hit INT;
    DECLARE v_uniq_cols VARCHAR(1000);
    DECLARE v_min_expr LONGTEXT;
    DECLARE v_max_expr LONGTEXT;
    DECLARE v_regex VARCHAR(1000);
    DECLARE v_err TEXT;
    DECLARE cur CURSOR FOR
        SELECT t.TABLE_NAME
        FROM information_schema.TABLES t
        WHERE t.TABLE_SCHEMA = p_schema
          AND t.TABLE_TYPE = 'BASE TABLE'
          AND (p_exclude_regex IS NULL OR TRIM(p_exclude_regex) = ''
               OR NOT REGEXP_LIKE(t.TABLE_NAME, p_exclude_regex, 'i'))
        ORDER BY t.TABLE_NAME;
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET v_done = TRUE;
    IF p_schema IS NULL OR p_schema = '' OR p_schema IN ('mysql','information_schema','performance_schema','sys') THEN
        SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Invalid or system schema';
    END IF;
    IF p_start IS NULL OR p_end IS NULL OR p_end < p_start THEN
        SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Invalid date range';
    END IF;
    IF NOT EXISTS (SELECT 1 FROM information_schema.SCHEMATA WHERE SCHEMA_NAME = p_schema) THEN
        SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Schema not found';
    END IF;
    -- åˆ—清单可能很长,避免 GROUP_CONCAT è¢«é»˜è®¤ 1024 å­—节截断
    SET SESSION group_concat_max_len = 1048576;
    SET v_seconds = TIMESTAMPDIFF(SECOND, p_start, p_end);
    -- ç©ºä¸²ä¸æ˜¯ NULL,必须显式归一化:MySQL 8 çš„ REGEXP_LIKE é‡åˆ°ç©ºæ¨¡å¼ä¼šæŠ› 3685
    IF p_include_regex IS NULL OR TRIM(p_include_regex) = '' THEN
        SET v_regex = '(^|_)(create|created|update|updated|modify|modified|order|production|produce|receipt|receive|delivery|ship|stock|inbound|outbound|start|end|finish|complete|plan|actual|inspect|check|approve|audit|pay|payment|due|expire|expiry|effective|business|biz|date|time|timestamp)(_|$)';
    ELSE
        SET v_regex = p_include_regex;
    END IF;
    DROP TEMPORARY TABLE IF EXISTS tmp_date_shift_preview;
    CREATE TEMPORARY TABLE tmp_date_shift_preview (
        table_name VARCHAR(64),
        anchor_column VARCHAR(64),
        shift_mode VARCHAR(16),
        field_count INT,
        affected_rows BIGINT,
        statement_text LONGTEXT
    );
    OPEN cur;
    loop_tables: LOOP
        FETCH cur INTO v_table;
        IF v_done THEN LEAVE loop_tables; END IF;
        SELECT COUNT(*), MAX(k.COLUMN_NAME) INTO v_pk_count, v_pk
        FROM information_schema.KEY_COLUMN_USAGE k
        WHERE k.TABLE_SCHEMA = p_schema AND k.TABLE_NAME = v_table AND k.CONSTRAINT_NAME = 'PRIMARY';
        IF v_pk_count <> 1 THEN ITERATE loop_tables; END IF;
        SELECT COUNT(*) INTO v_fields
        FROM information_schema.COLUMNS c
        WHERE c.TABLE_SCHEMA = p_schema AND c.TABLE_NAME = v_table
          AND c.DATA_TYPE IN ('date','datetime','timestamp')
          AND c.EXTRA NOT LIKE '%VIRTUAL GENERATED%' AND c.EXTRA NOT LIKE '%STORED GENERATED%'
          AND c.EXTRA NOT LIKE '%on update%'
          AND REGEXP_LIKE(c.COLUMN_NAME, v_regex, 'i');
        IF v_fields = 0 THEN ITERATE loop_tables; END IF;
        SELECT c.COLUMN_NAME INTO v_anchor
        FROM information_schema.COLUMNS c
        WHERE c.TABLE_SCHEMA = p_schema AND c.TABLE_NAME = v_table
          AND c.DATA_TYPE IN ('date','datetime','timestamp')
          AND c.EXTRA NOT LIKE '%VIRTUAL GENERATED%' AND c.EXTRA NOT LIKE '%STORED GENERATED%'
          AND c.EXTRA NOT LIKE '%on update%'
          AND REGEXP_LIKE(c.COLUMN_NAME, v_regex, 'i')
        ORDER BY CASE WHEN c.COLUMN_NAME IN ('create_time','created_at','creation_time','created_time') THEN 0 ELSE 1 END,
                 c.ORDINAL_POSITION
        LIMIT 1;
        -- å¾…平移的日期列中是否有落在唯一索引上的
        SELECT COUNT(*) INTO v_uniq_hit
        FROM information_schema.STATISTICS s
        JOIN information_schema.COLUMNS c
          ON c.TABLE_SCHEMA = s.TABLE_SCHEMA
         AND c.TABLE_NAME = s.TABLE_NAME
         AND c.COLUMN_NAME = s.COLUMN_NAME
        WHERE s.TABLE_SCHEMA = p_schema AND s.TABLE_NAME = v_table AND s.NON_UNIQUE = 0
          AND c.DATA_TYPE IN ('date','datetime','timestamp')
          AND c.EXTRA NOT LIKE '%VIRTUAL GENERATED%' AND c.EXTRA NOT LIKE '%STORED GENERATED%'
          AND c.EXTRA NOT LIKE '%on update%'
          AND REGEXP_LIKE(c.COLUMN_NAME, v_regex, 'i');
        SET @tbl = CONCAT('`', REPLACE(p_schema,'`','``'), '`.`', REPLACE(v_table,'`','``'), '`');
        SET @anchor = CONCAT('`', REPLACE(v_anchor,'`','``'), '`');
        inner_block: BEGIN
            DECLARE EXIT HANDLER FOR SQLEXCEPTION
            BEGIN
                GET DIAGNOSTICS CONDITION 1 v_err = MESSAGE_TEXT;
                INSERT INTO tmp_date_shift_preview
                VALUES (v_table, v_anchor, 'failed', v_fields, 0, CONCAT('[error] ', v_err));
            END;
            IF v_uniq_hit > 0 THEN
                -- ç»Ÿä¸€å¹³ç§»ï¼šæ•´è¡¨ä½¿ç”¨åŒä¸€åç§»ï¼Œä¿ç•™å”¯ä¸€é”®ã€‚
                -- åç§»é‡æŒ‰æ‰€æœ‰å¾…平移列的整体跨度计算,保证每一列都落在目标窗口内。
                -- COALESCE ä¿æŠ¤ï¼šæ•´åˆ—为 NULL æ—¶ MIN/MAX è¿”回 NULL,会让 LEAST/GREATEST æ•´ä½“变 NULL
                SELECT GROUP_CONCAT(CONCAT('COALESCE(MIN(`', REPLACE(c.COLUMN_NAME,'`','``'), '`), ''9999-12-31 23:59:59'')')
                                    ORDER BY c.ORDINAL_POSITION SEPARATOR ', '),
                       GROUP_CONCAT(CONCAT('COALESCE(MAX(`', REPLACE(c.COLUMN_NAME,'`','``'), '`), ''1000-01-01 00:00:00'')')
                                    ORDER BY c.ORDINAL_POSITION SEPARATOR ', ')
                INTO v_min_expr, v_max_expr
                FROM information_schema.COLUMNS c
                WHERE c.TABLE_SCHEMA = p_schema AND c.TABLE_NAME = v_table
                  AND c.DATA_TYPE IN ('date','datetime','timestamp')
                  AND c.EXTRA NOT LIKE '%VIRTUAL GENERATED%' AND c.EXTRA NOT LIKE '%STORED GENERATED%'
                  AND c.EXTRA NOT LIKE '%on update%'
                  AND REGEXP_LIKE(c.COLUMN_NAME, v_regex, 'i');
                SET @sql = CONCAT('SELECT LEAST(', v_min_expr, '), GREATEST(', v_max_expr,
                                  ') INTO @min_all, @max_all FROM ', @tbl);
                PREPARE stmt FROM @sql;
                EXECUTE stmt;
                DEALLOCATE PREPARE stmt;
                SET @span_days = TIMESTAMPDIFF(DAY, DATE(@min_all), DATE(@max_all));
                SET @range_days = TIMESTAMPDIFF(DAY, DATE(p_start), DATE(p_end));
                IF @min_all IS NULL OR @min_all >= '9999-01-01' THEN
                    INSERT INTO tmp_date_shift_preview
                    VALUES (v_table, v_anchor, 'skipped', v_fields, 0, 'no non-null date value');
                ELSEIF @span_days > @range_days THEN
                    INSERT INTO tmp_date_shift_preview
                    VALUES (v_table, v_anchor, 'skipped', v_fields, 0,
                            CONCAT('date span ', @span_days, ' days exceeds target range ', @range_days, ' days'));
                ELSE
                    -- æŒ‰æ•´æ—¥å¹³ç§»ï¼Œä¿ç•™æ—¶åˆ†ç§’与相对顺序
                    SET @shift_delta = (TIMESTAMPDIFF(DAY, DATE(@min_all), DATE(p_start))
                                        + FLOOR(RAND() * (@range_days - @span_days + 1))) * 86400;
                    -- éšæœºåˆ° 0 å¤©ç­‰äºŽæ²¡æ”¹ï¼Œå¼ºåˆ¶è‡³å°‘平移 1 å¤©
                    IF @shift_delta = 0 AND @span_days < @range_days THEN
                        SET @shift_delta = 86400;
                    END IF;
                    SELECT GROUP_CONCAT(
                               CONCAT('`', REPLACE(c.COLUMN_NAME,'`','``'), '` = DATE_ADD(`',
                                      REPLACE(c.COLUMN_NAME,'`','``'), '`, INTERVAL @shift_delta SECOND)')
                               ORDER BY c.ORDINAL_POSITION SEPARATOR ', ')
                    INTO v_cols
                    FROM information_schema.COLUMNS c
                    WHERE c.TABLE_SCHEMA = p_schema AND c.TABLE_NAME = v_table
                      AND c.DATA_TYPE IN ('date','datetime','timestamp')
                      AND c.EXTRA NOT LIKE '%VIRTUAL GENERATED%' AND c.EXTRA NOT LIKE '%STORED GENERATED%'
                      AND c.EXTRA NOT LIKE '%on update%'
                      AND REGEXP_LIKE(c.COLUMN_NAME, v_regex, 'i');
                    -- InnoDB é€è¡Œæ›´æ–°å”¯ä¸€é”®ä¼šçž¬æ—¶å†²çªï¼ŒæŒ‰åç§»æ–¹å‘决定扫描顺序:
                    -- æ­£åç§»å…ˆåŠ¨è¾ƒå¤§çš„å€¼ï¼Œè´Ÿåç§»å…ˆåŠ¨è¾ƒå°çš„å€¼
                    SELECT GROUP_CONCAT(CONCAT('`', REPLACE(d.COLUMN_NAME,'`','``'), '`')
                                        ORDER BY d.ORDINAL_POSITION SEPARATOR ', ')
                    INTO v_uniq_cols
                    FROM (
                        SELECT DISTINCT c.COLUMN_NAME, c.ORDINAL_POSITION
                        FROM information_schema.STATISTICS s
                        JOIN information_schema.COLUMNS c
                          ON c.TABLE_SCHEMA = s.TABLE_SCHEMA
                         AND c.TABLE_NAME = s.TABLE_NAME
                         AND c.COLUMN_NAME = s.COLUMN_NAME
                        WHERE s.TABLE_SCHEMA = p_schema AND s.TABLE_NAME = v_table AND s.NON_UNIQUE = 0
                          AND c.DATA_TYPE IN ('date','datetime','timestamp')
                          AND c.EXTRA NOT LIKE '%VIRTUAL GENERATED%' AND c.EXTRA NOT LIKE '%STORED GENERATED%'
                          AND c.EXTRA NOT LIKE '%on update%'
                          AND REGEXP_LIKE(c.COLUMN_NAME, v_regex, 'i')
                    ) d;
                    SET @update_sql = CONCAT('UPDATE ', @tbl, ' SET ', v_cols,
                                             ' ORDER BY ', v_uniq_cols, ' ',
                                             IF(@shift_delta >= 0, 'DESC', 'ASC'));
                    SET @count_sql = CONCAT('SELECT COUNT(*) INTO @affected FROM ', @tbl);
                    PREPARE stmt FROM @count_sql;
                    EXECUTE stmt;
                    DEALLOCATE PREPARE stmt;
                    IF p_execute THEN
                        PREPARE stmt FROM @update_sql;
                        EXECUTE stmt;
                        DEALLOCATE PREPARE stmt;
                    END IF;
                    SET v_count = v_count + @affected;
                    INSERT INTO tmp_date_shift_preview
                    VALUES (v_table, v_anchor, IF(p_execute, 'uniform-executed', 'uniform-preview'),
                            v_fields, @affected, @update_sql);
                    SET v_tables = v_tables + 1;
                END IF;
            ELSE
                -- é€è¡Œéšæœºï¼šæ¯è¡Œä¸€ä¸ªéšæœºç›®æ ‡æ—¶é—´ï¼Œä»¥é”šç‚¹æ•´ä½“平移
                SELECT GROUP_CONCAT(
                           CONCAT('`', REPLACE(c.COLUMN_NAME,'`','``'), '` = CASE WHEN `',
                                  REPLACE(c.COLUMN_NAME,'`','``'), '` IS NULL THEN NULL ELSE ',
                                  CASE WHEN c.DATA_TYPE = 'date' THEN 'DATE(' ELSE '' END,
                                  'DATE_ADD(`', REPLACE(c.COLUMN_NAME,'`','``'), '`, INTERVAL TIMESTAMPDIFF(SECOND, `',
                                  REPLACE(v_anchor,'`','``'), '`, __new_anchor) SECOND)',
                                  CASE WHEN c.DATA_TYPE = 'date' THEN ')' ELSE '' END, ' END')
                           ORDER BY c.ORDINAL_POSITION SEPARATOR ', ')
                INTO v_cols
                FROM information_schema.COLUMNS c
                WHERE c.TABLE_SCHEMA = p_schema AND c.TABLE_NAME = v_table
                  AND c.DATA_TYPE IN ('date','datetime','timestamp')
                  AND c.EXTRA NOT LIKE '%VIRTUAL GENERATED%' AND c.EXTRA NOT LIKE '%STORED GENERATED%'
                  AND c.EXTRA NOT LIKE '%on update%'
                  AND REGEXP_LIKE(c.COLUMN_NAME, v_regex, 'i');
                DROP TEMPORARY TABLE IF EXISTS tmp_shift_row_dates;
                SET @sql = CONCAT('CREATE TEMPORARY TABLE tmp_shift_row_dates AS SELECT `',
                                  REPLACE(v_pk,'`','``'), '` AS __pk, DATE_ADD(?, INTERVAL FLOOR(RAND() * ',
                                  v_seconds + 1, ') SECOND) AS __new_anchor FROM ', @tbl,
                                  ' WHERE ', @anchor, ' IS NOT NULL');
                IF p_execute THEN
                    PREPARE stmt FROM @sql;
                    SET @start_date = p_start;
                    EXECUTE stmt USING @start_date;
                    DEALLOCATE PREPARE stmt;
                    SET @update_sql = CONCAT('UPDATE ', @tbl, ' t JOIN tmp_shift_row_dates s ON t.`',
                                             REPLACE(v_pk,'`','``'), '` = s.__pk SET ',
                                             REPLACE(v_cols, '__new_anchor', 's.__new_anchor'));
                    SET @count_sql = 'SELECT COUNT(*) INTO @affected FROM tmp_shift_row_dates';
                    PREPARE stmt FROM @count_sql;
                    EXECUTE stmt;
                    DEALLOCATE PREPARE stmt;
                    PREPARE stmt FROM @update_sql;
                    EXECUTE stmt;
                    DEALLOCATE PREPARE stmt;
                    SET v_count = v_count + @affected;
                    INSERT INTO tmp_date_shift_preview
                    VALUES (v_table, v_anchor, 'random-executed', v_fields, @affected, @update_sql);
                    DROP TEMPORARY TABLE tmp_shift_row_dates;
                ELSE
                    SET @count_sql = CONCAT('SELECT COUNT(*) INTO @affected FROM ', @tbl, ' WHERE ', @anchor, ' IS NOT NULL');
                    PREPARE stmt FROM @count_sql;
                    EXECUTE stmt;
                    DEALLOCATE PREPARE stmt;
                    INSERT INTO tmp_date_shift_preview
                    VALUES (v_table, v_anchor, 'random-preview', v_fields, @affected,
                            CONCAT('Will shift ', v_fields, ' date columns per row; anchor=', v_anchor));
                END IF;
                SET v_tables = v_tables + 1;
            END IF;
        END inner_block;
    END LOOP;
    CLOSE cur;
    SELECT * FROM tmp_date_shift_preview ORDER BY shift_mode, table_name;
    SELECT p_schema AS database_name, p_execute AS executed,
           v_tables AS tables_processed, v_count AS rows_targeted;
END$$
DELIMITER ;