订单相关以及佣金相关修复
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m57s

This commit is contained in:
2026-04-30 09:52:39 +08:00
parent 4f4e42f27e
commit 66e12e9629
29 changed files with 1199 additions and 181 deletions

View File

@@ -1123,7 +1123,7 @@ components:
DtoAssetResolveResponse:
properties:
activated_at:
description: 激活时间
description: 激活时间(未来准备移除,请勿依赖)
format: date-time
nullable: true
type: string
@@ -3440,6 +3440,12 @@ components:
network_status:
description: 网络状态0停机 1开机asset_type=card时有效
type: integer
real_name_status:
description: 实名状态 (0:未实名, 1:已实名)
type: integer
real_name_status_name:
description: 实名状态名称(中文)
type: string
realname_policy:
description: 实名认证策略 (none:无需实名, before_order:先实名后充值/购买, after_order:先充值/购买后实名)
type: string
@@ -5449,11 +5455,17 @@ components:
commission_config_version:
description: 佣金配置版本
type: integer
commission_result:
description: 佣金业务结果 (0:未知, 1:有佣金, 2:无佣金, 3:链路异常)
type: integer
commission_result_name:
description: 佣金业务结果名称(中文)
type: string
commission_status:
description: 佣金状态 (1:待计算, 2:已计算)
description: 佣金流程状态 (1:待计算, 2:已完成, 3:待人工处理)
type: integer
commission_status_name:
description: 佣金状态名称(中文)
description: 佣金流程状态名称(中文)
type: string
created_at:
description: 创建时间
@@ -5494,15 +5506,15 @@ components:
nullable: true
type: array
operator_id:
description: 操作者ID
description: 操作者账号ID真实发起操作的账号/主体ID
minimum: 0
nullable: true
type: integer
operator_name:
description: 操作者名称
description: 操作者名称(按账号语义展示)
type: string
operator_type:
description: 操作者类型 (platform:平台, agent:代理)
description: 操作者账号类型 (platform:平台, agent:代理, enterprise:企业, personal_customer:个人客户)
type: string
order_no:
description: 订单号
@@ -5533,6 +5545,14 @@ components:
purchase_role:
description: 订单角色 (self_purchase:自己购买, purchased_by_parent:上级代理购买, purchased_by_platform:平台代购, purchase_for_subordinate:给下级购买)
type: string
seller_shop_id:
description: 销售店铺ID业务归属字段
minimum: 0
nullable: true
type: integer
seller_shop_name:
description: 销售店铺名称(业务归属展示)
type: string
total_amount:
description: 订单总金额(分)
type: integer
@@ -6741,11 +6761,17 @@ components:
type: object
DtoSetSwitchModeRequest:
properties:
iot_card_id:
description: 目标卡资产ID必须是系统内状态正常且已实名的卡
minimum: 1
type: integer
switch_mode:
description: 切卡模式 (0:自动切卡, 1:手动切卡)
nullable: true
type: integer
required:
- switch_mode
- iot_card_id
type: object
DtoSetWiFiRequest:
properties:
@@ -13385,7 +13411,7 @@ paths:
- 设备管理
/api/admin/devices/by-identifier/{identifier}/switch-mode:
post:
description: 通过虚拟号/IMEI/SN 设置设备切卡模式0:自动切卡, 1:手动切卡)。设备必须已配置 IMEI。
description: 通过虚拟号/IMEI/SN 设置设备切卡模式0:自动切卡, 1:手动切卡)。设备必须已配置 IMEI且必须指定一张系统内状态正常并已实名的目标卡资产ID
parameters:
- description: 设备标识符(支持虚拟号/IMEI/SN)
in: path

View File

@@ -80,7 +80,7 @@
| `virtual_used_mb` | float64 | 当前主套餐展示已用量 MB |
| `reduction_pct` | float64 | 展示增幅比例,公式为 `(real_total_mb / virtual_total_mb) - 1` |
| `device_protect_status` | string | 保护期状态:`none` / `stop` / `start`(仅 device |
| `activated_at` | time | 激活时间 |
| `activated_at` | time | 激活时间(未来准备移除,请勿依赖) |
| `created_at` | time | 创建时间 |
| `updated_at` | time | 更新时间 |
| **绑定关系card 时)** | | |

View File

