跳转至

DIP1 UAT 可用性排查报告

编号:DWLG-20260810-03 测试人:听写(DT) 测试日期:2026-08-10 测试范围:opr-admin 运营后台 18 个页面 + 后端 27 个 API 端点 + 6 条核心业务流程 测试方法:浏览器实跑 + curl smoke test + 静态代码审查 交付对象:kimi(Code Agent)按本报告 §四 修改策略执行修复


一、执行摘要

1.1 总体结论

维度 结果
登录链路 ✅ 已修复(forwardRef bug 已解决)
18 个页面可用性 6 个正常 / 5 个部分异常 / 7 个阻断(含 1 个白屏)
27 个后端端点 21 个正常 / 6 个缺失或路径不匹配
6 条核心流程 4 条基本打通 / 2 条断裂(报价管理、客户订单关联)
种子数据 ✅ 已就位(3 客户 + 3 师傅 + 2 楼宇 + 2 报价 + 4 订单 L1/L3/L6/DONE)

1.2 严重程度分布

严重程度 数量 说明
🔴 P0 阻断(页面不可用) 7 必须修复才能开展 UAT
🟡 P1 异常(部分功能失效) 5 影响 UX 但页面可访问
🟢 P2 待优化 4 不阻断但建议修复

1.3 已修复的 Bug(本次排查中处置)

# Bug 文件 处置
F0 登录表单 Input 组件未用 forwardRef,导致 react-hook-form 捕获不到输入值 form.tsx L26-L34 已修复
F1 demo.py _cleanup 未清理 cst_sessions 外键引用,导致重跑失败 demo.py L57-L63 已修复

二、测试环境

2.1 服务端口

服务 端口 状态
后端 FastAPI localhost:8000 ✅ 运行中
前端 Next.js (opr-admin) localhost:3000 ✅ 运行中
PostgreSQL localhost:5432 ✅ 运行中
Redis localhost:6379 ✅ 运行中

2.2 种子数据(demo.py 生成)

实体 数量 示例 ID
报价参数版本 1 QPV0DEMO00000000000001
楼宇 2 BLD0DEMO00000000000001/02
师傅 3 WKR0DEMO00000000000001/02/03(L1/L2/L3)
客户 3 CUS0DEMO00000000000001/02/03
报价 2 QTE0DEMO01(DRAFT)/ QTE0DEMO02(CONFIRMED)
订单 4 ORD0DEMO01(L1)/02(L3)/03(L6)/04(DONE)
管理员 1 admin / admin123(TL 角色,全权限)

三、Bug 清单(按严重程度排序)

3.1 🔴 P0 阻断级(7 个)

BUG-01:CPT 看板 pool 键完全不匹配

  • 影响页面/(驾驶舱)、/orders(订单看板)
  • 现象:驾驶舱 6 个跟踪池卡片全部显示 0;订单看板 6 列全部"暫無卡片"。实际数据库有 4 个订单(L1/L3/L6/DONE)
  • 根因
  • 后端 cpt_service.py:64 返回 _POOL_LABELS = ("TODAY", "OVERDUE", "APPOINTMENT_SOON", "NORMAL")时间维度 4 池
  • 前端 format.ts:99-106page.tsx:13-20 期望 POOL_L1_NEW_LEADS / POOL_L3_PENDING_QUOTE / POOL_L4_PENDING_ASSIGN / POOL_L6_INSTALL_TODAY / POOL_L8_PENDING_FOLLOWUP / POOL_EXCEPTION阶段维度 6 池
  • 实测GET /api/v1/cpt/orders/board 返回 {"TODAY":[1单],"OVERDUE":[],"APPOINTMENT_SOON":[1单],"NORMAL":[1单]}
  • 修复策略:见 §四 策略 1

BUG-02:GET /qsv/quotes 列表端点缺失(405)

  • 影响页面/quotes(报价列表)、/(驾驶舱"待審批報價"统计卡)
  • 现象:报价列表页显示"重試"按钮(query.isError);驾驶舱"待審批報價"显示"—"
  • 根因:后端 qsv.py 只有 POST /quotesGET /quotes/{quote_id}无 GET 列表端点
  • 实测GET /api/v1/qsv/quotes?page=1&page_size=20 → 405 Method Not Allowed
  • 修复策略:见 §四 策略 2

