|
|
@@ -0,0 +1,554 @@
|
|
|
+# 出库放行审核 · 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、待审核默认仅展示双工单侧完成、两 Tab 常驻隐藏「出库质检/生产全作废」行。
|
|
|
+
|
|
|
+**Architecture:** 在 `list_reviews_paginated`(输注计划主表)增加 `review_phase`、`ready_for_review_only`,以及常驻 `NOT (qc_all_void OR production_all_void)`。工单经 `infusion_schedule_id` 关联出库质检/出库生产。前端同页组件加 Tab +「展示全部待审核」复选框,中试/基础实验/临床入口一并生效。
|
|
|
+
|
|
|
+**Tech Stack:** FastAPI + SQLAlchemy、Vue 3 + Element Plus、pytest
|
|
|
+
|
|
|
+**Spec:** `docs/superpowers/specs/2026-07-29-outbound-release-tabs-ready-void-design.md`
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## File Map
|
|
|
+
|
|
|
+| 文件 | 职责 |
|
|
|
+|------|------|
|
|
|
+| `backend/tests/test_outbound_release_review_filters.py` | **新建** normalize / 谓词单测 |
|
|
|
+| `backend/app/crud/outbound_release_review.py` | phase / ready / void EXISTS + `list_reviews_paginated` |
|
|
|
+| `backend/app/api/outbound_release_review.py` | Query 透传 `reviewPhase`、`readyForReviewOnly` |
|
|
|
+| `web-admin/src/api/outbound-release-review.ts` | 列表 params 类型 |
|
|
|
+| `web-admin/src/views/pilot-service/outbound-release-review/index.vue` | Tab、复选框、请求、样式 |
|
|
|
+| `docs/superpowers/specs/2026-07-29-outbound-release-tabs-ready-void-design.md` | 实现后标「已实现」 |
|
|
|
+
|
|
|
+不改:详情/审核/物料 API、卡片列表结构、表结构。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+### Task 1: Backend — normalize、EXISTS、列表过滤、API
|
|
|
+
|
|
|
+**Files:**
|
|
|
+- Create: `backend/tests/test_outbound_release_review_filters.py`
|
|
|
+- Modify: `backend/app/crud/outbound_release_review.py`
|
|
|
+- Modify: `backend/app/api/outbound_release_review.py`
|
|
|
+
|
|
|
+- [ ] **Step 1: 写失败单测**
|
|
|
+
|
|
|
+创建 `backend/tests/test_outbound_release_review_filters.py`:
|
|
|
+
|
|
|
+```python
|
|
|
+from app.crud.outbound_release_review import (
|
|
|
+ normalize_outbound_ready_for_review_only,
|
|
|
+ normalize_outbound_review_phase,
|
|
|
+ outbound_work_orders_all_void,
|
|
|
+)
|
|
|
+
|
|
|
+
|
|
|
+def test_normalize_outbound_review_phase():
|
|
|
+ assert normalize_outbound_review_phase("pending") == "pending"
|
|
|
+ assert normalize_outbound_review_phase("REVIEWED") == "reviewed"
|
|
|
+ assert normalize_outbound_review_phase(" reviewed ") == "reviewed"
|
|
|
+ assert normalize_outbound_review_phase(None) is None
|
|
|
+ assert normalize_outbound_review_phase("") is None
|
|
|
+ assert normalize_outbound_review_phase("all") is None
|
|
|
+
|
|
|
+
|
|
|
+def test_normalize_outbound_ready_for_review_only():
|
|
|
+ assert normalize_outbound_ready_for_review_only(True) is True
|
|
|
+ assert normalize_outbound_ready_for_review_only("true") is True
|
|
|
+ assert normalize_outbound_ready_for_review_only("1") is True
|
|
|
+ assert normalize_outbound_ready_for_review_only("yes") is True
|
|
|
+ assert normalize_outbound_ready_for_review_only(False) is None
|
|
|
+ assert normalize_outbound_ready_for_review_only("false") is None
|
|
|
+ assert normalize_outbound_ready_for_review_only(None) is None
|
|
|
+
|
|
|
+
|
|
|
+def test_outbound_all_void_predicate():
|
|
|
+ assert outbound_work_orders_all_void([]) is False
|
|
|
+ assert outbound_work_orders_all_void([""]) is False
|
|
|
+ assert outbound_work_orders_all_void(["yzf"]) is True
|
|
|
+ assert outbound_work_orders_all_void(["yzf", "yzf"]) is True
|
|
|
+ assert outbound_work_orders_all_void([" YZF ", "yzf"]) is True
|
|
|
+ assert outbound_work_orders_all_void(["yzf", "ywc"]) is False
|
|
|
+ assert outbound_work_orders_all_void(["yzf", ""]) is False
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 2: 跑单测确认失败**
|
|
|
+
|
|
|
+```bash
|
|
|
+cd backend && source venv/bin/activate && PYTHONPATH=. pytest tests/test_outbound_release_review_filters.py -v
|
|
|
+```
|
|
|
+
|
|
|
+Expected: FAIL(导入失败)
|
|
|
+
|
|
|
+- [ ] **Step 3: 实现 normalize、谓词、EXISTS,并接入 `list_reviews_paginated`**
|
|
|
+
|
|
|
+在 `backend/app/crud/outbound_release_review.py`:
|
|
|
+
|
|
|
+1) 顶部将 sqlalchemy 导入改为:
|
|
|
+
|
|
|
+```python
|
|
|
+from sqlalchemy import and_, exists, func, or_
|
|
|
+```
|
|
|
+
|
|
|
+(已有 `and_`/`func`/`or_` 则只补 `exists`。)
|
|
|
+
|
|
|
+2) 在 `_total_pages` 之后(或 Helpers 区)增加:
|
|
|
+
|
|
|
+```python
|
|
|
+def normalize_outbound_review_phase(review_phase: Optional[str]) -> Optional[str]:
|
|
|
+ p = (review_phase or "").strip().lower()
|
|
|
+ if p in ("pending", "reviewed"):
|
|
|
+ return p
|
|
|
+ return None
|
|
|
+
|
|
|
+
|
|
|
+def normalize_outbound_ready_for_review_only(raw) -> Optional[bool]:
|
|
|
+ """Return True when ready-only filter should apply; None means no filter."""
|
|
|
+ if raw is None:
|
|
|
+ return None
|
|
|
+ if isinstance(raw, bool):
|
|
|
+ return True if raw else None
|
|
|
+ s = str(raw).strip().lower()
|
|
|
+ if s in ("true", "1", "yes"):
|
|
|
+ return True
|
|
|
+ return None
|
|
|
+
|
|
|
+
|
|
|
+def outbound_work_orders_all_void(statuses) -> bool:
|
|
|
+ """True when ≥1 non-empty status and every one equals yzf."""
|
|
|
+ cleaned = [(s or "").strip() for s in (statuses or [])]
|
|
|
+ if not cleaned or not any(s for s in cleaned):
|
|
|
+ return False
|
|
|
+ return all(s.lower() == "yzf" for s in cleaned)
|
|
|
+
|
|
|
+
|
|
|
+def _outbound_qc_unfinished_exists_for_schedule(tenant_no: str):
|
|
|
+ """Any unfinished outbound schedule QC (dpz/ypz) for this infusion schedule."""
|
|
|
+ return (
|
|
|
+ exists()
|
|
|
+ .where(
|
|
|
+ and_(
|
|
|
+ BizQcWorkOrder.tenant_no == tenant_no,
|
|
|
+ BizQcWorkOrder.del_flag == 0,
|
|
|
+ BizQcWorkOrder.qc_biz_type == "outbound",
|
|
|
+ BizQcWorkOrder.trigger_source == "schedule",
|
|
|
+ BizQcWorkOrder.infusion_schedule_id == BizPilotInfusionSchedule.id,
|
|
|
+ BizQcWorkOrder.work_order_status.in_(("dpz", "ypz")),
|
|
|
+ )
|
|
|
+ )
|
|
|
+ .correlate(BizPilotInfusionSchedule)
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+def _outbound_production_unfinished_exists_for_schedule(tenant_no: str):
|
|
|
+ return (
|
|
|
+ exists()
|
|
|
+ .where(
|
|
|
+ and_(
|
|
|
+ BizProductionWorkOrder.tenant_no == tenant_no,
|
|
|
+ BizProductionWorkOrder.del_flag == 0,
|
|
|
+ BizProductionWorkOrder.work_order_type == "outbound",
|
|
|
+ BizProductionWorkOrder.infusion_schedule_id
|
|
|
+ == BizPilotInfusionSchedule.id,
|
|
|
+ BizProductionWorkOrder.work_order_status.in_(("dpz", "jxz")),
|
|
|
+ )
|
|
|
+ )
|
|
|
+ .correlate(BizPilotInfusionSchedule)
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+def _outbound_qc_all_void_for_schedule(tenant_no: str):
|
|
|
+ base = [
|
|
|
+ BizQcWorkOrder.tenant_no == tenant_no,
|
|
|
+ BizQcWorkOrder.del_flag == 0,
|
|
|
+ BizQcWorkOrder.qc_biz_type == "outbound",
|
|
|
+ BizQcWorkOrder.trigger_source == "schedule",
|
|
|
+ BizQcWorkOrder.infusion_schedule_id == BizPilotInfusionSchedule.id,
|
|
|
+ ]
|
|
|
+ any_wo = exists().where(and_(*base)).correlate(BizPilotInfusionSchedule)
|
|
|
+ any_non_void = (
|
|
|
+ exists()
|
|
|
+ .where(
|
|
|
+ and_(
|
|
|
+ *base,
|
|
|
+ or_(
|
|
|
+ BizQcWorkOrder.work_order_status.is_(None),
|
|
|
+ BizQcWorkOrder.work_order_status != "yzf",
|
|
|
+ ),
|
|
|
+ )
|
|
|
+ )
|
|
|
+ .correlate(BizPilotInfusionSchedule)
|
|
|
+ )
|
|
|
+ return and_(any_wo, ~any_non_void)
|
|
|
+
|
|
|
+
|
|
|
+def _outbound_production_all_void_for_schedule(tenant_no: str):
|
|
|
+ base = [
|
|
|
+ BizProductionWorkOrder.tenant_no == tenant_no,
|
|
|
+ BizProductionWorkOrder.del_flag == 0,
|
|
|
+ BizProductionWorkOrder.work_order_type == "outbound",
|
|
|
+ BizProductionWorkOrder.infusion_schedule_id == BizPilotInfusionSchedule.id,
|
|
|
+ ]
|
|
|
+ any_wo = exists().where(and_(*base)).correlate(BizPilotInfusionSchedule)
|
|
|
+ any_non_void = (
|
|
|
+ exists()
|
|
|
+ .where(
|
|
|
+ and_(
|
|
|
+ *base,
|
|
|
+ or_(
|
|
|
+ BizProductionWorkOrder.work_order_status.is_(None),
|
|
|
+ BizProductionWorkOrder.work_order_status != "yzf",
|
|
|
+ ),
|
|
|
+ )
|
|
|
+ )
|
|
|
+ .correlate(BizPilotInfusionSchedule)
|
|
|
+ )
|
|
|
+ return and_(any_wo, ~any_non_void)
|
|
|
+```
|
|
|
+
|
|
|
+3) 修改 `list_reviews_paginated` 签名,在 `infusion_time_end` 之后增加:
|
|
|
+
|
|
|
+```python
|
|
|
+ review_phase: Optional[str] = None,
|
|
|
+ ready_for_review_only=None,
|
|
|
+```
|
|
|
+
|
|
|
+4) **替换**现有「`# ── review_conclusion 过滤 ──`」整块为(保留旧 `review_conclusion=pending` 兼容,但 `review_phase` 优先):
|
|
|
+
|
|
|
+```python
|
|
|
+ # ── reviewPhase / review_conclusion 过滤 ──
|
|
|
+ phase = normalize_outbound_review_phase(review_phase)
|
|
|
+ rc = (review_conclusion or "").strip()
|
|
|
+ if phase == "pending" or (phase is None and rc == "pending"):
|
|
|
+ reviewed_schedule_ids = (
|
|
|
+ db.query(BizOutboundReleaseReview.infusion_schedule_id)
|
|
|
+ .filter(
|
|
|
+ BizOutboundReleaseReview.tenant_no == tenant_no,
|
|
|
+ BizOutboundReleaseReview.del_flag == 0,
|
|
|
+ BizOutboundReleaseReview.review_conclusion.isnot(None),
|
|
|
+ BizOutboundReleaseReview.review_conclusion != "",
|
|
|
+ )
|
|
|
+ .subquery()
|
|
|
+ )
|
|
|
+ q0 = q0.filter(~BizPilotInfusionSchedule.id.in_(reviewed_schedule_ids))
|
|
|
+ elif phase == "reviewed":
|
|
|
+ reviewed_schedule_ids = (
|
|
|
+ db.query(BizOutboundReleaseReview.infusion_schedule_id)
|
|
|
+ .filter(
|
|
|
+ BizOutboundReleaseReview.tenant_no == tenant_no,
|
|
|
+ BizOutboundReleaseReview.del_flag == 0,
|
|
|
+ BizOutboundReleaseReview.review_conclusion.isnot(None),
|
|
|
+ BizOutboundReleaseReview.review_conclusion != "",
|
|
|
+ )
|
|
|
+ .subquery()
|
|
|
+ )
|
|
|
+ q0 = q0.filter(BizPilotInfusionSchedule.id.in_(reviewed_schedule_ids))
|
|
|
+ if rc in ("tg", "btg"):
|
|
|
+ conclusion_ids = (
|
|
|
+ db.query(BizOutboundReleaseReview.infusion_schedule_id)
|
|
|
+ .filter(
|
|
|
+ BizOutboundReleaseReview.tenant_no == tenant_no,
|
|
|
+ BizOutboundReleaseReview.del_flag == 0,
|
|
|
+ BizOutboundReleaseReview.review_conclusion == rc,
|
|
|
+ )
|
|
|
+ .subquery()
|
|
|
+ )
|
|
|
+ q0 = q0.filter(BizPilotInfusionSchedule.id.in_(conclusion_ids))
|
|
|
+ elif rc in ("tg", "btg"):
|
|
|
+ reviewed_schedule_ids = (
|
|
|
+ db.query(BizOutboundReleaseReview.infusion_schedule_id)
|
|
|
+ .filter(
|
|
|
+ BizOutboundReleaseReview.tenant_no == tenant_no,
|
|
|
+ BizOutboundReleaseReview.del_flag == 0,
|
|
|
+ BizOutboundReleaseReview.review_conclusion == rc,
|
|
|
+ )
|
|
|
+ .subquery()
|
|
|
+ )
|
|
|
+ q0 = q0.filter(BizPilotInfusionSchedule.id.in_(reviewed_schedule_ids))
|
|
|
+
|
|
|
+ if normalize_outbound_ready_for_review_only(ready_for_review_only) is True:
|
|
|
+ q0 = q0.filter(
|
|
|
+ ~_outbound_qc_unfinished_exists_for_schedule(tenant_no),
|
|
|
+ ~_outbound_production_unfinished_exists_for_schedule(tenant_no),
|
|
|
+ )
|
|
|
+
|
|
|
+ # 出库质检/生产「有工单且全部 yzf」→ 列表隐藏(两 Tab 常驻)
|
|
|
+ q0 = q0.filter(
|
|
|
+ ~or_(
|
|
|
+ _outbound_qc_all_void_for_schedule(tenant_no),
|
|
|
+ _outbound_production_all_void_for_schedule(tenant_no),
|
|
|
+ )
|
|
|
+ )
|
|
|
+```
|
|
|
+
|
|
|
+将上述块插在**原 review_conclusion 过滤位置**,且必须在 `if user_info:` / `total = ...` **之前**。删除旧的仅 `rc == "pending"` / `elif rc:` 重复逻辑,避免双重过滤。
|
|
|
+
|
|
|
+- [ ] **Step 4: API 透传**
|
|
|
+
|
|
|
+在 `backend/app/api/outbound_release_review.py` 的 `list_pilot_release_reviews`:
|
|
|
+
|
|
|
+在 `infusionTimeEnd: Optional[str] = None,` 后增加:
|
|
|
+
|
|
|
+```python
|
|
|
+ reviewPhase: Optional[str] = Query(None),
|
|
|
+ readyForReviewOnly: Optional[bool] = Query(None),
|
|
|
+```
|
|
|
+
|
|
|
+在 `list_reviews_paginated(...)` 调用中增加:
|
|
|
+
|
|
|
+```python
|
|
|
+ review_phase=reviewPhase,
|
|
|
+ ready_for_review_only=readyForReviewOnly,
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 5: 跑单测**
|
|
|
+
|
|
|
+```bash
|
|
|
+cd backend && source venv/bin/activate && PYTHONPATH=. pytest tests/test_outbound_release_review_filters.py -v
|
|
|
+```
|
|
|
+
|
|
|
+Expected: PASS
|
|
|
+
|
|
|
+- [ ] **Step 6: Commit**
|
|
|
+
|
|
|
+```bash
|
|
|
+git add backend/tests/test_outbound_release_review_filters.py \
|
|
|
+ backend/app/crud/outbound_release_review.py \
|
|
|
+ backend/app/api/outbound_release_review.py
|
|
|
+git commit -m "$(cat <<'EOF'
|
|
|
+feat(outbound-release): add reviewPhase, ready filter, and void hide
|
|
|
+
|
|
|
+List supports pending/reviewed phase, readyForReviewOnly, and always hides schedules whose outbound QC or production WOs are all voided.
|
|
|
+EOF
|
|
|
+)"
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+### Task 2: Frontend — API params + Tab + 复选框
|
|
|
+
|
|
|
+**Files:**
|
|
|
+- Modify: `web-admin/src/api/outbound-release-review.ts`
|
|
|
+- Modify: `web-admin/src/views/pilot-service/outbound-release-review/index.vue`
|
|
|
+
|
|
|
+- [ ] **Step 1: API params**
|
|
|
+
|
|
|
+修改 `fetchOutboundReleaseReviewList` 的 params 类型与调用,增加:
|
|
|
+
|
|
|
+```typescript
|
|
|
+ reviewPhase?: 'pending' | 'reviewed'
|
|
|
+ readyForReviewOnly?: boolean
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 2: 页面结构 — Tab + 复选框 + 去掉空「待审核」**
|
|
|
+
|
|
|
+在 `index.vue` 模板中,于 `panel-top` 之后、`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>
|
|
|
+```
|
|
|
+
|
|
|
+审核结论 `el-select`:**删除** `<el-option label="待审核" value="" />`,仅保留 `reviewConclusionOptions` 循环。
|
|
|
+
|
|
|
+在 `filter-actions-row` 内、`filter-actions-end` 之前插入:
|
|
|
+
|
|
|
+```vue
|
|
|
+ <label
|
|
|
+ v-if="activeTab === 'pending'"
|
|
|
+ class="storage-release-ready-check"
|
|
|
+ >
|
|
|
+ <el-checkbox v-model="searchForm.showAllPending" />
|
|
|
+ <span class="storage-release-ready-check__copy">
|
|
|
+ <span class="storage-release-ready-check__title">展示全部待审核</span>
|
|
|
+ <span class="storage-release-ready-check__hint">
|
|
|
+ 默认不勾选:仅展示质检与生产工单均为完成的记录;勾选后展示全部。
|
|
|
+ </span>
|
|
|
+ </span>
|
|
|
+ </label>
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 3: script 逻辑**
|
|
|
+
|
|
|
+在 `defineOptions` 附近增加:
|
|
|
+
|
|
|
+```typescript
|
|
|
+ type ReviewTab = 'pending' | 'reviewed'
|
|
|
+
|
|
|
+ const STATUS_TABS = [
|
|
|
+ { name: 'pending' as const, label: '待审核' },
|
|
|
+ { name: 'reviewed' as const, label: '已审核' }
|
|
|
+ ]
|
|
|
+
|
|
|
+ const activeTab = ref<ReviewTab>('pending')
|
|
|
+```
|
|
|
+
|
|
|
+`searchForm` 增加 `showAllPending: false`。
|
|
|
+
|
|
|
+改写 `loadData`:
|
|
|
+
|
|
|
+```typescript
|
|
|
+ async function loadData() {
|
|
|
+ loading.value = true
|
|
|
+ try {
|
|
|
+ const readyOnly =
|
|
|
+ activeTab.value === 'pending' && !searchForm.showAllPending ? true : undefined
|
|
|
+ const res = await fetchOutboundReleaseReviewList({
|
|
|
+ page: pagination.page,
|
|
|
+ pageSize: pagination.pageSize,
|
|
|
+ reviewConclusion: searchForm.reviewConclusion || undefined,
|
|
|
+ keyword: searchForm.keyword || undefined,
|
|
|
+ bizType: effectiveBizType.value,
|
|
|
+ infusionTimeStart: searchForm.infusionTimeRange?.[0] || undefined,
|
|
|
+ infusionTimeEnd: searchForm.infusionTimeRange?.[1] || undefined,
|
|
|
+ reviewPhase: activeTab.value,
|
|
|
+ readyForReviewOnly: readyOnly
|
|
|
+ })
|
|
|
+ tableData.value = res.list || []
|
|
|
+ pagination.total = res.total ?? 0
|
|
|
+ } finally {
|
|
|
+ loading.value = false
|
|
|
+ }
|
|
|
+ }
|
|
|
+```
|
|
|
+
|
|
|
+增加:
|
|
|
+
|
|
|
+```typescript
|
|
|
+ function handleTabChange(name: ReviewTab) {
|
|
|
+ if (activeTab.value === name) return
|
|
|
+ activeTab.value = name
|
|
|
+ pagination.page = 1
|
|
|
+ selectedRows.value = []
|
|
|
+ if (name === 'pending') {
|
|
|
+ searchForm.showAllPending = false
|
|
|
+ }
|
|
|
+ loadData()
|
|
|
+ }
|
|
|
+```
|
|
|
+
|
|
|
+`handleReset` 增加:`searchForm.showAllPending = false`(若当前在待审核;或始终重置该字段)。
|
|
|
+
|
|
|
+注意:现有 `watchDebounced(searchForm, ...)` 会在勾选复选框时自动搜索——保留即可。Tab 切换走 `handleTabChange`,不要让 `activeTab` 进 `searchForm` 以免重复请求逻辑混乱。
|
|
|
+
|
|
|
+- [ ] **Step 4: 样式**
|
|
|
+
|
|
|
+若文件无 `<style scoped>`,在 script 后增加;若已有则合并。复用入库样式类名:
|
|
|
+
|
|
|
+```scss
|
|
|
+<style scoped lang="scss">
|
|
|
+ .filter-form :deep(.el-form-item) {
|
|
|
+ margin-bottom: 0;
|
|
|
+ min-width: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .storage-release-ready-check {
|
|
|
+ display: inline-flex;
|
|
|
+ align-items: flex-start;
|
|
|
+ gap: 10px;
|
|
|
+ margin-right: auto;
|
|
|
+ max-width: 520px;
|
|
|
+ padding: 10px 12px;
|
|
|
+ border: 1px dashed #a7f3d0;
|
|
|
+ border-radius: 8px;
|
|
|
+ background: var(--primary-bg, #ecfdf5);
|
|
|
+ cursor: pointer;
|
|
|
+ user-select: none;
|
|
|
+ }
|
|
|
+
|
|
|
+ .storage-release-ready-check__copy {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 2px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .storage-release-ready-check__title {
|
|
|
+ font-size: 13px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: var(--text, #1e293b);
|
|
|
+ line-height: 1.4;
|
|
|
+ }
|
|
|
+
|
|
|
+ .storage-release-ready-check__hint {
|
|
|
+ font-size: 12px;
|
|
|
+ color: var(--text-muted, #64748b);
|
|
|
+ line-height: 1.45;
|
|
|
+ }
|
|
|
+</style>
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 5: Commit**
|
|
|
+
|
|
|
+```bash
|
|
|
+git add web-admin/src/api/outbound-release-review.ts \
|
|
|
+ web-admin/src/views/pilot-service/outbound-release-review/index.vue
|
|
|
+git commit -m "$(cat <<'EOF'
|
|
|
+feat(web-admin): outbound release pending/reviewed tabs and ready checkbox
|
|
|
+
|
|
|
+Match storage-release review UX for pilot/basic-experiment outbound release lists.
|
|
|
+EOF
|
|
|
+)"
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+### Task 3: 规格状态 + 手工验收
|
|
|
+
|
|
|
+**Files:**
|
|
|
+- Modify: `docs/superpowers/specs/2026-07-29-outbound-release-tabs-ready-void-design.md`
|
|
|
+
|
|
|
+- [ ] **Step 1:** 将 `> 状态:已确认` 改为 `> 状态:已实现`
|
|
|
+
|
|
|
+- [ ] **Step 2: 手工验收清单(实现者报告中粘贴给用户)**
|
|
|
+
|
|
|
+1. `http://localhost:3006/#/market/pilot-service/outbound-release-review` 与基础实验同路径入口:双 Tab 默认待审核。
|
|
|
+2. 待审核默认:有未完成出库质检/生产 → 不出现;双完成或双无工单 → 可出现。
|
|
|
+3. 勾选「展示全部待审核」→ 可见未完成行;全作废行仍不可见。
|
|
|
+4. 出库质检或出库生产全部 `yzf` → 两 Tab 均不出现。
|
|
|
+5. 已审核无复选框;作废隐藏仍生效。
|
|
|
+6. 详情直链仍可打开。
|
|
|
+
|
|
|
+- [ ] **Step 3: Commit**
|
|
|
+
|
|
|
+```bash
|
|
|
+git add docs/superpowers/specs/2026-07-29-outbound-release-tabs-ready-void-design.md
|
|
|
+git commit -m "$(cat <<'EOF'
|
|
|
+docs: mark outbound release tabs/ready/void design as implemented
|
|
|
+EOF
|
|
|
+)"
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## Spec coverage (self-review)
|
|
|
+
|
|
|
+| Spec | Task |
|
|
|
+|------|------|
|
|
|
+| 双 Tab pending/reviewed | Task 2 + Task 1 phase |
|
|
|
+| 去掉空「待审核」选项 | Task 2 |
|
|
|
+| ready 复选框仅待审核、默认不勾选 | Task 2 |
|
|
|
+| ready SQL 双 NOT EXISTS | Task 1 |
|
|
|
+| 出库 WO 范围 schedule + outbound | Task 1 |
|
|
|
+| 作废全 yzf 常驻隐藏 | Task 1 |
|
|
|
+| 详情 API 不改 | 无 Task 改详情 |
|
|
|
+| 中试/基础实验同页 | Task 2 单页 |
|
|
|
+| 单测 | Task 1 |
|
|
|
+
|
|
|
+无 TBD。函数名统一为 `outbound_work_orders_all_void`。
|