merge: bring storage release review pending/reviewed tabs into OA deploy branch

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
zlwuandCursor committed 2026-07-16 11:42:45 +08:00
commit f6efca8fdf
19 files changed
+10224 -8438

No files matched your search

@@ -0,0 +1,365 @@
# 入库放行审核双 Tab(待审核 / 已审核)Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 将入库放行审核列表拆成「待审核 / 已审核」两个 Tab,服务端按 `reviewPhase` 过滤并正确分页,默认进入待审核。
**Architecture:** 列表 API 增加可选参数 `reviewPhase=pending|reviewed`;CRUD `list_reviews` 按 `review_conclusion` 是否为空过滤。前端对齐样本接收的 `panel-pill-tabs`,切 Tab 时重置页码与勾选、保留筛选,请求始终带对应 `reviewPhase`。审核/入库弹窗与业务规则不改。
**Tech Stack:** FastAPI + SQLAlchemy(backend)、Vue 3 + Element Plus(web-admin)
**Spec:** `docs/superpowers/specs/2026-07-16-storage-release-review-tabs-design.md`
---
## File Map
| 文件 | 职责 |
|------|------|
| `backend/app/crud/storage_release_review.py` | `normalize_review_phase` + `list_reviews` 阶段过滤 |
| `backend/app/api/storage_release_review.py` | 列表接口增加 `reviewPhase` Query |
| `backend/tests/test_storage_release_review_phase.py` | **新建** 阶段参数规范化单测 |
| `web-admin/src/api/storage-release-review.ts` | 列表 params 增加 `reviewPhase` |
| `web-admin/src/views/quality-inspection/storage-release-review/index.vue` | pill Tabs、切 Tab、请求带 phase、去掉无效「待审核」选项 |
---
### Task 1: Backend — `reviewPhase` 规范化与列表过滤
**Files:**
- Modify: `backend/app/crud/storage_release_review.py`
- Create: `backend/tests/test_storage_release_review_phase.py`
- Modify: `backend/app/api/storage_release_review.py`
- [ ] **Step 1: 写失败单测(规范化函数)**
创建 `backend/tests/test_storage_release_review_phase.py`:
```python
from app.crud.storage_release_review import normalize_review_phase
def test_normalize_review_phase_accepts_pending_and_reviewed():
assert normalize_review_phase("pending") == "pending"
assert normalize_review_phase("reviewed") == "reviewed"
assert normalize_review_phase(" PENDING ") == "pending"
assert normalize_review_phase("Reviewed") == "reviewed"
def test_normalize_review_phase_rejects_unknown():
assert normalize_review_phase(None) is None
assert normalize_review_phase("") is None
assert normalize_review_phase("all") is None
assert normalize_review_phase("tg") is None
```
- [ ] **Step 2: 跑单测确认失败**
```bash
cd backend && source venv/bin/activate && pytest tests/test_storage_release_review_phase.py -v
```
Expected: FAIL(`normalize_review_phase` 未定义或导入失败)
- [ ] **Step 3: 实现 `normalize_review_phase` 并在 `list_reviews` 中应用过滤**
在 `backend/app/crud/storage_release_review.py` 中,于 `list_reviews` 之前增加:
```python
def normalize_review_phase(review_phase: Optional[str]) -> Optional[str]:
"""Return 'pending' | 'reviewed' or None when absent/invalid."""
p = (review_phase or "").strip().lower()
if p in ("pending", "reviewed"):
return p
return None
```
修改 `list_reviews` 签名,增加 `review_phase: Optional[str] = None`(放在 `keyword` 与 `user_info` 之间):
```python
def list_reviews(
db: Session,
*,
tenant_no: str,
page: int = 1,
page_size: int = 10,
reception_source: Optional[str] = None,
review_conclusion: Optional[str] = None,
storage_status: Optional[str] = None,
keyword: Optional[str] = None,
review_phase: Optional[str] = None,
user_info=None,
) -> Dict[str, Any]:
```
在现有 `reception_source` 过滤之后、`review_conclusion` 过滤之前插入:
```python
phase = normalize_review_phase(review_phase)
if phase == "pending":
q0 = q0.filter(
or_(
BizStorageReleaseReview.review_conclusion.is_(None),
BizStorageReleaseReview.review_conclusion == "",
)
)
elif phase == "reviewed":
q0 = q0.filter(
BizStorageReleaseReview.review_conclusion.isnot(None),
BizStorageReleaseReview.review_conclusion != "",
)
```
说明:`or_` 已在文件顶部从 `sqlalchemy` 导入,无需新增 import。
- [ ] **Step 4: API 增加 Query 并传入 CRUD**
修改 `backend/app/api/storage_release_review.py` 的 `list_storage_release_reviews`:
```python
@router.get("/storage-release-reviews")
async def list_storage_release_reviews(
page: int = Query(1, ge=1),
pageSize: int = Query(10, ge=1, le=100),
keyword: Optional[str] = None,
receptionSource: Optional[str] = None,
reviewConclusion: Optional[str] = None,
storageStatus: Optional[str] = None,
reviewPhase: Optional[str] = None,
db: Session = Depends(get_db),
user: dict = Depends(require_menu_code("biz:storageReleaseReview:list")),
):
try:
tenant_no = _get_tenant_no(db, user)
result = srr_svc.list_reviews(
db,
tenant_no=tenant_no,
page=page,
page_size=pageSize,
reception_source=receptionSource,
review_conclusion=reviewConclusion,
storage_status=storageStatus,
keyword=keyword,
review_phase=reviewPhase,
user_info=user,
)
return success_response(result)
except PermissionError as e:
return error_response(403, str(e))
except Exception as e:
return error_response(400, str(e))
```
- [ ] **Step 5: 跑单测确认通过**
```bash
cd backend && source venv/bin/activate && pytest tests/test_storage_release_review_phase.py -v
```
Expected: PASS(2 passed)
- [ ] **Step 6: Commit**
```bash
git add backend/app/crud/storage_release_review.py \
backend/app/api/storage_release_review.py \
backend/tests/test_storage_release_review_phase.py
git commit -m "$(cat <<'EOF'
feat: add reviewPhase filter for storage release review list
EOF
)"
```
---
### Task 2: Frontend API 类型
**Files:**
- Modify: `web-admin/src/api/storage-release-review.ts`
- [ ] **Step 1: 列表 params 增加 `reviewPhase`**
将 `fetchStorageReleaseReviewList` 的 params 类型改为:
```typescript
export function fetchStorageReleaseReviewList(params?: {
page?: number
pageSize?: number
receptionSource?: string
reviewConclusion?: string
storageStatus?: string
keyword?: string
reviewPhase?: 'pending' | 'reviewed'
}) {
return request.get<PageList<StorageReleaseReview>>({
url: '/web/admin/storage-release-reviews',
params
})
}
```
- [ ] **Step 2: Commit**
```bash
git add web-admin/src/api/storage-release-review.ts
git commit -m "$(cat <<'EOF'
feat: add reviewPhase param to storage release review list API
EOF
)"
```
---
### Task 3: 列表页双 Tab UI 与请求
**Files:**
- Modify: `web-admin/src/views/quality-inspection/storage-release-review/index.vue`
- Reference: `web-admin/src/views/quality-inspection/sample-reception/index.vue`(pill tabs 结构)
- [ ] **Step 1: 在 `panel-top` 之后插入 pill Tabs**
在 `</div>`(panel-top 结束)与 `<div class="filter-block">` 之间插入:
```vue
<div class="panel-pill-tabs panel-pill-tabs--lg">
<div class="panel-pill-tabs__track" role="tablist">
<button
v-for="tab in STATUS_TABS"
:key="tab.name"
type="button"
role="tab"
class="panel-pill-tabs__btn"
:class="{ 'is-active': activeTab === tab.name }"
:aria-selected="activeTab === tab.name"
@click="handleTabChange(tab.name)"
>
{{ tab.label }}
</button>
</div>
</div>
```
- [ ] **Step 2: 去掉「审核结论」下拉中的无效「待审核」选项**
删除:
```vue
<el-option label="待审核" value="" />
```
保留 `clearable` + placeholder「全部」+ `reviewConclusionOptions`(通过/不通过)。
- [ ] **Step 3: 增加 Tab 状态与切换逻辑**
在 `<script setup>` 中、`searchForm` 之前增加:
```typescript
type ReviewTab = 'pending' | 'reviewed'
const STATUS_TABS = [
{ name: 'pending' as const, label: '待审核' },
{ name: 'reviewed' as const, label: '已审核' }
]
const activeTab = ref<ReviewTab>('pending')
function handleTabChange(name: ReviewTab) {
if (activeTab.value === name) return
activeTab.value = name
pagination.page = 1
selectedRows.value = []
loadData()
}
```
注意:`handleTabChange` 使用了 `pagination` / `selectedRows` / `loadData`,须放在这些声明之后;或先声明 `activeTab`/`STATUS_TABS`,把 `handleTabChange` 放在 `loadData` 之后。推荐顺序:`STATUS_TABS` + `activeTab` → `searchForm` / `pagination` / `selectedRows` → `loadData` → `handleTabChange`。
- [ ] **Step 4: `loadData` 请求带上 `reviewPhase`**
```typescript
async function loadData() {
loading.value = true
try {
const res = await fetchStorageReleaseReviewList({
page: pagination.page,
pageSize: pagination.pageSize,
receptionSource: searchForm.receptionSource || undefined,
reviewConclusion: searchForm.reviewConclusion || undefined,
storageStatus: searchForm.storageStatus || undefined,
keyword: searchForm.keyword || undefined,
reviewPhase: activeTab.value
})
tableData.value = res.list || []
pagination.total = res.total ?? 0
} finally {
loading.value = false
}
}
```
`handleReset` **不要**重置 `activeTab`(仅清空筛选);重置后仍在当前 Tab 刷新。
- [ ] **Step 5: 本地手测清单**
启动后端与 `web-admin`(`pnpm dev`),打开 `/#/market/cell-storage/storage-release-review`:
1. 默认「待审核」高亮;列表行审核结论均为「待审核」
2. 切到「已审核」:仅通过/不通过;用「审核结论」再筛有效
3. 切 Tab:页码回 1、勾选清空、筛选保留
4. 待审核提交审核后该行消失;已审核可再改结论;入库按钮行为不变
- [ ] **Step 6: 更新图谱(前端 + 后端)**
```bash
./scripts/update-graph.sh all
```
- [ ] **Step 7: 将 design spec 状态改为已确认**
编辑 `docs/superpowers/specs/2026-07-16-storage-release-review-tabs-design.md` 头部:
```markdown
> 状态:已确认
```
- [ ] **Step 8: Commit**
```bash
git add web-admin/src/views/quality-inspection/storage-release-review/index.vue \
docs/superpowers/specs/2026-07-16-storage-release-review-tabs-design.md \
backend/graphify-out web-admin/graphify-out
git commit -m "$(cat <<'EOF'
feat: split storage release review list into pending and reviewed tabs
EOF
)"
```
(若图谱无变更可从 `git add` 中去掉 graphify-out 路径。)
---
## Spec Coverage Checklist
| Spec 要求 | Task |
|-----------|------|
| 双 Tab 待审核/已审核 | Task 3 |
| 默认待审核 | Task 3 `activeTab = 'pending'` |
| 已审核含 tg+btg,可再筛结论 | Task 1 phase + Task 3 筛选 |
| 已审核仍可改结论 | 不改弹窗;操作列保持「审核」 |
| 两 Tab 筛选项相同 | Task 3(仅去掉无效待审核 option) |
| `reviewPhase` API + 缺省兼容 | Task 1 |
| 非法 phase 忽略 | Task 1 `normalize_review_phase` |
| 切 Tab 重置页码/勾选、保留筛选 | Task 3 `handleTabChange` |
| 审核后刷新当前 Tab | 现有 `onReviewSubmitted` → `loadData` |
| 不做角标/不改弹窗/不改菜单 | 范围外,无任务 |
---
## 完成定义
- [ ] `pytest tests/test_storage_release_review_phase.py` 通过
- [ ] 页面双 Tab 行为符合 spec §6 验收要点
- [ ] 相关 commit 已完成(若执行时用户要求提交)
@@ -0,0 +1,143 @@
# 入库放行审核 · 待审核 / 已审核双 Tab
> 日期:2026-07-16
> 状态:已确认
> 模块:细胞存储 / 质检 → 入库放行审核
> 关联:`docs/superpowers/specs/2026-05-23-storage-release-review-design.md`
> 界面模式:P22(审核列表)+ 样本接收式 `panel-pill-tabs`
> 参考页:`web-admin/src/views/quality-inspection/sample-reception/index.vue`
## 1. 背景与目标
### 现状
「入库放行审核」列表将所有记录(待审核 + 已审核)放在同一表格中。筛选区虽有「审核结论」,但选项「待审核」绑定空字符串,请求时被转成 `undefined`,后端不会按空结论过滤,实际无法单独查看待办。
### 目标
将列表拆成两个独立 Tab:
1. **待审核**:`review_conclusion` 为空的记录(审核员日常主工作台)
2. **已审核**:已有结论(通过 `tg` + 不通过 `btg`),可用「审核结论」再筛
默认进入「待审核」。分页与总数按当前 Tab 正确计算。
### 范围外
- 不改审核弹窗、查看弹窗、入库业务规则
- 不新增菜单、不拆独立路由页
- 不做 Tab 角标数量(后续可单独立项)
## 2. 需求确认记录
| 维度 | 决定 |
|------|------|
| Tab 结构 | 两个:`待审核` \| `已审核`(已审核含通过 + 不通过) |
| 已审核改结论 | 仍可点「审核」修改(与现网一致) |
| 筛选区 | 两 Tab 筛选项完全相同;已审核可用「审核结论」再筛 |
| 默认 Tab | 进入页面默认「待审核」 |
| 实现路径 | 前端双 Tab + 列表 API 增加 `reviewPhase`(方案 1) |
## 3. UI 与交互
### 3.1 布局顺序
与样本接收一致:
`panel-top` 标题 → **pill Tabs**(`panel-pill-tabs`)→ 筛选区 → 工具栏 → 表格 + 分页
### 3.2 Tabs
| key | 文案 | 默认 |
|-----|------|------|
| `pending` | 待审核 | ✓ |
| `reviewed` | 已审核 | |
### 3.3 切换 Tab
- 页码回到 1
- 清空表格勾选
- **保留**当前筛选条件
- 重新请求列表(带对应 `reviewPhase`)
### 3.4 筛选区(两 Tab 相同)
字段:关键词、协议类别、审核结论、入库状态。
「审核结论」下拉:
- 去掉无效的「待审核」选项(空值曾被当成全部)
- 保留:清空 = 当前 Tab 内「全部」+ 通过 / 不通过
- 待审核 Tab 下若选通过/不通过,与阶段叠加,结果为空属预期
### 3.5 工具栏与操作列(两 Tab 相同)
- 工具栏:已选 N 项、批量审核、批量入库
- 行操作:查看、审核;通过且待入库时显示「样本入库」
- 已审核 Tab 仍可「审核」改结论
### 3.6 审核提交后
- 刷新**当前** Tab 列表,不强制跳 Tab
- 从待审核提交后,该行自然从待审核消失;需到已审核查看
## 4. API 与数据流
### 4.1 接口
沿用 `GET /web/admin/storage-release-reviews`,新增可选查询参数:
| 参数 | 类型 | 说明 |
|------|------|------|
| `reviewPhase` | `pending` \| `reviewed` | Tab 阶段;**缺省**时不按阶段过滤(与现网兼容) |
其余参数不变:`page`、`pageSize`、`keyword`、`receptionSource`、`reviewConclusion`、`storageStatus`。
### 4.2 过滤语义
在租户 / 软删 / 数据权限 / 其它筛选项之上叠加:
| reviewPhase | SQL 语义 |
|-------------|----------|
| `pending` | `review_conclusion IS NULL OR review_conclusion = ''` |
| `reviewed` | `review_conclusion IS NOT NULL AND review_conclusion != ''` |
再叠加:`keyword`、`receptionSource`、`reviewConclusion`(仅 `tg`/`btg`)、`storageStatus`。
非法 `reviewPhase`:忽略该参数,按无阶段处理(避免误伤调用方)。
### 4.3 前端请求约定
- 待审核 Tab:始终传 `reviewPhase=pending`
- 已审核 Tab:始终传 `reviewPhase=reviewed`
- `reviewConclusion` 仅在有明确值(通过/不通过)时传入;不再用空字符串表示「待审核」
### 4.4 不变接口
详情、单条/批量审核、单条/批量入库接口与行为不变。
## 5. 改动清单
| 层 | 文件 | 改动 |
|----|------|------|
| Backend API | `backend/app/api/storage_release_review.py` | 增加 `reviewPhase` Query,传入 CRUD |
| Backend CRUD | `backend/app/crud/storage_release_review.py` | `list_reviews` 按阶段过滤 |
| Frontend API | `web-admin/src/api/storage-release-review.ts` | 列表参数增加 `reviewPhase` |
| Frontend 页 | `web-admin/src/views/quality-inspection/storage-release-review/index.vue` | pill Tabs、切 Tab 刷新、下拉去掉无效「待审核」、请求带 `reviewPhase` |
不改:`StorageReleaseReviewDialog.vue`、`StorageReleaseViewDialog.vue`、菜单/路由、表结构。
## 6. 验收要点
1. 进入页面默认「待审核」,仅无结论记录;分页总数正确
2. 「已审核」仅有通过/不通过;可用「审核结论」再筛
3. 两 Tab 筛选字段一致;切 Tab 保留筛选、重置页码与勾选
4. 待审核提交审核后,该行从待审核消失,出现在已审核
5. 已审核仍可「审核」改结论;通过且待入库仍可入库 / 批量入库
6. 不传 `reviewPhase` 时列表行为与改前兼容
## 7. 非目标
- Tab 角标数量
- 独立菜单或独立列表页
- 修改审核/入库业务规则与弹窗结构