BUG-03:POST /qsv/calculate 路径错误(404)

  • 影响页面/quotes/new(报价试算器)
  • 现象:报价试算器实时预览永远显示错误;品类下拉框为空
  • 根因:前端 quotes/new/page.tsx:98 调用 POST /qsv/calculate,但后端实际端点是 POST /qsv/calculator/preview
  • 实测POST /api/v1/qsv/calculate → 404;POST /api/v1/qsv/calculator/preview → 200
  • 修复策略:见 §四 策略 3

BUG-04:/mds/categories 返回裸数组而非分页对象

  • 影响页面/categories(品类库,白屏)、/quotes/new(品类下拉空)、/workers/[id](技能矩阵空)
  • 现象:品类库页面完全白屏(0 个 DOM 元素);报价试算器品类下拉为空
  • 根因
  • 后端 mds.py:39-53 返回 list[MdsSclCategory](裸数组)
  • 前端 3 处用 api.get<Page<SclCategory>>("/mds/categories") 然后访问 data.items,得到 undefined
  • 品类库页面访问 query.data.items.length 抛出 TypeError 导致白屏
  • 实测GET /api/v1/mds/categories 返回 [{category_id:"CAT...",...}](裸数组)
  • 修复策略:见 §四 策略 4

BUG-05:PATCH /qsv/quotes/{id} 端点缺失(405)

  • 影响页面/quotes/[id](报价详情)
  • 现象:点击"確認報價"或"拒絕"按钮会 405 报错
  • 根因:后端 qsv.py 无 PATCH 端点;前端 quotes/[id]/page.tsx:39 调用 PATCH /qsv/quotes/{id}
  • 实测PATCH /api/v1/qsv/quotes/QTE0DEMO01 → 405
  • 修复策略:见 §四 策略 5

BUG-06:客户详情"歷史訂單"永远空

  • 影响页面/customers/[id](客户详情)
  • 现象:客户详情页"歷史訂單"区块永远显示"暫無訂單記錄",实际该客户有 2 个订单
  • 根因:前端 customers/[id]/page.tsx:37q: customerId(CUS0DEMO...)查询订单,但后端 q 参数按 customer_name 模糊搜索
  • 实测GET /api/v1/cpt/orders?q=CUS0DEMO00000000000001 返回 {total:0}GET /api/v1/cpt/orders?q=陳太 返回 2 个订单
  • 修复策略:见 §四 策略 6

BUG-07:/qsv/params/versions 端点缺失(404)

  • 影响页面/categories(品类库下半部分)
  • 现象:品类库页面"QSV 参数版本历史"区块显示 ErrorState
  • 根因:后端 qsv.py/params/versions 路由
  • 实测GET /api/v1/qsv/params/versions → 404
  • 修复策略:见 §四 策略 7

3.2 🟡 P1 异常级(5 个)

BUG-08:登录响应缺 user 字段

  • 影响:全局(所有依赖 user.roles 的 UI)
  • 现象:登录后 localStorage.weavely_current_user 未存储;getCachedUser() 返回 null;/admin 页面显示"⚠ 當前賬號角色(未知)非 TL/SPO"警告
  • 根因:后端 auth.py:36-40 TokenResponse 只有 {access_token, refresh_token, token_type, expires_in},无 user 字段;前端 auth.ts:11-16 期望 data.user
  • 修复策略:见 §四 策略 8

BUG-09:/auth/me 字段名 role vs roles 不匹配

  • 影响:全局(角色判断失效)
  • 现象:即使调用 fetchCurrentUser()user.roles 仍是 undefinedhasRole() 永远返回 false
  • 根因:后端 UserMeResponse 返回 role: "TL"(单字符串);前端 CurrentUser 期望 roles: Role[](数组)
  • 修复策略:见 §四 策略 9

