订单相关以及佣金相关修复
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,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
|
||||
Reference in New Issue
Block a user