@@ -0,0 +1,41 @@
## 订单操作者与佣金语义兼容映射清单
### 1. 历史字段现状
- `tb_order.operator_id`:历史上混用了“真实操作者账号 ID”和“代理店铺 ID”两种语义。
- `tb_order.operator_type`:历史上仅承载 `platform/agent`,且与 `operator_id` 的 ID 体系并不稳定一致。
- `tb_order.creator`:后台订单通常可回溯真实账号,但旧响应逻辑没有统一优先使用该字段补齐平台操作者展示。
- `tb_order.commission_status`:历史上仅有 `1=待计算``2=已计算`,无法表达“无佣金”和“链路异常待人工处理”。
### 2. 新增权威字段
- `operator_account_id`:真实操作者账号/主体 ID。
- `operator_account_type`:真实操作者类型,使用 `platform/agent/enterprise/personal_customer` 语义。
- `operator_account_name`:真实操作者名称快照。
- `commission_result`:佣金业务结果,使用 `0=未知 1=有佣金 2=无佣金 3=链路异常`
### 3. 写路径兼容策略
- 新订单统一写入 `operator_account_*` 权威字段。
- 保留写入 legacy `operator_id/operator_type`,但仅作为兼容读取兜底,不再作为新语义权威来源。
- `seller_shop_id` 继续作为差价佣金链路起点,不再从 `operator_id/operator_type` 推导佣金归属。
### 4. 读路径兼容策略
- 响应层优先读取 `operator_account_*`
- 若新字段为空:
- 优先尝试 `creator` 回填后台账号名称。
- 对客户端个人客户订单,回填个人客户昵称。
- 对历史代理订单,若 `operator_id/operator_type` 仍承载店铺语义,则回退展示店铺名称,但不回写新字段。
- `commission_result` 为空时,按历史订单状态与佣金记录兜底推断,不直接篡改旧列值。
### 5. 高风险兼容点
- `internal/store/postgres/order_store.go` 中旧逻辑把 `operator_id IN subordinateShopIDs` 作为代理权限过滤条件;当 `operator_id` 不再表示店铺 ID 后,该条件必须迁移到 `seller_shop_id`,历史订单仅保留兜底分支。
- `internal/service/order/service.go` 当前 `operator_name` 仅在 `operator_type=agent` 时按店铺名称回填,会导致平台旧订单长期空白。
- `internal/service/commission_calculation/service.go` 当前仅把订单写成“已计算”,无法区分“有佣金 / 无佣金 / 链路异常”。
### 6. 本次实现范围内的迁移原则
- 只做增量字段迁移,不删除旧列。
- 不在本次 migration 中批量强制回填历史订单,只在运行时兼容读取并为后续人工校验提供依据。

View File

