Files
x-server-mobile/docs/TEST_REPORT_APPROVAL_20260301.md
root 7598e363f2 docs: 添加审批流程测试报告 (2026-03-01)
- 测试环境: 生产环境 (45.153.130.80)
- 测试场景: 会签、拒绝、重复操作防护、撤销
- 测试结果: 4个场景全部通过
- 待解决问题: 飞书消息 emoji 编码问题
2026-03-01 09:18:44 +08:00

372 lines
10 KiB
Markdown
Raw Permalink 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.
# 审批流程测试报告
**测试日期**: 2026-03-01
**测试版本**: feature/debug-feishu-message (commit: 1e2d337)
**测试环境**: 生产环境 (45.153.130.80)
**测试人员**: Claude
---
## 📋 测试环境配置
### 服务器信息
```
IP: 45.153.130.80
域名: uniservice-mobile.hnxinyouhe.com
后端: x-server-backend-prod (PHP 8.5.3)
数据库: 1Panel-mysql-XyxS (MySQL)
```
### 节点配置
```
节点ID: 7
节点名称: 李佳博
节点类型: and_approve (会签)
所属流程: 2 (出差审批)
节点顺序: 0
```
### 测试用户
```
申请人: 李佳博 (ID: 1)
审批人:
- 陈兰 (ID: 2)
- 陈标 (ID: 3)
```
---
## 🧪 测试场景
### 场景1: 会签流程 - 所有人通过
#### 流程图
```
提交申请 → 审批人1通过 → 审批人2通过 → 订单完成
↓ ↓(pending) ↓(approved)
pending 1/2 已通过 2/2 已通过
```
#### 测试步骤
| 步骤 | 操作 | 参数 | 预期结果 | 实际结果 | 状态 |
|------|------|------|----------|----------|------|
| 1 | 创建审批订单 | `form_data.approvers = [2, 3]`<br>`current_nodes = [7]`<br>`status = "pending"` | 订单ID创建成功 | 订单ID=14 | ✅ |
| 2 | 审批人1通过 | `order_id = 14`<br>`approver_id = 2`<br>`action = "approve"` | 状态=pending<br>已审批=1/2 | 状态=pending<br>已审批=1/2 | ✅ |
| 3 | 审批人2通过 | `order_id = 14`<br>`approver_id = 3`<br>`action = "approve"` | 状态=approved<br>已审批=2/2 | 状态=approved<br>已审批=2/2 | ✅ |
| 4 | 验证审批记录 | `order_id = 14`<br>`action = "approve"` | 2条记录 | 2条记录 | ✅ |
#### 审批记录
```
ID 1: 陈兰 (ID:2) 在节点 7 审批通过
ID 2: 陈标 (ID:3) 在节点 7 审批通过
```
#### 测试代码
```php
// 创建订单
$order = ApprovalOrder::create([
'business_type' => 'business_trip',
'business_id' => 100,
'title' => '会签测试',
'applicant_id' => 1,
'status' => 'pending',
'current_nodes' => [7],
'flow_id' => 2,
'form_data' => ['approvers' => [2, 3]],
]);
// 审批人1通过
$service->approve(14, 2);
// 审批人2通过
$service->approve(14, 3);
```
---
### 场景2: 拒绝流程 - 任一人拒绝
#### 流程图
```
提交申请 → 审批人1拒绝 → 订单结束
↓ ↓(rejected)
pending 流程终止
```
#### 测试步骤
| 步骤 | 操作 | 参数 | 预期结果 | 实际结果 | 状态 |
|------|------|------|----------|----------|------|
| 1 | 创建审批订单 | `form_data.approvers = [2, 3]`<br>`status = "pending"` | 订单ID创建成功 | 订单ID=15 | ✅ |
| 2 | 审批人1拒绝 | `order_id = 15`<br>`approver_id = 2`<br>`action = "reject"`<br>`comment = "测试拒绝"` | 状态=rejected | 状态=rejected | ✅ |
| 3 | 验证审批记录 | `order_id = 15`<br>`action = "reject"` | 1条拒绝记录 | 1条拒绝记录 | ✅ |
#### 审批记录
```
ID 1: 陈兰 (ID:2) 在节点 7 审批拒绝 - 原因: "测试拒绝"
```
#### 测试代码
```php
$order = ApprovalOrder::create([
'business_type' => 'business_trip',
'business_id' => 101,
'title' => '拒绝测试',
'applicant_id' => 1,
'status' => 'pending',
'current_nodes' => [7],
'flow_id' => 2,
'form_data' => ['approvers' => [2, 3]],
]);
// 审批人1拒绝
$service->reject(15, 2, '测试拒绝');
```
---
### 场景3: 重复操作防护 - 同一人不能重复审批
#### 流程图
```
提交申请 → 审批人1通过 → 审批人1再次通过
↓ ↓(success) ↓(异常)
pending 记录已创建 抛出异常
```
#### 测试步骤
| 步骤 | 操作 | 参数 | 预期结果 | 实际结果 | 状态 |
|------|------|------|----------|----------|------|
| 1 | 创建审批订单 | `form_data.approvers = [2, 3]`<br>`status = "pending"` | 订单ID创建成功 | 订单ID=16 | ✅ |
| 2 | 审批人1第一次通过 | `order_id = 16`<br>`approver_id = 2` | 操作成功 | 操作成功 | ✅ |
| 3 | 审批人1第二次通过 | `order_id = 16`<br>`approver_id = 2` | 抛出异常 | "您已经通过过此申请" | ✅ |
#### 异常信息
```
Exception: 您已经通过过此申请,无需重复操作
```
#### 测试代码
```php
$order = ApprovalOrder::create([
'business_type' => 'business_trip',
'business_id' => 102,
'title' => '重复操作测试',
'applicant_id' => 1,
'status' => 'pending',
'current_nodes' => [7],
'flow_id' => 2,
'form_data' => ['approvers' => [2, 3]],
]);
try {
$service->approve(16, 2); // 第一次 - 成功
$service->approve(16, 2); // 第二次 - 异常
} catch (Exception $e) {
// 捕获重复操作异常
}
```
---
### 场景4: 撤销流程 - 申请人可撤销
#### 流程图
```
提交申请 → 申请人撤销 → 订单结束
↓ ↓(cancelled)
pending 流程终止
```
#### 测试步骤
| 步骤 | 操作 | 参数 | 预期结果 | 实际结果 | 状态 |
|------|------|------|----------|----------|------|
| 1 | 创建审批订单 | `applicant_id = 1`<br>`status = "pending"` | 订单ID创建成功 | 订单ID=17 | ✅ |
| 2 | 申请人撤销 | `order_id = 17`<br>`user_id = 1` | 状态=cancelled | 状态=cancelled | ✅ |
| 3 | 验证撤销记录 | `order_id = 17`<br>`action = "cancel"` | 1条撤销记录 | 1条撤销记录 | ✅ |
#### 审批记录
```
ID 1: 李佳博 (ID:1) 撤销了申请
```
#### 测试代码
```php
$order = ApprovalOrder::create([
'business_type' => 'business_trip',
'business_id' => 103,
'title' => '撤销测试',
'applicant_id' => 1,
'status' => 'pending',
'current_nodes' => [7],
'flow_id' => 2,
'form_data' => ['approvers' => [2, 3]],
]);
// 申请人撤销
$service->cancel(17, 1);
```
---
## 📊 核心逻辑验证
### 节点完成判断逻辑
**代码位置**: `ApprovalService::isNodeCompleted()`
```php
private function isNodeCompleted(int $orderId, int $nodeId): bool
{
$node = ApprovalNode::findOrFail($nodeId);
$records = ApprovalRecord::where('order_id', $orderId)
->where('node_id', $nodeId)
->where('action', 'approve')
->get();
// 从 form_data 获取实际审批人列表
$order = ApprovalOrder::find($orderId);
$formData = $order->form_data ?? [];
$approvers = $formData['approvers'] ?? [];
if (!empty($approvers) && is_array($approvers[0] ?? null) && isset($approvers[0]['id'])) {
$approverIds = array_column($approvers, 'id');
} else {
$approverIds = $approvers;
}
if ($node->type === ApprovalNode::TYPE_AND_APPROVE) {
// 会签:需要所有人通过
return count($records) >= count($approverIds);
} elseif ($node->type === ApprovalNode::TYPE_OR_APPROVE) {
// 或签:任一人通过即可
return count($records) >= 1;
} else {
// 单人审批:需要一人通过
return count($records) >= 1;
}
}
```
**验证结果**: ✅ 通过
| 节点类型 | 完成条件 | 测试结果 |
|----------|----------|----------|
| and_approve (会签) | 已审批人数 >= 总审批人数 | ✅ |
| or_approve (或签) | 已审批人数 >= 1 | ⏳ 未测试 |
| approver (单人) | 已审批人数 >= 1 | ⏳ 未测试 |
---
## 🔔 消息发送功能
### 当前状态
- ⚠️ **飞书消息发送失败** (emoji 编码问题)
### 错误信息
```
ErrCode: 200621;
ErrMsg: parse card json err, please check whether the card json is correct
```
### 原因分析
PHP 的 `json_encode()` 默认将 emoji 编码成 Unicode 转义序列:
- `🔔` → `\ud83d\udd14`
- `📋` → `\ud83d\udccb`
飞书 API 不接受这种编码格式。
### 待修复项
1. 修改 `FeishuNotificationService` 的 JSON 编码选项
2. 使用 `JSON_UNESCAPED_UNICODE` 标志
---
## 🗄️ 数据库表状态
### feishu_message_log
```sql
CREATE TABLE IF NOT EXISTS feishu_message_log (
id bigint unsigned NOT NULL AUTO_INCREMENT,
action varchar(20) NOT NULL COMMENT '操作类型:send=发送, update=更新, update_card=更新卡片',
order_id bigint unsigned NOT NULL COMMENT '审批订单ID',
node_id bigint unsigned DEFAULT NULL COMMENT '审批节点ID',
user_id bigint unsigned DEFAULT NULL COMMENT '用户ID',
message_id varchar(64) DEFAULT NULL COMMENT '飞书消息ID',
status varchar(10) NOT NULL COMMENT '状态:success=成功, failed=失败',
request_data text DEFAULT NULL COMMENT '请求数据(JSON)',
response_data text DEFAULT NULL COMMENT '响应数据(JSON)',
error_message text DEFAULT NULL COMMENT '错误信息',
created_at timestamp NULL DEFAULT CURRENT_TIMESTAMP,
updated_at timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY idx_order_id (order_id),
KEY idx_action (action),
KEY idx_status (status),
KEY idx_created_at (created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='飞书消息日志表';
```
**状态**: ✅ 已创建并包含 `updated_at` 字段
---
## ✅ 测试结论
### 通过的测试
1. ✅ 会签流程 - 所有人通过后订单完成
2. ✅ 拒绝流程 - 任一人拒绝后订单结束
3. ✅ 重复操作防护 - 同一人不能重复审批
4. ✅ 撤销流程 - 申请人可撤销申请
5. ✅ 审批记录正确保存
6. ✅ 节点完成判断逻辑正确
### 待解决的问题
1. ⚠️ 飞书消息发送失败 (emoji 编码问题)
2. ⏳ 或签 (or_approve) 场景未测试
3. ⏳ 单人审批 (approver) 场景未测试
4. ⏳ 多节点流转未测试
### 建议
1. 优先修复飞书消息 emoji 编码问题
2. 补充或签和单人审批场景测试
3. 测试多节点流转场景
4. 测试消息卡片更新功能(或签/单人审批完成后)
---
## 📝 附录
### 测试文件位置
```
/www/x-server-mobile/docs/TEST_REPORT_APPROVAL_20260301.md
```
### 相关代码文件
```
backend/app/service/ApprovalService.php # 审批服务
backend/app/service/FeishuNotificationService.php # 飞书通知服务
backend/app/model/ApprovalOrder.php # 审批订单模型
backend/app/model/ApprovalRecord.php # 审批记录模型
backend/app/model/ApprovalNode.php # 审批节点模型
backend/app/model/FeishuMessageLog.php # 消息日志模型
```
### 测试执行命令
```bash
# 会签流程测试
docker exec x-server-backend-prod php /app/test_approval_logic.php
# 全场景测试
docker exec x-server-backend-prod php /app/test_approval_all.php
```
---
**报告生成时间**: 2026-03-01 09:18:00
**测试执行者**: Claude Code
**审核状态**: 待审核