feat(pricing): 开发定价方案
This commit is contained in:
+86
-145
@@ -1,188 +1,129 @@
|
||||
# 定价工具 API
|
||||
# 定价试算与门店方案 API
|
||||
|
||||
> 所有接口要求 `X-Atlas-Platform-Id`。首期仅支持 Ozon、市场 `RU`、`rfbs` 和已审核启用的 CEL 规则。报价引擎只读取 Atlas 已持久化的汇率、佣金和运价版本;请求期间绝不直接请求 CBR、CEL 或 Ozon。
|
||||
> 2026-09-28:维护者批准 SKU 就地试算、详情弹窗、随商品草稿原子确认。本契约替换原规划的独立 `pricing-confirms` 入口。
|
||||
> 稳定 DTO 位于 `packages/contracts/src/pricing.ts`;金额、汇率和比例均使用十进制字符串。
|
||||
|
||||
## 1. 统一口径
|
||||
## 1. 目录
|
||||
|
||||
- API 使用规范单位:采购成本、国内费用为 `CNY` 字符串;重量为 `kg` 字符串;尺寸为 `cm` 字符串。前端的 g/mm 输入必须先转换。
|
||||
- 所有比例以 `0`–`1` 字符串表示,如 `"0.20"` 是 20%。`desired_markup_rate` 是目标净利润 ÷ 采购成本;`list_price_discount_rate` 是买家相对划线价的折扣率。
|
||||
- 销售价和划线价均为 `RUB`;`merchandise_value_cny` 是销售价按本次汇率折算得到的 CNY,专门用于选择 CEL 产品类型。
|
||||
- 引擎枚举“佣金档 × CEL 产品类型 × 运输方式”,再回验佣金价格区间、CEL 货值区间、重量、尺寸及计费重。没有稳定有效组合时不能返回建议售价。
|
||||
- 系统汇率、方案固定汇率、一次性覆盖及临时佣金率都固化到后续 SKU 报价快照,不会改写全局规则或定价方案。
|
||||
### GET `/pricing/catalog?store_id={uuid}`
|
||||
|
||||
## 2. 只读上下文
|
||||
需要已认证工作空间。返回 `{data: PricingCatalog}`:
|
||||
|
||||
### 2.1 当前上下文
|
||||
- `store`:门店 ID、名称及报价币种(CNY / RUB)。
|
||||
- `rate_cards[]`:该门店平台、RU / rFBS 的有效费率卡及运输方式。正式环境必须 active、在有效期内、有审核时间及审核人。`development_only=true` 表示本地开发模式临时可选的 draft 卡。
|
||||
- `pending_rate_cards[]`:该平台 RU / rFBS 待审核草稿的名称、版本和状态,仅用于说明阻断原因,不作为方案或报价可选项。
|
||||
- `policies[]`:该门店活动方案,默认方案优先,包含完整参数和 revision。
|
||||
- `exchange_rate`:CBR 最新 CNY→RUB 官方参考汇率,包含 ID、日期和 stale;缺少数据时为 null。超过 72 小时提示过期参考,不自动代填其他来源。
|
||||
|
||||
`GET /api/pricing/context`
|
||||
### GET `/pricing/categories?store_id={uuid}&q={text}`
|
||||
|
||||
返回当前平台、默认市场、当前工作空间该平台的默认店铺及默认定价方案。没有默认店铺返回 `422 DEFAULT_STORE_REQUIRED`,不猜测目标店铺。
|
||||
检索门店平台下活动商品类目,返回 `{data:[{id,name,path}]}`,最多 50 条。`q` 最长 200 字;继续输入名称缩小范围。
|
||||
|
||||
```json
|
||||
{ "data": {
|
||||
"platform": { "id": "uuid", "code": "OZON", "name": "Ozon" },
|
||||
"market_code": "RU",
|
||||
"store": { "id": "uuid", "name": "Ozon 主店", "currency_code": "RUB" },
|
||||
"default_pricing_policy": { "id": "uuid", "name": "CEL 常规" }
|
||||
} }
|
||||
```
|
||||
## 2. POST `/pricing/quote-previews`
|
||||
|
||||
### 2.2 商品类目、物流目录与系统汇率
|
||||
|
||||
`GET /api/platform-product-categories?parent_id={uuid|null}&q={keyword}&active_only=true`
|
||||
|
||||
返回 header 平台的商品类目。`parent_id`(懒加载树)与 `q`(搜索)不能同时使用。每项有 `id`、`name`、`path`、`has_children`、`pricing_rule_status`:
|
||||
|
||||
- `available`:当前市场、履约模式下存在唯一有效映射和正式佣金规则;
|
||||
- `missing`:缺少有效映射或规则;
|
||||
- `ambiguous`:多个同优先级映射,普通用户不能猜测。
|
||||
|
||||
不暴露佣金类目 ID 让客户端自行映射。
|
||||
|
||||
`GET /api/pricing/catalog?fulfillment=rfbs&store_id={uuid}`
|
||||
|
||||
`store_id` 省略时取默认店铺;传入时必须属于当前工作空间与 header 平台。仅返回当前已启用价卡、服务商和运输方式:
|
||||
|
||||
```json
|
||||
{ "data": { "providers": [{
|
||||
"id": "uuid", "code": "CEL", "name": "CEL",
|
||||
"rate_card": { "id": "uuid", "version": "CEL-OZON-RFBS-V6.17", "effective_from": "2026-09-01T00:00:00Z" },
|
||||
"transport_methods": [
|
||||
{ "id": "uuid", "code": "air", "name": "陆空特快", "speed_label": "5–10 天" },
|
||||
{ "id": "uuid", "code": "land_air", "name": "陆空标准", "speed_label": "10–15 天" },
|
||||
{ "id": "uuid", "code": "land", "name": "陆运经济", "speed_label": "15–25 天" }
|
||||
]
|
||||
}] } }
|
||||
```
|
||||
|
||||
若规则仍为 `draft` 或未通过真实账单复核,catalog 不返回它;试算请求返回 `422 PRICING_RULES_UNAVAILABLE`。
|
||||
|
||||
`GET /api/pricing/exchange-rate?base_currency_code=CNY"e_currency_code=RUB`
|
||||
|
||||
读取最新 CBR `official_reference`:
|
||||
|
||||
```json
|
||||
{ "data": {
|
||||
"id": "uuid", "base_currency_code": "CNY", "quote_currency_code": "RUB", "rate": "12.33430000",
|
||||
"source": "cbr", "kind": "official_reference", "effective_on": "2026-09-02",
|
||||
"fetched_at": "2026-09-02T02:00:00Z", "is_stale": false
|
||||
} }
|
||||
```
|
||||
|
||||
没有系统汇率返回 `404 SYSTEM_EXCHANGE_RATE_NOT_FOUND`;超过 72 小时仍返回但 `is_stale=true`,前端必须明确提示。
|
||||
|
||||
## 3. 即时试算
|
||||
|
||||
`POST /api/pricing/quote-previews`
|
||||
|
||||
Headers:`Idempotency-Key` 必填。接口不创建 `pricing_quotes`;`preview_id` 最多可用于后续 SKU 确认 15 分钟,确认时仍会重新计算。
|
||||
要求 `Idempotency-Key`。成功返回 201;同键同输入重放原响应,同键不同输入返回 409。
|
||||
|
||||
```json
|
||||
{
|
||||
"store_id": "uuid",
|
||||
"store_id": "<store UUID>",
|
||||
"platform_product_category_id": "<category UUID>",
|
||||
"fulfillment": "rfbs",
|
||||
"platform_product_category_id": "uuid",
|
||||
"purchase_cost_cny": "80.00",
|
||||
"package": { "weight_kg": "0.990", "length_cm": "30", "width_cm": "31", "height_cm": "11" },
|
||||
"binding": {"profile_id":"<profile UUID>","variant_id":"<variant UUID>","client_key":null},
|
||||
"purchase_cost_cny": "28.00",
|
||||
"package": {"shipping_weight_g":"380","length_mm":"220","width_mm":"160","height_mm":"90","status":"confirmed"},
|
||||
"pricing": {
|
||||
"pricing_policy_id": "uuid",
|
||||
"transport_method_ids": ["uuid", "uuid", "uuid"],
|
||||
"domestic_shipping_and_label_fee_cny": "5.00",
|
||||
"ad_rate": "0.03", "last_mile_rate": "0.02", "withdrawal_rate": "0.01", "other_rate": "0.02",
|
||||
"desired_markup_rate": "0.20", "list_price_discount_rate": "0.10",
|
||||
"fx": { "mode": "system" },
|
||||
"temporary_commission_rate": null
|
||||
"pricing_policy_id": "<policy UUID>",
|
||||
"rate_card_id": "<rate-card UUID>",
|
||||
"transport_method_ids": ["<transport UUID>"],
|
||||
"default_transport_method_id": "<transport UUID>",
|
||||
"domestic_shipping_and_label_fee_cny":"3",
|
||||
"ad_rate":"0.03","last_mile_rate":"0.02","withdrawal_rate":"0.01","other_rate":"0.02",
|
||||
"desired_markup_rate":"0.50","list_price_discount_rate":"0.20",
|
||||
"mode":"target_markup","selling_price":null,
|
||||
"fx":{"mode":"policy_fixed"},"temporary_commission_rate":null
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
规则:
|
||||
- 独立试算 `binding=null`;SKU 必须提供 profile,已有 variant_id 与新建 client_key 二选一。验证资源归属,已上架 SKU 不接受新报价。
|
||||
- 包装输入是编辑草稿合并后的有效 g/mm 值,必须已确认;尺寸最多三位小数,重量至少 1 g。保存时从实际持久化草稿重新合并并核对,不能仅信任客户端摘要。
|
||||
- `mode=target_markup`:目标利润=采购成本×加价率;求解销售价。`fixed_sale`:selling_price 必填,按该售价核算利润。
|
||||
- 百分比费用以销售额为基数;除加价率外每项小于 1、最多六位小数。引擎拒绝总扣费率≥1。划线价=销售价÷(1−折扣率)。
|
||||
- 枚举运输方式×产品类型×佣金档,按最终币种舍入后回验货值、佣金、重量和体积重边界。默认运输不可用时 recommended_option_id=null,不替换为另一方式。
|
||||
- `fx.mode=system` 读取 PostgreSQL 最新 CBR;`policy_fixed` 必须绑定有固定汇率的方案;`one_time_override` 必须明确 rate 和 reason。固定汇率取方案值,不信任客户端覆盖。采用的汇率固定在本次预览中,15 分钟内确认不重新拉取外部汇率。
|
||||
- 只有不存在有效正式佣金规则且无方案或方案允许时,才能 temporary_commission_rate。已有正式规则禁止覆盖。
|
||||
|
||||
- `store_id` 可省略,服务端取默认店铺;非空时必须与 header 平台一致。
|
||||
- `pricing_policy_id` 非空时方案提供默认值;请求中的费用、目标、运输方式和汇率仅覆盖本次试算,不修改方案。
|
||||
- 无方案时所有费用、费率、目标、汇率模式和至少一个运输方式必填。
|
||||
- `fx.mode` 为 `system`、`policy_fixed` 或 `one_time_override`;后者必填 `rate`,建议附 `reason`。`policy_fixed` 只对 `fx_strategy=fixed` 方案有效。
|
||||
- 正式佣金规则缺失时,仅无方案即时试算或方案 `allow_commission_override=true` 可传 `temporary_commission_rate`。它必须被明确标记为临时覆盖。
|
||||
- 未指定运输方式时取方案已启用的比较方式;仍为空则取方案默认运输方式。它们必须属于同一启用价卡。
|
||||
响应包含 `preview_id,expires_at,input_hash,recommended_option_id,options,unavailable_options,context`。
|
||||
每个 option 包含目标币种销售/划线价、CNY/RUB 利润、计费重、佣金/物流引用、费用明细、warnings 和 can_confirm。
|
||||
亏损可查看,但 can_confirm=false;判定使用未舍入利润,显示为 0.00 的微亏也不能确认。
|
||||
context 保存规则版本、实际汇率与来源,以及系统汇率记录 ID / 日期。参数/规则快照由服务端持久化。
|
||||
|
||||
返回:
|
||||
预览按用户、工作空间、门店、SKU、规范化输入与规则摘要绑定,15 分钟有效。Worker 删除过期两天以上的预览。
|
||||
|
||||
```json
|
||||
{ "data": {
|
||||
"preview_id": "opaque-short-lived-id", "expires_at": "2026-09-02T08:15:00Z",
|
||||
"context": {
|
||||
"store_id": "uuid", "platform_id": "uuid", "market_code": "RU",
|
||||
"rate_card": { "id": "uuid", "version": "CEL-OZON-RFBS-V6.17" },
|
||||
"exchange_rate": { "rate": "12.33430000", "source": "cbr", "effective_on": "2026-09-02", "is_stale": false }
|
||||
},
|
||||
"recommended_option_id": "candidate-1",
|
||||
"options": [{
|
||||
"id": "candidate-1", "is_valid": true,
|
||||
"transport_method": { "id": "uuid", "code": "land_air", "name": "陆空标准" },
|
||||
"logistics_product_type": { "code": "budget", "name": "Budget" },
|
||||
"commission": { "rate": "0.120000", "source": "manual_rule", "version": "2026-09", "price_range": { "min": null, "max": "1500.00", "max_inclusive": true } },
|
||||
"prices": { "selling_price_rub": "999.00", "list_price_rub": "1110.00", "merchandise_value_cny": "80.99" },
|
||||
"weights": { "actual_weight_kg": "0.990", "volume_weight_kg": null, "billable_weight_kg": "0.990", "billing_basis": "actual_only" },
|
||||
"fees": {
|
||||
"purchase_cost_rub": "986.74", "logistics_fee_cny": "49.6600", "logistics_fee_rub": "612.55",
|
||||
"domestic_fee_cny": "5.0000", "domestic_fee_rub": "61.67", "commission_fee_rub": "119.88",
|
||||
"ad_fee_rub": "29.97", "last_mile_fee_rub": "19.98", "withdrawal_fee_rub": "9.99", "other_fee_rub": "19.98",
|
||||
"total_cost_rub": "1860.76", "profit_rub": "-861.76", "gross_margin_rate": "-0.862"
|
||||
},
|
||||
"calculation_trace": { "rounding": [], "formula_steps": [], "rule_versions": [] }, "warnings": []
|
||||
}],
|
||||
"unavailable_options": [{ "transport_method_id": "uuid", "code": "PACKAGE_DIMENSION_EXCEEDED", "message": "最长边超过该方式限制" }]
|
||||
} }
|
||||
```
|
||||
## 3. 商品保存时确认
|
||||
|
||||
数值只示意响应形状,非真实 CEL 结果。每种运输方式独立成功或列出不可报价原因;一个方式失败不阻断其他方式。推荐仅按完整成本、目标利润和规则有效性计算,不承诺时效。
|
||||
|
||||
缺少成本、有效包装规格、类目、汇率或佣金规则时返回 `422` 和可修复字段;不返回伪造价格。
|
||||
|
||||
## 4. 定价方案
|
||||
|
||||
方案属于 `store + platform`,币种从店铺读取。方案资源的核心形状:
|
||||
`PUT /products/{id}/editor-draft` 沿用商品/profile revision 的 `If-Match` 和 `Idempotency-Key`,每个 variant 增加:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "uuid", "store_id": "uuid", "platform_id": "uuid", "name": "CEL 常规", "note": null,
|
||||
"status": "active", "is_default": true, "fulfillment": "rfbs",
|
||||
"rate_card": { "id": "uuid", "version": "CEL-OZON-RFBS-V6.17", "provider": { "code": "CEL", "name": "CEL" } },
|
||||
"default_transport_method_id": "uuid", "comparison_transport_method_ids": ["uuid", "uuid"],
|
||||
"fees": { "domestic_shipping_and_label_fee_cny": "5.00", "ad_rate": "0.03", "last_mile_rate": "0.02", "withdrawal_rate": "0.01", "other_rate": "0.02" },
|
||||
"desired_markup_rate": "0.20", "list_price_discount_rate": "0.10", "allow_commission_override": false,
|
||||
"fx": { "strategy": "system", "fixed_cny_to_rub_rate": null }, "revision": 1
|
||||
"pricing_draft": "本次 PricingParameters 对象或 null",
|
||||
"pricing_selection": {"preview_id":"<UUID>","option_id":"<UUID>","input_hash":"<64-char SHA256>"}
|
||||
}
|
||||
```
|
||||
|
||||
`GET /api/stores/{store_id}/pricing-policies?status=active&cursor={opaque}&limit=20`
|
||||
两个字段均可省略以兼容既有客户端;读取 listing 返回 `pricing_draft,pricing_result,pricing_valid`。
|
||||
|
||||
按 `is_default DESC, updated_at DESC, id DESC` 返回方案。路径店铺必须属于当前工作空间与 header 平台。
|
||||
确认在现有商品事务中执行:先保存草稿和新 SKU,再核对实际有效输入、用户绑定、有效期、审核规则摘要、币种及候选资格,最后创建不可变报价/候选快照并切换 listing 售价引用。
|
||||
新 SKU 的 client_key 在同一事务映射到真实 ID。独立预览、其他用户或其他 SKU 的预览不能用于确认。
|
||||
|
||||
`GET /api/pricing-policies/{policy_id}` 返回完整方案。
|
||||
- 携带失效确认意图:409 `PRICING_PREVIEW_STALE`,details 定位具体 SKU,整笔保存回滚。
|
||||
- 不携带确认意图:允许保存未定价草稿;输入/币种变化使报价失效。用户可在冲突弹窗明确选择“仅保存草稿,稍后定价”。
|
||||
- 未变化的已确认报价继续使用原快照,无时间过期,不重新读规则或汇率。方案更新/归档不改变其价格。
|
||||
- 发布校验要求 pricing_valid=true,无有效报价返回 `PRICING_REQUIRED`;已导入 SKU 沿用既有不可再次首次上架边界。
|
||||
- 数据库复合外键保证 quote/profile/product/variant/store/platform 归属,RLS 隔离租户。确认快照拒绝 UPDATE;成本、包装、类目、店铺或币种变化触发失效。
|
||||
|
||||
`POST /api/stores/{store_id}/pricing-policies`
|
||||
## 4. 门店方案
|
||||
|
||||
Headers:`Idempotency-Key` 必填。请求体采用上方形状的可写字段:创建时 `rate_card_id` 代替 `rate_card`,运输方式以 UUID 数组提交。首个 active 方案强制默认;后续 `is_default=true` 在同一事务取消旧默认。
|
||||
| 方法 | 路径 | 约束 |
|
||||
|---|---|---|
|
||||
| GET | `/stores/{id}/pricing-policies` | 返回当前门店活动方案数组 |
|
||||
| POST | `/stores/{id}/pricing-policies` | Idempotency-Key;成功 201 |
|
||||
| PATCH | `/pricing-policies/{id}` | Idempotency-Key + If-Match(带引号的 revision);冲突 409 |
|
||||
| POST | `/pricing-policies/{id}/set-default` | Idempotency-Key;门店锁保证唯一默认;成功 200 |
|
||||
| DELETE | `/pricing-policies/{id}` | Idempotency-Key;归档并清除默认标志,返回 `{id,archived:true}` |
|
||||
|
||||
`PATCH /api/pricing-policies/{policy_id}`
|
||||
写入 `{name,note,parameters,allow_commission_override}`。只保存费用、加价、折扣、固定/系统汇率与运输偏好;采购价、包装、手动售价及临时佣金不属于方案持久字段。
|
||||
方案的默认和比较运输必须属于同一可用费率卡;正式环境仅允许已审核卡。历史快照不会被方案修改;尚未确认的预览因方案版本改变要求重算。
|
||||
|
||||
Headers:`If-Match`、`Idempotency-Key` 必填。仅改提交字段;变更履约、价卡或运输方式时服务端重新校验所有关联。变更只影响未来试算/SKU 新确认,绝不重算旧报价。
|
||||
## 5. 数据启用与错误
|
||||
|
||||
`POST /api/pricing-policies/{policy_id}/set-default`
|
||||
迁移中的 CEL 目录为 `draft`,覆盖六种产品类型、三种运输方式,香港空运不纳入。真实账单复核、有效日期/审核人以及平台类目佣金映射仍是正式使用前置条件,接口不会回退到示例费率或固定佣金。
|
||||
|
||||
Headers:`Idempotency-Key` 必填。仅 active 方案可设默认。
|
||||
商品编辑联调期间,API 在 `NODE_ENV=development` 时默认允许 draft 费率卡参与方案保存、预览与草稿报价确认;可设 `PRICING_ALLOW_DRAFT_RATES=false` 关闭。测试环境只有显式设 `PRICING_ALLOW_DRAFT_RATES=true` 才开启;生产环境始终关闭。该模式不修改费率卡状态、不生成审核记录。目录的 `rate_cards[].development_only` 和预览的 `context.development_only` 标识试算性质;基于此类预览确认的 SKU 报价可在编辑页查看,但首次上架校验必须返回 `PRICING_RATE_REVIEW_REQUIRED`,直至使用已审核费率重新试算确认。佣金和汇率仍按现有输入规则处理。
|
||||
|
||||
`DELETE /api/pricing-policies/{policy_id}`
|
||||
### 系统管理员审核 CEL 费率
|
||||
|
||||
Headers:`Idempotency-Key` 必填。将方案置为 `archived`,不物理删除快照。删除默认方案时,若还有 active 方案则返回 `422 DEFAULT_PRICING_POLICY_REPLACEMENT_REQUIRED`,必须先选择替代默认;否则允许无默认方案。
|
||||
系统管理「物流费率审核」使用以下接口;全部要求登录且具备实时有效的 `system_admin_grants` 授权,普通门店用户返回 403 `SYSTEM_ADMIN_REQUIRED`:
|
||||
|
||||
## 5. SKU 确认衔接
|
||||
| 方法 | 路径 | 行为 |
|
||||
|---|---|---|
|
||||
| GET | `/system/pricing/rate-cards?platform_id={uuid}` | 查看该平台 RU/rFBS 草稿及已启用费率,含产品类型、规则、审核记录与 `rules_hash` |
|
||||
| GET | `/system/pricing/rate-cards/{id}` | 重读单个费率版本 |
|
||||
| POST | `/system/pricing/rate-cards/{id}/reviews` | `Idempotency-Key` UUID 必填;原子保存审核证据并启用费率,成功 201 |
|
||||
|
||||
即时试算不落 `pricing_quotes`。商品编辑模块将定义“确认 SKU 报价”:接收 `preview_id`、SKU、最终选中候选及可选一次性覆盖,重新计算后以事务写入 `pricing_quotes + pricing_quote_options`,固化输入、汇率、佣金、CEL 规则版本和费用拆分。
|
||||
审核请求包含 `rules_hash`、`source_reference`(资费合同/实际账单批次)、至少 20 字的 `review_note`、`effective_from`(含时区 ISO 时间)和 `samples`。每条样本包含 `rule_id`、唯一的 `invoice_reference`、`merchandise_value_cny`、包装重量 g / 长宽高 mm、`billed_logistics_cny`。每种 CEL 产品类型至少一条,合计不少于六条。服务端使用与正式报价相同的包装适用性和运费函数复算;样本货值须命中对应类型,账单运费与规则计算差额不得超过 0.01 CNY。真实账单来源和口径仍由审核人负责确认,系统不会凭示例数字自行审核。
|
||||
|
||||
客户端不能提交预览中的价格、物流费或规则 ID 作为确认事实。预览过期、规则/汇率更新或包装规格变化时,服务端拒绝旧预览并要求重新试算。临时切换方案或运输方式只进入本次 SKU 快照,绝不修改默认方案。
|
||||
仅支持 Ozon/CEL 草稿版本。规则必须仍为 16 条 draft,类型为指定六种,且 `rules_hash` 与管理员查看时一致;否则返回 409/422。通过后在同一数据库事务保存不可修改的审核记录、把规则与费率卡改为 active、设置审核人与生效日期。若生效日期在未来,直到该时间才出现在卖家端可用费率中。数据库拒绝改写已审核的费率卡、产品类型、运输方式和规则;修改资费应新建版本重新审核,不覆盖已确认 SKU 报价。已启用费率不提供再次审核入口。
|
||||
|
||||
## 6. 后台维护边界
|
||||
系统管理不会自动配置平台类目佣金映射;缺少正式佣金规则时,卖家试算仍需满足临时佣金覆盖条件。当前 CEL 种子保持 draft,尚无真实账单证据时不得提交启用。
|
||||
|
||||
普通卖家端不提供佣金类目、佣金规则、物流价卡或汇率历史的写接口。它们由受控运营后台、seed/migration 或 Worker 维护;若将来开放运营 API,必须独立鉴权、审计、版本发布和回滚。
|
||||
| HTTP / code | 用户动作 |
|
||||
|---|---|
|
||||
| 422 VALIDATION_ERROR | 修正输入格式或缺项 |
|
||||
| 422 PRICING_RULES_UNAVAILABLE | 配置并审核物流费率 |
|
||||
| 422 PRICING_COMMISSION_UNAVAILABLE | 配置正式映射,或使用允许临时佣金的方案 |
|
||||
| 422 PRICING_FX_UNAVAILABLE | 等待 CBR 同步,或明确采用临时/方案固定汇率 |
|
||||
| 409 PRICING_PREVIEW_STALE | 重算、比较并重新应用,或显式仅保存草稿 |
|
||||
| 409 VERSION_CONFLICT | 重新读取版本后修改 |
|
||||
|
||||
独立上下文、汇率信息整合在 catalog;本期不开放原规划的独立 context、exchange-rate、pricing-confirms 路由和报价历史列表。
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 商品编辑与首次上架 API
|
||||
|
||||
> M3.0~M3.4 已实现编辑上下文、草稿、SKU、来源素材、类目和动态属性。平台目录刷新方案第 1~4 步已把类目树、类型属性、字典读取及发布预检迁至平台级缓存:编辑器按需刷新,首次发布预检只刷新当前类型和实际使用的字典。AI 填报任务已实现;定价确认和真正调用 Ozon 的首次发布 job 仍未实现。
|
||||
> M3.0~M3.4 已实现编辑上下文、草稿、SKU、来源素材、类目和动态属性。平台目录刷新方案第 1~4 步已把类目树、类型属性、字典读取及发布预检迁至平台级缓存:编辑器按需刷新,首次发布预检只刷新当前类型和实际使用的字典。AI 填报任务及随商品保存的定价确认已实现;真正调用 Ozon 的首次发布 job 仍未实现。
|
||||
|
||||
> 此处的商品始终是采集箱内的 Atlas 草稿。所有平台范围接口要求 `X-Atlas-Platform-Id`。商品通用资料可以跨平台复用;`product_platform_profile`、SKU 首次导入状态和在线商品不能跨平台/门店复用。
|
||||
|
||||
@@ -248,17 +248,13 @@ Body:`{ "variant_ids": ["uuid", "uuid"] }`,最多 100 个且不能重复。
|
||||
|
||||
## 4. SKU 定价确认
|
||||
|
||||
`POST /api/product-platform-profiles/{profile_id}/variants/{variant_id}/pricing-confirms`
|
||||
定价确认与 `PUT /api/products/{product_id}/editor-draft` 同一事务完成。每个 SKU 可携带 `pricing_draft` 和 `pricing_selection: { preview_id, option_id, input_hash }`;新 SKU 使用 `client_key` 绑定预览,保存时映射为真实 ID。独立试算不能直接确认到 SKU。
|
||||
|
||||
Headers:`Idempotency-Key` 必填。Body:
|
||||
服务端核对预览归属、15 分钟有效期、当前采购价、有效包装、类目、门店、币种及规则版本,再写入不可变 `pricing_quotes`、全部候选和 listing 价格。任何一个明确选择的报价失效,整个商品保存回滚。客户端可提示用户重新计算,或经用户选择清除本次确认意图后仅保存草稿。
|
||||
|
||||
```json
|
||||
{ "preview_id": "opaque-id", "selected_option_id": "candidate-1", "one_time_overrides": null }
|
||||
```
|
||||
没有报价时仍可保存商品,但首次发布预检返回 `PRICING_REQUIRED`。已确认报价不会随预览到期而失效;采购、包装、目标门店/类目、履约或门店币种改变后须重新确认。调整默认方案、汇率或费率不会反向改写历史快照。详见[定价 API](./pricing.md)。
|
||||
|
||||
服务端重新解析 SKU 的有效包装规格、验证预览仍未过期且配置/汇率/规则未变化,重新计算后写入 `pricing_quotes`、全部 `pricing_quote_options` 和当前平台 listing 的售价/划线价/币种/报价引用。成功返回 `201` 与不可变报价摘要。
|
||||
|
||||
包装规格缺失或 `ambiguous`、预览失效、候选不属于预览、目标店铺不一致、物流不适用时返回 `422`,不覆盖既有价格。临时更换方案或运输方式仅写入本次报价,不改门店默认方案。
|
||||
开发环境可按定价 API 的草稿费率模式完成商品编辑联调;这种报价即使已保存,也会在首次发布预检返回 `PRICING_RATE_REVIEW_REQUIRED`。费率完成正式审核后,须用已审核卡重新试算并确认 SKU。
|
||||
|
||||
## 5. 素材选择与水印派生
|
||||
|
||||
@@ -319,3 +315,10 @@ Headers:`Idempotency-Key` 必填。Body:
|
||||
## 8. 已替代的旧草案
|
||||
|
||||
本文替代 [`../product/api.md`](../product/api.md) 中与商品编辑有关的路径。旧草案保留为历史背景,实施以本文件、[`pricing.md`](./pricing.md) 与 [`collection-and-extension.md`](./collection-and-extension.md) 为准。
|
||||
|
||||
### 2026-09-28 SKU 定价确认修订
|
||||
|
||||
草稿的每个 variant 可携带 `pricing_draft` 和 `pricing_selection`,与商品保存原子提交。
|
||||
读取 listing 返回 `pricing_draft`、`pricing_result` 和 `pricing_valid`。
|
||||
确认失败整笔回滚并定位 SKU;无报价允许保存,但发布预检返回 `PRICING_REQUIRED`。
|
||||
输入变化使报价失效;已确认快照保留。具体 DTO 与错误见 pricing.md 实施修订。
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
# SKU 定价试算与确认:产品及实施提案
|
||||
|
||||
> 2026-09-28 · 维护者已批准本方案,进入实施。第 5 节的契约变更同步到 API 文档与共享契约;原型示例数据不作为生产费率。
|
||||
|
||||
## 1. 结论
|
||||
|
||||
建议采用「SKU 就地自动试算 + 详细试算弹窗 + 商品页统一保存」。采购价失焦或按 Enter 后,用该 SKU 当前方案计算并回填销售价、划线价和预估净利润;保留“计算”按钮供主动重算。用户可以关闭自动试算。弹窗用来查看费用、比较物流、调整本次参数,点击“应用到 SKU”回到未保存表单。商品页“保存”才确认报价并持久化。
|
||||
|
||||
把用户的两种意图分清:输入成本时需要立即看到结果;保存商品时才承诺这次售价。避免每个 SKU 都弹出一次确认框,也避免一次失焦就写入正式报价。
|
||||
|
||||
## 2. 已阅读的依据与当前差距
|
||||
|
||||
- 新项目:`requirements/pricing-tool.md`、`requirements/cel-ozon-rfbs-rates-review.md`、`api/pricing.md`、`api/product-editor-and-publishing.md`、`architecture/exchange-rates.md`,以及现有 pricing 表和 SKU 表单。
|
||||
- 旧项目:`studio/src/pages/trial/TrialPage.tsx` 的「2 价格试算」、`TrialPricingPanel.tsx`、`studio/src/pricing/pricing.ts`。
|
||||
- 旧页面通过 `recalc()` 在参数变化时计算,并通过 `onSave()` 保存参数、售价、划线价与汇率。左右布局、包装数据带入、费用明细和中俄币种对照值得保留。
|
||||
- 旧公式把采购成本上的加价率叫“净利率”,使用固定 12%/18% 佣金和 3.5% 其他扣费;low/high/high2 不能表达新的 CEL 货值类型与运输方式,不适合直接迁入 Atlas。
|
||||
- Atlas 目前的 SKU 摘要已有采购价输入、只读售价和“净利润 —”;定价工具还是占位页。已有 pricing 基础表,但预览服务、报价确认、汇率 Worker 和方案管理尚需实现。
|
||||
- 现有 `pricing-confirms` 契约独立落库,而本提案建议随草稿保存确认;不能只改前端按钮文案,必须先调整保存事务与契约。
|
||||
|
||||
## 3. SKU 与弹窗交互
|
||||
|
||||
### 3.1 SKU 卡片
|
||||
|
||||
摘要行保留:采购价 CNY、销售价(目标店铺币种)、预估净利润 CNY、“计算”、“试算详情”。第二行以较轻的文字显示方案、采用的运输方式、报价状态;包装缺失时显示具体缺项和补充入口。
|
||||
|
||||
自动算价默认开启,作为用户的编辑偏好。有效值发生变化且字段失焦/按 Enter 后触发,不在每次键入时创建请求。采购价、有效包装规格、目标店铺、类目或 SKU 参数变化均使之前的结果失效;同值失焦不重复请求。包装继承关系仍由 SPU/SKU 原有模型解析。
|
||||
|
||||
SKU 状态为:未试算 → 计算中 → 已试算·待保存 → 已确认。缺少资料、请求失败或参数已变时显示可修复状态,不把旧利润继续当成当前结果。
|
||||
|
||||
“按方案计算全部”逐 SKU 返回结果;成功项更新表单,失败项保留输入并标出原因。手动售价不被批量推荐价覆盖,手动项按原售价重新核算利润。
|
||||
|
||||
### 3.2 手动售价
|
||||
|
||||
用户可直接修改售价,系统切换到“手动售价”模式,按这个价格计算物流适用性、佣金及利润。此后修改采购价或包装数据,保留售价,仅更新成本和利润。“使用建议售价”是显式退出手动模式的动作。
|
||||
|
||||
预估净利润只读,不接受手填。标签使用“预估净利润”;它是扣除已配置费用后的预计利润,不是账单结算利润。低于目标或为负时明确显示差额。首期建议亏损报价可保留为草稿,但不得自动成为可发布报价;后续若允许强制确认亏损价,需另行确定权限与确认规则。
|
||||
|
||||
### 3.3 试算弹窗
|
||||
|
||||
- 顶部:SKU 名称、当前目标店铺及币种、定价方案。进入时复制该 SKU 的表单输入与参数,取消不修改 SKU。
|
||||
- 左侧:采购价、有效包装重量/尺寸、目标加价率、划线折扣率;高级费用折叠展示国内运费/贴单、广告、尾程、提现、其他费、汇率来源和临时覆盖。
|
||||
- 页面中的加价率、折扣率、广告、尾程、提现、其他费用率及临时佣金率均按百分数展示和输入,统一保留两位小数;API 仍使用十进制比例字符串,例如页面 `50.00%` 对应请求 `"0.5"`。
|
||||
- 右侧:大号销售价、划线价、预估净利润及利润占销售额比例;费用明细与规则来源按需展开。
|
||||
- 物流比较:展示方案默认运输方式,同时允许比较其他可用方式。最低价只是标签,不自动替换方案指定的运输方式;指定方式不可用时要求用户选择。
|
||||
- 修改参数后延迟约 400 ms 试算,输入未完整时不发送请求;取消旧请求并校验返回版本,防止旧结果覆盖新参数。
|
||||
- 底部:取消 / 应用到 SKU。应用只更新当前 SKU 的待保存报价和本次参数,不修改门店默认方案。
|
||||
- 用户在弹窗修正包装时,明确写入该 SKU 的包装覆盖值,不修改 SPU 默认规格;原型只演示数值,正式组件需显示继承来源并提供恢复继承操作。
|
||||
|
||||
### 3.4 独立定价工具与定价方案
|
||||
|
||||
独立“即时试算”和 SKU 弹窗使用同一输入组件、结果组件与服务端引擎。独立试算不要求已有商品,不提供“应用到 SKU”。
|
||||
|
||||
“定价方案”负责门店范围的默认扣费、利润目标、折扣、汇率策略和运输偏好;每个门店最多一个默认方案。新 SKU 首次计算时采用它,已有 SKU 继续使用自己的方案/本次覆盖。方案编辑不重算历史已确认报价。将临时参数另存方案应是独立明确动作,采购价和包装尺寸不随之保存。
|
||||
|
||||
## 4. 保存、变化与失效
|
||||
|
||||
| 操作或事件 | 表单结果 | 已确认快照 |
|
||||
|---|---|---|
|
||||
| 成本失焦,输入完整 | 自动预览并回填,标记待保存 | 保留 |
|
||||
| 弹窗点击应用 | 覆盖当前 SKU 的待保存报价 | 保留 |
|
||||
| 手改售价 | 保留手动价,重算费用/利润 | 保留 |
|
||||
| 包装/类目/店铺变化 | 旧预览失效;重算或提示缺项 | 保留历史,新草稿标记未满足发布报价条件 |
|
||||
| 方案或系统汇率更新 | 不静默替换已确认价格 | 保持原输入、版本、汇率与结果 |
|
||||
| 点击商品页保存 | 服务端核对并确认本次报价 | 生成新不可变快照,并切换 listing 引用 |
|
||||
| 预览过期或规则在保存前变化 | 重算并展示新旧价格/费用差异 | 未经重新接受不替换 |
|
||||
| 取消弹窗/离开未保存页面 | 放弃相应临时修改 | 不变 |
|
||||
|
||||
保存不能因为某个尚未定价的 SKU 阻止保存整个商品草稿;该 SKU 可保存为“待定价”,发布时阻断。对于携带了报价确认意图的 SKU,只要存在过期/版本冲突,就返回具体 SKU 错误,整个保存事务回滚,不悄悄丢弃确认意图。界面可提供“仅保存草稿,稍后定价”的明确后续操作。
|
||||
|
||||
已确认报价不因时间经过而自动过期;15 分钟有效期只作用于未确认预览。影响报价的商品输入改变后,当前草稿必须重新确认。发布时检查快照与待发布输入一致,已导入 SKU 沿用既有编辑边界。
|
||||
|
||||
## 5. 后端及契约建议
|
||||
|
||||
维护者已批准以下变化:
|
||||
|
||||
1. **预览上下文**:扩展现有 preview 契约以识别 `profile_id + variant_id`,并接收明确的未保存采购价、包装覆盖和本次参数。服务端校验资源归属,并按与草稿保存相同的逻辑合并包装。未保存的新 SKU 需支持当前商品内的客户端草稿标识,保存时原子映射到真实 SKU ID;或首版在首次保存后才开放报价确认。
|
||||
2. **两种定价意图**:支持目标加价率求售价,以及固定售价反算利润。输出统一的售价、划线价、利润、费用和规则快照。金额使用十进制定点计算,HTTP 仍传字符串。
|
||||
3. **预览标识**:预览绑定用户/工作空间、平台/店铺、SKU、规范化输入摘要、规则/方案/汇率版本及有效期。可保存在有过期清理的 PostgreSQL 预览表;不得只凭客户端提供的金额确认,不以 Redis 作为确认事实来源。
|
||||
4. **统一保存**:在 `editor-draft` 中增加每个 SKU 的可选 `pricing_selection`(preview id、候选 id、输入摘要),并为包含报价确认的保存提供幂等键。草稿、SKU、报价/候选快照、listing 售价与报价引用在同一事务提交,沿用 product/profile revision 乐观锁。
|
||||
5. **复用确认逻辑**:将当前规划的 `pricing-confirms` 行为放入可被草稿保存事务调用的领域服务。是否保留独立 HTTP 入口需要统一决策;不能采用前端“先保存草稿,再逐个确认报价”来伪装原子保存。
|
||||
6. **严格的同归属约束**:补齐报价与 product/profile/variant/listing 的复合外键和工作空间 RLS;现有基础 DDL 中 `pricing_quotes.product_variant_id` 仅是逻辑关联,不能直接当作完整领域约束。
|
||||
7. **确认校验**:检查预览输入和待保存草稿一致、版本可用、候选有效、币种及舍入后结果一致;不在确认请求中重新抓取外部汇率。未变化的已确认 SKU 不重复创建报价。
|
||||
8. **币种与单位**:售价和划线价以目标店铺支持的币种持久化,CNY 成本为统一输入,RUB 阶梯按本次汇率判定;明确现有 RUB-only 报价列与 CNY 店铺售价的映射。界面可切换展示单位,内部统一 g/mm 或规范化 kg/cm,禁止混算。
|
||||
|
||||
## 6. 计算引擎
|
||||
|
||||
复用旧项目的拆分方式与展示经验,重写纯函数计算器。服务端引擎是业务事实来源,前端只负责预览展示。所有入口调用同一引擎。
|
||||
|
||||
目标加价率基于采购成本:目标利润 = 采购成本 × 加价率。固定成本包含采购、最终跨境物流、国内运费/代贴单及固定附加费。各百分比费用基于最终销售价;划线价 = 销售价 ÷ (1 − 划线折扣率)。
|
||||
|
||||
引擎枚举“运输方式 × CEL 产品类型 × 佣金档”,计算售价后回验货值范围、重量、尺寸、计抛、佣金边界和最终舍入价格。固定售价模式直接以输入售价确定区间并计算利润。无自洽候选时返回原因,不任意选档或无限迭代。
|
||||
|
||||
CEL 表尚需真实账单复核;佣金映射与规则也需审核版本。开发可先使用明确标记的测试规则与方案固定汇率推进全链路;正式环境缺少有效规则时禁止伪造价格。CBR 更新、规则发布和过期提示按现有文档落地。
|
||||
|
||||
## 7. 开发顺序与验收
|
||||
|
||||
1. **确认交互及契约**:先确认自动失焦回填、手动价保护、随草稿保存这三个选择;再更新需求/API/contracts。影响 Web、API、Worker 汇率任务、database 和 contracts,不复用旧 SQLite 或无鉴权 API。
|
||||
2. **引擎和规则**:实现 Decimal 计算、CEL/佣金版本读取、单位转换、候选枚举和固定价反算;测试价格跨档、重量/体积重边界、取整后跨档、总费率 ≥100%、负利润及不可报价。
|
||||
3. **首个垂直交付**:一个已保存 SKU、一个门店方案、采购价失焦预览、弹窗改参数、统一保存、刷新重读及发布预检引用报价。此阶段就验证租户隔离、旧响应丢弃、幂等重放、并发版本冲突、确认失败整笔回滚、方案变更不改历史报价。
|
||||
4. **扩展编辑效率**:多 SKU 批量试算、缺项定位、包装继承、新 SKU 保存映射、手动售价保护、预览过期后的差异接受。
|
||||
5. **完整工具页**:复用同一组件和引擎补独立试算、方案 CRUD、默认方案、汇率同步及真实账单复核。试算记录列表后置。
|
||||
|
||||
首轮验收:修改采购价后能在 SKU 行看到新售价与利润;弹窗取消不改草稿;应用只改对应 SKU;成本变化不覆盖手动价;保存后重读能解释该价格所用的包装、汇率、佣金和物流规则;失败 SKU 不阻塞其他 SKU 的试算;缺少报价的草稿可保存但不可发布。
|
||||
+21
-4
@@ -1,12 +1,12 @@
|
||||
# Atlas Seller Kit 交接说明
|
||||
|
||||
> 更新:2026-09-27。此文是新 AI 对话、架构讨论与开发工作的首读入口。开始开发前,必须先阅读并遵守 [`development-collaboration-contract.md`](./development-collaboration-contract.md) 和仓库 `AGENTS.md`。下方按日期记录的旧方案仅供追溯;当前状态以本节及最新的 1.47 节为准。
|
||||
> 更新:2026-09-28。此文是新 AI 对话、架构讨论与开发工作的首读入口。开始开发前,必须先阅读并遵守 [`development-collaboration-contract.md`](./development-collaboration-contract.md) 和仓库 `AGENTS.md`。下方按日期记录的旧方案仅供追溯;当前状态以本节及最新的 1.47 节为准。
|
||||
|
||||
## 1. 项目定位
|
||||
|
||||
这是一个全新的云端产品,不是 `ozon-seller-kit` 的本地版重构。目标用户可拥有多个门店,每个门店只能绑定一个平台;平台自身可支持多个市场、国家、语言和币种。因此语言、币种、类目和发布规则不能写死为 Ozon 或中文/俄文。
|
||||
|
||||
pnpm monorepo 包含 Web、NestJS API、BullMQ Worker、WXT 浏览器扩展、共享契约、数据库包和本地基础设施。认证、工作空间隔离、门店/素材、采集箱、商品编辑草稿、平台级商品参考目录、发布预检和商品 AI 填报已有实现;真正的 Ozon 首次发布、在线商品同步和定价确认仍未形成完整闭环。不要从旧项目复制 SQLite 模型、无鉴权 API、旧 `/trial/:id` 或商品编辑页组件;复用纯规则前须适配多租户和云端边界。
|
||||
pnpm monorepo 包含 Web、NestJS API、BullMQ Worker、WXT 浏览器扩展、共享契约、数据库包和本地基础设施。认证、工作空间隔离、门店/素材、采集箱、商品编辑草稿、平台级商品参考目录、发布预检和商品 AI 填报已有实现;SKU 定价试算与确认、在线商品同步已有实现,真正的 Ozon 首次发布仍待接入。不要从旧项目复制 SQLite 模型、无鉴权 API、旧 `/trial/:id` 或商品编辑页组件;复用纯规则前须适配多租户和云端边界。
|
||||
|
||||
当前运行时基线:Node 24.20.0、pnpm 11.25.0、PostgreSQL 18.6、Redis 7.4.11、MinIO `RELEASE.2025-09-07T16-13-09Z`;Web 默认端口为 `8100`,API 默认端口为 `8200`,扩展 dev server 默认端口为 `8110`(仅本地开发用),Worker 无 HTTP 端口。完整目录、依赖边界与版本表见 [`architecture/project-structure.md`](../architecture/project-structure.md) 和 [`architecture/technology-and-deployment.md`](../architecture/technology-and-deployment.md)。
|
||||
|
||||
@@ -14,12 +14,28 @@ pnpm monorepo 包含 Web、NestJS API、BullMQ Worker、WXT 浏览器扩展、
|
||||
|
||||
已完成“采集 → 采集箱 → 商品编辑草稿”的主要数据闭环、平台级类目/属性/字典目录与发布预检。编辑页已接入异步「AI 填报」:基于来源资料与 Atlas 类目树生成类目建议、型号/SKU 货号、证据充分的包装建议及 SPU 中俄文案;单 SKU 文案由用户单独生成并确认应用。AI 结果只回填未保存表单,保存草稿仍是持久化边界。Ozon 动态类目属性可按已选类目加载并手工填写,**当前不由 AI 自动填报**。SKU 图片选择现支持采集候选图、工作空间上传图和网络导入图;在弹窗确认后回填草稿,页面保存后持久化。
|
||||
|
||||
尚未完成可直接发布的闭环:定价确认、真正的 Ozon 发布 job/import 及结果轮询、在线商品同步仍待开发。AI 类目推荐和文案已能运行,但不能等同于跨来源样本验收;尤其应继续用 1688/淘宝/天猫样本核验 Atlas 类目候选质量,以及俄文文案、包装证据和 SKU 继承行为。AI 套图生图仍是占位入口;图片「编辑」菜单中的一键 3:4、添加水印和单张 AI 编辑已接入异步素材任务。
|
||||
SKU 定价已打通试算、表单应用与原子保存确认,在线商品同步已有 API/Worker。可直接发布的闭环仍缺真正的 Ozon 发布 job/import 及结果轮询;正式定价还需完成 CEL 费率及类目佣金映射审核。AI 类目推荐和文案已能运行,但不能等同于跨来源样本验收;尤其应继续用 1688/淘宝/天猫样本核验 Atlas 类目候选质量,以及俄文文案、包装证据和 SKU 继承行为。AI 套图生图仍是占位入口;图片「编辑」菜单中的一键 3:4、添加水印和单张 AI 编辑已接入异步素材任务。
|
||||
|
||||
当前优先事项:用真实来源样本回归 AI 类目推荐、文案与图片选择,补做真实图片上传/网络导入联调;完成定价确认;让首次发布 job 复用既有发布预检,再实现 Ozon import 与结果轮询。平台参考目录目前由平台级参考源和同步任务维护,不能把 9 月 10 日的门店级缓存描述当作现行架构。
|
||||
当前优先事项:用真实来源样本回归 AI 类目推荐、文案与图片选择,补做真实图片上传/网络导入联调;审核正式物流费率与佣金映射,并用实际结算账单核对 Ozon 平台服务费、收款费和其他扣费;让首次发布 job 复用既有发布预检,再实现 Ozon import 与结果轮询。平台参考目录目前由平台级参考源和同步任务维护,不能把 9 月 10 日的门店级缓存描述当作现行架构。
|
||||
|
||||
接手前请先阅读 `git status --short`,确认当前工作区状态;本轮图片选择的 Web、API、contracts、迁移、测试和文档随本报告一起提交。不要用 `git reset --hard` 或批量清理未跟踪文件。共享 contracts 类型需先构建,再执行 Web/API/Worker 检查。开发验证需要时可按 `AGENTS.md` 先确认进程与端口,再启动或重启 API/Worker;下方旧章节中的“不可重启”说法已失效。
|
||||
|
||||
### 2026-09-28:SKU 定价试算与确认
|
||||
|
||||
- SKU 采购价失焦/回车自动计算、手动算价、批量计算和保留手动售价;明细弹窗比较运输方案、调整参数并应用当前 SKU。
|
||||
- 独立试算、门店方案管理和参数另存已实现。Decimal 计算器由服务端统一提供,保存商品时原子确认不可变报价;变更输入使报价失效,首次发布要求有效报价。
|
||||
- Worker 增加 CBR 汇率同步与过期预览清理。新增迁移 `043`~`048`;CEL 初始化数据保持 draft,不能直接用于正式算价。
|
||||
- 系统管理补充 CEL 费率审核页与管理员 API;迁移 `049` 保存不可修改的审核证据,`050`/`051` 锁定已审核版本及规则归属。六类真实账单样本须按正式物流计费规则复算后才可启用,当前 CEL 种子依然是 draft。
|
||||
- `pnpm dev` 会先构建共享依赖;Web 开发读取 contracts 源码,解决新增导出未构建导致启动失败的问题。
|
||||
- 本轮影响 Web、API、Worker、contracts、database。验收记录及后续工作见[SKU 定价验证记录](../reviews/sku-pricing-verification.md),HTTP 以[定价 API](../api/pricing.md)为准。
|
||||
|
||||
### 2026-09-28:Ozon 定价附加费率调查
|
||||
|
||||
- 已将 Ozon Global 官方公开规则、适用条件、来源链接及 Atlas 字段对应关系写入[Ozon 尾程、收款与其他费率调查](../requirements/ozon-fulfillment-and-payment-fees-research.md)。本次仅新增调查文档和交接记录,没有修改定价实现或方案默认值。
|
||||
- Ozon 合作配送的物流平台接入服务费为每票货值的 2%,每票最低 15 ₽、最高 200 ₽;实际配送费另按承运商规则计算。“尾程费率”不能直接理解为全部尾程配送费。
|
||||
- 中国卖家的钱包公开费率为 PandaPay 1.10%,LianLian Pay、Payful (GEP)、PingPong 1.20%;“其他费率”没有统一数值。另有适用于 2026-09-01 起订单的国际运输合同服务费,每票货值 0.33%。
|
||||
- 当前引擎将 `last_mile_rate`、`withdrawal_rate`、`other_rate` 均按单个 SKU 销售价计入成本。下一步须用 Ozon 结算明细及 CEL 合同/账单确认收费主体、基数、是否重复计入;若要用于正式报价,还需处理每票计费、2% 上下限和合包分摊,不能直接把本调查的比例当作已审核规则。
|
||||
|
||||
### 1.1 开发进度(2026-09-02)
|
||||
|
||||
今日完成三项工作,全部通过 typecheck、生产构建,并经浏览器或启动实测:
|
||||
@@ -608,6 +624,7 @@ User → Workspace → Membership / Role
|
||||
- [商品采集图片与存储策略](../architecture/media-storage-strategy.md)
|
||||
- [实现前待决与待落地事项](../reviews/implementation-readiness-open-items.md)
|
||||
- [浏览器采集插件需求与技术方案](../requirements/collector-browser-extension.md)
|
||||
- [Ozon 尾程、收款与其他费率调查](../requirements/ozon-fulfillment-and-payment-fees-research.md)
|
||||
- [M2.2 / M2.3 扩展采集交接](./m2.2-m2.3-extension-collection.md)
|
||||
- [M2.4 来源解析与测试页交接](./m2.4-source-parsers-and-inspection.md)
|
||||
- [技术栈、异步任务与部署方案](../architecture/technology-and-deployment.md)
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
# Ozon 尾程、收款与其他费率调查
|
||||
|
||||
> 调查日期:2026-09-28。官方页面查询区域选择“中国”。本文件记录 Ozon Global 官方公开规则及其与 Atlas 定价字段的对应关系,不代替店铺合同、CEL 合同和实际结算账单。
|
||||
|
||||
## 1. 结论
|
||||
|
||||
| 费用 | Ozon 官方公开规则 | 对 Atlas 当前定价字段的含义 |
|
||||
|---|---|---|
|
||||
| “尾程费” | 没有统一的“尾程配送费率”。Ozon 合作配送由物流平台接入服务费和承运商配送费等组成;前者为每票货值的 **2%**,每票最低 **15 ₽**、最高 **200 ₽**;后者由承运商按实重或体积重等规则计算。 | `last_mile_rate = 2.00%` 只能近似表示物流平台接入服务费,不能代表全部尾程运费,也无法体现每票最低、最高收费。 |
|
||||
| 提现/收款 | 中国卖家可用的钱包费率:**PandaPay 1.10%**;**LianLian Pay、Payful (GEP)、PingPong 1.20%**。 | `withdrawal_rate` 应按店铺实际使用的收款服务商设置;这些是收款服务商费率,不是 Ozon 对所有卖家统一收取的“提现费”。 |
|
||||
| 其他 | 没有统一的“其他费率”。可明确识别的一项为“签订国际运输组织合同服务费”:**每票货值的 0.33%**,适用于 **2026-09-01 起创建的订单**。买家付款的收单费由处理付款的银行定价;其他付费服务各有规则。 | `other_rate = 0.33%` 仅可作为上述服务适用且未在别处计入时的近似值,不能视为全部其他费用。 |
|
||||
|
||||
来源:[Ozon 合作配送费用](https://global-help.ozon.com/commissions/ozon-fees/partner-delivery-rates)、[Ozon 收款及结算方式](https://global-help.ozon.com/accounting/receiving-payments/settlements)、[Ozon 其他服务费用](https://global-help.ozon.com/commissions/ozon-fees/commissions-for-other-services)。
|
||||
|
||||
## 2. “尾程费”具体指什么
|
||||
|
||||
物流语境中的“尾程”通常指包裹进入目的地配送网络后,送至买家、提货点或自提柜的最后一段。它描述配送阶段,并不天然对应一个固定百分比。
|
||||
|
||||
Ozon 官方的合作配送费用说明将费用拆成至少两类:
|
||||
|
||||
1. **接入 Ozon 物流平台的电子服务费**:每票货值 × 2%,但每票最低 15 ₽、最高 200 ₽。
|
||||
2. **商品送达买家的承运商配送费**:按承运商公式计算,以实重或体积重为基础。退货、部分特定渠道还有单独费用。不能将其概括为 2%。
|
||||
|
||||
因此,当前需求文档使用的“尾程(Ozon 代理费)”名称容易把**平台服务费**与**实际配送费**混在一起。CEL 的 Ozon-rFBS 资费表又另按“每票固定费 + 计费重量单价”给出跨境物流报价。尚无证据证明 CEL 报价包含或不包含 Ozon 上述单独扣费;设置方案前应对照 CEL 合同与 Ozon 结算明细,避免漏计或重复计费。[Ozon 合作配送费用](https://global-help.ozon.com/commissions/ozon-fees/partner-delivery-rates)、[CEL 资费规则复核](./cel-ozon-rfbs-rates-review.md)
|
||||
|
||||
## 3. 收款费率的适用范围
|
||||
|
||||
Ozon 官方“中国”区域结算说明列出的电子钱包费率如下:
|
||||
|
||||
| 收款服务商 | 官方列示费率 | 可用结算币种 |
|
||||
|---|---:|---|
|
||||
| PandaPay | 1.10% | CNY |
|
||||
| LianLian Pay | 1.20% | CNY |
|
||||
| Payful (GEP) | 1.20% | CNY、CNH |
|
||||
| PingPong | 1.20% | CNY |
|
||||
|
||||
这些服务商的公开费率应与实际使用的钱包、合同和到账流水核对。官方页面称其为服务商“佣金”,未在所查说明中明确它与每个 SKU 销售价之间的计算关系。Atlas 当前按 SKU 销售价乘 `withdrawal_rate`,只能用于报价估算;不能把单件估算金额当作最终提现扣费。[Ozon 收款及结算方式](https://global-help.ozon.com/accounting/receiving-payments/settlements)
|
||||
|
||||
## 4. “其他费率”的边界
|
||||
|
||||
Ozon 官方其他服务费用页说明:
|
||||
|
||||
- “签订国际运输组织合同服务费”按每票货值的 **0.33%**收取,订单创建日期从 **2026-09-01** 起适用,在票件交付或按页面规定的退回状态后扣费。
|
||||
- 买家付款的**收单费**由实际处理支付的合作银行定价,因此没有统一的公开百分比。
|
||||
- 品牌推广、促销、订阅等服务按是否开通及各自规则另行收费;它们不能被一笔固定“其他费率”准确概括。
|
||||
|
||||
`other_rate` 在 Atlas 需求中还预留给货损等估算项目。将官方 0.33% 纳入方案前,需确认店铺订单适用该项、结算中实际产生该扣费,且 CEL 费用或其他成本项未覆盖同一笔费用。[Ozon 其他服务费用](https://global-help.ozon.com/commissions/ozon-fees/commissions-for-other-services)、[Atlas 定价需求](./pricing-tool.md)
|
||||
|
||||
## 5. 与当前定价实现的差异及使用建议
|
||||
|
||||
Atlas 当前将 `last_mile_rate`、`withdrawal_rate`、`other_rate` 相加,分别乘以单个 SKU 的销售价计入成本;字段默认值均为 `0`。需求文档和 API 示例里的比例是方案输入或示例值,并非已审核的 Ozon 官方默认费率。[定价引擎](../../apps/api/src/pricing/pricing-engine.ts)、[定价契约](../../packages/contracts/src/pricing.ts)、[定价需求](./pricing-tool.md)、[定价 API](../api/pricing.md)
|
||||
|
||||
在尚未完成店铺账单核对时,可按以下口径做**临时试算**,并在方案备注中写清来源和假设:
|
||||
|
||||
| 字段 | 临时输入口径 | 需要进一步核实的内容 |
|
||||
|---|---|---|
|
||||
| `last_mile_rate` | 若 Ozon 的物流平台接入服务费需单独承担,可输入 **2.00%**。 | CEL 报价是否已覆盖同一费用;每票最低 15 ₽、最高 200 ₽;合包订单分摊方式。 |
|
||||
| `withdrawal_rate` | PandaPay 输入 **1.10%**;LianLian Pay、Payful (GEP)、PingPong 输入 **1.20%**。 | 实际钱包、合同费率、计费基数及账单扣费。 |
|
||||
| `other_rate` | 确认适用并单独扣收国际运输合同服务费时,可先输入 **0.33%**;其他项目按事实另估。 | 0.33% 是否产生及是否重复计入;收单费和已开通付费服务。 |
|
||||
|
||||
**精度限制**:Ozon 的 2% 与 0.33% 均以“每票货值”为基数,而 Atlas 当前以“单个 SKU 销售价”为基数;合包、拆单、每票上下限或费用减免时,两者会有差异。正式报价应以 Ozon 店铺后台的**财务 → 店铺经济**、**财务 → 单据**中的扣费明细及 CEL 账单复核,并在确认收费口径后调整数据模型或计算规则。[Ozon 收款及结算方式](https://global-help.ozon.com/accounting/receiving-payments/settlements)
|
||||
|
||||
## 6. 官方来源
|
||||
|
||||
1. [Ozon Global:合作配送费用](https://global-help.ozon.com/commissions/ozon-fees/partner-delivery-rates),查询区域:中国;含物流平台 2% 及每票上下限、承运商配送费规则。
|
||||
2. [Ozon Global:收款及结算方式](https://global-help.ozon.com/accounting/receiving-payments/settlements),查询区域:中国;含四家电子钱包费率及财务明细入口。
|
||||
3. [Ozon Global:其他服务费用](https://global-help.ozon.com/commissions/ozon-fees/commissions-for-other-services),查询区域:中国;含 0.33% 服务费生效时间、收单费及可选付费服务。
|
||||
@@ -41,6 +41,8 @@
|
||||
|
||||
`product_variant_platform_listings` 同时引用平台发布配置(profile)和通用 SKU(variant),但必须强制:`variant.product_id = profile.product_id`。同一条归属链也适用于属性、报价、发布任务项和在线镜像映射。
|
||||
|
||||
2026-09-28:迁移 `043`~`045` 已补齐报价的同商品、同 profile、同门店/平台归属,以及已选候选属于当前报价的复合外键;确认快照不可更新。跨 SKU 和跨租户测试已通过。
|
||||
|
||||
实施时优先选用可维护的复合外键/冗余 `product_id` 加一致性约束;如 PostgreSQL 外键表达能力不足,再使用约束触发器。不得只依赖服务层校验。
|
||||
|
||||
### 2. 类目切换后未映射属性的持久化
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
# SKU 定价实现与验证记录
|
||||
|
||||
日期:2026-09-28。方案:[SKU 定价工作流](../product/sku-pricing-workflow-proposal.md)。接口:[定价 API](../api/pricing.md)。
|
||||
|
||||
## 实现范围
|
||||
|
||||
- SKU 采购价失焦/回车自动计算,可关闭自动计算;支持按钮计算和批量计算,手动售价保留。
|
||||
- 明细弹窗支持费用、汇率、包装、运输方式比较;取消不改草稿,应用仅回填当前 SKU,保存商品才确认报价。
|
||||
- 独立试算与 SKU 共用服务端 Decimal 计算器;门店方案支持创建、编辑、默认、归档和参数另存,另存后列表立即更新。
|
||||
- 商品保存事务核对预览归属、期限、输入和规则;确认任一 SKU 失败则全部回滚。新 SKU 使用 client_key 映射真实 ID。
|
||||
- 报价与候选不可变,输入改变使有效标记失效;未定价允许保存草稿,首次发布预检要求有效报价。
|
||||
- Worker 增加 CBR 官方参考汇率同步、并发锁及过期预览清理。
|
||||
|
||||
跨领域影响:Web 交互、API 计算/确认、Worker 同步、contracts DTO、database 约束与初始化,以及开发构建依赖。现有 `.env.example` 用户改动未修改。
|
||||
|
||||
## 自动验证
|
||||
|
||||
| 验证 | 结果 |
|
||||
| --- | --- |
|
||||
| 定价引擎单元测试 | 9 项通过:十进制、币种舍入、体积重、边界、规则适用性、负利润及输入规范化 |
|
||||
| 定价 HTTP/PostgreSQL 集成测试 | 11 项通过:幂等、租户与 SKU 归属、原子确认、并发、失效、不可变快照、方案管理 |
|
||||
| 既有商品编辑集成测试 | 12 项通过 |
|
||||
| 系统物流费率审核 HTTP/PostgreSQL 集成测试 | 1 项通过:管理员鉴权、六类账单复算、重复账单拒绝、并发幂等、已审核版本和规则归属不可改写;与上述测试合计 33 项 |
|
||||
| CBR 解析测试 | 2 项通过:面额换算与非法响应 |
|
||||
| contracts、API、API 测试、Web、Worker 类型检查 | 通过 |
|
||||
| contracts、database、API、Worker 构建及 Web 生产构建 | 通过;Web 仍有较大 bundle 提示 |
|
||||
| Turbo dev dry-run | API/Worker 先构建 contracts/database,Web/扩展先构建 contracts |
|
||||
| git diff --check | 通过 |
|
||||
|
||||
CBR 官方公开接口实际请求成功,解析得到生效日期 `2026-09-26`、CNY/RUB `12.5355000000`。同步函数检测到数据库两小时内已有官方记录而跳过重复抓取;本轮未强制改写既有汇率。
|
||||
|
||||
## 浏览器联调
|
||||
|
||||
使用隔离工作空间、临时平台、门店及标明测试用途的费率,真实调用本地 API:
|
||||
|
||||
- 采购价变化自动回填售价与利润;手动售价 90 在采购价变化后保持。
|
||||
- 明细取消保留原草稿,应用后保存并重读显示“已确认”。
|
||||
- 批量计算保留手动售价,缺采购价的 SKU 单独提示,允许其保存未定价草稿。
|
||||
- 独立试算样例采购 28 CNY,包装 380 g / 220×160×90 mm,返回售价 77.44 CNY、利润 14 CNY。
|
||||
- 参数另存弹窗成功保存,方案管理列表立即显示新方案。
|
||||
- Ozon 正式上下文没有审核费率时显示明确空状态。
|
||||
- 2026-09-28 补充:目录返回待复核费率卡;试算与方案页明确显示草稿版本及新建方案的阻断原因,并向系统管理员提供审核页入口。
|
||||
- 2026-09-28 后续补齐:系统管理新增物流费率审核页及管理员 API。账单样本按正式计费函数复算,审核记录和启用状态同事务保存;普通用户 403、差额不符 422、成功/幂等/重复审核由 PostgreSQL 集成测试覆盖。CEL 正式草稿仍保持待审核,未使用测试账单启用。
|
||||
- 管理员真实会话浏览器联调通过:菜单可进入审核页,展示 Ozon CEL 六种产品类型与 16 条规则;审核表单具备六类样本输入与可滚动区域,定价页可直达入口。未提交正式资费审核。
|
||||
|
||||
联调发现并修复 contracts 开发导出缺失、JSONB 键顺序/金额末尾零导致报价误失效、弹窗应用后自动重算覆盖候选和方案列表未即时更新。最终自动回归以修复后的正式测试文件为准;过程中旧临时夹具出现的失败不计入通过结果。
|
||||
|
||||
## 本地操作与清理
|
||||
|
||||
- 迁移器应用原先待执行的 `033`~`042` 及本次 `043`~`051`。新增迁移已应用,不应再编辑其内容。
|
||||
- 维护者 Web `8100`、API `8200` 继续运行;未主动停止这两个服务,开发监听器随文件修改自动重载。
|
||||
- 临时测试应用已退出;测试平台、门店及工作空间数据已清理,查询临时平台剩余数为 0。
|
||||
- 临时测试代码和凭证文件已删除,浏览器恢复 Ozon 平台,任务页面已关闭。
|
||||
- 审核测试创建的未来生效 QA 费率已清理(剩余 0);CEL 正式版本 `cel-rfbs-review-2026-09` 仍为 draft,16 条规则仍为 draft,审核时间为空。
|
||||
|
||||
## 后续业务工作
|
||||
|
||||
1. 按真实账单审核 CEL 费率和适用边界,补齐正式类目佣金映射。本次 CEL 种子保持 draft,测试费率没有进入正式平台。
|
||||
2. 使用真实商品、CNY/RUB 门店回归费用与利润;产品中的净利润是已配置费用下的预估值。
|
||||
3. 将 Ozon 首次发布 job、import 提交和结果轮询接到已有发布预检及报价快照。当前预检 ready 不等于已发布。
|
||||
|
||||
现在可直接运行 `pnpm dev`,无需每次手工先执行完整 `pnpm build`。部署仍须按顺序执行迁移和生产构建。
|
||||
Reference in New Issue
Block a user