diff --git a/docs/v2.1/HANDOFF.md b/docs/v2.1/HANDOFF.md index 5a2874f..e41dd49 100644 --- a/docs/v2.1/HANDOFF.md +++ b/docs/v2.1/HANDOFF.md @@ -2,7 +2,7 @@ > 更新:2026-08-31 > 读者:下一个接手工作的 AI / 开发者(工具无关)。先读本文再动手,避免重复探索。 -> 配套文档:[`README`](../../README.md)(项目总览)、[`collect.md`](./collect.md)(采集)、[`trial-page.md`](./trial-page.md)(试算页)、[`multi-sku-pricing.md`](./multi-sku-pricing.md)(多 SKU 试算与导出方案)、[`image-suite.md`](./image-suite.md)(生图)、[`background-jobs.md`](./background-jobs.md)(**后台任务中心方案、实施状态与路线图**)、[`image-asset-lifecycle.md`](./image-asset-lifecycle.md)(图片与水印存储规则)、[`api.md`](./api.md)(契约)、[`backend-structure.md`](./backend-structure.md)(后端结构) +> 配套文档:[`README`](../../README.md)(项目总览)、[`collect.md`](./collect.md)(采集)、[`trial-page.md`](./trial-page.md)(试算页)、[`multi-sku-pricing.md`](./multi-sku-pricing.md)(多 SKU 试算与导出方案)、[`multi-sku-measurements.md`](./multi-sku-measurements.md)(**实物规格与包装物流规格的双口径设计**)、[`image-suite.md`](./image-suite.md)(生图)、[`background-jobs.md`](./background-jobs.md)(**后台任务中心方案、实施状态与路线图**)、[`image-asset-lifecycle.md`](./image-asset-lifecycle.md)(图片与水印存储规则)、[`api.md`](./api.md)(契约)、[`backend-structure.md`](./backend-structure.md)(后端结构) --- diff --git a/docs/v2.1/README.md b/docs/v2.1/README.md index cf14c53..59c041e 100644 --- a/docs/v2.1/README.md +++ b/docs/v2.1/README.md @@ -111,6 +111,7 @@ V2(原计划) V2.1(现在) | [`collect.md`](./collect.md) | 采集方案:扩展并入 `extensions/collector/` + 上报开关 + 自动打开试算页(已实施) | 做插件改动时读 | | [`trial-page.md`](./trial-page.md) | 商品试算页:页面结构、计价公式与校验、文案、采购地址、入库数据结构、CSV/组合码导出 | 做试算页前后端时读 | | [`multi-sku-pricing.md`](./multi-sku-pricing.md) | 多 SKU:采集契约、父商品/SKU 数据模型、试算页明细、CSV 导出与分阶段迁移方案 | 扩展规格级计价、导出或采集时读 | +| [`multi-sku-measurements.md`](./multi-sku-measurements.md) | **多 SKU 双规格**:实物规格与包装物流规格的字段、采集、试算、尺寸图、导出和迁移设计 | 改 SKU、尺寸、重量、试算或尺寸图前必读 | | [`image-suite.md`](./image-suite.md) | 图片生成:套图规划→一键生成、模型路由、单张 AI 生图、水印、生成图回写与导出 | 做图片功能时读 | | [`background-jobs.md`](./background-jobs.md) | **后台任务中心**:统一任务模型、Worker、状态、恢复、已完成阶段和后续路线图 | 改生图、水印或任何耗时任务前先读 | | [`image-asset-lifecycle.md`](./image-asset-lifecycle.md) | 图片原图、水印当前版、对象路径与删除回收流程 | 改图片、水印或存储前先读 | diff --git a/docs/v2.1/multi-sku-measurements.md b/docs/v2.1/multi-sku-measurements.md new file mode 100644 index 0000000..e0c9ceb --- /dev/null +++ b/docs/v2.1/multi-sku-measurements.md @@ -0,0 +1,172 @@ +# 多 SKU 的实物规格与包装物流规格设计 + +> 状态:调研与设计结论,尚未实施。 +> 关联:[多 SKU 商品处理与试算页扩展方案](./multi-sku-pricing.md)。本文补充其“尺寸与重量”的双口径模型。 +> 核心原则:每个 SKU 可以有独立的实物规格和包装物流规格;两者绝不混用。 + +--- + +## 1. 问题与结论 + +尺寸与重量有两种不同的业务口径: + +| 口径 | 数据 | 用途 | +|---|---|---| +| **实物规格** | 商品本体长、宽、高、净重 | 商品规格展示、俄文文案、AI 尺寸标注图 | +| **包装物流规格** | 发货包装长、宽、高、含包装毛重 | 物流尺寸限制、物流费、价格试算、CSV 上品 | + +同一个商品还可能有多个 SKU。容量、尺寸、套装数等 SKU 往往在两组数据上都不同;仅颜色不同的 SKU 则通常可继承父商品的两组默认值。 + +因此目标模型不是“商品加一张 SKU 价格表”,而是: + +```text +商品(SPU / 父记录) +├─ 共用:标题、描述、采购链接、图片、文案、计价默认值 +├─ 默认实物规格:长宽高、净重 +├─ 默认包装物流规格:长宽高、包装毛重 +├─ SKU A:规格、两组规格覆盖、采购价、试算结果、货号 +├─ SKU B:规格、两组规格覆盖、采购价、试算结果、货号 +└─ SKU C:规格、两组规格覆盖、采购价、试算结果、货号 +``` + +SKU 的某个值为空代表继承父商品默认值,不代表零。采集不到或来源未说明口径的“尺寸/重量”必须标记为待确认,不能猜测写入任意一组。 + +## 2. 当前实现风险 + +当前 `/trial/:id` 将 `price`、`old_price`、`weight`、`depth/width/height`、`offer_id` 放在单一 `products` 记录中: + +- `TrialPricingPanel` 使用重量尺寸做物流费和尺寸校验,且会回填商品价格;一个 SKU 会覆盖另一个 SKU。 +- AI 套图规划从采集参数表寻找“尺寸”,无法确认取到的是实物尺寸还是包装尺寸。 +- `/api/materials` 的 `_apply_weight_dims` 以“包装重量/重量”“包装长度/长度”等泛关键词解析,通用“产品尺寸”“重量”可能被当成物流规格。 +- 图片资产仅保存 `variant_name`,显示名能重复或改名,无法稳定绑定 SKU。 +- CSV 目前按商品一行导出,无法表达 SKU 的独立包装数据和报价。 + +因此不能只给当前价格卡增加输入框,必须先建立 SKU 与双规格的持久化模型。 + +## 3. 数据模型 + +### 3.1 命名、单位和继承 + +数据库统一使用 `mm`、`g`;前端用 `cm`、`g` 编辑。新功能不得再用裸 `weight`、`depth`、`width`、`height` 表达业务规格。 + +父商品和 `product_variants` 均使用相同、口径明确的字段: + +| 字段组 | 字段 | 使用者 | +|---|---|---| +| 实物规格 | `physical_length_mm`、`physical_width_mm`、`physical_height_mm`、`net_weight_g` | 尺寸图、规格展示、文案 | +| 包装物流规格 | `package_length_mm`、`package_width_mm`、`package_height_mm`、`shipping_weight_g` | 试算、物流校验、导出 | +| SKU 身份 | `source_variant_key`、`variant_name`、`specs` | 平台原始 SKU ID、展示规格、颜色/尺寸/容量 | +| 销售与计价 | `source_price`、`purchase_price`、`offer_id`、`barcode`、`logistics_level`、`pricing` | SKU 独立的输入、结果和上品标识 | +| 审计 | `measurements_meta`、`enabled`、时间戳 | 来源、原始文本、确认状态、是否参与导出 | + +`measurements_meta` 至少记录两组数据各自的 `status`(`confirmed` / `ambiguous` / `missing`)、来源、原始文本和更新时间。采集提供的数据不是人工确认的事实时,不应伪装为已确认。 + +SKU 的有效值按“SKU 覆盖 → 父商品默认值”逐字段合并。实物规格与包装物流规格独立继承:实物尺寸相同不代表包装尺寸也相同。 + +### 3.2 关联和旧字段 + +- 新增持久化 `product_variants` 表,而不是长期把 SKU 数组放进 `product.pricing` JSON。 +- `product_assets` 增加可空 `product_variant_id`;现有 `variant_name` 仅保留展示兼容。 +- 父商品 `pricing` 只保存共享默认参数,如汇率、利润率、贴单费和默认物流等级;SKU `pricing` 保存输入快照、有效包装规格、计算结果和计算时间。 +- 当前 `products.depth/width/height/weight` 在迁移期间仅是兼容读写别名;双规格字段是新功能唯一事实来源。 + +## 4. 采集和后端接口 + +`POST /api/materials` 保持旧契约兼容,并新增可选 `variants`: + +```jsonc +{ + "source": { "platform": "1688", "itemId": "...", "url": "..." }, + "variants": [ + { + "source_variant_key": "平台原始 SKU ID", + "variant_name": "白色 / 33 cm", + "specs": { "颜色": "白色", "尺寸": "33 cm" }, + "source_price": 28.5, + "physical_measurements": { + "length_mm": 150, "width_mm": 150, "height_mm": 330, "net_weight_g": null, + "status": "confirmed" + }, + "package_measurements": { + "length_mm": 180, "width_mm": 180, "height_mm": 380, "shipping_weight_g": 850, + "status": "confirmed" + }, + "image_url": "..." + } + ] +} +``` + +规则: + +- `source_variant_key` 优先使用来源平台稳定 ID;展示名不能作为幂等键。 +- 未采到的字段传 `null`;无法判断口径时保留原始参数、设 `status: "ambiguous"`。 +- 只有明确“包装/外箱/发货”口径的参数可写入包装物流规格;明确“商品/产品/本体”的参数可写入实物规格;泛称参数不自动落入任何组。 +- 服务端按来源 SKU ID 幂等更新,并把 SKU 图片关联至 `product_variant_id`。 +- SKU 使用独立接口,例如 `GET/POST /api/products/{id}/variants`、`PATCH/DELETE /api/products/{id}/variants/{variant_id}`;不要用整份 `ProductUpdate` 承担 SKU 并发编辑。 + +服务端新增统一的 `PricingService`:前端可即时预览,但保存与导出时必须按“有效包装物流规格”重新计算,避免浏览器传入结果被篡改或前后端公式漂移。 + +## 5. 前端设计 + +页面拆成两个清晰区域: + +1. **商品规格与尺寸图**:维护实物长宽高和净重;多 SKU 时可维护商品默认值,或为一个 SKU 覆盖。尺寸标注图唯一读取这里的已确认实物尺寸。 +2. **价格试算**:维护包装长宽高、包装毛重、进货价、利润率、物流等级、贴单费、汇率;物流校验与价格结果只在此区域展示。 + +多 SKU 时,价格区使用明细表,列为“规格 / 实物规格摘要 / 包装规格摘要 / 进货价 / 销售价 / 状态”。点击一行打开抽屉或展开编辑器,而不是把八项字段强塞进表格: + +```text +实物规格(用于尺寸图) 包装物流规格(用于试算) +长 / 宽 / 高 / 净重 长 / 宽 / 高 / 包装毛重 +状态:SKU 覆盖 / 继承 / 待确认 状态:SKU 覆盖 / 继承 / 待确认 +``` + +交互约束: + +- 每个字段可以“恢复继承”;空值不是 `0`。 +- 修改父商品默认值只影响未覆盖字段;可以将某一组默认规格批量应用给选中的 SKU。 +- 包装物流规格缺失/无效时,该 SKU 不能试算和导出,但其他 SKU 不受影响。 +- 实物规格缺失时仍可试算/导出,但不能生成有数值的尺寸标注图。 +- 父商品列表不应任选一个 SKU 回写 `product.price`;应展示最低价、价格区间,或显式选择代表 SKU。 + +## 6. AI 尺寸图、导出与迁移 + +### AI 尺寸图 + +尺寸标注图是实物信息,不是物流信息。规划器、提示词和任务上下文必须接收显式 `physical_*` 字段,不能从混合原始参数表兜底。 + +- 多 SKU 实物尺寸不同:生成前必须选择 SKU;任务保存 `product_variant_id` 与实物规格快照。 +- 多 SKU 实物尺寸相同:可以使用父商品默认规格生成一张通用图。 +- 实物规格为 `missing` 或 `ambiguous`:规划器不安排尺寸图,也不能提交带数值的尺寸图任务。 +- 规格修改后,历史图依其快照标记为“基于旧规格生成”。 + +### CSV 和上品 + +CSV 从“每商品一行”改为“每启用 SKU 一行”。进货价、包装毛重、包装尺寸和物流等级来自合并后的 SKU 有效值;实物尺寸绝不作为物流尺寸导出。导出前逐 SKU 校验货号、价格和包装物流规格,并列出失败原因。 + +每 SKU 一个 Ozon offer、只上代表 SKU、或未来平台变体合并,仍是待确认的商业策略;未确认前系统只生成可审阅的 SKU 级 CSV。 + +### 历史数据迁移 + +历史商品无 SKU 时视为一个隐式 SKU。把旧 `pricing`、`weight`、`depth/width/height`、`offer_id` 迁为默认 SKU 的**包装物流规格**,因为它们原本用于价格试算;实物规格一律为 `missing`,不得依据旧字段自动生成尺寸图。 + +## 7. 实施顺序与验收 + +| 阶段 | 内容 | 验收标准 | +|---|---|---| +| 0. 业务确认 | 明确 Ozon offer 策略、货号规则、尺寸图是否逐 SKU 生成 | 形成 CSV/上品规则 | +| 1. 数据和采集 | SKU 表、父/SKU 双规格字段、迁移、`variants` 契约;收窄泛规格解析 | 未知口径不被伪造成实物或包装数据 | +| 2. 服务端计算 | 继承合并、统一计算/校验、SKU 级保存和查询接口 | 两 SKU、两组规格不相互覆盖 | +| 3. 试算页 | 实物规格区、包装物流试算区、SKU 表和编辑抽屉 | 刷新后继承、状态、价格和告警一致 | +| 4. 导出和图片 | SKU 行 CSV;尺寸图上下文绑定 SKU 与实物快照 | 包装尺寸绝不进入尺寸图 | + +至少覆盖:单 SKU 回归、多 SKU 不同价格、仅颜色不同继承两组规格、实物相同包装不同、包装相同实物不同、泛称规格待确认、一个 SKU 导出失败但其他可导出、尺寸图只使用实物规格、SKU 改名但稳定 ID 不变、历史商品迁移。 + +## 8. 待确认决策 + +1. Ozon 中多 SKU 是否一律拆成独立 offer,还是要保留变体合并能力? +2. 货号由父商品自动派生,还是必须人工逐 SKU 维护? +3. 哪些采集来源的实物/包装字段可信,哪些只能作为待确认原始信息? +4. 尺寸图是否必须逐 SKU 生成;AI 主图和场景图由谁指定代表 SKU? +5. 父商品在采集箱中展示最低价、价格区间,还是代表 SKU 的价格? diff --git a/docs/v2.1/multi-sku-pricing.md b/docs/v2.1/multi-sku-pricing.md index bd940f8..5abbeea 100644 --- a/docs/v2.1/multi-sku-pricing.md +++ b/docs/v2.1/multi-sku-pricing.md @@ -3,6 +3,7 @@ > 状态:设计方案,尚未实施。 > 范围:采集 → 采集箱商品 → `/trial/:id` 价格试算 → CSV 导出;Ozon API 直传仍处于冻结状态。 > 目标:同一商品的不同规格可以独立试算,避免以一个 SKU 的价格、重量和尺寸覆盖全部规格。 +> 尺寸/重量的实物与包装物流双口径设计见 [`multi-sku-measurements.md`](./multi-sku-measurements.md);该文补充并约束本方案。 ---