docs: 多规格商品试算编辑

This commit is contained in:
2026-08-31 22:38:59 +08:00
parent 8b54f7ed1e
commit d7247ca2da
4 changed files with 175 additions and 1 deletions
+1 -1
View File
@@ -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)(后端结构)
---
+1
View File
@@ -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) | 图片原图、水印当前版、对象路径与删除回收流程 | 改图片、水印或存储前先读 |
+172
View File
@@ -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 的价格?
+1
View File
@@ -3,6 +3,7 @@
> 状态:设计方案,尚未实施。
> 范围:采集 → 采集箱商品 → `/trial/:id` 价格试算 → CSV 导出;Ozon API 直传仍处于冻结状态。
> 目标:同一商品的不同规格可以独立试算,避免以一个 SKU 的价格、重量和尺寸覆盖全部规格。
> 尺寸/重量的实物与包装物流双口径设计见 [`multi-sku-measurements.md`](./multi-sku-measurements.md);该文补充并约束本方案。
---