跳转至

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

具体修复方案(详见 §四):

  1. 方案 A(推荐 · 配置层):升级 next.config.mjs 开发环境禁用 webpack persistent cache
  2. 方案 B(脚本层):新增 dev:clean npm script 一键清缓存启动
  3. 方案 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.pyc.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_codecode
测试脚本归档 ✅ 需要(移至 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 联调方案):

  1. 接口依赖清单确认(65 端点 × 5 域 × 3 端)
  2. 测试数据准备方案(扩展至 50 单 UAT 规模)
  3. OBS-01 修复(方案 A 落地 next.config.mjs
  4. 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(司徒总)需终审签名后正式归档。