修复订单金额落库不对的问题
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 8m17s

This commit is contained in:
Break
2026-06-01 11:21:29 +08:00
parent 6c8352491c
commit 944526d9ef
16 changed files with 454 additions and 37 deletions

View File

@@ -0,0 +1,79 @@
## MODIFIED Requirements
### Requirement: 后台订单 TotalAmount 语义修正
系统 SHALL 在后台创建订单时,`Order.TotalAmount` MUST 始终存储零售价(客户面值),不得被成本价覆盖。`Order.ActualPaidAmount` MUST 存储实际支付的成本价。
**字段语义规范**
- `total_amount`零售价所有场景均为套餐零售单价之和C 端和后台均可安全展示
- `actual_paid_amount`:实付成本价,代购/钱包支付场景为买家实际支付的成本金额
**各场景赋值规则**
| 场景 | total_amount | actual_paid_amount |
|------|-------------|-------------------|
| 平台自营 offline | 零售价 | 零售价(无差价) |
| 平台代购 offline | 零售价 | 成本价(买家成本) |
| 代理自购 wallet | 零售价 | 成本价(自己的成本价) |
| 代理代购 wallet | 零售价 | 操作方成本价 |
| 赠送套餐 offline | 零售价 | 0 |
**钱包扣款**`createOrderWithWalletPayment` MUST 使用 `order.ActualPaidAmount` 作为扣款金额,不得使用 `order.TotalAmount`
#### Scenario: 平台代购时 TotalAmount 为零售价
- **WHEN** 平台为代理商下属资产创建 offline 订单,套餐零售价 9900 分,代理成本价 7000 分
- **THEN** `order.total_amount = 9900``order.actual_paid_amount = 7000`
#### Scenario: 代理自购时 TotalAmount 为零售价
- **WHEN** 代理商以 wallet 支付为自己的资产购买套餐,套餐零售价 9900 分,代理成本价 7000 分
- **THEN** `order.total_amount = 9900``order.actual_paid_amount = 7000`,钱包扣款 7000 分
#### Scenario: 代理代购时 TotalAmount 为零售价
- **WHEN** 上级代理以 wallet 支付为下级代理资产购买套餐,零售价 9900 分,操作方成本价 8000 分,买家成本价 7000 分
- **THEN** `order.total_amount = 9900``order.actual_paid_amount = 8000`(操作方实付),钱包扣款 8000 分
#### Scenario: 赠送套餐时 TotalAmount 为零售价
- **WHEN** 平台以 offline 方式赠送套餐(含赠送标记),套餐零售价 9900 分
- **THEN** `order.total_amount = 9900``order.actual_paid_amount = 0`
---
### Requirement: OrderItem 新增 CostPrice 字段
系统 SHALL 在 `tb_order_item` 中新增 `cost_price BIGINT NOT NULL DEFAULT 0` 字段,存储套餐成本单价快照。`unit_price` 字段 MUST 改为存储零售单价。
**字段规范**
- `unit_price`:零售单价(分),所有场景均为套餐对外售价
- `cost_price`:成本单价(分),代购/钱包支付场景为买家实际成本;平台自营或赠送时为 0
**赋值来源**`cost_price``itemUnitPriceMap`(成本价 map中取值`unit_price` 从零售价 map 中取值。
#### Scenario: 代理自购时 OrderItem 同时记录两种价格
- **WHEN** 代理商购买套餐,零售价 9900 分,成本价 7000 分
- **THEN** `order_item.unit_price = 9900``order_item.cost_price = 7000`
#### Scenario: 平台自营时 CostPrice 为 0
- **WHEN** 平台以 offline 方式为平台自营资产购买套餐,零售价 9900 分
- **THEN** `order_item.unit_price = 9900``order_item.cost_price = 0`
#### Scenario: 赠送套餐时 CostPrice 为 0
- **WHEN** 平台赠送套餐,零售价 9900 分
- **THEN** `order_item.unit_price = 9900``order_item.cost_price = 0`
---
### Requirement: 后台订单 API 响应格式更新
系统 SHALL 在后台订单创建响应中,`total_amount` 字段返回零售价,`actual_paid_amount` 字段返回成本价。
#### Scenario: wallet 订单响应 total_amount 为零售价
- **WHEN** 代理在后台创建 wallet 订单成功,零售价 9900 分,成本价 7000 分
- **THEN** 响应中 `total_amount = 9900``actual_paid_amount = 7000`

View File

@@ -0,0 +1,40 @@
## MODIFIED Requirements
### Requirement: D2 套餐订单列表接口价格展示
系统 SHALL 在 C 端订单列表接口中,`total_amount` 字段 MUST 返回零售价(客户面值)。后台代购/钱包支付场景下C 端客户看到的是套餐零售价,而非成本价。
由于 `Order.TotalAmount` 语义已修正为零售价(见 `admin-order-creation` 变更C 端列表接口无需修改代码,直接受益于数据层修正。
#### Scenario: 后台代购订单在 C 端展示零售价
- **WHEN** 平台为客户代购套餐(成本价 7000 分,零售价 9900 分),客户查询 C 端订单列表
- **THEN** 列表项 `total_amount = 9900`,展示零售价
#### Scenario: 代理钱包支付订单在 C 端展示零售价
- **WHEN** 代理以钱包支付为客户购买套餐(成本价 7000 分,零售价 9900 分),客户查询 C 端订单列表
- **THEN** 列表项 `total_amount = 9900`,展示零售价
#### Scenario: 赠送套餐在 C 端展示零售价
- **WHEN** 平台赠送套餐(零售价 9900 分,实付 0 元),客户查询 C 端订单列表
- **THEN** 列表项 `total_amount = 9900`,展示零售价
---
### Requirement: D3 套餐订单详情接口价格展示
系统 SHALL 在 C 端订单详情接口中,`total_amount` 字段 MUST 返回零售价,套餐明细中每项的 `price` 字段 MUST 返回零售单价。
`ClientOrderPackageItem.price` 来源于 `OrderItem.UnitPrice`,由于 `UnitPrice` 语义已修正为零售单价,详情接口无需修改代码,直接受益于数据层修正。
#### Scenario: 后台代购订单详情在 C 端展示零售价
- **WHEN** 平台为客户代购套餐(成本价 7000 分,零售价 9900 分),客户查询 C 端订单详情
- **THEN** `total_amount = 9900`,套餐明细 `price = 9900`
#### Scenario: 赠送套餐详情在 C 端展示零售价
- **WHEN** 平台赠送套餐(零售价 9900 分,实付 0 元),客户查询 C 端订单详情
- **THEN** `total_amount = 9900`,套餐明细 `price = 9900`

View File

@@ -0,0 +1,81 @@
## MODIFIED Requirements
### Requirement: 套餐使用记录新增零售价快照字段
系统 SHALL 在 `tb_package_usage` 中新增 `retail_amount BIGINT NULLABLE` 字段,在创建 `PackageUsage` 记录时快照套餐零售价。
**字段规范**
- 数据库字段:`retail_amount BIGINT NULLABLE`
- Go 字段:`RetailAmount *int64`(单位:分)
- 赋值来源:`order.TotalAmount`(零售价),直接赋值,不转换
- 为 null 的情况:无订单关联的企业分配套餐(`order_id = 0`
**现有 `PaidAmount` 字段保持不变**:继续快照 `order.ActualPaidAmount`(成本价)。
**覆盖范围(所有 PackageUsage 写入点)**
- `internal/service/order/service.go` `activateMainPackage()`
- `internal/service/order/service.go` `activateAddonPackage()`
- `internal/task/auto_purchase.go` `activateMainPackage()`
- `internal/task/auto_purchase.go` `activateAddonPackage()`
**存量数据回填**:通过迁移 SQL 从关联订单回填,已知历史订单 `total_amount` 可能为成本价(历史数据问题),回填值仅作参考,接受不准确。
#### Scenario: 代理钱包支付套餐激活时快照零售价和成本价
- **WHEN** 代理以钱包支付购买套餐,零售价 9900 分,成本价 7000 分
- **THEN** 激活生成的 `PackageUsage.retail_amount = 9900``PackageUsage.paid_amount = 7000`
#### Scenario: 平台代购套餐激活时快照零售价和成本价
- **WHEN** 平台代购套餐,零售价 9900 分,成本价 7000 分
- **THEN** 激活生成的 `PackageUsage.retail_amount = 9900``PackageUsage.paid_amount = 7000`
#### Scenario: 赠送套餐激活时 paid_amount 为 0retail_amount 为零售价
- **WHEN** 平台赠送套餐,零售价 9900 分,实付 0 元
- **THEN** `PackageUsage.retail_amount = 9900``PackageUsage.paid_amount = 0`
#### Scenario: 无订单关联的套餐 retail_amount 为 null
- **WHEN** 企业分配套餐(无订单),`order_id = 0`
- **THEN** `PackageUsage.retail_amount = nil`,接口响应中 `retail_amount` 字段缺省omitempty
---
### Requirement: 资产套餐接口按调用方角色过滤成本价
系统 SHALL 在 `/api/admin/assets/{identifier}/packages``/api/admin/assets/{identifier}/current-package` 接口中,根据调用方账号类型决定是否返回 `paid_amount`(成本价)字段。
**过滤规则**
- 调用方账号类型为 `platform`:返回 `paid_amount``retail_amount` 两个字段
- 调用方账号类型为其他(`agent``enterprise``personal_customer``paid_amount` 不返回null/omitempty仅返回 `retail_amount`
**实现方式**`GetPackages` / `GetCurrentPackage` 新增 `callerAccountType string` 参数Service 层根据此参数决定是否填充 `PaidAmount`Handler 层从 middleware 读取账号类型后传入。
**`AssetPackageResponse` DTO 新增字段**
- `retail_amount *int64`:零售价(分),来源 `PackageUsage.RetailAmount`omitempty
#### Scenario: 平台账号查询资产套餐返回两种价格
- **WHEN** 平台账号调用 `/api/admin/assets/{identifier}/packages`,套餐零售价 9900 分,成本价 7000 分
- **THEN** 响应中 `retail_amount = 9900``paid_amount = 7000`,两个字段均返回
#### Scenario: 代理账号查询资产套餐只返回零售价
- **WHEN** 代理账号调用 `/api/admin/assets/{identifier}/packages`,套餐零售价 9900 分,成本价 7000 分
- **THEN** 响应中 `retail_amount = 9900``paid_amount` 字段不返回null
#### Scenario: 平台账号查询当前生效套餐返回两种价格
- **WHEN** 平台账号调用 `/api/admin/assets/{identifier}/current-package`
- **THEN** 响应中 `retail_amount``paid_amount` 均有值
#### Scenario: 代理账号查询当前生效套餐只返回零售价
- **WHEN** 代理账号调用 `/api/admin/assets/{identifier}/current-package`
- **THEN** 响应中 `retail_amount` 有值,`paid_amount` 不返回
#### Scenario: 套餐无零售价快照时 retail_amount 为 null
- **WHEN** 历史套餐使用记录 `retail_amount` 为 null存量数据
- **THEN** 响应中 `retail_amount` 字段缺省omitempty不返回