docs: 更新 PROJECT_ISSUES.md 记录审批相关问题
添加问题记录: - 问题25:Tab 状态持久化问题 - 问题26:我已审批列表不显示(ApprovalService 数据源错误) - 问题27:审批流程不自动跳过通知节点 - 问题28:架构问题总结 - 数据模型混淆 - 问题29:数据库完整性建议 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -696,3 +696,246 @@ CREATE TABLE IF NOT EXISTS `feishu_login_session` (
|
||||
### 配置要求
|
||||
- 飞书应用 `tenant_key`: `14e7bc0fe5985740`
|
||||
- 飞书授权回调地址: `https://xsm.dev.hnxinyouhe.com/login/qrcode/{id}`
|
||||
|
||||
---
|
||||
|
||||
## 25. 审批列表 Tab 状态持久化问题
|
||||
|
||||
**问题**:查看审批详情后返回上一页,tab 重置为"我发起的",而不是保持用户之前选择的 tab
|
||||
|
||||
**原因**:
|
||||
1. React Router 的 `navigate(-1)` 不传递 `location.state`
|
||||
2. 页面组件重新挂载时,`useState` 初始值恢复为默认值
|
||||
3. 即使使用 `navigate(path, { state: { fromTab: activeTab } })`,`navigate(-1)` 时 state 仍然为 null
|
||||
|
||||
**解决方案**:
|
||||
使用 `sessionStorage` 持久化 tab 状态,而不是依赖 `location.state`:
|
||||
|
||||
```typescript
|
||||
// 优先从 sessionStorage 恢复 tab,其次从 location.state,最后默认为 'my'
|
||||
const savedTab = sessionStorage.getItem('approval-tab') as string
|
||||
const stateTab = ((location.state as any)?.fromTab as string) || undefined
|
||||
const initialTab = (savedTab && ['my', 'pending', 'approved', 'cc'].includes(savedTab)) ? savedTab : (stateTab || 'my')
|
||||
const [activeTab, setActiveTab] = useState<'my' | 'pending' | 'approved' | 'cc'>(initialTab)
|
||||
|
||||
// 切换标签时保存到 sessionStorage
|
||||
const handleTabChange = (key: string) => {
|
||||
setActiveTab(key as 'my' | 'pending' | 'approved' | 'cc')
|
||||
sessionStorage.setItem('approval-tab', key) // 保存到 sessionStorage
|
||||
setList([])
|
||||
setHasMore(false)
|
||||
setPage(1)
|
||||
loadData(1, true, key)
|
||||
}
|
||||
|
||||
// 查看详情时同时保存 state(兼容其他场景)
|
||||
const handleViewDetail = (id: number) => {
|
||||
navigate(`/approval/${id}`, { state: { fromTab: activeTab } })
|
||||
}
|
||||
```
|
||||
|
||||
**相关文件**:
|
||||
- `frontend/src/pages/approval/list.tsx`
|
||||
- `frontend/src/pages/business-trip/list.tsx`
|
||||
|
||||
---
|
||||
|
||||
## 26. "我已审批"列表不显示已审批订单
|
||||
|
||||
**问题**:用户审批完成后,在"我已审批"列表中看不到该订单
|
||||
|
||||
**根本原因**:架构问题 - 数据模型混淆
|
||||
|
||||
### 问题分析
|
||||
|
||||
审批系统中有两个数据来源,它们的职责混淆了:
|
||||
|
||||
1. **`approval_node.approvers`** - 静态模板数据
|
||||
- 用途:定义审批流程模板中每个节点的默认审批人配置
|
||||
- 问题:运行时不会根据用户实际选择的审批人更新
|
||||
|
||||
2. **`approval_order.form_data.approvers`** - 运行时数据
|
||||
- 用途:存储用户提交申请时实际选择的审批人
|
||||
- 正确:这才是判断审批状态应该使用的数据
|
||||
|
||||
### 历史演变
|
||||
|
||||
| 时间 | BusinessTripService | ApprovalService | 结果 |
|
||||
|------|---------------------|-----------------|------|
|
||||
| 早期 | 使用 `approval_node.approvers` | 使用 `approval_node.approvers` | 一致但错误 |
|
||||
| 修复后 | 使用 `form_data.approvers` | 使用 `approval_node.approvers` | 不一致!|
|
||||
| 现在 | 使用 `form_data.approvers` | 使用 `form_data.approvers` | 一致且正确 |
|
||||
|
||||
### 代码修复
|
||||
|
||||
**ApprovalService::isNodeCompleted()** - 判断节点是否完成
|
||||
```php
|
||||
// ❌ 旧代码:从 approval_node 获取审批人(错误)
|
||||
$node = ApprovalNode::findOrFail($nodeId);
|
||||
$approvers = $node->approvers ?? []; // 静态模板数据,可能为空
|
||||
|
||||
// ✅ 新代码:从 form_data 获取审批人(正确)
|
||||
$order = ApprovalOrder::find($orderId);
|
||||
$formData = $order->form_data ?? [];
|
||||
$approvers = $formData['approvers'] ?? []; // 运行时数据,用户实际选择
|
||||
```
|
||||
|
||||
**ApprovalService::getPendingOrderIds()** - 获取待审批订单
|
||||
```php
|
||||
// ❌ 旧代码
|
||||
$node = ApprovalNode::whereIn('id', $currentNodeIds)->first();
|
||||
$approvers = $node->approvers ?? [];
|
||||
|
||||
// ✅ 新代码
|
||||
$formData = $order->form_data ?? [];
|
||||
$approvers = $formData['approvers'] ?? [];
|
||||
```
|
||||
|
||||
**兼容性处理**:
|
||||
```php
|
||||
// 兼容两种格式:[{id:1, name:'...'}] 或 [1]
|
||||
if (!empty($approvers) && is_array($approvers[0] ?? null) && isset($approvers[0]['id'])) {
|
||||
$approverIds = array_column($approvers, 'id');
|
||||
} else {
|
||||
$approverIds = $approvers;
|
||||
}
|
||||
```
|
||||
|
||||
**相关文件**:`backend/app/service/ApprovalService.php`
|
||||
|
||||
---
|
||||
|
||||
## 27. 审批流程不自动跳过通知节点
|
||||
|
||||
**问题**:审批节点完成后,下一节点是"通知"节点时,流程状态仍为"审批中",`current_nodes` 包含通知节点 ID
|
||||
|
||||
**原因**:
|
||||
- `ApprovalService::approve()` 没有检查下一节点类型
|
||||
- 通知节点(notify)不应该阻止流程完成,应该自动跳过
|
||||
|
||||
**解决方案**:
|
||||
```php
|
||||
// 移动到下一节点
|
||||
$nextNodes = $this->getNextNodes($order->flow, $currentNode->order);
|
||||
|
||||
if (empty($nextNodes)) {
|
||||
// 没有下一节点,审批完成
|
||||
$order->status = ApprovalOrder::STATUS_APPROVED;
|
||||
$order->current_nodes = [];
|
||||
} else {
|
||||
// 检查第一个下一节点
|
||||
$firstNextNode = $nextNodes[0] ?? null;
|
||||
if ($firstNextNode && $firstNextNode['type'] === 'notify') {
|
||||
// 下一个是通知节点,跳过并完成流程
|
||||
$order->status = ApprovalOrder::STATUS_APPROVED;
|
||||
$order->current_nodes = [];
|
||||
} else {
|
||||
// 下一个是审批节点,移动到该节点
|
||||
$order->current_nodes = collect($nextNodes)->pluck('id')->toArray();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**相关文件**:`backend/app/service/ApprovalService.php`
|
||||
|
||||
---
|
||||
|
||||
## 28. 架构问题总结 - 为什么问题一直出现
|
||||
|
||||
### 根本原因:数据模型职责不清晰
|
||||
|
||||
审批系统长期存在问题的根源是**静态模板数据**和**运行时数据**混淆:
|
||||
|
||||
| 数据源 | 表/字段 | 用途 | 问题 |
|
||||
|--------|---------|------|------|
|
||||
| **静态模板** | `approval_node.approvers` | 流程模板中每个节点的默认审批人 | 不应随用户选择变化 |
|
||||
| **运行时数据** | `approval_order.form_data.approvers` | 用户提交时实际选择的审批人 | 判断审批状态的正确来源 |
|
||||
|
||||
### 为什么会反复出现问题
|
||||
|
||||
1. **两个服务使用不同数据源**
|
||||
- `BusinessTripService` 修复后使用 `form_data`
|
||||
- `ApprovalService` 仍使用 `approval_node`
|
||||
- 导致相同操作在不同页面结果不一致
|
||||
|
||||
2. **没有明确的数据访问规范**
|
||||
- 开发时容易混淆应该读取哪个数据源
|
||||
- 缺少代码注释说明数据来源的用途
|
||||
|
||||
3. **缺少单元测试**
|
||||
- 数据逻辑变更后没有自动化测试验证
|
||||
- 依赖手工测试,容易遗漏边界情况
|
||||
|
||||
### 长期解决方案
|
||||
|
||||
1. **代码层面**:
|
||||
- 在 `ApprovalNode` 模型上添加注释,明确 `approvers` 是模板数据
|
||||
- 在 `ApprovalOrder` 模型上添加辅助方法 `getRuntimeApprovers()`,统一访问入口
|
||||
- 添加单元测试覆盖所有审批流程场景
|
||||
|
||||
2. **数据库层面**:
|
||||
- 添加外键约束确保数据一致性
|
||||
- 考虑将 `approval_node.approvers` 重命名为 `default_approvers`,更清晰表达语义
|
||||
|
||||
3. **文档层面**:
|
||||
- 在开发文档中明确说明数据模型的设计原则
|
||||
- 提供常见问题的排查指南
|
||||
|
||||
---
|
||||
|
||||
## 29. 数据库完整性建议
|
||||
|
||||
### 当前问题
|
||||
- `approval_order` 表记录可能被孤立(业务表引用了不存在的 approval_order_id)
|
||||
- 没有外键约束确保引用完整性
|
||||
- 清理数据时可能遗留孤立记录
|
||||
|
||||
### 建议的修复方案
|
||||
|
||||
```sql
|
||||
-- 1. 添加外键约束(确保数据一致性)
|
||||
ALTER TABLE business_trip
|
||||
ADD CONSTRAINT fk_business_trip_approval_order
|
||||
FOREIGN KEY (approval_order_id) REFERENCES approval_order(id)
|
||||
ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- 2. 清理孤立数据
|
||||
DELETE FROM approval_record
|
||||
WHERE order_id NOT IN (SELECT id FROM approval_order);
|
||||
|
||||
-- 3. 添加触发器自动维护一致性
|
||||
DELIMITER $$
|
||||
CREATE TRIGGER before_approval_order_delete
|
||||
BEFORE DELETE ON approval_order
|
||||
FOR EACH ROW
|
||||
BEGIN
|
||||
UPDATE business_trip
|
||||
SET approval_order_id = NULL
|
||||
WHERE approval_order_id = OLD.id;
|
||||
END$$
|
||||
DELIMITER ;
|
||||
```
|
||||
|
||||
**相关文件**:`/tmp/fix_proposal.sql`
|
||||
|
||||
---
|
||||
|
||||
## 最新修改记录(2025-02)
|
||||
|
||||
### 前端文件
|
||||
- `frontend/src/pages/approval/list.tsx` - 添加"抄送我的"tab,使用 sessionStorage 持久化 tab 状态
|
||||
- `frontend/src/pages/business-trip/list.tsx` - 使用 sessionStorage 持久化 tab 状态
|
||||
- `frontend/src/modules/approval/types.ts` - 添加 'cc' 类型到 ApprovalListParams
|
||||
|
||||
### 后端文件
|
||||
- `backend/app/service/ApprovalService.php`
|
||||
- 添加 'cc' case 支持"抄送我的"列表
|
||||
- 修复 `isNodeCompleted()` 使用 `form_data.approvers`
|
||||
- 修复 `getPendingOrderIds()` 使用 `form_data.approvers`
|
||||
- 添加跳过 notify 节点逻辑
|
||||
|
||||
### Git 提交记录
|
||||
- `e904d5c` - fix: 修复 ApprovalService 使用 form_data.approvers
|
||||
- `8ce070b` - fix: 使用 sessionStorage 保持 tab 状态
|
||||
- `f3a9e44` - fix: 返回列表时保持之前的 tab 状态
|
||||
- `ea7f0f8` - feat: 添加"抄送我的"审批列表选项
|
||||
|
||||
Reference in New Issue
Block a user