DIP1 一线操作系统 · UAT 阶段交付总结¶
| 字段 | 值 |
|---|---|
| 报告编号 | DWLG-20260810-08 |
| 关联报告 | DWLG-20260810-04(R1 16 Bug)/ 05(R2 复测)/ 06(R2 修复)/ 07(R3 复测) |
| 覆盖范围 | UAT 三轮迭代(R1 首轮发现 → R2 修复 → R3 验收)全部 Bug + 遗留观察项 + 修复方案 |
| 服务版本 | DIP1-P2 V2.0 后端 + OPR V2.0 前端(category_code 适配版) |
| 交付日期 | 2026-08-10 |
| 维护人 | DT(听写) |
| 状态 | ✅ R3 全通过 · 待 SPO/TL 终审放行 |
一、UAT 三轮迭代全景¶
R1(首轮发现) → R2(修复 + 复测) → R3(最终验收)
16 项 Bug 修复 16 + 新增 4 4/4 通过 + 12/12 回归
7 P0 / 5 P1 BUG-NEW-01~04 ✅ 全绿放行
/ 4 P2
| 轮次 | DWLG 编号 | 主题 | 结果 |
|---|---|---|---|
| R1 | 04 | UAT 第一轮 16 项 Bug 修复(7 P0 + 5 P1 + 4 P2) | ✅ 全部修复 |
| R2 | 05 | R2 复测报告(发现 BUG-NEW-01~04) | ⚠️ 4 项新发现 |
| R2 | 06 | UAT 第二轮 4 项新 Bug 修复 | ✅ 全部修复 |
| R3 | 07 | R3 复测报告(4/4 通过 + 12/12 回归) | ✅ 全绿 |
| 总结 | 08 | 本文档:UAT 阶段交付总结 + OBS 修复方案 | ✅ 放行 |
二、Bug 清单总览(R1 16 项 + R2 4 项 = 20 项)¶
2.1 R1 第一轮 16 项 Bug(DWLG-04 修复)¶
P0 阻塞级(7 项)¶
| # | Bug ID | 现象 | 根因 | 修复方案 | 修复位置 | R3 状态 |
|---|---|---|---|---|---|---|
| 1 | BUG-001 | 登录 401 unauthorized | 密码哈希校验逻辑漏掉大小写 | 修复 bcrypt verify 流程 | auth_service.py | ✅ |
| 2 | BUG-002 | /health 503 | DB 健康检查超时未处理异常 | try/except + 默认 5s 超时 | health.py | ✅ |
| 3 | BUG-003 | 报价试算返回 500 | QSV service 缺少 param_version 兜底 | 默认 v1.0.0 | qsv_service.py | ✅ |
| 4 | BUG-004 | 订单看板空 | CPT board 查询未关联楼宇 | JOIN mds_buildings | cpt_repository.py | ✅ |
| 5 | BUG-005 | 师傅列表 500 | MDS worker 缺 phone 字段映射 | DTO 补 phone | mds.py (DTO) | ✅ |
| 6 | BUG-006 | 客户档案 500 | CPL PII 脱敏权限判断 NPE | 默认 view_full=False | mds_service.py | ✅ |
| 7 | BUG-007 | 楼宇列表 500 | BPL 地址字段为 NULL 时 .strip() 崩 | (addr or "") 兜底 | mds_service.py | ✅ |
P1 重要级(5 项)¶
| # | Bug ID | 现象 | 根因 | 修复方案 | 修复位置 | R3 状态 |
|---|---|---|---|---|---|---|
| 8 | BUG-008 | 报价创建重复提交 | 前端未禁用按钮 | 提交中 disabled | quotes/new/page.tsx | ✅ |
| 9 | BUG-009 | 订单详情地址显示 UUID | 前端用 building_id 而非 building_short_addr | 改用 short_addr 字段 | orders/[id]/page.tsx | ✅ |
| 10 | BUG-010 | 客户列表分页失效 | page_size 上限 100 误传 0 | 默认 20,clamp[1,100] | mds.py (API) | ✅ |
| 11 | BUG-011 | 师傅等级筛选无响应 | 后端枚举值大小写不一致 | upper() 统一 | mds.py (API) | ✅ |
| 12 | BUG-012 | 推荐奖励审批 403 | RBAC 缺 ops:reward:approve 权限 | 权限矩阵补 | rbac.py | ✅ |
P2 一般级(4 项)¶
| # | Bug ID | 现象 | 根因 | 修复方案 | 修复位置 | R3 状态 |
|---|---|---|---|---|---|---|
| 13 | BUG-013 | 报价列表时间格式 | 显示 ISO 原文 | dayjs format YYYY-MM-DD HH:mm | quotes/page.tsx | ✅ |
| 14 | BUG-014 | 订单卡片师傅显示 UUID | 前端用 worker_id 而非 worker_name | 改用 worker_name | orders/page.tsx | ✅ |
| 15 | BUG-015 | 驾驶舱今日安装显示总订单数 | 后端 POOL_L6 过滤条件缺 install_date | 加 date.today() | cpt_repository.py | ✅ |
| 16 | BUG-016 | 登出后路由未跳转 | 缺 router.push("/login") | 补 push | components/header.tsx | ✅ |
2.2 R2 第二轮 4 项新 Bug(DWLG-06 修复)¶
| # | Bug ID | 现象 | 根因 | 修复方案 | 修复位置 | R3 状态 |
|---|---|---|---|---|---|---|
| 17 | BUG-NEW-01 | 报价试算金额为 0(UUID 入参) | qsv_service.calculate_preview 直接用 category_id(UUID)作字典 key |
改为:UUID → db 查表 resolve_category → 取 category.code → 入参数字典 |
qsv_service.py:148-162 |
✅ 380/1200 |
| 18 | BUG-NEW-02 | 报价列表品类列为空 | 后端 DTO 缺 category_code;前端渲染 category_id |
① DTO 加 category_code + JOIN scl_categories;② 前端改用 r.category_code 映射 |
DTO qsv.py + quotes/page.tsx:70 |
✅ 衣櫃 |
| 19 | BUG-NEW-03 | 订单看板品类显示 UUID | 后端 DTO 缺 category_code;前端 CATEGORY_LABELS[category_id] 回退 UUID |
① DTO 加 category_code;② 前端双级回退 category_code ?? category_id |
DTO cpt.py + orders/page.tsx:53 |
✅ 衣櫃/床/廚櫃 |
| 20 | BUG-NEW-04 | 驾驶舱跟踪池全部显示 0 | Next.js 14 dev .next 缓存损坏致 React 未挂载(业务代码无 Bug) |
清 .next + 重启 dev server(业务代码无改动) |
环境层 | ✅ 1/1/0/1/0/0 |
2.3 Bug 分布与修复统计¶
| 维度 | 数量 | 占比 |
|---|---|---|
| P0 阻塞 | 7 | 35% |
| P1 重要 | 5 | 25% |
| P2 一般 | 4 | 20% |
| R2 新增(业务功能) | 3(NEW-01/02/03) | 15% |
| R2 新增(环境/测试) | 1(NEW-04) | 5% |
| 合计 | 20 | 100% |
| R3 验收通过 | 20/20 | 100% |
按修复层分布:
| 层 | Bug 数 | 占比 |
|---|---|---|
| 后端服务层(service/repository) | 9 | 45% |
| 后端 DTO / API 层 | 5 | 25% |
| 前端页面 / 组件 | 5 | 25% |
| 环境 / 运维 | 1 | 5% |
三、遗留观察项(OBS)¶
3.1 OBS-01:Next.js 14 Dev Server .next 缓存损坏¶
状态:⚠️ 真实问题(开发环境,不影响生产构建)
| 字段 | 内容 |
|---|---|
| 现象 | 浏览器控制台报 net::ERR_ABORTED(main-app.js / page.js / layout.css 404),React 未挂载,登录按钮触发原生 GET 提交(URL 带 ?username=&password=) |
| 触发条件 | dev server 启动后长时间运行(>2h)+ Hot Reload 累计多次;或多次停启 |
| 影响等级 | 中(仅 dev 环境,EAS Build 生产构建无此问题) |
| 根本原因 | Next.js 14.2.x dev server 已知 issue(next/font 缓存 + webpack chunk manifest 不一致) |
| 临时缓解 | Remove-Item -Recurse -Force dip1/frontend/apps/opr-admin/.next; npm run dev |
具体修复方案(详见 §四):
- 方案 A(推荐 · 配置层):升级
next.config.mjs开发环境禁用 webpack persistent cache - 方案 B(脚本层):新增
dev:cleannpm script 一键清缓存启动 - 方案 C(CI 层):GitHub Actions dev CI job 启动前强制清
.next
3.2 OBS-02:MDS /mds/categories 列表 category_code 为 null —— ⚠️ 测试脚本误报¶
状态:✅ 澄清为测试脚本误报,非真实 Bug
| 字段 | 内容 |
|---|---|
| R3 复测报告记录 | "MDS /mds/categories 列表 category_code 字段为 null" |
| 真相 | 后端 DTO SclCategoryResponse 字段名是 code(非 category_code),DB 中 code 字段值完整(WARDROBE/KITCHEN_CABINET/...) |
| 误报根因 | R3 复测脚本 _retest_mds_cat.py 用 c.get('category_code') 取值(错误字段名),应改为 c.get('code') |
| 二次验证 | 已用修正脚本 _retest_mds_cat_v2.py 验证,返回 code=WARDROBE 正常 |
| 结论 | OBS-02 关闭,无后端代码修改需求 |
衍生改进(详见 §四 OBS-02 修复方案):测试脚本规范化,统一字段名约定。
四、OBS 修复方案(下一轮迭代执行)¶
4.1 OBS-01 修复方案¶
方案 A:next.config.mjs 配置层(推荐)¶
文件:dip1/frontend/apps/opr-admin/next.config.mjs
在现有 nextConfig 中追加开发环境配置:
/** @type {import('next').NextConfig} */
const nextConfig = {
// 现有配置...
// ===== OBS-01 修复:dev 环境禁用 webpack persistent cache =====
webpack: (config, { dev, isServer }) => {
if (dev) {
// 禁用持久化缓存,避免 .next/cache 损坏导致 chunk manifest 不一致
config.cache = false;
}
return config;
},
};
export default nextConfig;
优点: - 一次配置,全局生效 - 不改变开发流程 - 不影响生产构建(仅 dev 生效)
代价: - 首次冷启动慢约 2-3s(无缓存预热) - 但避免了随机崩溃的更大代价
方案 B:新增 dev:clean npm script(辅助)¶
文件:dip1/frontend/apps/opr-admin/package.json
{
"scripts": {
"dev": "next dev -p 3000",
"dev:clean": "rimraf .next && next dev -p 3000",
"build": "next build",
"start": "next start -p 3000"
},
"devDependencies": {
"rimraf": "^5.0.5"
}
}
使用方式:
# 常规开发
npm run dev
# 缓存损坏时一键清理启动
npm run dev:clean
方案 C:DIP1-IMP 开发规范补充¶
文件:dip1/docs/DIP1-IMP-Code-Agent实施指南.md §4 环境初始化
新增步骤:
每日首次启动前:执行
npm run dev:clean而非npm run dev,确保.next缓存全新。生产构建(npm run build)不受影响。
综合建议¶
| 阶段 | 措施 | 优先级 |
|---|---|---|
| 立即 | 落地方案 A(config 配置) | P0 |
| 短期 | 落地方案 B(dev:clean script) | P1 |
| 文档 | 落地方案 C(IMP 规范补充) | P2 |
4.2 OBS-02 修复方案¶
真相澄清¶
经二次验证,OBS-02 不存在后端 Bug,DTO 字段定义正确:
# dip1/backend/app/application/dtos/mds.py:10-21
class SclCategoryResponse(BaseModel):
"""安装服务品类(SCL)列表项."""
category_id: str
code: str # ← 字段名是 code,非 category_code
name_zh: str
base_fee_hkd: Decimal
base_hours: Decimal
cap_price_hkd: Decimal
required_worker_level: str
display_order: int | None
is_active: bool
DB 实际数据:
CAT299723485B2B188EE7ED1C | WARDROBE | 衣柜 | t
CAT29972348F8A8C3C602FE51 | KITCHEN_CABINET | 橱柜 | t
CAT29972348B96CAF06E831DA | BED | 床 | t
...(共 8 条,6 active + 2 inactive)
衍生改进:测试脚本规范化¶
问题:UAT 三轮测试脚本(_retest_*.py)散落在 dip1/docs/worklog/,字段名靠人工记忆,易出错。
方案:将测试脚本归档至 dip1/backend/tests/smoke/,并对齐 OpenAPI YAML 字段名。
目录规划:
dip1/backend/tests/
├── unit/ # 单元测试(已有)
├── integration/ # 集成测试(已有)
└── smoke/ # UAT 冒烟测试(新增)
├── __init__.py
├── conftest.py # 共享 fixture(token、base_url、字段断言)
├── smoke_qsv_preview_uuid.py # BUG-NEW-01 回归(源自 _retest_calc.py)
├── smoke_12_endpoints.py # 12 接口回归(源自 _retest_batch.py)
├── smoke_mds_categories.py # MDS 品类(源自 _retest_mds_cat_v2.py)
└── README.md # 执行说明:pytest tests/smoke/ -v
conftest.py 关键 fixture:
import pytest
import urllib.request
import json
@pytest.fixture(scope="session")
def base_url():
return "http://localhost:8000/api/v1"
@pytest.fixture(scope="session")
def auth_token(base_url):
body = json.dumps({"username": "admin", "password": "admin123"}).encode("utf-8")
req = urllib.request.Request(
f"{base_url}/auth/login",
data=body,
headers={"Content-Type": "application/json"},
method="POST",
)
with urllib.request.urlopen(req) as resp:
return json.loads(resp.read().decode("utf-8"))["access_token"]
@pytest.fixture
def auth_headers(auth_token):
return {"Authorization": f"Bearer {auth_token}"}
字段断言规范化:
# smoke_mds_categories.py
def test_mds_categories_code_field(auth_headers, base_url):
"""OBS-02 回归:MDS 品类列表 code 字段必须非空."""
req = urllib.request.Request(
f"{base_url}/mds/categories?page=1&page_size=20",
headers=auth_headers,
)
with urllib.request.urlopen(req) as resp:
data = json.loads(resp.read().decode("utf-8"))
assert data["total"] >= 6, f"品类数应≥6,实际 {data['total']}"
for item in data["items"]:
assert item["code"], f"品类 {item['category_id']} 的 code 字段为空"
assert item["code"] in {
"WARDROBE", "KITCHEN_CABINET", "BED",
"DINING_TABLE", "BOOKSHELF", "TV_CABINET",
"OFFICE_DESK", "COMBINATION_FURNITURE",
}, f"未知品类 code: {item['code']}"
OBS-02 关闭结论¶
| 项 | 结论 |
|---|---|
| 后端代码修改 | ❌ 不需要(DTO 与 DB 均正确) |
| 测试脚本修改 | ✅ 需要(字段名 category_code → code) |
| 测试脚本归档 | ✅ 需要(移至 dip1/backend/tests/smoke/) |
| 整体状态 | ✅ OBS-02 关闭(误报澄清 + 测试规范化改进) |
五、版本基线快照(R3 通过时刻)¶
5.1 后端服务¶
| 项 | 值 |
|---|---|
| 服务地址 | http://localhost:8000 |
| 健康检查 | GET /health → 200 ✅ |
| 关键修复文件 | qsv_service.py(UUID 查表)/ cpt_repository.py(JOIN)/ mds_service.py(PII 脱敏) |
| DTO 新增字段 | QsvQuoteRead.category_code / CptOrderCard.category_code |
| 数据库 | PostgreSQL 16,22 表,6 品类 / 3 客户 / 3 师傅 / 2 楼宇 / 2 报价 / 4 订单 |
5.2 前端服务¶
| 项 | 值 |
|---|---|
| 服务地址 | http://localhost:3000 |
| 关键修复文件 | quotes/page.tsx:70(category_code 渲染)/ orders/page.tsx:53(双级回退) |
| 端到端验证 | 登录→驾驶舱→订单看板→报价列表→报价试算 全链路通 |
5.3 UAT 测试数据快照¶
| 实体 | 数量 | 关键样本 |
|---|---|---|
| 报价(qsv_quotes) | 2 | QTE0DEMO00000000000001(衣櫃 HK$ 380 已確認)/ QTE0DEMO00000000000002(床 HK$ 419 已確認) |
| 订单(cpt_orders) | 4 | ORD0DEMO01(L1 衣櫃 陳太)/ ORD0DEMO02(L3 床 林先生)/ ORD0DEMO03(L6 廚櫃 黃小姐 16:00)/ ORD0DEMO04(其他池) |
| 客户(mds_cpl_customers) | 3 | 陳太 / 林先生 / 黃小姐 |
| 師傅(mds_wpl_workers) | 3 | 李師傅 L1 / 陳師傅 L2 / 王師傅 L3 |
| 楼宇(mds_bpl_buildings) | 2 | 德福花園 A 座(私人屋苑有電梯)/ 大圍村南道唐樓(村屋無電梯) |
| 品类(mds_scl_categories) | 6 active + 2 inactive | 衣柜/橱柜/床/餐桌/书柜/电视柜 + 书桌/组合定制 |
六、R3 验收 Checklist 全项¶
6.1 R2 新增 4 项 Bug 验收¶
| Bug ID | 后端 API | 前端 E2E | 综合判定 |
|---|---|---|---|
| BUG-NEW-01 | ✅ base_fee=380 / cap=1200 | ✅ 預覽「HK$ 380 · HK$ 1,200」 | PASS |
| BUG-NEW-02 | ✅ DTO 返回 category_code=WARDROBE | ✅ 列表显示「衣櫃」 | PASS |
| BUG-NEW-03 | ✅ DTO 返回 category_code | ✅ 3 卡片显示「衣櫃/床/廚櫃」 | PASS |
| BUG-NEW-04 | ✅ 6 池结构完整 | ✅ 显示 1/1/0/1/0/0 | PASS |
6.2 R1 16 项 Bug 回归抽测(12 接口 + 6 前端路径)¶
| 类别 | 项数 | 通过 |
|---|---|---|
| API 接口 | 12 | 12/12 ✅ |
| 前端 E2E 关键路径 | 6 | 6/6 ✅ |
| 合计 | 18 | 18/18 ✅ |
6.3 OBS 项处置¶
| OBS ID | 处置 | 状态 |
|---|---|---|
| OBS-01 Next.js 缓存损坏 | §4.1 三方案(config + script + IMP 规范) | 🟡 待下一轮迭代落地 |
| OBS-02 MDS code 字段 null | §4.2 澄清误报 + 测试脚本规范化 | ✅ 关闭 |
七、放行决策¶
7.1 放行判定矩阵¶
| 维度 | 标准 | 实际 | 判定 |
|---|---|---|---|
| P0 Bug | 100% 修复 | 7/7 | ✅ |
| P1 Bug | 100% 修复 | 5/5 | ✅ |
| P2 Bug | 100% 修复 | 4/4 | ✅ |
| R2 新增 Bug | 100% 修复 | 4/4 | ✅ |
| 回归测试 | 无回归 | 18/18 | ✅ |
| OBS 项 | 已有方案且不影响生产 | OBS-01 有方案 / OBS-02 关闭 | ✅ |
| 综合 | 可放行进入下一阶段 | ✅ 放行 |
7.2 下一阶段建议¶
进入 DIP1-P3 一线操作系统联调阶段(详见 DWLG-20260810-09 P3 联调方案):
- 接口依赖清单确认(65 端点 × 5 域 × 3 端)
- 测试数据准备方案(扩展至 50 单 UAT 规模)
- OBS-01 修复(方案 A 落地
next.config.mjs) - OBS-02 测试脚本归档(移至
dip1/backend/tests/smoke/)
7.3 §7.1 状态矩阵变更建议¶
| 矩阵 ID | 当前状态 | 建议变更 | 理由 |
|---|---|---|---|
| C-B1 QSV 报价域 | 🟡 | 🟡(保持) | UAT 通过但 32 用例单测覆盖率未达验收线 |
| C-B2 CPT 跟踪 | 🟡 | 🟡(保持) | UAT 通过但 L1→L8 状态机 0 越权测试未跑 |
| C-B3 MDS 四库 | 🟡 | 🟡(保持) | UAT 通过但地址联想 / 批量打标 NFR 未压测 |
| C-C2 opr-admin Web | 🟡 | 🟡(保持) | UAT 通过但导航矩阵 15 页全覆盖未跑完 |
| DPL-04 OL UAT 50 单 | 🟡 | 🟡(保持) | 当前仅 4 单 UAT,需扩展至 50 单 |
矩阵变更需 TL 终审后正式更新 DIP1-IMP §7.1。
八、附录:临时测试脚本归档清单¶
| 文件(当前位置) | 用途 | 建议归档位置 |
|---|---|---|
_token.txt |
临时 token | ⚠️ 删除(含敏感凭据) |
_relogin.py |
刷新 token | dip1/backend/tests/smoke/conftest.py(fixture 化) |
_retest_calc.py |
BUG-NEW-01 复现 | dip1/backend/tests/smoke/smoke_qsv_preview_uuid.py |
_retest_batch.py |
12 接口回归 | dip1/backend/tests/smoke/smoke_12_endpoints.py |
_retest_mds_cat.py |
误报脚本 | ❌ 删除(已被 v2 替代) |
_retest_mds_cat_v2.py |
MDS 品类正确测试 | dip1/backend/tests/smoke/smoke_mds_categories.py |
— 听写 DT,2026-08-10。本文档为 AI 辅助草案,SPO(曾总)/ TL(司徒总)需终审签名后正式归档。