BUG-10:报价详情字段名 calc vs calc_snapshot 不匹配

  • 影响页面/quotes/[id](报价详情因子展开卡)
  • 现象:因子展开卡显示"後端未返回 calc 快照"
  • 根因:前端 types.ts:133 期望 quote.calc;后端返回 calc_snapshot;且 factor_breakdown 后端是对象,前端期望数组
  • 修复策略:见 §四 策略 10

BUG-11:/admin 权限键名完全不匹配

  • 影响页面/admin(角色权限矩阵 Tab)
  • 现象:角色权限矩阵显示 42 个复选框(21 个 false 的前端硬编码 + 21 个 true 的后端键);保存时会把无效键 PUT 给后端
  • 根因:前端 admin/page.tsx:28-34 PERMISSION_KEYSqsv:calculate / cpt:order:view / mds:customer:view;后端返回 qsv:calc / cpt:list:all / mds:cpl:view
  • 修复策略:见 §四 策略 11

BUG-12:/qsv/quotes/{id}/export 端点缺失(404)

  • 影响页面/quotes/[id](导出 PDF 按钮)
  • 现象:点击"導出 PDF"按钮 Toast 报错
  • 根因:后端无 PDF 导出端点
  • 修复策略:见 §四 策略 12

3.3 🟢 P2 待优化(4 个)

BUG-13:后端金额字段返回字符串而非数字

  • 影响:全局金额显示(如报价详情)
  • 现象:金额显示为 "HK$ 380.00" 而非 "HK$ 380"
  • 根因:后端 DTO 返回 "final_hkd":"380.00"(字符串);前端 formatMoney(hkd?: number) 期望数字
  • 修复策略:见 §四 策略 13

BUG-14:admin 用户 roles 为空数组

  • 影响页面/admin(用户管理表格"角色"列)
  • 现象:admin 用户的"角色"列显示为空
  • 根因:种子数据 data.py 未为 admin 用户填充 sys_user_roles 关联
  • 修复策略:见 §四 策略 14

BUG-15:驾驶舱调试代码泄漏

  • 影响页面/(驾驶舱"逾期訂單"统计卡)
  • 现象:页面文本显示 pool_label = OVERDUE(变量名泄漏到 UI)
  • 根因:前端 page.tsx 模板字符串未正确转义
  • 修复策略:见 §四 策略 15

BUG-16:楼宇 autocomplete 未用专用端点

  • 影响页面/buildings(地址联想)
  • 现象:功能可用但用了 /mds/buildings?q= 而非专用的 /mds/buildings/autocomplete
  • 修复策略:见 §四 策略 16

四、修改策略指导(交付给 kimi)

执行原则:每个策略都标注了【后端】或【前端】侧的修改,建议成对修复以避免字段名/结构再次错配。修改后请跑 pytest tests/ + ruff check app tests 确保不回归。

策略 1:统一 CPT 看板 pool 键(BUG-01)— 最高优先级

推荐方案:后端改为返回阶段维度 6 池(对齐 CPT-D 设计文档的 8 阶段跟踪池语义)

后端修改cpt_service.py):

# 当前(错误):
_POOL_LABELS = ("TODAY", "OVERDUE", "APPOINTMENT_SOON", "NORMAL")

# 改为(对齐前端期望):
_POOL_LABELS = (
    "POOL_L1_NEW_LEADS",       # L1 新线索
    "POOL_L3_PENDING_QUOTE",   # L3 待报价
    "POOL_L4_PENDING_ASSIGN",  # L4 待派单
    "POOL_L6_INSTALL_TODAY",   # L6 今日安装
    "POOL_L8_PENDING_FOLLOWUP",# L8 待回访
    "POOL_EXCEPTION",          # 异常池
)

# 同时修改 _classify_order 方法,按 order.stage + order.status 分类:
# - stage=L1 → POOL_L1_NEW_LEADS
# - stage=L3 且无 confirmed_quote → POOL_L3_PENDING_QUOTE
# - stage=L4 且无 assigned_worker → POOL_L4_PENDING_ASSIGN
# - stage=L6 且 expected_start_at=today → POOL_L6_INSTALL_TODAY
# - stage=L8 且无 NPS → POOL_L8_PENDING_FOLLOWUP
# - status=EXCEPTION_PAUSED → POOL_EXCEPTION
# - 其他 → 不入池(或入 NORMAL 备用池)

