## 1. 数据库结构变更 - [x] 1.1 创建订单表字段迁移脚本(`migrations/xxx_add_operator_fields_to_orders.up.sql`),新增 `operator_id`、`operator_type`、`actual_paid_amount`、`purchase_role` 字段,添加字段注释 - [x] 1.2 在迁移脚本中创建索引(`idx_orders_operator_id`、`idx_orders_purchase_role`),使用 CONCURRENTLY 避免锁表 - [x] 1.3 创建钱包流水表字段迁移脚本(`migrations/xxx_add_transaction_subtype_to_wallet_transaction.up.sql`),检查并添加 `transaction_subtype` 和 `related_shop_id` 字段(如果不存在) - [x] 1.4 创建数据回滚迁移脚本(`*.down.sql`),包含 DROP INDEX 和 DROP COLUMN 语句 - [x] 1.5 在测试环境执行迁移,验证字段创建成功,检查 `\d tb_order` 和 `\d tb_agent_wallet_transaction` 输出 ## 2. Model 层:订单角色追踪 - [x] 2.1 在 `internal/model/order.go` 中的 `Order` 结构体添加新字段:`OperatorID`、`OperatorType`、`ActualPaidAmount`、`PurchaseRole`,添加 gorm 标签和中文注释 - [x] 2.2 在 `internal/model/order.go` 中定义订单角色枚举常量(`PurchaseRoleSelfPurchase`、`PurchaseRolePurchasedByParent`、`PurchaseRolePurchasedByPlatform`、`PurchaseRolePurchaseForSubordinate`),添加中文注释 - [x] 2.3 在 `internal/model/agent_wallet.go` 中确认 `AgentWalletTransaction` 结构体包含 `TransactionSubtype` 和 `RelatedShopID` 字段(如果不存在则添加) - [x] 2.4 运行 `go build ./...` 验证编译通过 ## 3. 常量定义:钱包流水子类型 - [x] 3.1 在 `pkg/constants/wallet.go` 中新增钱包交易子类型常量(`WalletTransactionSubtypeSelfPurchase`、`WalletTransactionSubtypePurchaseForSubordinate`),添加中文注释 - [x] 3.2 运行 `go build ./...` 验证编译通过 ## 4. DTO 层:订单请求和响应 - [x] 4.1 在 `internal/model/dto/order_dto.go` 的 `OrderResponse` 中添加新字段:`OperatorID`、`OperatorType`、`OperatorName`、`ActualPaidAmount`、`PurchaseRole`、`IsPurchasedByParent`、`PurchaseRemark`,添加 JSON 标签和 description 注释 - [x] 4.2 在 `internal/model/dto/order_dto.go` 的 `OrderListRequest` 中添加 `PurchaseRole` 筛选字段,添加验证标签(`validate:"omitempty,oneof=self_purchase purchased_by_parent purchased_by_platform purchase_for_subordinate"`) - [x] 4.3 运行 `go build ./...` 验证编译通过 ## 5. Store 层:订单查询 OR 逻辑 - [x] 5.1 修改 `internal/store/postgres/order_store.go` 的 `List()` 方法,支持 `shop_id` 筛选时使用 OR 查询:`WHERE (buyer_type = 'agent' AND buyer_id = ?) OR operator_id = ?` - [x] 5.2 在 `List()` 方法中添加 `purchase_role` 精确匹配筛选支持 - [x] 5.3 运行 `go build ./...` 验证编译通过 - [x] 5.4 使用 PostgreSQL MCP 工具验证查询逻辑:创建测试订单,执行 `SELECT * FROM tb_order WHERE (buyer_id = X) OR (operator_id = X)` 并检查 EXPLAIN 输出 ## 6. Service 层:成本价查询辅助方法 - [x] 6.1 在 `internal/service/order/service.go` 中新增 `getCostPrice(ctx, shopID, packageID)` 方法,通过 `ShopPackageAllocation` 查询店铺对套餐的成本价 - [x] 6.2 添加错误处理:如果查询失败,返回 `errors.New(errors.CodeInvalidParam, "店铺没有该套餐的分配配置")` - [x] 6.3 运行 `go build ./...` 验证编译通过 ## 7. Service 层:钱包流水创建方法 - [x] 7.1 在 `internal/service/order/service.go` 中新增 `createWalletTransaction(ctx, tx, walletID, orderID, amount, purchaseRole, relatedShopID)` 方法 - [x] 7.2 在方法中根据 `purchaseRole` 确定 `transaction_subtype` 和 `remark`:自购场景填充"购买套餐",代购场景查询下级店铺名称填充"为下级代理【XX】购买套餐" - [x] 7.3 创建 `AgentWalletTransaction` 记录,设置 `TransactionType` = `AgentTransactionTypeDeduct`,`TransactionSubtype`、`Amount`(负数)、`RelatedShopID`、`Remark` - [x] 7.4 运行 `go build ./...` 验证编译通过 ## 8. Service 层:钱包支付订单创建方法 - [x] 8.1 在 `internal/service/order/service.go` 中新增 `createOrderWithWalletPayment(ctx, order, items, operatorShopID, buyerShopID)` 方法 - [x] 8.2 在方法开头(事务外)检查钱包余额,如果余额不足返回错误 - [x] 8.3 开启 GORM 事务,在事务中依次执行:创建订单(`tx.Create(order)`)、创建订单明细(`tx.CreateInBatches(items, 100)`) - [x] 8.4 在事务中扣减钱包余额,使用乐观锁:`WHERE id = ? AND balance >= ? AND version = ?`,更新 `balance = balance - ?` 和 `version = version + 1` - [x] 8.5 检查 `RowsAffected`,如果为 0 返回 `errors.New(errors.CodeInsufficientBalance, "余额不足或并发冲突")` - [x] 8.6 在事务中调用 `createWalletTransaction()` 创建钱包流水 - [x] 8.7 在事务中调用 `activatePackage()` 激活套餐 - [x] 8.8 事务外判断是否入队佣金计算:`if order.OperatorID == nil { s.enqueueCommissionCalculation() }`(平台代购才入队) - [x] 8.9 运行 `go build ./...` 验证编译通过 ## 9. Service 层:订单创建流程重构 - [x] 9.1 在 `internal/service/order/service.go` 的 `Create()` 方法中,在幂等性检查后添加场景判断逻辑 - [x] 9.2 提取资源所属店铺 ID(从 `validationResult.Card.ShopID` 或 `validationResult.Device.ShopID`) - [x] 9.3 处理 `offline` 场景:设置 `operator_id = nil`、`operator_type = "platform"`、`purchase_role = "purchased_by_platform"`,调用 `resolvePurchaseOnBehalfInfo()` 获取买家成本价,保持现有逻辑调用 `createOrderWithActivation()` - [x] 9.4 处理 `wallet` 场景:获取操作者店铺 ID,判断资源是否属于操作者 - [x] 9.5 如果资源属于操作者(自购):设置 `buyer = operator`、`purchase_role = "self_purchase"`、`is_purchase_on_behalf = false`,调用 `getCostPrice()` 获取成本价,`total_amount = actual_paid_amount = 操作者成本价` - [x] 9.6 如果资源不属于操作者(代购):设置 `buyer = 资源所属者`、`operator = 操作者`、`purchase_role = "purchase_for_subordinate"`、`is_purchase_on_behalf = true`,分别调用 `getCostPrice()` 获取买家和操作者成本价,`total_amount = 买家成本价`、`actual_paid_amount = 操作者成本价` - [x] 9.7 `wallet` 场景调用 `createOrderWithWalletPayment()` 而不是 `orderStore.Create()` - [x] 9.8 运行 `go build ./...` 验证编译通过 ## 10. Service 层:订单响应构建方法 - [x] 10.1 在 `internal/service/order/service.go` 的 `buildOrderResponse()` 方法中添加新字段映射:`OperatorID`、`OperatorType`、`ActualPaidAmount`、`PurchaseRole` - [x] 10.2 添加 `OperatorName` 字段逻辑:如果 `operator_type = "agent"` 且 `operator_id` 不为空,查询 `Shop` 表获取店铺名称 - [x] 10.3 添加 `IsPurchasedByParent` 派生字段:`purchase_role == "purchased_by_parent"` - [x] 10.4 添加 `PurchaseRemark` 派生字段:根据 `purchase_role` 和 `operator_name` 生成备注文本(如"由上级代理【XX】购买"、"由平台代购") - [x] 10.5 运行 `go build ./...` 验证编译通过 ## 11. Handler 层:权限检查调整 - [x] 11.1 在 `internal/handler/admin/order.go` 的 `Create()` 方法中,修改 `wallet` 支付方式的权限检查,允许代理、平台、超管使用 - [x] 11.2 保持 `offline` 支付方式只允许平台和超管使用的限制 - [x] 11.3 运行 `go build ./...` 验证编译通过 ## 12. Handler 层:订单查询参数传递 - [x] 12.1 在 `internal/handler/admin/order.go` 的 `List()` 方法中,从查询参数解析 `purchase_role` - [x] 12.2 将 `purchase_role` 传递给 Service 层的 `List()` 方法 - [x] 12.3 运行 `go build ./...` 验证编译通过 ## 13. 文档生成器更新(OpenAPI) - [x] 13.1 确认 `cmd/api/docs.go` 和 `cmd/gendocs/main.go` 中的 `Handlers` 结构体已包含 `Order` Handler(如果不存在则添加) - [x] 13.2 运行 `go run cmd/gendocs/main.go` 生成 OpenAPI 文档 - [x] 13.3 检查生成的文档中订单创建和列表接口的请求/响应字段是否包含新字段 ## 14. 集成测试:代理自购场景 - [ ] 14.1 使用 PostgreSQL MCP 工具创建测试数据:创建代理账号、代理钱包(余额 10000 分)、IoT 卡(shop_id = 代理店铺 ID)、套餐分配配置(成本价 8000 分) - [ ] 14.2 使用 Postman/curl 调用后台订单创建 API,代理账号创建订单,支付方式 wallet,选择自己的卡和套餐 - [ ] 14.3 验证响应:`payment_status` = 2,`operator_id` = 代理店铺 ID,`buyer_id` = 代理店铺 ID,`purchase_role` = "self_purchase",`total_amount` = 8000,`actual_paid_amount` = 8000 - [ ] 14.4 使用 PostgreSQL MCP 查询订单表,验证订单记录正确 - [ ] 14.5 使用 PostgreSQL MCP 查询钱包表,验证余额扣减:`balance` = 2000(10000 - 8000) - [ ] 14.6 使用 PostgreSQL MCP 查询钱包流水表,验证流水记录:`transaction_subtype` = "self_purchase",`amount` = -8000,`remark` = "购买套餐" - [ ] 14.7 使用 PostgreSQL MCP 查询套餐使用表(`tb_package_usage`),验证套餐已激活:`status` = 1 ## 15. 集成测试:代理代购场景 - [ ] 15.1 使用 PostgreSQL MCP 工具创建测试数据:一级代理(成本价 8000)、二级代理(成本价 10000,parent_shop_id = 一级代理)、一级代理钱包(余额 10000)、IoT 卡(shop_id = 二级代理店铺 ID)、套餐分配配置 - [ ] 15.2 使用 Postman/curl 调用后台订单创建 API,一级代理账号创建订单,支付方式 wallet,选择二级代理的卡和套餐 - [ ] 15.3 验证响应:`payment_status` = 2,`operator_id` = 一级代理店铺 ID,`buyer_id` = 二级代理店铺 ID,`purchase_role` = "purchase_for_subordinate",`total_amount` = 10000,`actual_paid_amount` = 8000 - [ ] 15.4 使用 PostgreSQL MCP 查询订单表,验证订单记录正确 - [ ] 15.5 使用 PostgreSQL MCP 查询一级代理钱包,验证余额扣减:`balance` = 2000(10000 - 8000) - [ ] 15.6 使用 PostgreSQL MCP 查询钱包流水表,验证流水记录:`transaction_subtype` = "purchase_for_subordinate",`amount` = -8000,`related_shop_id` = 二级代理店铺 ID,`remark` 包含二级代理店铺名称 - [ ] 15.7 使用 PostgreSQL MCP 查询套餐使用表,验证套餐已激活 - [ ] 15.8 使用 PostgreSQL MCP 查询佣金表,验证未产生佣金记录(代理代购不产生佣金) ## 16. 集成测试:平台代购场景(回归测试) - [ ] 16.1 使用 PostgreSQL MCP 工具创建测试数据:代理、IoT 卡(shop_id = 代理店铺 ID)、套餐分配配置(成本价 10000) - [ ] 16.2 使用 Postman/curl 调用后台订单创建 API,平台账号创建订单,支付方式 offline,选择代理的卡和套餐 - [ ] 16.3 验证响应:`payment_status` = 2,`operator_id` = NULL,`operator_type` = "platform",`buyer_id` = 代理店铺 ID,`purchase_role` = "purchased_by_platform",`total_amount` = 10000,`actual_paid_amount` = NULL - [ ] 16.4 使用 PostgreSQL MCP 查询订单表,验证订单记录正确 - [ ] 16.5 使用 PostgreSQL MCP 查询套餐使用表,验证套餐已激活 - [ ] 16.6 验证平台代购逻辑未被破坏(不扣款、立即激活、产生佣金) ## 17. 集成测试:订单查询场景 - [ ] 17.1 使用 PostgreSQL MCP 工具创建测试数据:一级代理、二级代理、多个订单(自购、代购、被代购) - [ ] 17.2 使用 Postman/curl 调用后台订单列表 API,一级代理账号查询订单列表(不指定 purchase_role) - [ ] 17.3 验证响应包含:buyer_id = 一级代理的订单 + operator_id = 一级代理的订单 - [ ] 17.4 使用 Postman/curl 调用后台订单列表 API,一级代理账号查询订单列表,指定 `purchase_role=self_purchase` - [ ] 17.5 验证响应只包含自购订单 - [ ] 17.6 使用 Postman/curl 调用后台订单列表 API,一级代理账号查询订单列表,指定 `purchase_role=purchase_for_subordinate` - [ ] 17.7 验证响应只包含为下级代理购买的订单 ## 18. 集成测试:边界场景 - [ ] 18.1 测试钱包余额不足:代理钱包余额 5000,创建订单金额 8000,验证返回错误"余额不足" - [ ] 18.2 测试并发扣款:模拟两个请求同时为同一钱包扣款,验证乐观锁生效,只有一个请求成功 - [ ] 18.3 测试幂等性:同一买家对同一载体的同一套餐组合短时间内重复创建订单,验证返回相同订单 ID,不重复扣款 - [ ] 18.4 测试 H5 端 wallet 订单:使用 H5 端 API 创建 wallet 订单,验证订单状态为待支付(`payment_status` = 1),不影响后台逻辑 ## 19. 数据回填(可选) - [x] 19.1 编写数据回填脚本,将现有 `payment_method = 'offline'` 且 `is_purchase_on_behalf = true` 的订单回填 `purchase_role = 'purchased_by_platform'` 和 `operator_type = 'platform'` - [ ] 19.2 在测试环境执行回填脚本,验证历史订单可正常查询 ## 20. 文档更新 - [x] 20.1 更新接口文档说明订单创建 API 的行为变更(后台 wallet 支付一步完成) - [x] 20.2 更新接口文档说明订单响应新增字段的含义 - [x] 20.3 更新接口文档说明订单列表 API 新增 `purchase_role` 查询参数 ## 21. 生产环境部署准备 - [ ] 21.1 在测试环境充分验证所有场景通过 - [x] 21.2 准备生产环境迁移脚本和回滚脚本 - [x] 21.3 准备灰度发布计划:代码部署 → 观察日志 → 验证核心功能 → 全量发布 - [x] 21.4 准备监控指标:订单创建成功率、钱包扣款成功率、错误日志(余额不足、并发冲突)