554 lines
21 KiB
Markdown
554 lines
21 KiB
Markdown
# 体检机构项目套餐(Phase 1)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:** 在体检机构维度维护「项目套餐」(主子表),套餐总价为明细费用之和,明细价可编辑;机构项目软删除不阻断、不级联套餐行。
|
||
|
||
**Architecture:** 新增 `biz_checkup_institution_package` + `_package_item` 两张表;CRUD 追加到 `pilot_service.py`;API 在 `pilot_service.py` 路由文件增加 5 个端点,POST/PUT 一次提交 `items[]` 并在事务内全量替换明细;前端在 `checkup-institution/index.vue` 的「项目价格配置」弹窗内用 Tab 分离「项目列表 / 套餐管理」,套餐编辑交互对齐 `project-price-system` 主子表弹窗。
|
||
|
||
**Tech Stack:** Python 3.9 / FastAPI / SQLAlchemy / Pydantic v2;Vue 3 / Element Plus / TypeScript;MySQL 5.7
|
||
|
||
**Design Spec:** `docs/superpowers/specs/2026-05-18-checkup-project-package-design.md`
|
||
|
||
**SQL:** `docs/sql/checkup_institution_package.sql`
|
||
|
||
**Out of Scope (Phase 2):** 中试申请单体检「从套餐添加」展开预约明细
|
||
|
||
---
|
||
|
||
## File Structure
|
||
|
||
| # | 文件 | 操作 | 职责 |
|
||
|---|------|------|------|
|
||
| 1 | `docs/sql/checkup_institution_package.sql` | 已有 | 建表 DDL(实施前在目标库执行) |
|
||
| 2 | `docs/sql/zkxw.sql` | 追加 | 基线库同步两张新表(与增量 SQL 一致) |
|
||
| 3 | `backend/app/models/models.py` | 追加 | `BizCheckupInstitutionPackage` / `Item` |
|
||
| 4 | `backend/app/crud/pilot_service.py` | 追加 | 套餐 CRUD + 校验辅助函数 |
|
||
| 5 | `backend/app/api/pilot_service.py` | 追加 | 路由 + Pydantic Body |
|
||
| 6 | `web-admin/src/api/admin.ts` | 追加 | 5 个 fetch 函数 |
|
||
| 7 | `web-admin/src/views/pilot-service/checkup-institution/CheckupPackagePanel.vue` | 新建 | 套餐 Tab:列表 + 主子表弹窗 + 选项目子弹窗 |
|
||
| 8 | `web-admin/src/views/pilot-service/checkup-institution/index.vue` | 修改 | 项目配置弹窗改为 `el-tabs`;引入套餐面板 |
|
||
| 9 | `web-admin/src/App.vue` | 按需 | 若套餐弹窗标题需竖条,在 `panelTitleBarTextRules` 登记(可选) |
|
||
|
||
---
|
||
|
||
## Task 1: 数据库
|
||
|
||
**Files:**
|
||
- Execute: `docs/sql/checkup_institution_package.sql`
|
||
- Modify: `docs/sql/zkxw.sql`(在 `biz_checkup_institution_project` 表定义之后插入两张新表,内容与增量 SQL 一致)
|
||
|
||
- [ ] **Step 1: 在开发/测试库执行增量 SQL**
|
||
|
||
```bash
|
||
mysql -h127.0.0.1 -uroot -p zkxw < docs/sql/checkup_institution_package.sql
|
||
```
|
||
|
||
Expected: `Query OK`,`SHOW TABLES LIKE 'biz_checkup_institution_package%'` 返回 2 行。
|
||
|
||
- [ ] **Step 2: 将建表语句合并进 `docs/sql/zkxw.sql`**
|
||
|
||
在 `biz_checkup_institution_project` 段落后、`biz_clean_room` 之前插入 `checkup_institution_package.sql` 中两张 `CREATE TABLE`(勿重复 DROP 破坏全量脚本顺序时可只保留 CREATE)。
|
||
|
||
- [ ] **Step 3: Commit**
|
||
|
||
```bash
|
||
git add docs/sql/checkup_institution_package.sql docs/sql/zkxw.sql
|
||
git commit -m "feat(db): add checkup institution package tables"
|
||
```
|
||
|
||
---
|
||
|
||
## Task 2: 后端 Model
|
||
|
||
**Files:**
|
||
- Modify: `backend/app/models/models.py`(在 `BizCheckupInstitutionProject` 类之后、`# 生产管理` 注释之前)
|
||
|
||
- [ ] **Step 1: 追加两个 Model 类**
|
||
|
||
```python
|
||
class BizCheckupInstitutionPackage(Base):
|
||
"""体检机构项目套餐 biz_checkup_institution_package"""
|
||
|
||
__tablename__ = "biz_checkup_institution_package"
|
||
|
||
id = Column(String(64), primary_key=True, comment="主键ID")
|
||
checkup_institution_id = Column(String(64), nullable=False, comment="体检机构ID")
|
||
package_name = Column(String(200), nullable=False, comment="套餐名称")
|
||
package_code = Column(String(64), nullable=False, comment="套餐编码")
|
||
status = Column(CHAR(1), nullable=False, default="1", comment="1正常 0停用")
|
||
tenant_no = Column(String(64), nullable=False, default="zkxw", comment="租户编号")
|
||
order_num = Column(Integer, nullable=True, comment="排序号")
|
||
remark = Column(String(512), nullable=True, comment="备注")
|
||
create_by = Column(String(64), nullable=True, comment="创建人id")
|
||
create_dept_id = Column(String(64), nullable=False, default="", comment="创建人部门ID")
|
||
create_time = Column(DateTime, nullable=False, default=datetime.now, comment="创建时间")
|
||
update_by = Column(String(64), nullable=True, comment="更新人id")
|
||
update_time = Column(DateTime, nullable=False, default=datetime.now, onupdate=datetime.now, comment="更新时间")
|
||
del_flag = Column(SmallInteger, nullable=False, default=0, comment="删除标记")
|
||
|
||
|
||
class BizCheckupInstitutionPackageItem(Base):
|
||
"""体检机构项目套餐明细 biz_checkup_institution_package_item"""
|
||
|
||
__tablename__ = "biz_checkup_institution_package_item"
|
||
|
||
id = Column(String(64), primary_key=True, comment="主键ID")
|
||
package_id = Column(String(64), nullable=False, comment="套餐ID")
|
||
checkup_institution_project_id = Column(String(64), nullable=False, comment="机构项目ID")
|
||
project_name = Column(String(200), nullable=False, comment="项目名称快照")
|
||
project_fee = Column(Numeric(18, 2), nullable=True, comment="套餐内项目费用(元)")
|
||
tenant_no = Column(String(64), nullable=False, default="zkxw", comment="租户编号")
|
||
order_num = Column(Integer, nullable=True, comment="排序号")
|
||
remark = Column(String(512), nullable=True, comment="备注")
|
||
create_by = Column(String(64), nullable=True, comment="创建人id")
|
||
create_dept_id = Column(String(64), nullable=False, default="", comment="创建人部门ID")
|
||
create_time = Column(DateTime, nullable=False, default=datetime.now, comment="创建时间")
|
||
update_by = Column(String(64), nullable=True, comment="更新人id")
|
||
update_time = Column(DateTime, nullable=False, default=datetime.now, onupdate=datetime.now, comment="更新时间")
|
||
del_flag = Column(SmallInteger, nullable=False, default=0, comment="删除标记")
|
||
```
|
||
|
||
- [ ] **Step 2: 更新 `pilot_service.py` CRUD 顶部 import**
|
||
|
||
在 `backend/app/crud/pilot_service.py` 的 `from app.models.models import (` 中追加:
|
||
|
||
```python
|
||
BizCheckupInstitutionPackage,
|
||
BizCheckupInstitutionPackageItem,
|
||
```
|
||
|
||
- [ ] **Step 3: 验证 import**
|
||
|
||
```bash
|
||
cd backend && source venv/bin/activate && python -c "from app.models.models import BizCheckupInstitutionPackage, BizCheckupInstitutionPackageItem; print('OK')"
|
||
```
|
||
|
||
Expected: `OK`
|
||
|
||
- [ ] **Step 4: Commit**
|
||
|
||
```bash
|
||
git add backend/app/models/models.py backend/app/crud/pilot_service.py
|
||
git commit -m "feat: add SQLAlchemy models for checkup institution packages"
|
||
```
|
||
|
||
---
|
||
|
||
## Task 3: 后端 CRUD
|
||
|
||
**Files:**
|
||
- Modify: `backend/app/crud/pilot_service.py`(在 `soft_delete_checkup_institution_project` 之后、`# Shared utility` 之前新增 Entity 14 段)
|
||
|
||
- [ ] **Step 1: 实现字典转换与辅助函数**
|
||
|
||
新增函数(命名固定,便于 API 层调用):
|
||
|
||
| 函数 | 职责 |
|
||
|------|------|
|
||
| `_row_to_dict_checkup_institution_package(row, *, total_fee=None, item_count=None)` | 头表 camelCase + `totalFee` / `itemCount` |
|
||
| `_row_to_dict_checkup_institution_package_item(row, *, source_project_deleted=False)` | 明细 + `sourceProjectDeleted` |
|
||
| `_sum_package_item_fees(items)` | `sum(project_fee or 0)` |
|
||
| `checkup_package_code_exists(db, institution_id, code, tenant_no, exclude_id=None)` | 同机构 `del_flag=0` 编码唯一 |
|
||
| `_get_checkup_project_for_package(db, project_id, institution_id, tenant_no)` | 查源项目,不存在返回 None |
|
||
| `_validate_package_items_for_save(db, institution_id, items, tenant_no, existing_item_project_ids=None)` | 见下方规则 |
|
||
|
||
**`_validate_package_items_for_save` 规则:**
|
||
|
||
1. `items` 至少 1 条;同一请求内 `checkup_institution_project_id` 不重复。
|
||
2. 对每条明细:
|
||
- 若 `project_id` 属于 `existing_item_project_ids`(更新时保留的历史行):允许源项目 `del_flag=1`,但须校验曾属于本机构。
|
||
- 否则:源项目须 `del_flag=0`、`status=1`、且 `checkup_institution_id` 匹配。
|
||
3. 若任一新添加行源项目 `status=0`:抛出 `ValueError("套餐不能包含已停用的体检项目")`。
|
||
|
||
- [ ] **Step 2: 实现列表 / 详情 / 创建 / 更新 / 删除**
|
||
|
||
```python
|
||
def list_checkup_institution_packages(db, checkup_institution_id, tenant_no=None) -> List[Dict[str, Any]]:
|
||
# 查头表 del_flag=0,按 create_time desc
|
||
# 对每个 package 查明细 del_flag=0,计算 totalFee、itemCount
|
||
...
|
||
|
||
def get_checkup_institution_package(db, package_id, tenant_no=None):
|
||
# 头表单条,不存在返回 None
|
||
...
|
||
|
||
def get_checkup_institution_package_detail(db, package_id, tenant_no=None) -> Optional[Dict[str, Any]]:
|
||
# 头表 dict + items[](join 源项目 del_flag 填 sourceProjectDeleted)
|
||
...
|
||
|
||
def create_checkup_institution_package(
|
||
db, *, checkup_institution_id, package_name, package_code, status="1",
|
||
items, tenant_no, order_num=None, remark=None, user_id=None,
|
||
):
|
||
# 1. checkup_package_code_exists → ValueError
|
||
# 2. _validate_package_items_for_save(..., existing_item_project_ids=None)
|
||
# 3. 插入头表 + 循环插入明细(project_name 优先 body,否则源项目名)
|
||
# 4. commit;返回 get_checkup_institution_package_detail(...)
|
||
...
|
||
|
||
def update_checkup_institution_package(
|
||
db, package_id, *, package_name=None, package_code=None, status=None,
|
||
items=None, order_num=None, remark=None, user_id=None, tenant_no=None,
|
||
):
|
||
# 1. get 头表
|
||
# 2. 若改 package_code 做唯一校验
|
||
# 3. 若 items is not None:
|
||
# - 读取旧明细 project_id 集合 → existing_item_project_ids
|
||
# - _validate_package_items_for_save(..., existing_item_project_ids=...)
|
||
# - 旧明细全部 soft_delete (del_flag=1)
|
||
# - 插入新明细行(新 id)
|
||
# 4. commit;返回 detail
|
||
...
|
||
|
||
def soft_delete_checkup_institution_package(db, package_id, tenant_no=None):
|
||
# 头表 del_flag=1;其下明细 del_flag=1
|
||
...
|
||
```
|
||
|
||
**机构项目删除:** 确认 `soft_delete_checkup_institution_project` **不增加**套餐引用检查(保持现状即可,在 commit message 中注明符合设计)。
|
||
|
||
- [ ] **Step 3: 本地 CRUD 冒烟(可选,Django shell 等价)**
|
||
|
||
```bash
|
||
cd backend && source venv/bin/activate && python - <<'PY'
|
||
from app.core.database import SessionLocal
|
||
from app.crud import pilot_service as ps
|
||
db = SessionLocal()
|
||
# 替换为库内真实 institution_id / project_id
|
||
inst_id = "REPLACE"
|
||
proj_id = "REPLACE"
|
||
try:
|
||
detail = ps.create_checkup_institution_package(
|
||
db,
|
||
checkup_institution_id=inst_id,
|
||
package_name="基础套餐",
|
||
package_code="PKG_BASE_01",
|
||
status="1",
|
||
items=[{"checkup_institution_project_id": proj_id, "project_name": "血常规", "project_fee": 100.0}],
|
||
tenant_no="zkxw",
|
||
user_id="admin",
|
||
)
|
||
print("totalFee", detail.get("totalFee"), "items", len(detail.get("items", [])))
|
||
finally:
|
||
db.close()
|
||
PY
|
||
```
|
||
|
||
Expected: 打印 `totalFee 100.0 items 1`(无异常;测试后可软删该套餐)
|
||
|
||
- [ ] **Step 4: Commit**
|
||
|
||
```bash
|
||
git add backend/app/crud/pilot_service.py
|
||
git commit -m "feat: add CRUD for checkup institution project packages"
|
||
```
|
||
|
||
---
|
||
|
||
## Task 4: 后端 API 路由
|
||
|
||
**Files:**
|
||
- Modify: `backend/app/api/pilot_service.py`(在体检机构项目路由段之后追加)
|
||
|
||
- [ ] **Step 1: 定义 Pydantic Body**
|
||
|
||
```python
|
||
class CheckupPackageItemBody(BaseModel):
|
||
model_config = ConfigDict(populate_by_name=True)
|
||
|
||
checkup_institution_project_id: str = Field(..., alias="checkupInstitutionProjectId")
|
||
project_name: Optional[str] = Field(None, alias="projectName")
|
||
project_fee: Optional[float] = Field(None, alias="projectFee")
|
||
order_num: Optional[int] = Field(None, alias="orderNum")
|
||
remark: Optional[str] = None
|
||
|
||
|
||
class CheckupInstitutionPackageCreateBody(BaseModel):
|
||
model_config = ConfigDict(populate_by_name=True)
|
||
|
||
package_name: str = Field(..., alias="packageName")
|
||
package_code: str = Field(..., alias="packageCode")
|
||
status: Optional[str] = Field("1", alias="status")
|
||
order_num: Optional[int] = Field(0, alias="orderNum")
|
||
remark: Optional[str] = None
|
||
items: List[CheckupPackageItemBody] = Field(default_factory=list)
|
||
|
||
|
||
class CheckupInstitutionPackageUpdateBody(BaseModel):
|
||
model_config = ConfigDict(populate_by_name=True)
|
||
|
||
package_name: Optional[str] = Field(None, alias="packageName")
|
||
package_code: Optional[str] = Field(None, alias="packageCode")
|
||
status: Optional[str] = Field(None, alias="status")
|
||
order_num: Optional[int] = Field(None, alias="orderNum")
|
||
remark: Optional[str] = None
|
||
items: Optional[List[CheckupPackageItemBody]] = None
|
||
```
|
||
|
||
- [ ] **Step 2: 注册 5 个路由**
|
||
|
||
| 方法 | 路径 | 处理 |
|
||
|------|------|------|
|
||
| GET | `/checkup-institutions/{institution_id}/packages` | `list_checkup_institution_packages` → `success_response(list)` |
|
||
| POST | `/checkup-institutions/{institution_id}/packages` | `create_checkup_institution_package`,body 转 `items` 为 dict 列表 |
|
||
| GET | `/checkup-institution-packages/{package_id}` | `get_checkup_institution_package_detail` |
|
||
| PUT | `/checkup-institution-packages/{package_id}` | `update_checkup_institution_package` |
|
||
| DELETE | `/checkup-institution-packages/{package_id}` | `soft_delete_checkup_institution_package` |
|
||
|
||
权限:`require_admin_or_super_admin`;`tenant_no = _get_tenant_no(db, user)`。
|
||
|
||
- [ ] **Step 3: 启动后端并用 curl 验证(需有效 JWT)**
|
||
|
||
```bash
|
||
# 登录后替换 TOKEN、INSTITUTION_ID
|
||
curl -s -H "Authorization: Bearer TOKEN" \
|
||
"http://127.0.0.1:8000/api/web/admin/checkup-institutions/INSTITUTION_ID/packages" | head -c 500
|
||
```
|
||
|
||
Expected: `code:200`,`data` 为数组(可为 `[]`)。
|
||
|
||
- [ ] **Step 4: Commit**
|
||
|
||
```bash
|
||
git add backend/app/api/pilot_service.py
|
||
git commit -m "feat: add REST API for checkup institution project packages"
|
||
```
|
||
|
||
---
|
||
|
||
## Task 5: 前端 API
|
||
|
||
**Files:**
|
||
- Modify: `web-admin/src/api/admin.ts`(在 `fetchDeleteCheckupInstitutionProject` 之后)
|
||
|
||
- [ ] **Step 1: 追加 5 个函数**
|
||
|
||
```typescript
|
||
// 中试服务管理 — 体检机构项目套餐
|
||
export function fetchCheckupInstitutionPackages(institutionId: string) {
|
||
return request.get({ url: `/web/admin/checkup-institutions/${institutionId}/packages` })
|
||
}
|
||
|
||
export function fetchCheckupInstitutionPackageDetail(packageId: string) {
|
||
return request.get({ url: `/web/admin/checkup-institution-packages/${packageId}` })
|
||
}
|
||
|
||
export function fetchCreateCheckupInstitutionPackage(
|
||
institutionId: string,
|
||
data: Record<string, unknown>
|
||
) {
|
||
return request.post({
|
||
url: `/web/admin/checkup-institutions/${institutionId}/packages`,
|
||
params: data
|
||
})
|
||
}
|
||
|
||
export function fetchUpdateCheckupInstitutionPackage(
|
||
packageId: string,
|
||
data: Record<string, unknown>
|
||
) {
|
||
return request.put({ url: `/web/admin/checkup-institution-packages/${packageId}`, params: data })
|
||
}
|
||
|
||
export function fetchDeleteCheckupInstitutionPackage(packageId: string) {
|
||
return request.del({ url: `/web/admin/checkup-institution-packages/${packageId}` })
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 2: Commit**
|
||
|
||
```bash
|
||
git add web-admin/src/api/admin.ts
|
||
git commit -m "feat(web-admin): add API helpers for checkup institution packages"
|
||
```
|
||
|
||
---
|
||
|
||
## Task 6: 前端 — `CheckupPackagePanel.vue`
|
||
|
||
**Files:**
|
||
- Create: `web-admin/src/views/pilot-service/checkup-institution/CheckupPackagePanel.vue`
|
||
|
||
**Props:**
|
||
|
||
```typescript
|
||
const props = defineProps<{
|
||
institutionId: string
|
||
institutionName: string
|
||
}>()
|
||
```
|
||
|
||
- [ ] **Step 1: 套餐列表区**
|
||
|
||
- 工具栏:已选 N 项 +「新增套餐」(唯一 `type="primary"`)
|
||
- 表格列:序号、套餐名称、套餐编码、项目数(`itemCount`)、合计费用(`totalFee`)、状态(tag)、备注、操作(编辑/删除)
|
||
- `loadPackages()` → `fetchCheckupInstitutionPackages(props.institutionId)`,解析 `Array.isArray(res) ? res : res?.list ?? []`
|
||
|
||
- [ ] **Step 2: 套餐新增/编辑弹窗(主子表)**
|
||
|
||
对齐 `project-price-system/index.vue`:
|
||
|
||
**头部表单**(`form-grid-2`,`label-position="top"`):
|
||
|
||
| 字段 | 校验 |
|
||
|------|------|
|
||
| packageName | required |
|
||
| packageCode | required |
|
||
| status | `1` / `0` |
|
||
| remark | 可选 |
|
||
|
||
**明细区**:
|
||
|
||
- 按钮「添加项目」→ 打开子弹窗
|
||
- 表格列:序号、项目名称(+ `sourceProjectDeleted` 时 `<span class="form-label-tip">源项目已删除</span>`)、项目费用(可编辑 `el-input-number`)、操作(删除行)
|
||
- 底部:`套餐合计:¥ {{ packageTotalFee }}`(computed,`detailItems` 的 `projectFee` 求和)
|
||
|
||
**提交 payload:**
|
||
|
||
```typescript
|
||
{
|
||
packageName: string,
|
||
packageCode: string,
|
||
status: string,
|
||
remark?: string,
|
||
items: detailItems.value.map((row, idx) => ({
|
||
checkupInstitutionProjectId: row.checkupInstitutionProjectId,
|
||
projectName: row.projectName,
|
||
projectFee: row.projectFee,
|
||
orderNum: idx + 1
|
||
}))
|
||
}
|
||
```
|
||
|
||
- 新增:`fetchCreateCheckupInstitutionPackage(institutionId, payload)`
|
||
- 编辑:`fetchUpdateCheckupInstitutionPackage(id, payload)`;打开时 `fetchCheckupInstitutionPackageDetail(id)` 填充 `detailItems`
|
||
|
||
- [ ] **Step 3: 「添加项目」子弹窗**
|
||
|
||
- 调用已有 `fetchCheckupInstitutionProjects(props.institutionId)`
|
||
- 仅展示 `status == '1'` 且未在 `detailItems` 中的项目
|
||
- 多选确认后 push:
|
||
|
||
```typescript
|
||
{
|
||
checkupInstitutionProjectId: row.id,
|
||
projectName: row.projectName,
|
||
projectFee: row.projectFee ?? 0,
|
||
sourceProjectDeleted: false
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 4: 删除套餐**
|
||
|
||
`ElMessageBox.confirm` → `fetchDeleteCheckupInstitutionPackage` → `loadPackages()`
|
||
|
||
- [ ] **Step 5: Commit**
|
||
|
||
```bash
|
||
git add web-admin/src/views/pilot-service/checkup-institution/CheckupPackagePanel.vue
|
||
git commit -m "feat(web-admin): add checkup package panel component"
|
||
```
|
||
|
||
---
|
||
|
||
## Task 7: 前端 — 接入 `index.vue` Tab
|
||
|
||
**Files:**
|
||
- Modify: `web-admin/src/views/pilot-service/checkup-institution/index.vue`
|
||
|
||
- [ ] **Step 1: 将「项目价格配置」弹窗正文改为 `el-tabs`**
|
||
|
||
```vue
|
||
<el-tabs v-model="itemConfigTab" class="item-config-tabs">
|
||
<el-tab-pane label="项目列表" name="projects">
|
||
<!-- 现有 list-toolbar + el-table + 项目嵌套弹窗 移入此 pane -->
|
||
</el-tab-pane>
|
||
<el-tab-pane label="套餐管理" name="packages">
|
||
<CheckupPackagePanel
|
||
v-if="itemConfigTab === 'packages' && currentInstitutionId"
|
||
:institution-id="currentInstitutionId"
|
||
:institution-name="currentInstitutionName"
|
||
/>
|
||
</el-tab-pane>
|
||
</el-tabs>
|
||
```
|
||
|
||
- [ ] **Step 2: script 增加**
|
||
|
||
```typescript
|
||
import CheckupPackagePanel from './CheckupPackagePanel.vue'
|
||
const itemConfigTab = ref<'projects' | 'packages'>('projects')
|
||
```
|
||
|
||
- `openItemDialog` 时重置 `itemConfigTab = 'projects'`
|
||
|
||
- [ ] **Step 3: 弹窗宽度**
|
||
|
||
项目配置弹窗 `width` 调至 `960px` 或 `1000px`,避免 Tab 内表格拥挤。
|
||
|
||
- [ ] **Step 4: 机构操作列(可选)**
|
||
|
||
主列表操作列可增加「套餐配置」按钮,`openItemDialog(row)` 且 `itemConfigTab = 'packages'`(非必须,Tab 已可达)。
|
||
|
||
- [ ] **Step 5: Commit**
|
||
|
||
```bash
|
||
git add web-admin/src/views/pilot-service/checkup-institution/index.vue
|
||
git commit -m "feat(web-admin): integrate package tab in checkup institution config dialog"
|
||
```
|
||
|
||
---
|
||
|
||
## Task 8: 联调与验收
|
||
|
||
- [ ] **Step 1: 后端 lint / 启动**
|
||
|
||
```bash
|
||
cd backend && source venv/bin/activate && python -c "from app.api.pilot_service import router; print('routes OK')"
|
||
cd web-admin && pnpm lint
|
||
```
|
||
|
||
- [ ] **Step 2: 手工验收清单(Phase 1)**
|
||
|
||
| # | 场景 | 预期 |
|
||
|---|------|------|
|
||
| 1 | 新增套餐,编码必填,选 2 个项目并改价 | 保存成功;列表 `totalFee` = 两行费用之和 |
|
||
| 2 | 同机构重复套餐编码 | 接口 400,提示编码已存在 |
|
||
| 3 | 套餐含停用项目(新加行) | 保存失败 |
|
||
| 4 | 编辑套餐:删除一行、改价 | 合计随动;保存后列表更新 |
|
||
| 5 | 软删除某机构项目(被套餐引用) | 删除成功;编辑套餐见「源项目已删除」,合计仍含该行 |
|
||
| 6 | 删除套餐 | 列表不再显示 |
|
||
|
||
- [ ] **Step 3: Commit(若有验收期小修)**
|
||
|
||
```bash
|
||
git commit -m "fix: checkup package QA adjustments after manual test"
|
||
```
|
||
|
||
---
|
||
|
||
## Spec Coverage(自检)
|
||
|
||
| 设计要求 | 任务 |
|
||
|----------|------|
|
||
| 套餐编码必填、机构内唯一 | Task 3 `checkup_package_code_exists`;Task 6 表单校验 |
|
||
| 总价 = 明细之和、不落库 | Task 3 `_sum_package_item_fees`;Task 6 computed |
|
||
| 明细价可改、不回写机构项目 | Task 3 只写 package_item;Task 6 可编辑列 |
|
||
| 机构项目软删不拦截、明细保留 | Task 3 校验 + `sourceProjectDeleted`;Task 3 不改 delete 逻辑 |
|
||
| 含停用项禁止保存(新行) | Task 3 `_validate_package_items_for_save` |
|
||
| 5 个 API 端点 | Task 4 |
|
||
| Tab:项目 / 套餐 | Task 7 |
|
||
| Phase 2 从套餐带入 | **未纳入本计划** |
|
||
|
||
---
|
||
|
||
## Phase 2 备忘(本计划不实施)
|
||
|
||
中试申请单体检模块开发时:
|
||
|
||
- 选机构 →「从套餐添加」→ 仅展开 `del_flag=0 && status=1` 的源项目行
|
||
- 写入 `biz_pilot_checkup_reserve_item` 快照
|
||
|
||
详见设计文档 §6。
|