前端无需修改(已对齐)。

验证GET /api/v1/cpt/orders/board 应返回 {"POOL_L1_NEW_LEADS":[1单],"POOL_L3_PENDING_QUOTE":[1单],"POOL_L6_INSTALL_TODAY":[1单],"POOL_L8_PENDING_FOLLOWUP":[1单],...}


策略 2:新增 GET /qsv/quotes 列表端点(BUG-02)

后端修改qsv.py):

@router.get("", response_model=Page[QsvQuoteSummary])
async def list_quotes(
    status: str | None = Query(None, description="按状态过滤: DRAFT/CONFIRMED/REJECTED/EXPIRED"),
    customer_id: str | None = Query(None),
    page: int = Query(1, ge=1),
    page_size: int = Query(20, ge=1, le=100),
    session: AsyncSession = Depends(get_session),
    _: TokenClaims = Depends(require_permission("qsv:read")),
) -> Page[QsvQuoteSummary]:
    """报价列表(分页 + 状态过滤)."""
    # 返回标准分页结构 {total, page, page_size, items}
    # items 中每条包含:quote_id, customer_id, customer_name, category_code,
    #                   final_hkd, status, created_at, confirmed_at

前端无需修改(已用 api.get<Page<QsvQuote>>("/qsv/quotes", {params:{page,page_size,status}}))。

验证GET /api/v1/qsv/quotes?page=1&page_size=20 → 200 + {total:2, items:[...]}


策略 3:修正报价试算 API 路径(BUG-03)

前端修改quotes/new/page.tsx:98):

// 当前(错误):
const { data } = await api.post<QsvCalcPreview>("/qsv/calculate", payload);

// 改为:
const { data } = await api.post<QsvCalcPreview>("/qsv/calculator/preview", payload);

同时检查响应字段名types.ts): - 后端返回 base_fee_hkd,前端期望 base_fee → 统一为 base_fee_hkd - 后端返回 capped_flag,前端期望 capped → 统一为 capped_flag - 后端 factor_breakdown 是对象 {category_base: {name_zh, value, running_total}},前端期望数组 → 前端改为 Object.values(data.factor_breakdown)


策略 4:/mds/categories 包装为分页响应(BUG-04)— 导致白屏,紧急

后端修改mds.py:39-53):

@router.get("/categories", response_model=Page[MdsSclCategory])
async def list_categories(
    page: int = Query(1, ge=1),
    page_size: int = Query(100, ge=1, le=500),
    session: AsyncSession = Depends(get_session),
    _: TokenClaims = Depends(require_permission("mds:scl:view")),
) -> Page[MdsSclCategory]:
    """品类列表(分页)."""
    # 返回 {total, page, page_size, items}

前端无需修改(3 处都已用 api.get<Page<SclCategory>> + data.items)。

替代方案(如果不想改后端):前端 3 处去掉 .items 访问,直接用 data 作为数组。但建议统一为分页结构(与其他端点一致)。


策略 5:新增 PATCH /qsv/quotes/{id} 端点(BUG-05)

后端修改qsv.py):

class QuoteStatusUpdate(BaseModel):
    status: Literal["CONFIRMED", "REJECTED", "EXPIRED"]
    reject_reason: str | None = None

@router.patch("/{quote_id}", response_model=QsvQuote)
async def update_quote_status(
    quote_id: str,
    payload: QuoteStatusUpdate,
    session: AsyncSession = Depends(get_session),
    claims: TokenClaims = Depends(require_permission("qsv:update")),
) -> QsvQuote:
    """更新报价状态(确认/拒绝/过期)."""
    # 若 status=CONFIRMED,设置 confirmed_by + confirmed_at
    # 若 status=REJECTED,记录 reject_reason

