feat: 方案设计、数据库和接口设计基本完成

This commit is contained in:
R524809
2026-09-02 14:47:18 +08:00
parent a3fe066a5f
commit 04c94e75db
30 changed files with 3305 additions and 122 deletions
+16 -4
View File
@@ -1,16 +1,20 @@
# 商品编辑 API 契约
> 接口前缀与鉴权细节待基础架构确认。所有请求由当前用户、工作空间、门店上下文授权;客户端不能跨租户指定 ID。
> 接口前缀与鉴权细节待基础架构确认。所有请求由当前用户、工作空间、门店上下文授权;客户端不能跨租户指定 ID。浏览器插件采集入口以 [`../requirements/collector-browser-extension.md`](../requirements/collector-browser-extension.md) 为准。
## 1. 采集与读取
| 接口 | 用途 |
|---|---|
| `POST /api/stores/{store_id}/materials` | 上报采集商品与可选 SKU 数组;无 SKU 数组时创建默认 SKU。 |
| `GET /api/extension/session` | 供插件确认当前 Atlas 登录状态和工作空间是否可用;不返回平台、门店或凭据。 |
| `POST /api/extension/materials` | 扩展上报 `CollectedProductPayload`。服务端从扩展令牌解析工作空间,幂等创建/复用未绑定目标平台/门店的采集记录、商品草稿、真实 SKU 草稿和素材转存任务。请求体不得传入 `workspace_id`、`platform_id` 或 `store_id`。 |
| `POST /api/collection-items/{collection_item_id}/seller-enrichment-observations` | 插件在本地 Seller 观察开关开启时上报第 7.3 节四接口的净化结果;不接收 Cookie。 |
| `GET /api/collection-items/{collection_item_id}` | 返回采集记录、已发现 SKU、素材转存状态、Seller 观察结果摘要与警告。 |
| `POST /api/collection-items/{collection_item_id}/start-editing` | ERP 根据页头当前 `platform_id` 进入编辑。服务端验证平台,取得/创建该商品的平台发布配置并预选当前工作空间在该平台的默认店铺后返回编辑页地址;请求不得传 `store_id`。 |
| `GET /api/products/{product_id}/editor` | 返回编辑页所需商品、SKU、属性 schema、媒体和草稿版本。 |
| `GET /api/products/{product_id}/validation` | 返回父商品和逐 SKU 的本地发布校验。 |
采集变体按 `platform + source_item_key + source_variant_key` 幂等。只有来源明确给出 SKU—图片关联时才能绑定 SKU;其他图片进入商品候选池。未知口径的规格保留原文并标为 `ambiguous`。
采集记录在同一工作空间内按 `source_platform + source_item_key` 幂等;其变体按 `source_platform + source_item_key + source_variant_key` 幂等。采集阶段不写入目标平台或店铺。只有来源明确给出 SKU—图片关联时才能绑定 SKU;其他图片进入商品候选池。未知口径的规格保留原文并标为 `ambiguous`。扩展的四个 Seller 网页内部接口必须在用户浏览器本地执行,API 只接收净化结果或 `unavailable` 原因。
## 2. 草稿、SKU 与属性
@@ -21,6 +25,9 @@
| `PATCH /api/products/{product_id}/variants/{variant_id}` | 修改 SKU;可继承字段传 `null` 恢复默认值。 |
| `DELETE /api/products/{product_id}/variants/{variant_id}` | 软删除未发布 SKU;已发布 SKU 改停用并留给发布流程处理。 |
| `GET /api/products/{product_id}/attribute-schema` | 按门店平台和当前类目返回动态属性 schema/字典。 |
| `PUT /api/products/{product_id}/platform-profiles/{platform_id}/target-store` | 更改当前平台发布配置预选的已授权门店;不修改既有 SKU 发布快照。 |
| `POST /api/products/{product_id}/platform-profiles/{platform_id}/ai-category-suggestions` | 基于采集证据和当前平台类目树创建 AI 类目候选任务;返回候选、置信度与依据。 |
| `POST /api/products/{product_id}/platform-profiles/{platform_id}/ai-category-suggestions/{suggestion_id}/apply` | 用户确认后应用一个候选类目,再拉取对应属性 schema。 |
schema 适配器对 Ozon 调用 `/v1/description-category/attribute` 和字典值接口,读取 `type`、`is_required`、`is_collection`、`max_value_count`、`dictionary_id`、`is_aspect`。对其他平台提供同一内部 schema 形状。
@@ -31,6 +38,7 @@ schema 适配器对 Ozon 调用 `/v1/description-category/attribute` 和字典
| `POST /api/products/{product_id}/variants/{variant_id}/pricing/quote` | 用服务端有效包装规格计算预览,不落价。 |
| `POST /api/products/{product_id}/variants/{variant_id}/pricing/confirm` | 重新计算、校验、保存快照并回填售价/划线价。 |
| `GET/PUT /api/products/{product_id}/variants/{variant_id}/media` | 读取候选媒体/保存 SKU 已选媒体及顺序。 |
| `POST /api/products/{product_id}/variants/{variant_id}/media/{asset_id}/watermark` | 用用户选择的模板创建水印派生图;确认后替换当前 SKU 图片位置,原图不变。 |
| `POST /api/products/{product_id}/ai/copy` | 生成父商品的多语言标题、标签、简介等候选草稿。 |
| `POST /api/products/{product_id}/ai/suite-plans` | 创建显式绑定目标 SKU 的套图规划。 |
| `POST /api/products/{product_id}/variants/{variant_id}/ai/dimension-image` | 创建单 SKU 尺寸图任务,并固化实物规格快照。 |
@@ -39,4 +47,8 @@ schema 适配器对 Ozon 调用 `/v1/description-category/attribute` 和字典
## 4. 后续发布
未来平台适配器接受“父商品 + 单 SKU”规范化对象。Ozon 实现将每个启用 SKU 转换为一个 `/v3/product/import.items[]`:复制通用标题、简介、类目和通用属性;填入 SKU offer、价格、包装规格、图片和变体属性。批量按平台限制分组,结果按 SKU 独立保存。
平台适配器接受“平台发布配置 + 单 SKU”规范化对象。Ozon 实现将用户选中的、尚未成功导入的 SKU 转换为一个 `/v3/product/import.items[]`:复制当前平台的标题、简介、类目和通用属性;填入 SKU offer、价格、包装规格、图片和变体属性。批量按平台限制分组,结果按 SKU 独立保存;成功导入 SKU 不允许由采集箱再次提交。
首期导入不调用库存接口,也不选择目标仓库。`/v3/product/import` 不要求仓库;`/v2/products/stocks` 才要求 `warehouse_id`,且库存 `0` 会令商品不可购买。因此库存由用户在 Ozon 后台设置,采集箱的“上架”仅表示首次导入成功,在线商品另行展示实际 `visible` 和库存状态。
Ozon 导入请求的 `images` 字段需要 HTTPS 公网可访问 URL,但 Atlas 不把该 URL 作为素材的长期主数据。发布适配器从已选 `media_asset` 的 `storage_provider + bucket + object_key` 生成适合 Ozon 拉取的短时或受控分发 URL,并将实际发送值固化在发布快照/请求审计中;素材表本身始终以 `object_key` 为准。
+16 -10
View File
@@ -9,20 +9,26 @@ users ── workspace_memberships ── workspaces
└─ stores ── platform_connections
```
`stores` 保存平台标识、市场/国家、默认 locale、默认币种和连接状态。敏感凭据经加密后保存在专用连接密钥记录中,绝不放进前端、日志或商品 JSON。
`stores` 只保存其唯一绑定的平台标识、店铺默认计价币种和连接状态;一个门店不能跨平台使用。市场/国家、默认语言、可用语言和可用币种属于平台能力配置,不能从门店推导。敏感凭据经加密后保存在专用连接密钥记录中,绝不放进前端、日志或商品 JSON。
商品编辑、定价和佣金规则中的 `market_code` 是当前**平台业务上下文**的一部分:首期 Ozon 使用平台默认市场 `RU`;未来平台支持多市场时,由顶部全局上下文选择或使用平台默认值,而非读取门店字段。
## 2. 商品领域
```text
stores 1 ── N products (SPU) 1 ── N product_variants (SKU)
├── product_attributes
├── product_assets ── variant_media
└── pricing_defaults ── variant_pricing_snapshots
workspaces 1 ── N collection_items ── 0..1 products (SPU) 1 ── N product_variants (SKU)
│ │
└─ N product_platform_profiles ── N product_variant_platform_listings
(平台发布配置) (平台 SKU 首次发布映射)
```
每个父商品至少有一条 SKU。`products` 保存通用标题、简介、型号、条形码、采买地址、货源备注、目标市场/类目引用、富内容、默认规格及默认计价参数。可翻译内容不能只存一列,应以 `product_translations`(语言/市场、字段、值、来源、状态)或等价模型保存。
`collection_items` 是工作空间级采集箱记录,保存来源平台、来源商品键、原始证据、规范化结果、素材任务和可空的草稿商品关联;它在采集阶段没有 `target_platform_id` 或 `target_store_id`。每个父商品至少有一条 SKU。`products` 仅是**采集箱商品**的跨平台可复用资料:来源标题、用户填写的型号、条形码、采买地址、货源备注、默认规格和来源证据;采集阶段型号允许为空,不能从来源数据猜测生成。首次上架前型号必须已由用户确认填写;不把任何一个平台的类目、价格、属性或上架状态直接写到该表。
`product_variants` 保存:稳定来源键、展示名、启用/排序、SKU 货号、采购价、售价、划线价、币种、SKU 规格、包装物流规格、实物规格、发布映射和时间戳。`offer_id` 对同一门店/平台连接必须唯一。
`product_platform_profiles` 是 `product_id + platform_id` 唯一的平台发布配置。用户在页头选中平台并进入编辑时才创建或读取它;它保存当前目标门店、该平台的标题/简介/标签、类目和类型、平台通用属性及本平台草稿版本。A 平台与 B 平台独立配置、独立状态;A 已上架不会让 B 显示已上架。门店只是配置中可改的当前发布目标,实际每次发布仍会在 SKU 映射中固化当时的 `store_id`。
`product_variants` 保存:稳定来源键、展示名、删除状态、排序、采购价、SKU 规格、包装物流规格、实物规格和时间戳。`product_variant_platform_listings` 保存平台 SKU 维度的数据:`profile_id`、`variant_id`、用户填写的本平台 `offer_id`(货号)、售价、划线价、币种、平台变体属性、首次导入状态/失败原因、首次导入任务和当时 `store_id`、Ozon `product_id`。采集时不得根据 `source_sku` 自动填入 `offer_id`;用户填写后,它对同一门店/平台连接必须唯一,并且是首次上架的必填项。成功导入的 SKU 映射不可再次创建导入任务;删除 Atlas SKU 只软删 Atlas 关联,不删除平台商品和审计记录。
`platform_listings`(实际建表名待定)保存店铺级在线商品 SKU 镜像,一条记录对应 Ozon 的一个 `product_id` / `offer_id`,而非采集箱 SPU:`store_id`、平台 `product_id`、`offer_id`、平台 SKU、名称、价格、可见性、审核/错误、库存、图片摘要、完整同步快照、最近同步时间和来源(`atlas_published` / `external_sync`)。它是**平台 + 门店独有**的外部事实记录,绝不跨平台、跨门店复用,也不因关联了采集箱 SKU 就变成采集箱商品。它由异步同步任务更新,包含非 Atlas 发布的商品;通过 `store_id + offer_id/product_id` 可选关联 Atlas SKU。采集箱不因成功上架而删除,其当前平台状态由首次导入映射汇总;在线商品的 `visible` 只用于在线商品实际可见性展示。
## 3. 双规格和继承
@@ -35,10 +41,10 @@ stores 1 ── N products (SPU) 1 ── N product_variants (SKU)
## 4. 动态属性和媒体
`product_attributes` 使用结构化行保存 `scope`(common/variant)、本地 key、平台属性 ID、复杂属性 ID、JSON 值、语言、来源与确认状态。平台 schema 驱动类型、必填、字典、多值和 `is_aspect`;`is_aspect=true` 默认 variant scope,但允许业务覆盖。
`product_attributes` 使用结构化行保存 `profile_id`、可空 `variant_id`、本地 key、平台属性 ID、复杂属性 ID、JSON 值、语言、来源与确认状态。平台 schema 驱动类型、必填、字典、多值和 `is_aspect`;`is_aspect=true` 默认 variant scope,但允许业务覆盖。AI 类目推荐保存为独立候选记录,包含输入证据摘要、候选类目、置信度、依据、模型版本、用户确认结果和时间,不可直接覆盖人工选择。
`product_assets` 保存资产事实和对象存储 URL;`variant_media` 保存 SKU 对资产的图片/视频/封面选择与顺序。资产归属一律用稳定 ID;采集平台无法稳定给 SKU 归属时,只作为商品级候选池。
`product_assets`(落地时复用基础表 `media_assets` 或以其为资产事实来源)保存 `storage_provider`、`bucket`、`object_key`、内容哈希、MIME 类型、尺寸和归属关系;不保存长期有效或公开的对象存储 URL。`variant_media` 首期只保存 SKU 对图片资产稳定 ID 的选择、原图/水印派生图关系与顺序;视频相关字段可为未来预留但不在首期 UI 或发布请求中启用。资产归属一律用稳定 ID;采集平台无法稳定给 SKU 归属时,只作为商品级候选池。来源页面图片 URL 仅保存在采集证据中;前端预览/下载 URL 由服务端按 `object_key` 临时签发。
## 5. 定价、任务和发布
`variant_pricing_snapshots` 保存确认时的输入、合并后的包装规格、公式版本、汇率、结果、告警和时间;导出/发布时服务端重新校验,不能信任浏览器结果。所有 AI、媒体、导出和发布任务保存 `workspace_id`、`product_id` 与可空/必填的 `variant_id`,可重试、可恢复并隔离租户。
`variant_pricing_snapshots` 保存确认时的输入、合并后的包装规格、公式版本、汇率、结果、告警和时间;报价快照还应关联 `profile_id`,因为货币和店铺定价方案属于平台发布配置。导出/发布时服务端重新校验,不能信任浏览器结果。所有 AI、媒体、导出和发布任务保存 `workspace_id`、`product_id`、可空/必填的 `profile_id` 与 `variant_id`,可重试、可恢复并隔离租户。
+23 -7
View File
@@ -4,31 +4,41 @@
页面编辑一个父商品及其 SKU 集合。通用信息只维护一次;每个 SKU 是独立可售单元。单规格商品也显示一张默认 SKU 卡片,因而定价、媒体、货号与发布没有特殊分支。
采集箱商品本身不属于平台或门店。这里的“草稿”指 Atlas 内尚未或不再向平台提交的可编辑数据,并非 Ozon 后台草稿。用户从采集箱进入编辑时,ERP 使用页头当前平台(首期默认 Ozon),服务端取得或创建“商品 + 当前平台”的发布配置,预选该平台默认门店;用户可改选该平台的另一已授权门店。切换页头平台会进入另一套独立的平台发布配置:A 平台已上架不影响 B 平台的未上架状态。没有任何可用门店时停留在进入编辑的引导页,允许回采集箱但不能编辑平台属性、定价或发布。
```text
页头:返回 · 商品标题 · 草稿状态 · 来源
页头:返回 · 商品标题 · 当前平台 · 目标门店 · 当前平台上架状态 · 来源
├─ 商品通用信息
│ ├─ 类目、标题、品牌、型号、主题标签、简介、JSON 富内容
│ ├─ 类目、标题、品牌、型号、主题标签、简介
│ ├─ 条形码、采买地址、货源备注
│ └─ 当前平台/市场类目的动态属性
├─ SKU 商品:卡片(默认)/ 紧凑列表
│ └─ N 个 SKU
└─ 固定操作栏
├─ 左:AI 标题/标签/描述 · AI 套图生成
└─ 右:取消并关闭 · 保存草稿 · 上架至平台(后续)
├─ 左:AI 类目 · AI 标题/标签/描述 · AI 套图生成
└─ 右:取消并关闭 · 保存草稿 · 选择未上架 SKU 并首次上架
```
## 2. 保存和校验
页面以编辑草稿工作,不采用字段失焦即写库。保存草稿需原子提交父商品、SKU、属性和媒体顺序;存在未保存修改时关闭页面须二次确认。SKU 定价确认是例外:它会即时持久化该 SKU 的价格与定价快照。
采集进入采集箱时,商品**型号**和各 SKU 的目标平台**货号(Ozon `offer_id`)**默认都为空,不从来源标题、来源 SKU 或规格名称猜测生成。用户在编辑页填写:型号属于商品通用资料;货号属于当前平台下的每个 SKU。两者允许随草稿保存而暂时为空,但首次上架校验必须全部通过:商品型号非空,且每个待上架 SKU 的货号非空并在目标店铺内唯一。
上架状态是“当前平台 + SKU”维度:`未上架`、`处理中`、`上架失败`、`已上架`。SPU 在当前平台的状态由未删除 SKU 汇总为 `未上架`、`已部分上架`、`已上架`。用户可勾选任意未上架 SKU 执行首次上架;已上架 SKU 不显示上架选择,也不发送更新请求。编辑已上架 SKU 时,保存按钮旁必须提示“仅保存 Atlas 草稿,不会更新 Ozon 商品”。
前端根据平台商品类目 schema 渲染属性控件、必填提示、字典选择、多值限制及变体属性。`is_aspect=true` 默认显示在 SKU 区;采购业务确有需要时,用户可把非 aspect 属性(如容量)改为 SKU 属性。类目变更后,失配属性保留为未映射草稿,不能自动进入发布请求。
## 3. SKU 卡片和列表
卡片默认展示:序号、SKU 名称、启用状态、最多 3 张图片缩略图(可切换为 5)、视频封面/视频、货号、采购价、售价、划线价、定价按钮、两组规格与变体属性。列表仅是紧凑视图,字段和数据完全相同。
卡片默认展示:序号、SKU 名称、当前平台上架状态、最多 3 张图片缩略图(可切换为 5)、货号、采购价、售价、划线价、定价按钮、两组规格与变体属性。列表仅是紧凑视图,字段和数据完全相同。货号由用户填写,不自动复用来源 SKU;缺少货号的 SKU 可保存但不能被选择首次上架。首期不支持视频上传、编辑或发布;视频能力待 Ozon 对应接口和审核规则确定后再开启。
SKU 名称只用于展示;采集 SKU 用稳定 `source_variant_key` 关联,手工 SKU 用本地 UUID。规格字段 `null` 仅表示没有覆盖父商品默认值,绝不能据此判断是否单规格或缺少 SKU。
无论 SPU 或 SKU 在当前平台是否已上架,用户都可以新增 SKU 变体、复制任一既有 SKU 作为新变体、删除 SKU。复制出的 SKU 没有平台发布映射,始终从 `未上架` 开始。删除已上架 SKU 只从 Atlas 商品结构和当前平台的可发布 SKU 集合移除,**不会删除、下架或修改 Ozon 已创建的商品**;删除未上架 SKU 后若当前平台只剩已上架 SKU,SPU 状态变为 `已上架`。
删除 SKU 是商品结构的全局编辑,不会跨平台删除任何已创建的平台商品;其他平台下该 SKU 的发布状态和历史映射仍保留在审计记录中。各平台各自仅对当前未删除 SKU 计算 SPU 状态。
## 4. 定价弹窗
点击 SKU 的“定价”打开弹窗,显示该 SKU 合并后的包装物流规格,并允许调整采购价、期望加价率、贴单费、物流方式、划线折扣率和汇率。服务端计算预览、尺寸校验和告警;确认后保存不可变快照,并回填 SKU 销售价和划线价。划线价统一按 `划线价 = 销售价 ÷ (1 − 划线折扣率)` 计算,不使用或兼容“划线价倍数”。
@@ -37,6 +47,12 @@ SKU 名称只用于展示;采集 SKU 用稳定 `source_variant_key` 关联,
## 5. 媒体与 AI
SKU 图片编辑弹窗左侧是该 SKU 的已选图片(排序/移除),右侧分组展示 AI 生图记录、采集主图、采集 SKU 图和采集详情图。商品级素材可被多个 SKU 选择;明确有稳定来源关联的 SKU 图只展示给对应 SKU。
SKU 图片编辑弹窗左侧是该 SKU 的已选图片(排序/移除),右侧分组展示 AI 生图记录、采集主图、采集 SKU 图和采集详情图。商品级素材可被多个 SKU 选择;明确有稳定来源关联的 SKU 图只展示给对应 SKU。Ozon 首期每个 SKU 最多选择 15 张发布图片,第一张为主图,可拖动排序;平台适配器以自身限制驱动该上限。
AI 文案针对父商品生成候选标题、标签、简介与富内容,用户选择后写入草稿。AI 套图由左侧抽屉承载,必须显式选择目标 SKU;尺寸图一 SKU 一任务,并保存实物规格快照。
已选图片悬浮时显示“编辑”菜单,至少包含“添加水印”。点击后在弹窗中选择一个工作空间水印模板,后台创建带水印的派生素材;原图和其他 SKU 的选择不被改写。用户确认后,以派生图替换该 SKU 当前图片位置,并可在同一弹窗撤销为原图。首期不在发布时自动加水印,也不因门店默认水印自动处理图片;后续再支持默认模板的预选、批量应用或自动应用。
首期素材规则:发布图片仅接受 JPG、PNG、WebP;单文件最大 20 MB,最短边低于 700 px 给出警告且不得作为主图,超过 10,000 px 的边长在上传时拒绝;建议 1:1 或 3:4,允许其他比例但在预览中提示裁切风险。采集候选素材可保留原始文件供编辑选择,实际发布时始终按目标平台的图片数、格式和可访问性校验。Ozon 发布图片必须是可由 Ozon 拉取的 HTTPS 地址。
AI 文案针对当前平台的商品信息生成候选标题、标签与简介,用户选择后写入草稿;首期不生成或编辑富内容。AI 套图由左侧抽屉承载,必须显式选择目标 SKU;尺寸图一 SKU 一任务,并保存实物规格快照。
AI 类目由用户主动触发:系统将采集标题、规格、参数、图片摘要等证据与当前平台可发布的类目树交给 AI,返回候选类目、置信度和简短依据,用户必须选择确认后才写入当前平台发布配置。来源为 Ozon 的商品优先保留采集到的 Ozon 类目,默认不自动调用 AI,但始终允许用户主动重新推荐;1688、淘宝、天猫等其他来源默认提供“AI 推荐类目”以提升上品速度。AI 不得直接自动落类目或跳过类目属性校验。