diff --git a/app/schemas/compare_record.py b/app/schemas/compare_record.py index 9fa196e..bd84735 100644 --- a/app/schemas/compare_record.py +++ b/app/schemas/compare_record.py @@ -11,7 +11,7 @@ from __future__ import annotations from datetime import datetime -from pydantic import BaseModel, ConfigDict, Field +from pydantic import BaseModel, ConfigDict, Field, field_validator # ===== 上报请求 ===== @@ -21,8 +21,36 @@ class ComparisonItemIn(BaseModel): name: str qty: int = 1 + # specs 仅供比价记录展示(admin-web 详情 / app 记录页都按字符串数组渲染并 join)。 + # pricebot(2026-06-27 嵌套规格统一)起 calibration 的 specs 可能是规格对象 + # [{name, qty, sub_specs}] 而非字符串 → 下面的 before-validator 统一拍平成可读字符串, + # 兼容新旧两种输入、保持 list[str] 契约不变(下游零改动)。 + # ⚠️ 两个坑都踩过, 必须"拍平"而非别的: ① 直接声明 list[str] 不拍平 → 对象 specs 整条 + # 422 被拒、不入库(同下方 platform_results list→dict 同类事故); ② 仅放宽成裸 list 又会让 + # 下游 join 出 "[object Object]"/对象 toString 的乱码。 specs: list[str] | None = None + @field_validator("specs", mode="before") + @classmethod + def _flatten_specs(cls, v: object) -> object: + """pricebot 规格对象 [{name, qty, sub_specs}] → 可读字符串数组; 字符串元素原样保留; + 非 list 原样交还(让 pydantic 照常报类型错)。嵌套规格拼成 '主项(子1,子2)'。""" + if not isinstance(v, list): + return v + out: list[str] = [] + for it in v: + if isinstance(it, str): + s = it + elif isinstance(it, dict): + name = str(it.get("name") or "").strip() + subs = [str(x).strip() for x in (it.get("sub_specs") or []) if str(x).strip()] + s = f"{name}({','.join(subs)})" if name and subs else (name or ",".join(subs)) + else: + continue + if s: + out.append(s) + return out + class AppliedCouponIn(BaseModel): """单笔已用优惠(来自 comparison_results[].applied_coupons)。amount 单位:元、正数。"""