前端无需修改(已用 api.patch("/qsv/quotes/{id}", {status, reject_reason}))。


策略 6:客户详情订单查询改用 customer_id(BUG-06)

推荐方案:后端 list_orders 增加 customer_id 查询参数(更语义化)

后端修改cpt.pylist_orders):

@router.get("", response_model=Page[CptOrderCard])
async def list_orders(
    q: str | None = Query(None, description="按客户名/地址模糊搜索"),
    customer_id: str | None = Query(None, description="按客户ID精确过滤"),
    stage: str | None = Query(None),
    # ... 其他参数
):
    if customer_id:
        query = query.where(CptOrder.customer_id == customer_id)
    if q:
        query = query.where(CptOrder.customer_name.ilike(f"%{q}%"))

前端修改customers/[id]/page.tsx:37):

// 当前(错误):
const { data } = await api.get<Page<CptOrderCard>>("/cpt/orders", {
  params: { q: customerId, page: 1, page_size: 50 },

// 改为:
const { data } = await api.get<Page<CptOrderCard>>("/cpt/orders", {
  params: { customer_id: customerId, page: 1, page_size: 50 },


策略 7:处理 /qsv/params/versions 404(BUG-07)

方案 A(推荐):后端新增端点

@router.get("/params/versions", response_model=Page[QsvParamVersion])
async def list_param_versions(
    page: int = Query(1, ge=1),
    page_size: int = Query(10, ge=1, le=50),
    session: AsyncSession = Depends(get_session),
    _: TokenClaims = Depends(require_permission("qsv:read")),
) -> Page[QsvParamVersion]:
    """报价参数版本列表."""

方案 B(快速):前端移除该区块 - categories/page.tsx:29 删除 versionsQuery + 对应的"QSV 参数版本历史"UI 区块


策略 8:登录响应增加 user 字段(BUG-08)

后端修改auth.pylogin 端点):

class LoginResponse(BaseModel):
    access_token: str
    refresh_token: str
    token_type: str = "bearer"
    expires_in: int
    user: CurrentUser  # 新增

@router.post("/login", response_model=LoginResponse)
async def login(payload: LoginRequest, session: AsyncSession = Depends(get_session)):
    # ... 验证密码 ...
    user_info = CurrentUser(
        user_id=user.user_id,
        username=user.username,
        display_name=user.display_name,
        roles=[user.role],  # 转为数组
        permissions=[...],  # 查询权限
    )
    return LoginResponse(
        access_token=..., refresh_token=..., expires_in=900,
        user=user_info,
    )

或前端修改auth.ts:11-16):登录后立即调用 fetchCurrentUser()

export async function login(username: string, password: string): Promise<CurrentUser> {
  const { data } = await api.post<LoginResponse>("/auth/login", { username, password });
  window.localStorage.setItem(TOKEN_KEY, data.access_token);
  if (data.refresh_token) window.localStorage.setItem(REFRESH_KEY, data.refresh_token);
  // 登录后立即获取用户信息(如果后端未在 login 响应中返回 user)
  const user = data.user ?? await fetchCurrentUser();
  window.localStorage.setItem(USER_KEY, JSON.stringify(user));
  return user;
}


策略 9:/auth/me 字段名 roleroles(BUG-09)

后端修改auth.pyUserMeResponse):

class UserMeResponse(BaseModel):
    user_id: str
    username: str
    display_name: str
    roles: list[str]  # 改为复数数组(当前是 role: str)
    permissions: list[str]

前端无需修改(已期望 roles: Role[])。


策略 10:报价详情字段名对齐(BUG-10)

前端修改types.ts:133):

export interface QsvQuote {
  // ...
  calc_snapshot: {       // 改名:calc → calc_snapshot
    category_code: string;
    factors?: Record<string, { name_zh: string; value: string; running_total: string }>;
  };
  factor_breakdown: Record<string, { name_zh: string; value: string; running_total: string }>;  // 改为数组或保持对象
}

前端渲染修改quotes/[id]/page.tsx):

