订单相关以及佣金相关修复
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m57s
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m57s
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-04-29
|
||||
@@ -0,0 +1,109 @@
|
||||
## Context
|
||||
|
||||
当前订单领域同时承载了三类不同语义:
|
||||
|
||||
1. **谁操作了订单**:应该回答“哪个账号发起了这次下单动作”。
|
||||
2. **订单属于谁/卖给谁**:由 `buyer_type/buyer_id/seller_shop_id/purchase_role` 等业务字段表达。
|
||||
3. **这笔订单的差价佣金该给谁**:应从 `seller_shop_id` 出发,沿完整父级链逐级计算。
|
||||
|
||||
现状问题在于:`operator_id/operator_type` 被混成了“真实账号 + 店铺 ID + 平台空值”三种含义,导致平台订单无法稳定展示操作者;`commission_status` 又被混成“流程状态 + 业务结果”,导致“未触发计算”“已算完但没有佣金”“链路异常待处理”无法区分。历史提案已经明确:支付成功后要自动入队佣金计算,代购订单只跳过一次性佣金,不跳过差价佣金。因此本次设计不是新增分佣能力,而是把已有能力的语义纠偏为可长期维护的模型。
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
- 将订单操作者统一收敛为“真实账号语义”,平台与代理都能准确落库、展示和追溯。
|
||||
- 将订单佣金模型拆成“流程状态 + 业务结果”两层,明确区分无佣金与异常待人工处理。
|
||||
- 将差价佣金统一定义为基于 `seller_shop_id` 的完整上级链分配,不再受 `operator_id/operator_type` 影响。
|
||||
- 修正平台代扣、代理自购、代理为下级代购、代购自动完单等触发链路,保证所有适用订单都能进入佣金计算。
|
||||
- 设计兼容迁移方案,避免直接改变旧字段含义造成历史数据误读。
|
||||
|
||||
**Non-Goals:**
|
||||
- 不重构一次性佣金规则,也不改变“代购订单不触发一次性佣金、不更新累计充值”的既有结论。
|
||||
- 不重写整套钱包模型,不额外引入外键、关联关系或新的异步基础设施。
|
||||
- 不在本次设计中解决所有历史脏数据,只定义兼容读取、渐进回填和后续修复路径。
|
||||
|
||||
## Decisions
|
||||
|
||||
### Decision 1:新增“权威操作者快照”,不直接复用被污染的旧 `operator_*` 字段
|
||||
|
||||
**选择**:订单模型新增一组权威操作者字段(账号 ID、账号类型、名称快照),作为“谁操作了这笔订单”的唯一来源;现有 `operator_id/operator_type` 视为历史兼容字段,在迁移期通过回填/映射兼容读取。
|
||||
|
||||
**原因**:
|
||||
- 直接把旧 `operator_id` 改成账号 ID 会让历史上保存店铺 ID 的订单立即失真。
|
||||
- 单独依赖 `creator` 虽然能覆盖部分后台订单,但缺少名称快照,且无法优雅覆盖未来其他入口。
|
||||
- 新增权威字段可以让写路径和读路径先稳定下来,再逐步清理历史值。
|
||||
|
||||
**备选方案**:
|
||||
- **直接复用 `operator_id/operator_type`**:实现快,但会让历史数据语义混乱,风险最高。
|
||||
- **只依赖 `creator/updater`**:不需要新字段,但响应层仍需额外查询/推导,且无法表达多入口场景下的操作者快照。
|
||||
|
||||
### Decision 2:佣金采用“双轴模型”——流程状态与业务结果分离
|
||||
|
||||
**选择**:保留 `commission_status` 作为流程状态字段,但重新定义为“待计算 / 已完成 / 待人工处理”;新增 `commission_result` 作为业务结果字段,至少表达“有佣金 / 无佣金 / 链路异常”。
|
||||
|
||||
**原因**:
|
||||
- “无佣金”是计算结果,不是待处理状态。
|
||||
- “链路断裂”需要触发人工排障,不应与普通完成混淆。
|
||||
- 双轴模型最适合补偿扫描:补偿只看流程状态,运营展示同时看流程状态和业务结果。
|
||||
|
||||
**备选方案**:
|
||||
- **仅扩展 `commission_status` 单字段**:字段值会迅速膨胀,前后端更容易再次把流程与结果混用。
|
||||
- **仅依赖佣金记录条数推导结果**:0 条记录既可能是无佣金,也可能是任务未执行或链路异常,无法可靠区分。
|
||||
|
||||
### Decision 3:差价佣金只从 `seller_shop_id` 出发,沿完整父级链逐级计算
|
||||
|
||||
**选择**:佣金归属只依赖 `seller_shop_id` 的完整上级链,以及每一层对应的成本价/分配配置;`operator_id/operator_type/purchase_role` 不参与佣金归属判断,只参与审计和展示。
|
||||
|
||||
**原因**:
|
||||
- 这样可以统一覆盖平台代扣、代理自购、代理为下级代购、多级代理链等所有场景。
|
||||
- 这与现有 `commission_calculation` 服务的主体算法一致,只是把现有“例子式理解”提升为正式规则。
|
||||
- 顶级代理“无佣金”只是父链终止后的自然结果,不需要单独特判。
|
||||
|
||||
**备选方案**:
|
||||
- **按操作者类型分支**:会错误跳过平台代扣二级/三级代理的上级差价佣金。
|
||||
- **按买家类型硬编码一级/二级场景**:无法覆盖灵活多级代理链,且后续每加场景都要加特判。
|
||||
|
||||
### Decision 4:所有适用订单都要先进入佣金计算,再由计算结果决定“有/无佣金”
|
||||
|
||||
**选择**:只要订单属于差价佣金适用范围,并在支付成功或自动完单后仍处于“待计算”,就必须入队 `commission:calculate`;worker 负责最终把订单落成“已完成+有佣金”“已完成+无佣金”或“待人工处理+链路异常”。
|
||||
|
||||
**原因**:
|
||||
- 这样可以修复“平台代扣代理钱包但未入队”的现有缺口。
|
||||
- “无佣金”订单也需要被 worker 明确盖章为完成,否则后台永远分不清是漏算还是确实没有佣金。
|
||||
- Asynq 的幂等和补偿扫描可以继续复用,只需调整判定口径。
|
||||
|
||||
**备选方案**:
|
||||
- **创建订单时先同步判断是否有佣金再决定是否入队**:会把链路计算逻辑分散到订单服务,增加重复实现和口径漂移风险。
|
||||
|
||||
### Decision 5:链路断裂统一落为待人工处理,并记录断点信息
|
||||
|
||||
**选择**:当父级链缺失、分配配置不存在、成本价无法继续求解等情况发生时,订单流程状态落为“待人工处理”,业务结果落为“链路异常”;同时保留待审佣金记录和断点备注,作为人工补偿入口。
|
||||
|
||||
**原因**:
|
||||
- 这与现有 `CommissionStatusPendingReview` 及待审记录机制兼容。
|
||||
- 运营和财务可以明确知道这不是“无佣金”,而是“规则/数据有问题”。
|
||||
|
||||
**备选方案**:
|
||||
- **仍然写成已完成但 0 佣金**:会掩盖真实异常,导致漏补偿。
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- **[历史订单语义混杂]** → 先新增权威字段、兼容读取,再做分批回填;旧 `operator_*` 不立即删除。
|
||||
- **[前端依赖旧 `operator_name` 为店铺名]** → 在提案中明确 BREAKING,订单响应补充账号名与业务店铺名分离的展示规则。
|
||||
- **[补偿扫描口径变化导致重复入队]** → 继续以流程状态做幂等守卫,worker 内部使用条件更新和重复执行跳过。
|
||||
- **[多级链路查询带来性能波动]** → 沿用现有逐级查询逻辑,但只在支付后异步执行;列表查询不做链路展开,避免影响接口时延。
|
||||
- **[历史挂起订单无法自动判断应为无佣金还是异常]** → 回填脚本只处理可确定样本,其余保持待人工处理,由运营复核。
|
||||
|
||||
## Migration Plan
|
||||
|
||||
1. **Schema 先行**:为订单新增权威操作者字段与佣金结果字段,保留旧字段不删。
|
||||
2. **读路径兼容**:订单详情/列表优先读新字段;若新字段为空,则按 `creator + 旧 operator_*` 做兼容回显,并在响应中明确异常/历史场景。
|
||||
3. **写路径切换**:后台订单创建、支付回调、自动完单全部改为写入新操作者字段,并统一使用新佣金状态/结果模型。
|
||||
4. **触发链修复**:所有适用支付成功路径统一调用佣金入队逻辑;worker 在完成时写回流程状态和业务结果。
|
||||
5. **历史数据回填**:优先回填可由 `creator` 明确推断的后台订单;旧 `operator_id` 为店铺 ID 的记录不强改旧列,只填新列。
|
||||
6. **补偿与验证**:扫描“已支付且待计算”的订单重新入队;手工验证顶级代理无佣金、多级链路有佣金、链路断裂待人工处理三类样本。
|
||||
7. **回滚策略**:如新字段展示异常,可回退到兼容读逻辑并暂停新状态写入;旧字段仍保留,数据可读不丢失。
|
||||
|
||||
## Open Questions
|
||||
|
||||
- 本次不保留开放性业务问题,默认采用“状态看流程、结果看业务”的双轴模型推进;如后续需要增加“计算中”状态,可在实现阶段作为非 breaking 扩展处理。
|
||||
@@ -0,0 +1,38 @@
|
||||
## Why
|
||||
|
||||
功能 ID:feature-001-order-operator-and-chain-commission-semantics。
|
||||
|
||||
当前订单链路把 `operator_id` 混用了“真实操作者账号”和“代理店铺 ID”两种语义,导致平台下单场景无法准确追溯是谁操作,订单详情中的 `operator_name` 也会在平台场景下长期为空。与此同时,`commission_status` 又被混用了“佣金计算流程状态”和“佣金业务结果”,使“未触发计算”“已计算但无佣金”“链路断裂待人工处理”三种完全不同的业务含义被压扁成同一个字段,已经影响后台判断、补偿逻辑和后续规则演进。
|
||||
|
||||
现在必须把这两个核心概念拆开:订单必须准确记录真实操作者;差价佣金必须按 `seller_shop_id` 的完整上级链逐级计算,“无佣金”必须成为明确结果而不是继续停留在“待计算”。
|
||||
|
||||
## What Changes
|
||||
|
||||
- 修正订单操作者语义:订单中的操作者字段统一表示“真实发起操作的账号”,不再复用为代理店铺 ID;平台、代理、后续其他账号类型都必须能被准确记录和展示。
|
||||
- 修正订单响应与查询语义:订单详情/列表中的操作者名称按账号语义回填,店铺归属与买家归属继续由独立业务字段表达,避免“账号名”和“店铺名”混淆。
|
||||
- 修正差价佣金计算规则:所有会参与差价佣金的订单都从 `seller_shop_id` 出发,沿完整父级链逐级计算,不再使用“一级/二级代理特判”口径,也不再从 `operator_id/operator_type` 推导佣金归属。
|
||||
- 修正平台代扣、代理自购、代理为下级代购等场景的触发规则:只要订单属于差价佣金适用范围,系统都必须在支付成功或自动完单后触发佣金计算;是否有佣金由链路和价差决定,而不是由操作者类型决定。
|
||||
- 拆分佣金流程状态与业务结果:**BREAKING** 订单佣金状态语义将从“二元待计算/已计算”调整为“流程状态 + 结果语义”模型,至少区分“待计算/已完成/待人工处理”以及“有佣金/无佣金/链路异常”。
|
||||
- 修正链路异常处理:当上级链断裂、配置缺失或无法继续向上分配时,系统必须明确落为“待人工处理/链路异常”,而不是和普通“已完成”混淆。
|
||||
- 补充兼容迁移与手工验证方案:历史订单中 `operator_id` 为 NULL 或保存店铺 ID 的数据需要有兼容解释和回填方案;核心验证覆盖多级代理链、无佣金订单、链路断裂订单和补偿入队场景。
|
||||
- 增补文档与 OpenAPI:更新订单管理、代购订单、佣金计算、佣金触发等规范,确保前后端、运营和补偿流程都以新语义为准。
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
- 无
|
||||
|
||||
### Modified Capabilities
|
||||
- `agent-order-role-tracking`: 将订单操作者从“店铺视角”修正为“真实账号视角”,并调整订单响应中的操作者展示规则。
|
||||
- `order-management`: 调整订单查询与响应中的操作者信息和佣金状态语义,明确订单字段各自承担的业务角色。
|
||||
- `purchase-on-behalf`: 修正平台代扣、代理自购、代理为下级代购等场景的差价佣金规则,统一为基于 `seller_shop_id` 的链路型计算。
|
||||
- `commission-calculation`: 将差价佣金定义为沿完整上级链逐级分配,并明确“无佣金”“链路异常待人工处理”等业务结果。
|
||||
- `commission-trigger`: 修正佣金任务触发条件与补偿口径,确保所有适用订单都能进入计算流程,且“无佣金”不再被误判为“待计算”。
|
||||
|
||||
## Impact
|
||||
|
||||
- 受影响代码:`internal/model/order.go`、`internal/model/dto/order_dto.go`、`internal/service/order/service.go`、`internal/service/commission_calculation/service.go`、`internal/store/postgres/order_store.go`、`internal/task/commission_calculation.go`、`pkg/constants/*`。
|
||||
- 受影响接口:后台订单创建、订单列表、订单详情,以及所有依赖订单佣金状态和操作者展示的管理端页面。
|
||||
- 受影响系统:Asynq 佣金任务触发/补偿链路、钱包入账链路、运营人工排障流程、OpenAPI 文档与前端展示逻辑。
|
||||
- 数据影响:可能需要为历史订单增加兼容解释或回填脚本;需要手工验证多级代理链路、0 佣金订单、链路断裂订单和平台代扣订单的状态落点。
|
||||
- 性能与测试:链路计算必须继续满足逐级查询可控、列表查询分页、核心场景手工回放可验证;本变更会补充针对多级链路和补偿场景的验证清单。
|
||||
@@ -0,0 +1,93 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: 订单操作者记录
|
||||
|
||||
系统 SHALL 在订单创建时记录操作者信息(谁下的单),并将其定义为**真实发起操作的账号**,区别于买家信息(资源所属者)和店铺归属信息。
|
||||
|
||||
#### Scenario: 平台创建订单
|
||||
- **WHEN** 平台账号创建订单
|
||||
- **THEN** 订单 MUST 记录平台账号作为操作者
|
||||
- **AND** 操作者信息不得再以 `NULL` 表示“平台”
|
||||
|
||||
#### Scenario: 代理创建订单
|
||||
- **WHEN** 代理账号创建订单
|
||||
- **THEN** 订单 MUST 记录代理账号作为操作者
|
||||
- **AND** 操作者信息不得再复用为代理店铺 ID
|
||||
|
||||
#### Scenario: 代理自购
|
||||
- **WHEN** 代理为自己的资源创建订单
|
||||
- **THEN** 订单的买家店铺可以等于操作者所属店铺
|
||||
- **BUT** 操作者字段仍表示账号身份而非店铺身份
|
||||
|
||||
#### Scenario: 代理代购
|
||||
- **WHEN** 代理为下级代理的资源创建订单
|
||||
- **THEN** 订单 MUST 同时区分买家店铺和操作者账号
|
||||
- **AND** 两者是否属于同一店铺不影响操作者语义
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 订单查询增强
|
||||
|
||||
系统 SHALL 支持代理查询其权限范围内的订单,但数据权限和筛选逻辑 MUST 以买家/卖家相关业务字段为准,不得再依赖已承载账号语义的操作者字段推断店铺权限。
|
||||
|
||||
#### Scenario: 代理查询自己相关的订单
|
||||
- **WHEN** 代理查询订单列表
|
||||
- **THEN** 系统返回买家店铺或卖家店铺在其权限范围内的订单
|
||||
- **AND** 不要求操作者字段继续承载店铺 ID 语义
|
||||
|
||||
#### Scenario: 按订单角色筛选
|
||||
- **WHEN** 代理查询订单列表,指定 `purchase_role = "self_purchase"`
|
||||
- **THEN** 系统只返回自己购买的订单
|
||||
|
||||
#### Scenario: 按订单角色筛选给下级购买的订单
|
||||
- **WHEN** 代理查询订单列表,指定 `purchase_role = "purchase_for_subordinate"`
|
||||
- **THEN** 系统只返回为下级代理购买的订单
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 订单响应包含角色信息
|
||||
|
||||
系统 SHALL 在订单响应中包含操作者和角色信息,前端展示时 MUST 将“账号操作者”与“业务店铺”分离展示。
|
||||
|
||||
#### Scenario: 订单响应包含操作者 ID
|
||||
- **WHEN** 查询订单详情
|
||||
- **THEN** 响应包含操作者账号 ID 和操作者类型字段
|
||||
|
||||
#### Scenario: 平台订单返回操作者名称
|
||||
- **WHEN** 查询平台账号创建的订单详情
|
||||
- **THEN** 响应 MUST 返回平台账号名称作为 `operator_name`
|
||||
- **AND** `operator_name` 不得因为操作者类型为平台而为空
|
||||
|
||||
#### Scenario: 代理订单返回操作者名称
|
||||
- **WHEN** 查询代理账号创建的订单详情
|
||||
- **THEN** 响应 MUST 返回代理账号名称作为 `operator_name`
|
||||
- **AND** 不得再以店铺名替代账号名
|
||||
|
||||
#### Scenario: 订单响应包含角色标识
|
||||
- **WHEN** 查询订单详情
|
||||
- **THEN** 响应包含 `purchase_role`、`is_purchased_by_parent`、`purchase_remark` 字段
|
||||
|
||||
#### Scenario: 上级代购订单的备注
|
||||
- **WHEN** 查询上级代理购买的订单
|
||||
- **THEN** `purchase_remark` 可以引用操作者账号名或业务文案
|
||||
- **AND** 不得依赖历史店铺型 `operator_id` 才能生成备注
|
||||
|
||||
#### Scenario: 平台代购订单的备注
|
||||
- **WHEN** 查询平台代购订单
|
||||
- **THEN** `purchase_remark` 为"由平台代购"或等价平台文案
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 向后兼容性
|
||||
|
||||
系统 SHALL 确保新增操作者语义不会导致历史订单无法查询,并对历史混合数据提供兼容解释。
|
||||
|
||||
#### Scenario: 现有订单字段为 NULL
|
||||
- **WHEN** 查询历史订单
|
||||
- **THEN** 若新操作者字段为空,系统 MAY 回退使用 `creator` 和旧 `operator_*` 进行兼容展示
|
||||
- **AND** 不影响订单查询结果
|
||||
|
||||
#### Scenario: 历史订单 operator_id 为店铺 ID
|
||||
- **WHEN** 查询历史订单且旧 `operator_id` 保存的是店铺 ID
|
||||
- **THEN** 系统 MUST 将该值视为历史兼容数据
|
||||
- **AND** 不得继续把该旧值作为新订单操作者语义写回
|
||||
@@ -0,0 +1,71 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: 成本价差收入计算
|
||||
|
||||
系统 SHALL 为代理链上的每一级代理计算成本价差收入。差价佣金 MUST 从订单的 `seller_shop_id` 出发,沿完整父级链逐级计算;佣金归属 MUST NOT 由 `operator_id`、`operator_type` 或“一级/二级代理”特判决定。
|
||||
|
||||
#### Scenario: 单级代理
|
||||
- **WHEN** 一级代理销售套餐,售价 100 元,成本价 80 元
|
||||
- **THEN** 一级代理获得 20 元(100 - 80)成本价差收入
|
||||
|
||||
#### Scenario: 多级代理
|
||||
- **WHEN** 三级代理销售套餐,售价 100 元,各级成本价为:平台 50 → 一级 60 → 二级 70 → 三级 80
|
||||
- **THEN** 三级获得 20 元(100 - 80),二级获得 10 元(80 - 70),一级获得 10 元(70 - 60),平台获得 10 元(60 - 50)
|
||||
|
||||
#### Scenario: 平台代扣二级及以上代理
|
||||
- **WHEN** 平台为任意非顶级代理创建并完成订单
|
||||
- **THEN** 系统仍从该订单的 `seller_shop_id` 向上逐级计算差价佣金
|
||||
- **AND** 只要上级链存在且存在有效价差,各级上级都获得对应佣金
|
||||
|
||||
#### Scenario: 顶级代理订单无上级
|
||||
- **WHEN** 顶级代理完成订单,`seller_shop_id` 无父级链
|
||||
- **THEN** 系统完成佣金计算流程
|
||||
- **AND** 不创建上级差价佣金记录
|
||||
- **AND** 该结果被标记为“无佣金”而非“待计算”
|
||||
|
||||
#### Scenario: 成本价相同
|
||||
- **WHEN** 某级代理成本价等于下级成本价
|
||||
- **THEN** 该级代理成本价差收入为 0,不创建佣金记录
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 代购订单佣金计算规则
|
||||
|
||||
代购订单 SHALL 计算差价佣金,但不触发一次性佣金。差价佣金的计算对象 MUST 是买家所属卖方链路上的全部有效上级,而不是仅限最近一级。
|
||||
|
||||
#### Scenario: 代购订单计算差价佣金
|
||||
- **WHEN** 代购订单(is_purchase_on_behalf = true)完成,买家有上级代理链
|
||||
- **THEN** 系统按 `seller_shop_id` 的完整上级链计算差价佣金
|
||||
- **AND** 将各级有效价差佣金分别发放给对应上级
|
||||
|
||||
#### Scenario: 代购订单不触发一次性佣金
|
||||
- **WHEN** 代购订单完成,佣金计算时检查订单类型
|
||||
- **THEN** 系统跳过一次性佣金判断逻辑,不发放一次性佣金
|
||||
|
||||
#### Scenario: 代购订单示例
|
||||
- **WHEN** 平台为三级代理代购,订单金额 100 元(三级成本价),各级成本价:一级 60 → 二级 70 → 三级 80
|
||||
- **THEN** 三级作为销售代理获得零售价差收入(如适用)
|
||||
- **AND** 二级获得 10 元(80 - 70)差价佣金,一级获得 10 元(70 - 60)差价佣金
|
||||
- **AND** 该订单不触发一次性佣金
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 佣金计算结果落单
|
||||
|
||||
系统 SHALL 在佣金计算完成后,将订单落为明确的流程状态和业务结果,至少区分“有佣金”“无佣金”“链路异常待人工处理”。
|
||||
|
||||
#### Scenario: 计算完成且生成佣金记录
|
||||
- **WHEN** 订单佣金计算成功,生成一条或多条佣金记录
|
||||
- **THEN** 订单佣金流程状态为“已完成”
|
||||
- **AND** 订单佣金业务结果为“有佣金”
|
||||
|
||||
#### Scenario: 计算完成但无佣金记录
|
||||
- **WHEN** 订单佣金计算成功,但整条有效上级链上都没有正差价
|
||||
- **THEN** 订单佣金流程状态为“已完成”
|
||||
- **AND** 订单佣金业务结果为“无佣金”
|
||||
|
||||
#### Scenario: 上级链断裂
|
||||
- **WHEN** 佣金计算过程中发现父级链、成本价配置或分配关系无法继续向上求解
|
||||
- **THEN** 系统创建待人工处理的异常记录或备注
|
||||
- **AND** 订单佣金流程状态为“待人工处理”
|
||||
- **AND** 订单佣金业务结果为“链路异常”
|
||||
@@ -0,0 +1,72 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: 支付成功后自动入队佣金计算任务
|
||||
|
||||
系统 SHALL 在订单首次进入“已支付”或自动完单后的待计算状态时自动 enqueue 佣金计算异步任务(`commission:calculate`),确保所有适用订单都进入佣金计算流程。系统 MUST NOT 预先根据操作者类型推断“这单没有佣金所以不用算”。
|
||||
|
||||
**触发条件**:
|
||||
- 订单从"待支付"变为"已支付",或订单创建后即自动完单
|
||||
- 订单佣金流程状态为待计算
|
||||
- 订单属于差价佣金适用范围
|
||||
|
||||
**任务参数**:
|
||||
- 任务类型:`commission:calculate`
|
||||
- Payload:`{"order_id": <订单ID>}`
|
||||
|
||||
#### Scenario: 首次支付成功触发计算
|
||||
- **WHEN** 订单支付成功,订单状态从"待支付"变为"已支付"
|
||||
- **THEN** 系统自动 enqueue `commission:calculate` 任务,payload 包含订单 ID
|
||||
- **AND** 订单佣金流程状态保持为待计算,直到 worker 写回最终结果
|
||||
|
||||
#### Scenario: 自动完单订单触发计算
|
||||
- **WHEN** 订单创建后即自动完单(如平台代购、适用的后台即时支付场景)
|
||||
- **THEN** 系统同样自动 enqueue `commission:calculate` 任务
|
||||
|
||||
#### Scenario: 平台代扣顶级代理订单仍然入队
|
||||
- **WHEN** 平台代扣顶级代理钱包订单,系统尚未知道最终是否有佣金
|
||||
- **THEN** 系统仍然 enqueue `commission:calculate` 任务
|
||||
- **AND** 由 worker 最终判定该订单结果为“无佣金”或其他结果
|
||||
|
||||
#### Scenario: 重复支付不重复触发
|
||||
- **WHEN** 订单已经是"已支付"状态,再次收到支付成功通知(幂等场景)
|
||||
- **THEN** 系统不重复 enqueue 佣金计算任务
|
||||
- **AND** 日志记录"订单已支付,跳过重复入队"
|
||||
|
||||
#### Scenario: 已完成佣金流程的订单不触发
|
||||
- **WHEN** 订单佣金流程状态已为“已完成”或“待人工处理”
|
||||
- **THEN** 系统跳过入队操作
|
||||
- **AND** 日志记录"订单佣金流程已结束,跳过入队"
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 佣金计算任务幂等性
|
||||
|
||||
系统 SHALL 确保佣金计算任务可重复执行,不重复发放佣金;worker 完成后 MUST 明确写回订单的佣金流程状态和业务结果。
|
||||
|
||||
**幂等检查**:
|
||||
- 任务执行前检查订单佣金流程状态
|
||||
- 如果流程已结束,则跳过计算并返回成功
|
||||
|
||||
**状态更新**:
|
||||
- 计算完成后将订单写为“已完成 + 有佣金/无佣金”或“待人工处理 + 链路异常”
|
||||
- 状态更新与佣金记录创建在同一事务中
|
||||
|
||||
#### Scenario: 任务重复执行跳过计算
|
||||
- **WHEN** 佣金计算任务执行时,订单佣金流程状态已为“已完成”
|
||||
- **THEN** 系统跳过佣金计算和钱包入账操作
|
||||
- **AND** 任务返回成功(避免 Asynq 重试)
|
||||
|
||||
#### Scenario: 并发任务只有一个成功
|
||||
- **WHEN** 同一订单的佣金计算任务被重复入队,两个 worker 并发执行
|
||||
- **THEN** 第一个任务成功完成计算并写回最终状态
|
||||
- **AND** 第二个任务检查到流程已结束后跳过计算
|
||||
|
||||
#### Scenario: 无佣金订单也写回完成状态
|
||||
- **WHEN** worker 执行完毕,确认该订单没有任何应发差价佣金
|
||||
- **THEN** worker MUST 将订单写回“已完成 + 无佣金”
|
||||
- **AND** 不允许订单继续停留在“待计算”
|
||||
|
||||
#### Scenario: 任务失败可安全重试
|
||||
- **WHEN** 佣金计算任务执行失败(数据库异常、钱包服务不可用)
|
||||
- **THEN** Asynq 自动重试任务
|
||||
- **AND** 重试时幂等检查确保不重复发放佣金
|
||||
@@ -0,0 +1,37 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 订单响应包含佣金流程状态与业务结果
|
||||
|
||||
系统 SHALL 在订单列表和订单详情响应中分别表达佣金流程状态和佣金业务结果,避免前端和运营把“待计算”“无佣金”“链路异常”混为一谈。
|
||||
|
||||
#### Scenario: 已计算无佣金订单
|
||||
- **WHEN** 查询一笔已完成计算但没有任何应发佣金的订单
|
||||
- **THEN** 响应中 MUST 返回“已完成”类佣金流程状态
|
||||
- **AND** MUST 返回“无佣金”类业务结果
|
||||
|
||||
#### Scenario: 生成佣金记录的订单
|
||||
- **WHEN** 查询一笔已经生成佣金记录的订单
|
||||
- **THEN** 响应中 MUST 返回“已完成”类佣金流程状态
|
||||
- **AND** MUST 返回“有佣金”类业务结果
|
||||
|
||||
#### Scenario: 链路断裂订单
|
||||
- **WHEN** 查询一笔因上级链断裂而需要人工处理的订单
|
||||
- **THEN** 响应中 MUST 返回“待人工处理”类佣金流程状态
|
||||
- **AND** MUST 返回“链路异常”类业务结果
|
||||
|
||||
### Requirement: 订单响应包含真实操作者展示信息
|
||||
|
||||
系统 SHALL 在订单列表和订单详情中返回真实操作者信息,并将账号操作者展示与业务店铺展示分离。
|
||||
|
||||
#### Scenario: 平台账号创建的订单
|
||||
- **WHEN** 查询平台账号创建的订单
|
||||
- **THEN** 响应 MUST 返回平台账号名称作为操作者名称
|
||||
|
||||
#### Scenario: 代理账号创建的订单
|
||||
- **WHEN** 查询代理账号创建的订单
|
||||
- **THEN** 响应 MUST 返回代理账号名称作为操作者名称
|
||||
|
||||
#### Scenario: 历史订单兼容展示
|
||||
- **WHEN** 查询尚未完成回填的历史订单
|
||||
- **THEN** 系统 MAY 通过 `creator` 或历史兼容字段补全操作者展示
|
||||
- **AND** 不得因为平台场景而默认返回空字符串
|
||||
@@ -0,0 +1,75 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: 代购订单自动完成
|
||||
|
||||
代购订单创建后 SHALL 自动完成支付流程,激活套餐,但不触发一次性佣金。
|
||||
|
||||
#### Scenario: 代购订单自动激活套餐
|
||||
- **WHEN** 创建代购订单成功
|
||||
- **THEN** 系统自动激活套餐(创建 PackageUsage 记录)
|
||||
|
||||
#### Scenario: 代购订单不扣钱包
|
||||
- **WHEN** 创建线下代购订单
|
||||
- **THEN** 系统不扣减任何钱包余额(线下已收款)
|
||||
|
||||
#### Scenario: 代购订单不更新累计充值
|
||||
- **WHEN** 代购订单完成
|
||||
- **THEN** 系统不更新卡/设备的 accumulated_recharge 字段
|
||||
|
||||
#### Scenario: 代购订单计算链路型差价佣金
|
||||
- **WHEN** 代购订单完成,买家存在有效上级代理链
|
||||
- **THEN** 系统从订单的 `seller_shop_id` 出发沿完整父级链计算差价佣金
|
||||
- **AND** 将各级有效差价分别发放给对应上级代理
|
||||
|
||||
#### Scenario: 代购订单无上级链
|
||||
- **WHEN** 代购订单对应的是顶级代理或其上级链没有任何正差价
|
||||
- **THEN** 系统完成佣金计算流程
|
||||
- **AND** 将订单结果标记为“无佣金”
|
||||
|
||||
#### Scenario: 代购订单不触发一次性佣金
|
||||
- **WHEN** 代购订单完成
|
||||
- **THEN** 系统不检查一次性佣金阈值,不发放一次性佣金
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 代理钱包代购
|
||||
|
||||
系统 SHALL 允许代理使用钱包支付(wallet)为下级代理创建代购订单,从自己钱包扣款并立即激活套餐。
|
||||
|
||||
#### Scenario: 代理为下级代理钱包代购
|
||||
- **WHEN** 代理选择下级代理的资源创建订单,支付方式为 wallet
|
||||
- **THEN** 系统创建订单,`buyer_id` = 下级代理店铺 ID,`is_purchase_on_behalf` = true,`payment_method` = "wallet",`payment_status` = 2(已支付)
|
||||
- **AND** 操作者字段记录真实代理账号,而不是代理店铺 ID
|
||||
|
||||
#### Scenario: 钱包代购扣款操作者钱包
|
||||
- **WHEN** 代理使用 wallet 为下级代理购买套餐
|
||||
- **THEN** 系统从操作者所属代理的钱包扣款
|
||||
|
||||
#### Scenario: 钱包代购使用操作者成本价扣款
|
||||
- **WHEN** 一级代理(成本价 80 元)为二级代理(成本价 100 元)的资源创建 wallet 代购订单
|
||||
- **THEN** 系统从一级代理钱包扣款 80 元(操作者成本价)
|
||||
|
||||
#### Scenario: 钱包代购订单金额显示买家成本价
|
||||
- **WHEN** 一级代理为二级代理钱包代购
|
||||
- **THEN** 订单的 `total_amount` = 100 元(买家成本价),`actual_paid_amount` = 80 元(操作者实际扣款)
|
||||
|
||||
#### Scenario: 钱包代购余额不足
|
||||
- **WHEN** 代理使用 wallet 代购,但钱包余额不足
|
||||
- **THEN** 系统返回错误"余额不足",订单创建失败
|
||||
|
||||
#### Scenario: 钱包代购自动激活套餐
|
||||
- **WHEN** 钱包代购订单创建成功
|
||||
- **THEN** 系统自动激活套餐(创建 PackageUsage 记录)
|
||||
|
||||
#### Scenario: 钱包代购触发差价佣金计算
|
||||
- **WHEN** 代理使用 wallet 代购订单完成
|
||||
- **THEN** 系统 MUST 触发差价佣金计算任务
|
||||
- **AND** 最终是否有佣金由 `seller_shop_id` 的完整上级链和价差结果决定
|
||||
|
||||
#### Scenario: 钱包代购不触发一次性佣金
|
||||
- **WHEN** 代理使用 wallet 代购订单完成
|
||||
- **THEN** 系统不检查一次性佣金阈值,不发放一次性佣金
|
||||
|
||||
#### Scenario: 钱包代购创建钱包流水
|
||||
- **WHEN** 代理使用 wallet 代购扣款成功
|
||||
- **THEN** 系统创建钱包流水记录,`transaction_type` = "deduct",`transaction_subtype` = "purchase_for_subordinate",`related_shop_id` = 下级代理店铺 ID
|
||||
@@ -0,0 +1,35 @@
|
||||
## 1. 数据模型与迁移基线
|
||||
|
||||
- [x] 1.1 梳理订单现有 `operator_*`、`creator`、`commission_status` 字段的历史语义与使用点,产出兼容映射清单,并用 `lsp_diagnostics` 确认相关模型文件当前无错误
|
||||
- [x] 1.2 创建订单操作者权威字段与佣金结果字段的 migration(保留旧字段不删除),并核对 up/down 文件命名、注释与回滚策略完整
|
||||
- [x] 1.3 更新订单模型常量与字段定义,明确“佣金流程状态”和“佣金业务结果”两套枚举,并用 `lsp_diagnostics` 确认 `internal/model/order.go`、`pkg/constants/*` 无错误
|
||||
|
||||
## 2. 订单操作者语义修复
|
||||
|
||||
- [x] 2.1 调整后台订单创建链路,统一写入真实操作者账号信息,覆盖平台下单、代理自购、代理为下级代购、平台代扣代理钱包等场景,并用手工代码走查验证每条路径都有明确操作者来源
|
||||
- [x] 2.2 调整订单响应构建逻辑,按账号语义回填 `operator_name`,并将账号展示与业务店铺展示分离,用 `lsp_diagnostics` 确认 `internal/service/order/service.go` 与 DTO 文件无错误
|
||||
- [x] 2.3 设计并实现历史订单兼容读取策略:新字段优先,旧字段与 `creator` 兜底;手工抽查平台旧订单、代理旧订单各至少一条,确认不会再出现平台操作者名称默认空白
|
||||
|
||||
## 3. 链路型差价佣金模型修复
|
||||
|
||||
- [x] 3.1 调整佣金计算服务,统一以 `seller_shop_id` 为起点沿完整父级链逐级计算差价佣金,移除对 `operator_id/operator_type` 的佣金归属依赖,并用代码走查确认顶级、二级、三级、多级场景口径一致
|
||||
- [x] 3.2 在佣金计算完成后显式写回“已完成+有佣金 / 已完成+无佣金 / 待人工处理+链路异常”结果,确保 0 佣金订单不再停留在待计算
|
||||
- [x] 3.3 保留并增强链路断裂待人工处理逻辑,手工检查断点备注、待审记录与订单状态结果三者一致
|
||||
|
||||
## 4. 佣金触发与补偿链修复
|
||||
|
||||
- [x] 4.1 统一修复所有差价佣金适用路径的入队逻辑,覆盖支付回调、自动完单、平台代理扣代理钱包、代理钱包代购等场景,并用代码走查确认不会遗漏 `self_purchase` 的平台代理扣订单
|
||||
- [x] 4.2 调整佣金任务幂等判断与补偿扫描口径,使其基于新流程状态工作,避免“无佣金订单”被重复补偿入队
|
||||
- [x] 4.3 手工验证三类样本:顶级代理无佣金订单、多级代理有佣金订单、链路断裂待人工处理订单,确认任务执行后的订单状态与佣金记录符合预期
|
||||
|
||||
## 5. 订单查询与文档同步
|
||||
|
||||
- [x] 5.1 更新订单列表/详情相关 DTO 与接口文档,明确操作者字段语义以及佣金流程状态/业务结果字段语义,并用 `lsp_diagnostics` 确认 DTO 与 Handler 文件无错误
|
||||
- [x] 5.2 若接口响应结构发生变化,按项目规范同步更新文档生成器相关注册与说明文档,确保 OpenAPI 输出与新语义一致
|
||||
- [x] 5.3 补充中文功能总结与手工验收清单,覆盖平台订单操作者展示、多级链路差价佣金、无佣金完成态、链路异常待人工处理四类核心验收场景
|
||||
|
||||
## 6. 最终手工验收
|
||||
|
||||
- [x] 6.1 使用数据库查询逐条核对订单、佣金记录、钱包流水与操作者字段,确认核心样本数据落库符合新语义
|
||||
- [x] 6.2 对本次变更涉及的关键 Go 文件运行 `lsp_diagnostics`,确认无错误、无遗留坏状态
|
||||
- [x] 6.3 按验收清单完成后台订单列表/详情、补偿入队、异常订单识别的手工验证,确认提案范围内的业务流程可闭环
|
||||
Reference in New Issue
Block a user