Files
wecom_it_smart_desk/docs/02-技术文档/技术架构/技术验证-U-1-审批与工单操作闭环可行性-v1.0.md
T
Simon 9292f41763 feat(agent/backend): 坐席端审批线降级跳转 + 回调最终一致回写
实现 Phase 0 审批线 T01+T02(依据 PRD-REQ-坐席-011 + U-1 技术验证结论)。

前端(T01):
- TaskDetailView.handleAction 移除 mock toast,审批类仅 console.info
- ApprovalDetail 通过/拒绝/转交 + 打开按钮接线为企微审批深链真实跳转(<a target="_blank">)
- useWebSocket 新增 todo_status_changed 实时刷新分支

后端(T02):
- approval.py 新增 writeback_approval_todo_status 主入口 + /approval/callback 接线
- approval_webhook.py 新增 _writeback_agent_todo 复用回调→WS 推送通道
- 以 approval:{sp_no} 为关联键,缓存就地改写 + 7天快照 + WS 推送,最终一致

测试:src/backend/tests/test_approval_todo_writeback.py(51 例全绿)
文档:PRD-REQ-坐席-011 v0.1、技术验证-U-1 v1.0
2026-08-09 00:46:31 +08:00

297 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 技术验证 U-1:坐席 PC Web 端审批与工单操作闭环可行性
> **文档版本**v1.0
> **验证人**:高见远(架构师)
> **验证日期**2026-08-08
> **关联 PRD**`PRD-REQ-坐席-011-统一工作队列重构-v0.1.md`U-1,§6 Phase 0,§7
> **任务性质**:技术可行性验证(**未修改任何业务源码**)
> **代码核查范围**:仅 `src/` 下活跃代码(`frontend-agent/`、`backend/`),忽略根目录废弃的 `frontend/`、`backend_bak/`。
---
## 0. 结论先行(可行性判定矩阵)
**核心结论**:坐席在 PC Web 端**无法**通过服务端接口直接完成审批单的「通过 / 拒绝 / 转交」动作;工单的「接单 / 开始处理 / 结单 / 转派」动作因 ITSM 操作类 OpenAPI 尚未落地,**暂不可判定可行**,需向平台方索取文档与权限。两者均**不能**在 Phase 0 内实现"服务端真实闭环",必须分级降级。
| 对象 | 动作 | 可行性判定 | 依据类型 | 关键约束 |
|------|------|-----------|---------|---------|
| 企微审批 | 通过(同意) | **需降级跳转** | 官方文档 | 企微无"代审批人执行同意"的服务端接口 |
| 企微审批 | 拒绝 | **需降级跳转** | 官方文档 | 同上 |
| 企微审批 | 转交 | **需降级跳转** | 官方文档 | 同上 |
| ITSM 工单 | 接单 | **待外部确认** | 代码实证 + 待确认 | 操作类 OpenAPI 未实现、未文档化 |
| ITSM 工单 | 开始处理 | **待外部确认** | 代码实证 + 待确认 | 同上 |
| ITSM 工单 | 结单 | **待外部确认** | 代码实证 + 待确认 | 同上 |
| ITSM 工单 | 转派 | **待外部确认** | 代码实证 + 待确认 | 同上 |
**判定值枚举说明**
- `可服务端闭环`:服务端 API 可代替坐席真实生效,无需跳转原系统。
- `需降级跳转`:服务端无代操作能力,必须跳转原系统(企微客户端 / ITSM Web)由坐席本人操作。
- `待外部确认`:能力是否存在取决于外部平台方提供的接口/权限,项目内暂无实证。
- `不可行`:经核查确认任何路径均无法达成。
**对 Phase 0 的整体影响(一句话)**Phase 0 的"审批操作接真实接口"**无法满足**"操作后外部系统状态真实变更"的验收标准,必须改为"降级跳转 +(可选)webhook 回写";工单部分**阻塞于外部依赖**,须等 ITSM 文档/权限到位方可进入闭环开发,否则同样降级为跳转。
---
## 1. V-1 企微审批:服务端代审批能力核查
### 1.1 企微审批的两套接口体系(背景,官方文档)
企业微信的审批能力在代码与文档中存在**两套独立体系**,需分别核查:
1. **「审批应用」体系**(企业微信「审批」应用自带的审批流)
- 回调事件:`sys_approval_change`
- 服务端接口:`gettemplatedetail``applyevent``getapprovaldetail``getapprovaldata`、批量获取审批编号。
2. **「审批流程引擎」体系**(自建应用内嵌审批,走 JS-SDK)
- 回调事件:`open_approval_change`
- 前端能力:`wx.invoke('thirdPartyOpenPage', {oaType:'10001'|'10002'})`
-`wx.agentConfig`(应用身份) + 企微客户端环境。
**核查结论**:无论哪套体系,**服务端均无"代替审批人执行同意/拒绝/转交"的接口**。(依据类型:官方文档)
### 1.2 官方服务端接口清单(代码实证 + 官方文档)
项目内 `src/backend/app/api/approval.py` 已实现/封装的企微审批服务端调用,经逐行核对**全部为"提交/查询/回调",无一为"代审批动作"**
| 函数 | 行号 | 对应企微 API | 性质 |
|------|------|-------------|------|
| `get_approval_token` | `approval.py:392` | 获取 access_token | 鉴权 |
| `get_template_detail` | `approval.py:401` | `oa/gettemplatedetail` | 查询模板 |
| `submit_approval_api` | `approval.py:423` | `oa/applyevent` | **提交申请**(非审批) |
| `get_approval_detail` | `approval.py:472` | `oa/getapprovaldetail` | 查询详情 |
| `get_approval_data` | `approval.py:505` | `oa/getapprovaldata` | 查询列表 |
| `/approval/jump` | `approval.py:840` | — | 生成跳转链接 |
| `/approval/submit` | `approval.py:861` | `oa/applyevent` | 提交申请 |
| `/approval/callback` | `approval.py:902` | `sys_approval_change` | 状态变化**回调**(当前仅 log,状态未回写业务) |
**官方文档佐证**:企微「审批应用」服务端 API 文档(https://developer.work.weixin.qq.com/document/path/91854)列出的全部接口即上述 5 类(模板详情、提交申请、状态变化回调、批量获取编号、获取详情),**不含任何"审批/驳回/转交"动作接口**。(依据类型:官方文档)
### 1.3 为什么 PC Web 端也无法用 JS-SDK 兜底
PRD U-1 提到现有交互依赖 `wx.invoke('thirdPartyOpenPage', {oaType:'10001'})` 原生表单。核对该能力约束:
- `wx.config` / `wx.agentConfig``wx.invoke` **仅在企业微信客户端内嵌的 H5 中生效**,普通 PC 浏览器调用无效(官方 JS-SDK 文档:https://developer.work.weixin.qq.com/document/path/94345;社区多源佐证)。
- `wxwork://launch?launch_code=xxx` URL Scheme **仅支持 Windows / Mac 唤起客户端打开"个人聊天窗口"**,不支持跳转审批详情页(官方 Scheme 文档:https://developer.work.weixin.qq.com/document/path/94345)。
- 坐席工作台是独立的 **PC Web 应用**(非企微内嵌 H5),因此上述原生表单能力**不可用**。(依据类型:官方文档 + 推断,推断部分为"坐席工作台非企微内嵌"——该事实以 PRD 上下文与项目前端独立部署形态为据)
### 1.4 降级跳转方案(推荐)
既然服务端无代审批接口,审批操作闭环采用**"跳转 + 状态回写"降级**
1. 前端审批详情页提供"在企微审批中打开"链接,跳转至企微审批管理后台/客户端由审批人本人在原系统操作。
- 代码实证:`src/frontend-agent/src/components/chat/task/ApprovalDetail.vue:78-86` 已使用该跳转模式(`https://app.work.weixin.qq.com/wework_admin/approval_v3#/?sp_id=...`)。
2. 操作后状态由**原系统回调**回写服务台:
- 代码实证:`src/backend/app/api/approval_webhook.py` 已具备接收企微审批状态变化并向前端 WebSocket 推送的能力(`sys_approval_change` → 状态变化 → WS 推送)。
- **缺口(待确认/待补全)**`approval.py:902``/approval/callback` 当前仅 `logger`,未将 `status_change_event`(同意=2/驳回=3/转审=4)回写业务状态;需在 Phase 0 补一段"回调 → 更新本地待办状态 → WS 通知"。
> ⚠️ 严格说,降级跳转方案**不满足** PRD Phase 0 验收标准"操作后外部系统状态真实变更且前端反馈一致"中的"前端直接闭环"——因为动作发生在原系统。但它是 U-1 不可行前提下的**唯一可行路径**,且状态可通过 webhook 回写实现"最终一致"。(依据类型:推断)
---
## 2. V-2 ITSM 工单:操作类 OpenAPI 核查
### 2.1 项目内 ITSM 调用现状(代码实证)
`src/backend/app/services/itsm_service.py` 全文件 261 行,**仅实现只读查询,无任何写操作**:
| 方法 | 行号 | 性质 | 说明 |
|------|------|------|------|
| `get_todo_list` | `itsm_service.py:113` | 读(占位) | 返回空列表 + 日志告警,API 待实现 |
| `get_todo_detail` | `itsm_service.py:132` | 读 | 调 `workitem/detail` |
| `_do_post` | `itsm_service.py:167` | 通用 POST | 带 `ITSMSigner` 签名发送,可复用于写 |
| `_get_workitem_detail` | `itsm_service.py:196` | 读 | `POST /openapi/v1/process/workitem/detail` |
| `_map_to_todo_item` | `itsm_service.py:217` | 映射 | 详情 → 统一 `TodoItemData` |
**关键事实**:已知的唯一 ITSM 端点 `POST /openapi/v1/process/workitem/detail` 是**只读详情接口**(代码实证 `itsm_service.py:27,196-215`)。接单 / 开始处理 / 结单 / 转派等**写操作端点路径、请求体 schema、成功/错误码在项目内完全不存在**。(依据类型:代码实证)
### 2.2 签名机制可复用(利好)
`src/backend/app/utils/itsm_signer.py``ITSMSigner.compute_signature(app_id, timestamp, app_secret, biz_data)` 是**纯静态工具**SHA1(`appSecret`+`appId`+`timestamp`+`bizData` 升序拼接 → `quote_plus` → SHA1 → 大写 hex)。该签名**不区分读写**,一旦获得写操作端点与请求体,可直接复用 `ITSMService._do_post``itsm_service.py:167`)发起写请求,无需新增鉴权逻辑。(依据类型:代码实证 + 推断,推断部分为"写操作可走同一签名/同一 `_do_post`"——基于签名与端点解耦的现状合理推断,但需 ITSM 平台方确认写接口是否复用同一套签名)
### 2.3 操作类 API 缺失的外部依赖(待确认清单)
工单动作是否可服务端闭环,**取决于 ITSM 平台方提供的接口与权限**,项目内无实证。需向平台方索取:
| 待确认项 | 说明 | 当前项目状态 |
|---------|------|-------------|
| ITSM 操作类 OpenAPI 文档 | 接单/开始处理/结单/转派 的端点、方法、请求体、响应码 | PRD-审批-001 Q1「ITSM API 完整接口规范」⏳待抓包 |
| `ITSM_APP_ID` / `ITSM_APP_SECRET` | 写操作所需的应用凭证 | PRD-审批-001 Q2.1 ⏳待申请;`docker-compose.yml` 未配置 |
| 操作类权限开通 | 当前 app_id 是否具备写权限 | 未知,需平台方确认 |
| 测试账号 / 测试工单 | 用于闭环联调 | 未提供 |
| 写操作成功/冲突语义 | 例如重复接单是否幂等、并发转派冲突码 | 未知 |
> 注:PRD-审批-001 已明确 Q5「代办状态更新交互」✅已确认:仅展示 + 跳转,不在服务台内直接操作。这与本验证"工单动作待外部确认"不冲突——Q5 是**产品决策**(先不内嵌操作),本验证是**技术可行性**(若要做内嵌,接口是否存在)。
### 2.4 结论
工单 4 动作**全部"待外部确认"**。在当前无任何操作类接口实证的前提下,**不能承诺服务端闭环**;若 ITSM 平台方提供写接口且权限到位,则因签名可复用,开发成本较低(主要工作量在补全 `ITSMService` 写方法 + 前端动作按钮接真实接口)。(依据类型:代码实证 + 待确认)
---
## 3. V-3 权限与身份模型
### 3.1 现状:操作以"应用身份"发起
| 系统 | 当前调用身份 | 代码实证 |
|------|------------|---------|
| 企微审批(读/提交) | 应用 access_tokenIT 支持应用 Secret | `approval.py:392` `get_approval_token` |
| ITSM(读) | app_id + SHA1 签名(应用级) | `itsm_service.py:106-107,180` |
服务端调用均使用**应用身份**,不携带坐席个人身份令牌。(依据类型:代码实证)
### 3.2 工单:坐席个人身份如何传递(待确认)
`ITSMService._get_workitem_detail` 在请求体中传入 `executor`(坐席 userid):
```python
# itsm_service.py:196-215
body = {
"process_instance_id": process_instance_id,
"executor": executor, # = self.agent_useriditsm_service.py:103
}
```
即服务台**主动声明**执行人为当前坐席 userid。(依据类型:代码实证)
但 ITSM 是否据此将" executor"认作**真实操作人并写入审计日志**,取决于 ITSM 侧实现——当前仅详情查询用到该字段,写操作未实现,**无法验证**。(依据类型:待确认)
### 3.3 审批:个人身份不可绕过(不可行)
企微审批的"同意/拒绝/转交"依法规与产品逻辑必须由**审批人本人在客户端**操作,服务端无代审批接口(见 §1.2)。因此 PC Web 代审批在**身份与合规层面不可行**,只能由审批人本人跳转原系统操作。(依据类型:官方文档 + 推断)
### 3.4 审计追溯结论
- **降级跳转方案下**:动作发生在原系统(企微/ITSM),审计由对方负责。服务台仅能记录"跳转动作"事件,**无法闭环确认结果**,需依赖 webhook / 回调回写状态(见 §1.4、§4.2)。
- **若合规要求"个人身份可追溯"**ITSM 侧需确认是否支持 impersonation 或坐席级令牌;企微审批侧 PC Web 代审批不可行,此路不通。(依据类型:推断 + 待确认)
---
## 4. V-4 结论与方案
### 4.1 可行性判定汇总(同 §0 矩阵,附依据)
- 审批 3 动作:`需降级跳转`(官方文档实证:无服务端代审批接口 + PC Web 无 JS-SDK 能力)。
- 工单 4 动作:`待外部确认`(代码实证:仅只读;待平台方提供写接口/权限/账号)。
### 4.2 分级降级方案
```mermaid
flowchart TD
A[坐席在 PC Web 服务台点击操作] --> B{动作类型}
B -->|审批:通过/拒绝/转交| C[降级:跳转企微审批原系统]
B -->|工单:接单/处理/结单/转派| D{ITSM 写接口是否到位?}
D -->|否| E[降级:跳转 ITSM Web 处理]
D -->|是| F[服务端调用 ITSM OpenAPI 真实闭环]
C --> G[企微 sys_approval_change 回调]
G --> H[/approval/callback 回写状态 + WS 推送/]
E --> I[ITSM 状态变化]
I --> J[待确认:ITSM 是否提供状态回调/Webhook]
F --> K[前端按接口返回分派 成功/失败/冲突 三态]
```
**降级层级**
1. **Level 0(立即可执行,不阻塞)**:审批全量降级跳转;工单在 ITSM 写接口未到位前同样降级跳转。前端按钮接真实"跳转链接"而非 mock toast。
2. **Level 1(需补开发)**:审批跳转后通过 `approval_webhook.py` 已具备的回调 → WS 推送实现**状态最终一致**(需补全 `approval.py:902` 回写逻辑)。
3. **Level 2(依赖外部)**:ITSM 写接口到位后,升级为服务端真实闭环,移除跳转降级。
### 4.3 对 PRD Phase 0 的影响与修订建议
PRD §6 Phase 0 原表:
| 原任务 | 原说明 | 修订后(基于本验证) |
|--------|--------|---------------------|
| 工单操作接真实接口 | 接单/开始处理/结单/转派 → ITSM API | **降级为跳转 ITSM**,并标注"服务端闭环待 ITSM 接口到位后升级"(受外部依赖阻塞) |
| 审批操作接真实接口 | 通过/拒绝/转交 → 企微审批(受 U-1 约束,方案未定) | **明确为"降级跳转 + webhook 回写"**,U-1 判定为"不可服务端闭环" |
| 失败态处理 | 移除无条件 `ElMessage.success` | 维持;跳转方案下改为"跳转成功提示 + 状态回写后刷新",仍禁止以 toast 作为验收依据 |
**对 Phase 0 阻塞关系的影响**
- 审批部分:**不阻塞** Phase 0 启动——降级跳转方案可立即落地,原系统回调回写可并行开发。
- 工单部分:**受外部依赖阻塞**——若坚持"服务端真实闭环"则必须等 ITSM 文档/权限;若接受降级跳转则与审批同步落地。
- 建议 PRD 将 U-1 结论由"待验证"改为"**已验证:审批不可服务端闭环,须降级跳转**",并新增 U-1.1「ITSM 写接口到位时间」作为工单闭环的外部阻塞项。
### 4.4 有序任务分解(供 Engineer 实施)
> 以下任务**仅含配置/前端/回调补完**,**不含任何工单写操作实现**(因接口待确认)。工单闭环任务在外部依赖到位后单独追加。
| 任务 ID | 任务名 | 涉及文件(相对路径) | 依赖 | 优先级 |
|---------|--------|---------------------|------|--------|
| T01 | 审批/工单详情页动作改为真实跳转链接(移除 mock) | `src/frontend-agent/src/components/chat/task/ApprovalDetail.vue``TicketDetail.vue``src/frontend-agent/src/components/chat/TaskDetailView.vue``handleAction` 不再仅 toast | — | P0 |
| T02 | 补全企微审批回调状态回写(sys_approval_change → 本地状态 → WS | `src/backend/app/api/approval.py``/approval/callback` 现状 `approval.py:902`)、`src/backend/app/api/approval_webhook.py` | T01 | P0 |
| T03 | ITSM 工单详情页跳转链接接入(wecom ITSM Web 深链) | `src/frontend-agent/src/components/chat/task/TicketDetail.vue``src/backend/app/services/itsm_service.py`(补充 `itsm_web_url` 构造) | T01 | P1 |
| T04 | 失败/冲突三态反馈(移除无条件 success) | `TaskDetailView.vue`、各 Detail 子组件、`src/backend/app/api/todo_items.py``PUT /{id}/status` 现状 `todo_items.py:158` display_only 保持不变,仅前端反馈改造) | T01 | P1 |
| T05 | 外部依赖跟进:向 ITSM 平台方索取写接口文档 + 申请 app_id/secret + 测试账号(阻塞工单闭环) | `docs/01-产品文档/06-审批与待办/PRD-REQ-审批-001-ITSM工单跳转-v1.0.md`(更新 Q1/Q2.1)、`docker-compose.yml`(补 ITSM_APP_ID/SECRET | — | P0(外部) |
**依赖顺序图**
```mermaid
graph TD
T01[T01 跳转链接改造] --> T02[T02 审批回调回写]
T01 --> T03[T03 ITSM 跳转接入]
T01 --> T04[T04 三态反馈]
T05[T05 ITSM 外部依赖] -.阻塞.-> T03
```
> 注:T05 为**外部协调任务**(非代码),其完成是工单服务端闭环(未来追加的 T06+)的前置,但不阻塞审批降级与跳转类任务。
---
## 5. 证据清单与方法说明
| 依据类型 | 含义 | 本文使用处 |
|---------|------|-----------|
| 官方文档 | 企业微信/ITSM 官方接口文档,附链接 | §1.2、§1.3、§1.4 |
| 代码实证 | 项目 `src/` 内源码,附 `文件:行号` | §1.2、§2.1、§3.1、§3.2 |
| 推断 | 基于上述事实的合理推论,**非证实事实** | §1.4、§2.2、§3.4、§4.2 |
| 待确认 | 需外部平台方/产品提供信息方可定论 | §2.3、§3.2、§3.4、T05 |
**核查边界声明**
- 未运行任何代码、未修改任何业务源码,仅静态阅读与官方文档交叉验证。
- 企微官方文档链接为验证时引用的权威来源;若文档版本更新导致接口增减,需重新核对。
- ITSM 操作类接口结论为"待外部确认",不表示"不可行";结论仅在"项目内当前无任何写接口实证"前提下成立。
---
## 6. 附:关键调用链路时序图(降级闭环)
### 6.1 审批降级跳转 + 状态回写(目标态)
```mermaid
sequenceDiagram
participant A as 坐席(PC Web)
participant F as 前端服务台
participant B as 后端
participant W as 企微审批(原系统)
participant H as approval_webhook/WS
A->>F: 点击"在企微审批中打开"
F->>W: 跳转 approval_v3#/?sp_id=... (新标签页)
Note over A,W: 审批人在企微客户端/管理后台执行 同意/拒绝/转交
W-->>B: sys_approval_change 回调 (status_change_event: 2/3/4)
B->>B: /approval/callback 回写本地待办状态 (待补全 approval.py:902)
B->>H: WebSocket 推送状态变更
H->>F: 待办状态刷新
F-->>A: 前端状态与企微一致
```
### 6.2 工单跳转(ITSM 接口未到位时)
```mermaid
sequenceDiagram
participant A as 坐席(PC Web)
participant F as 前端服务台
participant I as ITSM Web(原系统)
A->>F: 点击"在 ITSM 中打开"
F->>I: 跳转 ITSM workitem 详情深链 (新标签页)
Note over A,I: 坐席在 ITSM 内执行 接单/处理/结单/转派
Note over I: 状态变化由 ITSM 负责 (回写机制待确认)
```
---
*文档结束。本验证所有"推断"与"待确认"项均已明确标注,未将推断作为既成事实陈述。*