// 因子展开卡:从 factor_breakdown 对象转为数组渲染
const factors = quote.factor_breakdown ? Object.values(quote.factor_breakdown) : [];
factors.map(f => <li key={f.name_zh}>{f.name_zh}: {f.value} (累計 {f.running_total})</li>)


策略 11:权限键名对齐(BUG-11)

推荐方案:前端从后端动态获取权限键,不硬编码

前端修改admin/page.tsx:28-34):

// 当前(错误):硬编码 PERMISSION_KEYS
const PERMISSION_KEYS = ["qsv:calculate", "cpt:order:view", ...];

// 改为:从后端响应中动态提取
const PERMISSION_KEYS = Object.keys(rolesPermissionsResponse);  // 后端返回的键就是全部权限键

或后端新增权限字典端点 GET /admin/permissions 返回所有权限键+中文名。


策略 12:处理报价导出 404(BUG-12)

方案 A:后端新增 PDF 导出端点(工作量大,建议后期实施) 方案 B(推荐):前端移除"導出 PDF"按钮,或改为浏览器原生打印

// quotes/[id]/page.tsx
// 移除 exportPdf 函数,按钮改为 window.print()


策略 13:金额字段类型统一(BUG-13)

后端修改qsv.py DTO):

class QsvQuote(BaseModel):
    # 当前(错误):返回字符串
    # final_hkd: str
    # 改为:
    final_hkd: float
    base_fee_hkd: float
    subtotal_hkd: float
    cap_price_hkd: float
    # ... 所有金额字段统一为 float

或前端兼容format.ts):

export function formatMoney(hkd?: number | string | null): string {
  if (hkd == null) return "—";
  const n = typeof hkd === "string" ? parseFloat(hkd) : hkd;
  return `HK$ ${n.toLocaleString("zh-HK", { maximumFractionDigits: 0 })}`;
}


策略 14:种子数据填充 admin 用户角色(BUG-14)

后端修改data.py):

# 创建 admin 用户后,插入 sys_user_roles 关联
await session.execute(
    text("INSERT INTO sys_user_roles (user_id, role_name) VALUES (:uid, 'TL') ON CONFLICT DO NOTHING"),
    {"uid": ADMIN_USER_ID},
)


策略 15:移除驾驶舱调试代码(BUG-15)

前端修改page.tsx):

// 当前(错误):模板字符串泄漏变量名
`逾期訂單 ⏰ ${overdueCount} pool_label = OVERDUE`

// 改为:
`逾期訂單 ⏰ ${overdueCount}`


策略 16:楼宇 autocomplete 改用专用端点(BUG-16)

前端修改buildings/page.tsx:51):

// 当前:api.get("/mds/buildings", {params:{q, page_size:8}})
// 改为:api.get("/mds/buildings/autocomplete", {params:{q}})


五、流程交叉逻辑验证

5.1 ✅ 已打通的流程(4 条)

流程 链路 验证结果
L1→L8 生命周期 订单详情 → 阶段 Tab → 流转/阶段数据 GET /cpt/orders/{id} 200;POST /cpt/orders/{id}/transition 端点存在;PUT /cpt/orders/{id}/stages/{stage}/data 端点存在;L6 6S表单 + L8 NPS表单可用
线索→订单 线索池 → 创建线索 → 转化为订单 GET /ops/leads 200;POST /ops/leads 201;POST /ops/leads/{id}/convert 端点存在
派单 订单详情 → 推荐师傅 → 指派 GET /dsp/orders/{id}/recommend-workers 200;POST /dsp/orders/{id}/assign 端点存在
奖励 奖励列表 → 审批 → 发放 GET /ops/rewards 200;POST /ops/rewards/{id}/approve + /pay 端点存在

5.2 ❌ 断裂的流程(2 条)

流程 断裂点 影响 修复后验证方法
报价管理 GET /qsv/quotes 405 + PATCH /qsv/quotes/{id} 405 + POST /qsv/calculate 404 报价列表不可用 + 报价试算不可用 + 报价状态更新不可用 修复 BUG-02/03/05 后:创建报价 → 列表查看 → 详情确认 → 状态流转
客户订单关联 客户详情用 customer_id 查 q,但 q 按 name 搜索 客户历史订单永远空 修复 BUG-06 后:客户详情页应显示该客户的 2 个订单