@@ -0,0 +1,78 @@
## 订单操作者与产业链佣金语义修复功能总结
### 1. 变更目标
本次变更围绕两个历史语义问题展开:
1. 订单 `operator_id/operator_type` 混用了“真实操作者账号”和“代理店铺 ID”导致平台订单无法稳定显示真实操作者。
2. 订单 `commission_status` 只区分“待计算/已计算”,无法表达“有佣金 / 无佣金 / 链路异常待人工处理”的真实业务结果。
### 2. 已完成的核心改动
#### 2.1 订单操作者改为账号语义
- 新增权威字段:
- `operator_account_id`
- `operator_account_type`
- `operator_account_name`
- 后台订单创建、H5 代理订单创建、客户端个人客户订单、自动购包订单都会写入真实操作者快照。
-`operator_id/operator_type` 保留为历史兼容字段,不再作为新语义权威来源。
#### 2.2 订单响应分离“操作者账号”和“业务店铺”
- `operator_id/operator_type/operator_name` 现在按真实操作者账号语义返回。
- 新增 `seller_shop_id/seller_shop_name`,用于表达业务归属店铺展示。
- 历史订单优先使用新字段;若缺失,则按 `creator`、个人客户昵称、旧店铺型 `operator_id` 顺序兜底,避免平台旧订单出现空白操作者名称。
#### 2.3 订单佣金状态拆成“流程状态 + 业务结果”
- `commission_status` 现在表示流程状态:
- `1=待计算`
- `2=已完成`
- `3=待人工处理`
- 新增 `commission_result` 表示业务结果:
- `0=未知`
- `1=有佣金`
- `2=无佣金`
- `3=链路异常`
#### 2.4 差价佣金任务结果显式落单
- 佣金 worker 结束后会显式写回订单:
- `已完成 + 有佣金`
- `已完成 + 无佣金`
- `待人工处理 + 链路异常`
- 0 佣金订单不再长期停留在“待计算”。
#### 2.5 入队与幂等语义修复
- 钱包支付创建订单后,所有仍处于 `待计算` 的适用订单都会入队佣金任务,不再只覆盖特定购买角色。
- 重复支付回调、重复钱包支付不会重复入队。
- worker 对 `已完成/待人工处理` 订单直接跳过,避免重复补偿处理。
### 3. 兼容策略
- migration 仅新增字段,不删除旧列。
- 历史订单读取时:
- 优先使用 `operator_account_*`
- 其次使用 `creator` 回填账号名
- 对旧代理订单保留店铺名兜底展示
- 历史订单的 `commission_result` 若未回填,则在响应层按佣金记录只读推断。
### 4. 受影响文件
- `internal/model/order.go`
- `internal/model/dto/order_dto.go`
- `internal/service/order/service.go`
- `internal/service/client_order/service.go`
- `internal/service/commission_calculation/service.go`
- `internal/store/postgres/order_store.go`
- `internal/task/auto_purchase.go`
- `pkg/constants/iot.go`
- `migrations/000140_add_order_operator_snapshot_and_commission_result.*.sql`
### 5. 风险提示
- 历史订单若 `creator` 无法映射到账号,仍会退回旧店铺型操作者展示,仅保证“可读”而不强行伪造账号 ID。
- `commission_result=0` 仅作为历史兼容态存在;新计算链路应尽量落为明确业务结果。
- 本次不重构一次性佣金规则本身,只保证其异常待审记录能纳入订单最终流程结果判断。

View File

@@ -0,0 +1,39 @@
## 手工验收清单
### 一、平台订单操作者展示
- [ ] 创建或查询一条历史平台订单,确认响应中的 `operator_name` 不再为空。
- [ ] 确认该订单的 `operator_type=platform`,且操作者名称优先来自 `creator` 对应后台账号。
- [ ] 确认 `seller_shop_name``operator_name` 语义分离,不再混用店铺名代替平台账号名。
### 二、多级代理链差价佣金
- [ ] 准备一条 `seller_shop_id` 存在完整父级链的订单。
- [ ] 执行佣金任务后,确认各级代理按父级链逐级生成差价佣金记录。
- [ ] 确认订单 `commission_status=2``commission_result=1`
- [ ] 确认相关代理钱包流水与佣金记录金额一致。
### 三、无佣金完成态
- [ ] 准备一条顶级代理订单或无正差价订单。
- [ ] 执行佣金任务后,确认不产生正金额佣金记录。
- [ ] 确认订单不会继续停留在待计算,而是落为 `commission_status=2``commission_result=2`
### 四、链路异常待人工处理
- [ ] 准备一条上级链断裂、系列配置缺失或套餐分配缺失的订单。
- [ ] 执行佣金任务后,确认生成 `status=99` 的待人工修正佣金记录。
- [ ] 确认备注中包含链路断点信息。
- [ ] 确认订单落为 `commission_status=3``commission_result=3`
### 五、入队与幂等
- [ ] 钱包支付创建订单后,确认差价佣金任务会自动入队。
- [ ] 重复触发支付回调或重复调用钱包支付时,确认不会重复入队。
- [ ] 对已经完成或待人工处理的订单重复执行佣金任务,确认 worker 会直接跳过。
### 六、历史兼容读取
- [ ] 抽查至少一条历史平台订单,确认 `creator` 能兜底回填操作者名称。
- [ ] 抽查至少一条历史代理订单,确认新字段为空时仍可读出兼容操作者展示。
- [ ] 确认历史订单不会把旧 `operator_id` 误当成新的账号 ID 再写回数据库。

View File

@@ -756,7 +756,7 @@ GET /api/admin/assets/resolve/:identifier
| `series_name` | string | 套餐系列名称 |
| `first_commission_paid` | bool | 一次性佣金是否已发放 |
| `accumulated_recharge` | int64 | 累计充值金额(分) |
| `activated_at` | string | 激活时间 |
| `activated_at` | string | 激活时间(未来准备移除,请勿依赖) |
| `created_at` | string | 创建时间 |
| `updated_at` | string | 更新时间 |