六、修复优先级建议

第一批(P0 阻断,必须修复才能开展 UAT)

顺序 Bug 预估工时 依赖
1 BUG-04 品类库白屏(/mds/categories 分页) 0.5h
2 BUG-01 看板 pool 键不匹配 2h
3 BUG-02 GET /qsv/quotes 列表端点 1h
4 BUG-03 试算 API 路径 + 字段名 1h
5 BUG-05 PATCH /qsv/quotes/{id} 0.5h
6 BUG-06 客户订单关联 0.5h
7 BUG-07 /qsv/params/versions 0.5h(方案B) / 1h(方案A)

第一批合计:约 6-6.5h,修复后 7 个阻断页面可恢复可用。

第二批(P1 异常,修复后 UX 显著提升)

顺序 Bug 预估工时
8 BUG-08 + BUG-09 登录响应 + auth/me 字段 1h
9 BUG-10 报价详情字段名 0.5h
10 BUG-11 权限键名 1h
11 BUG-12 导出 PDF 0.5h(移除按钮)

第三批(P2 待优化,可延后)

顺序 Bug 预估工时
12 BUG-13 金额类型 0.5h
13 BUG-14 admin 角色种子 0.5h
14 BUG-15 调试代码 0.1h
15 BUG-16 autocomplete 端点 0.5h

七、测试通过页面清单(无需修改)

以下 6 个页面经测试功能正常,无需修改:

页面 URL 说明
线索池 /leads 6 场景 Tab + 状态筛选 + 新建线索 + 转化
推荐奖励 /rewards 状态筛选 + 审批 + 发放
订单详情(L1/L6/DONE) /orders/[id] 8 阶段 Tab + 流转 + 阶段数据 + 6S表单 + NPS表单
客户档案 /customers 搜索 + 标签筛选 + 列表
师傅档案 /workers 等级/状态筛选 + 列表
楼宇库 /buildings 地址联想 + 列表 + 新建

八、附录:后端 API 端点实测结果

状态码 端点 说明
✅ 200 GET /cpt/orders 订单列表(含 stage 过滤)
✅ 200 GET /cpt/orders/{id} 订单详情
✅ 200 GET /cpt/orders/board 看板(但 pool 键不匹配)
✅ 200 GET /mds/customers 客户列表
✅ 200 GET /mds/customers/{id} 客户详情
✅ 200 GET /mds/workers 师傅列表
✅ 200 GET /mds/workers/{id} 师傅详情
⚠️ 200 GET /mds/categories 返回裸数组(应为分页对象)
✅ 200 GET /mds/buildings 楼宇列表
✅ 200 GET /ops/leads 线索列表
✅ 200 GET /ops/rewards 奖励列表
✅ 200 GET /admin/users 用户列表
✅ 200 GET /admin/audit-logs 审计日志
✅ 200 GET /admin/roles/{role}/permissions 角色权限
✅ 200 GET /auth/me 当前用户(字段名不匹配)
✅ 200 GET /dsp/orders/{id}/recommend-workers 推荐师傅
✅ 200 GET /dsp/workers/load-board 师傅负载
✅ 200 GET /qsv/quotes/{id} 报价详情(字段名部分不匹配)
✅ 201 POST /auth/login 登录(缺 user 字段)
✅ 201 POST /qsv/quotes 创建报价
✅ 200 POST /qsv/calculator/preview 报价试算(前端路径错误)
❌ 405 GET /qsv/quotes 列表端点缺失
❌ 405 PATCH /qsv/quotes/{id} 状态更新端点缺失
❌ 404 POST /qsv/calculate 路径错误(应为 /calculator/preview)
❌ 404 GET /qsv/params/versions 端点缺失
❌ 404 GET /qsv/quotes/{id}/export 端点缺失

报告结束。kimi 请按 §四 策略 1-7(第一批)优先修复,修复后通知 DT 复测。