# Tasks: refactor-series-binding-to-series-id

## 1. 数据库迁移

- [x] 1.1 创建数据库迁移文件 `migrations/000XXX_refactor_series_binding_to_series_id.up.sql`，重命名 `tb_iot_card.series_allocation_id` 为 `series_id`，`tb_device.series_allocation_id` 为 `series_id`，更新字段注释
- [x] 1.2 创建回滚迁移文件 `migrations/000XXX_refactor_series_binding_to_series_id.down.sql`
- [x] 1.3 验证索引是否存在：检查 `tb_shop_series_allocation` 是否有 `(shop_id, series_id)` 复合索引，如不存在则添加
- [x] 1.4 执行迁移：运行 `migrate up`，验证字段重命名成功，无错误

## 2. Model 层修改

- [x] 2.1 修改 `internal/model/iot_card.go`：将 `SeriesAllocationID` 字段重命名为 `SeriesID`，更新 gorm 标签和注释
- [x] 2.2 修改 `internal/model/device.go`：将 `SeriesAllocationID` 字段重命名为 `SeriesID`，更新 gorm 标签和注释
- [x] 2.3 验证编译：运行 `go build ./internal/model/...`，确认无编译错误

## 3. DTO 层修改

- [x] 3.1 修改 `internal/model/dto/iot_card_dto.go`：更新 `ListStandaloneIotCardRequest` 的查询参数 `SeriesAllocationID` → `SeriesID`
- [x] 3.2 修改 `internal/model/dto/iot_card_dto.go`：更新 `StandaloneIotCardResponse` 的响应字段 `SeriesAllocationID` → `SeriesID`
- [x] 3.3 修改 `internal/model/dto/iot_card_dto.go`：更新 `BatchSetCardSeriesBindngRequest` 的请求字段 `SeriesAllocationID` → `SeriesID`，更新 description 为 "套餐系列ID（0表示清除关联）"
- [x] 3.4 修改 `internal/model/dto/device_dto.go`：更新 `ListDeviceRequest` 的查询参数 `SeriesAllocationID` → `SeriesID`
- [x] 3.5 修改 `internal/model/dto/device_dto.go`：更新 `DeviceResponse` 的响应字段 `SeriesAllocationID` → `SeriesID`
- [x] 3.6 修改 `internal/model/dto/device_dto.go`：更新 `BatchSetDeviceSeriesBindngRequest` 的请求字段 `SeriesAllocationID` → `SeriesID`，更新 description 为 "套餐系列ID（0表示清除关联）"
- [x] 3.7 验证编译：运行 `go build ./internal/model/dto/...`，确认无编译错误

## 4. Store 层修改

- [x] 4.1 修改 `internal/store/postgres/iot_card_store.go`：更新 `ListStandalone` 方法，将过滤条件 `series_allocation_id` 改为 `series_id`
- [x] 4.2 修改 `internal/store/postgres/iot_card_store.go`：更新 `Count` 方法，将过滤条件 `series_allocation_id` 改为 `series_id`
- [x] 4.3 修改 `internal/store/postgres/iot_card_store.go`：重命名方法 `BatchUpdateSeriesAllocation` 为 `BatchUpdateSeriesID`，更新 SQL 字段名
- [x] 4.4 修改 `internal/store/postgres/iot_card_store.go`：重命名方法 `ListBySeriesAllocationID` 为 `ListBySeriesID`，更新 WHERE 条件
- [x] 4.5 修改 `internal/store/postgres/device_store.go`：更新 `List` 方法，将过滤条件 `series_allocation_id` 改为 `series_id`
- [x] 4.6 修改 `internal/store/postgres/device_store.go`：重命名方法 `BatchUpdateSeriesAllocation` 为 `BatchUpdateSeriesID`，更新 SQL 字段名
- [x] 4.7 修改 `internal/store/postgres/device_store.go`：重命名方法 `ListBySeriesAllocationID` 为 `ListBySeriesID`，更新 WHERE 条件
- [x] 4.8 修改 `internal/store/postgres/shop_series_allocation_store.go`：新增方法 `GetByShopAndSeries(ctx, shopID, seriesID)`，实现根据店铺和系列查询分配配置
- [x] 4.9 验证或创建 `internal/store/postgres/package_series_store.go`：如不存在则创建，实现 `GetByID(ctx, id)` 方法
- [x] 4.10 如果创建了新 Store，在 `internal/bootstrap/stores.go` 中注册 `PackageSeriesStore`
- [x] 4.11 验证编译：运行 `go build ./internal/store/...`，确认无编译错误

## 5. Service 层修改 - iot_card

- [x] 5.1 修改 `internal/service/iot_card/service.go`：更新 `ListStandalone` 方法，将过滤条件 key `series_allocation_id` 改为 `series_id`
- [x] 5.2 修改 `internal/service/iot_card/service.go`：更新 `buildStandaloneResponse` 方法，将字段 `SeriesAllocationID` 改为 `SeriesID`
- [x] 5.3 修改 `internal/service/iot_card/service.go`：重构 `BatchSetSeriesBinding` 方法
- [x] 5.4 在 `internal/service/iot_card/service.go` 的 `Service` 结构体中添加 `packageSeriesStore` 依赖（如果不存在）
- [x] 5.5 验证编译：运行 `go build ./internal/service/iot_card/...`，确认无编译错误
- [x] 5.6 运行 `lsp_diagnostics` 检查 `internal/service/iot_card/service.go`，确认无类型错误

## 6. Service 层修改 - device

- [x] 6.1 修改 `internal/service/device/service.go`：更新 `List` 方法，将过滤条件 key `series_allocation_id` 改为 `series_id`
- [x] 6.2 修改 `internal/service/device/service.go`：更新 `buildDeviceResponse` 方法，将字段 `SeriesAllocationID` 改为 `SeriesID`
- [x] 6.3 修改 `internal/service/device/service.go`：重构 `BatchSetSeriesBinding` 方法
- [x] 6.4 在 `internal/service/device/service.go` 的 `Service` 结构体中添加 `packageSeriesStore` 依赖（如果不存在）
- [x] 6.5 验证编译：运行 `go build ./internal/service/device/...`，确认无编译错误
- [x] 6.6 运行 `lsp_diagnostics` 检查 `internal/service/device/service.go`，确认无类型错误

## 7. Service 层修改 - purchase_validation（关键）

- [x] 7.1 修改 `internal/service/purchase_validation/service.go`：重构 `ValidateCardPurchase` 方法
- [x] 7.2 修改 `internal/service/purchase_validation/service.go`：重构 `ValidateDevicePurchase` 方法
- [x] 7.3 更新 `ValidateCardPurchase` 和 `ValidateDevicePurchase` 的错误消息，从 "套餐系列分配不存在" 改为 "该卡/设备未关联套餐系列"
- [x] 7.4 验证编译：运行 `go build ./internal/service/purchase_validation/...`，确认无编译错误
- [x] 7.5 运行 `lsp_diagnostics` 检查 `internal/service/purchase_validation/service.go`，确认无类型错误

## 8. Service 层修改 - commission_calculation（关键）

- [x] 8.1 修改 `internal/service/commission_calculation/service.go`：重构 `CalculateOrderCommission` 方法
- [x] 8.2 修改 `internal/service/commission_calculation/service.go`：重构 `CalculateDeviceOrderCommission` 方法（同样的逻辑）
- [x] 8.3 验证编译：运行 `go build ./internal/service/commission_calculation/...`，确认无编译错误
- [x] 8.4 运行 `lsp_diagnostics` 检查 `internal/service/commission_calculation/service.go`，确认无类型错误

## 9. Service 层修改 - recharge

- [x] 9.1 修改 `internal/service/recharge/service.go`：重构充值相关方法，将获取 `seriesAllocationID` 的逻辑改为直接使用 `seriesID`
- [x] 9.2 修改 `internal/service/recharge/service.go`：更新返佣查询逻辑，使用 `GetByShopAndSeries(shopID, seriesID)` 而不是 `GetByID(allocationID)`
- [x] 9.3 验证编译：运行 `go build ./internal/service/recharge/...`，确认无编译错误
- [x] 9.4 运行 `lsp_diagnostics` 检查 `internal/service/recharge/service.go`，确认无类型错误

## 10. Service 层修改 - order

- [x] 10.1 检查 `internal/service/order/service.go` 中是否有直接使用 `SeriesAllocationID` 的地方，如有则更新为 `SeriesID`
- [x] 10.2 验证编译：运行 `go build ./internal/service/order/...`，确认无编译错误
- [x] 10.3 运行 `lsp_diagnostics` 检查 `internal/service/order/service.go`，确认无类型错误

## 11. Bootstrap 依赖注入

- [x] 11.1 如果创建了 `PackageSeriesStore`，在 `internal/bootstrap/stores.go` 中初始化并添加到 `Stores` 结构体
- [x] 11.2 在 `internal/bootstrap/services.go` 中，为 `iot_card.Service` 和 `device.Service` 注入 `packageSeriesStore` 依赖
- [x] 11.3 验证编译：运行 `go build ./internal/bootstrap/...`，确认无编译错误

## 12. Handler & Routes 层

- [x] 12.1 修改 `internal/routes/iot_card.go`：更新 `/series-binding` 路由的 Description，说明参数从 `series_allocation_id` 改为 `series_id`
- [x] 12.2 修改 `internal/routes/device.go`：更新 `/series-binding` 路由的 Description，说明参数从 `series_allocation_id` 改为 `series_id`
- [x] 12.3 验证 Handler 层代码：`internal/handler/admin/iot_card.go` 和 `device.go` 无需修改（使用 DTO）
- [x] 12.4 验证编译：运行 `go build ./internal/routes/... ./internal/handler/...`，确认无编译错误

## 13. Store 层测试更新

- [x] 13.1 修改 `internal/store/postgres/iot_card_store_test.go`：更新所有测试用例，将 `SeriesAllocationID` 改为 `SeriesID`
- [x] 13.2 修改 `internal/store/postgres/iot_card_store_test.go`：重命名测试函数 `TestIotCardStore_ListBySeriesAllocationID` 为 `TestIotCardStore_ListBySeriesID`
- [x] 13.3 修改 `internal/store/postgres/iot_card_store_test.go`：更新过滤条件测试，将 `series_allocation_id` 改为 `series_id`
- [x] 13.4 修改 `internal/store/postgres/device_store_test.go`：更新所有测试用例，将 `SeriesAllocationID` 改为 `SeriesID`
- [x] 13.5 修改 `internal/store/postgres/device_store_test.go`：重命名测试函数 `TestDeviceStore_ListBySeriesAllocationID` 为 `TestDeviceStore_ListBySeriesID`
- [x] 13.6 新增测试：在 `shop_series_allocation_store_test.go` 中添加 `TestShopSeriesAllocationStore_GetByShopAndSeries` 测试
- [x] 13.7 运行 Store 层测试：`source .env.local && go test -v ./internal/store/postgres/...`，确认全部通过

## 14. Service 层测试更新 - iot_card

- [x] 14.1 修改 `internal/service/iot_card/service_test.go`：更新 `TestIotCardService_BatchSetSeriesBinding` 测试
- [x] 14.2 更新测试数据准备顺序：先 `PackageSeries`，再 `ShopSeriesAllocation`，最后 `IotCard`
- [x] 14.3 运行 Service 层测试：`source .env.local && go test -v ./internal/service/iot_card/...`，确认全部通过

## 15. Service 层测试更新 - device

- [x] 15.1 修改 `internal/service/device/service_test.go`：更新 `TestDeviceService_BatchSetSeriesBinding` 测试
- [x] 15.2 更新测试数据准备顺序：先 `PackageSeries`，再 `ShopSeriesAllocation`，最后 `Device`
- [x] 15.3 运行 Service 层测试：`source .env.local && go test -v ./internal/service/device/...`，确认全部通过

## 16. Service 层测试更新 - purchase_validation

- [x] 16.1 修改 `internal/service/purchase_validation/service_test.go`：更新所有测试用例
- [x] 16.2 运行 Service 层测试：`source .env.local && go test -v ./internal/service/purchase_validation/...`，确认全部通过

## 17. Service 层测试更新 - commission_calculation

- [x] 17.1 修改 `internal/service/commission_calculation/service_test.go`：更新所有测试用例
- [x] 17.2 运行 Service 层测试：`source .env.local && go test -v ./internal/service/commission_calculation/...`，确认全部通过

## 18. Service 层测试更新 - recharge & order

- [x] 18.1 修改 `internal/service/recharge/service_test.go`：更新测试用例，将 `SeriesAllocationID` 改为 `SeriesID`
- [x] 18.2 修改 `internal/service/order/service_test.go`：更新测试用例，将 `SeriesAllocationID` 改为 `SeriesID`
- [x] 18.3 运行 Service 层测试：`source .env.local && go test -v ./internal/service/recharge/... ./internal/service/order/...`，确认全部通过

## 19. 集成测试更新 - iot_card

- [x] 19.1 修改 `tests/integration/iot_card_test.go`：更新 `TestIotCard_BatchSetSeriesBinding` 测试
- [x] 19.2 更新所有子测试用例的 JSON 请求体（约 10 个）
- [x] 19.3 运行集成测试：`source .env.local && cd tests/integration && go test -v -run "TestIotCard_BatchSetSeriesBinding"`，确认全部通过

## 20. 集成测试更新 - device

- [x] 20.1 修改 `tests/integration/device_test.go`：更新 `TestDevice_BatchSetSeriesBinding` 测试
- [x] 20.2 更新所有子测试用例的 JSON 请求体（约 10 个）
- [x] 20.3 运行集成测试：`source .env.local && cd tests/integration && go test -v -run "TestDevice_BatchSetSeriesBinding"`，确认全部通过

## 21. 单元测试更新 - commission_calculation

- [x] 21.1 修改 `tests/unit/commission_calculation_service_test.go`：更新所有测试用例
- [x] 21.2 运行单元测试：`source .env.local && go test -v ./tests/unit/...`，确认全部通过

## 22. 全量测试验证

- [x] 22.1 运行所有测试：`source .env.local && go test -v ./...`，确认全部通过，无遗漏
- [x] 22.2 运行编译检查：`go build ./...`，确认无编译错误
- [x] 22.3 使用 grep 搜索遗漏：`grep -r "SeriesAllocationID" internal/ --include="*.go"`，确认无遗漏
- [x] 22.4 使用 grep 搜索遗漏：`grep -r "series_allocation_id" internal/ --include="*.go"`，确认无遗漏（仅数据库注释除外）

## 23. API 手动测试

- [ ] 23.1 启动本地服务：`go run cmd/api/main.go`
- [ ] 23.2 测试 IoT 卡系列绑定 API：`PATCH /api/admin/iot-cards/series-binding`，使用 Postman 或 curl 发送请求，验证参数 `series_id` 生效
- [ ] 23.3 测试设备系列绑定 API：`PATCH /api/admin/devices/series-binding`，使用 Postman 或 curl 发送请求，验证参数 `series_id` 生效
- [ ] 23.4 测试卡列表查询：`GET /api/admin/iot-cards/standalone?series_id=1`，验证过滤生效
- [ ] 23.5 测试设备列表查询：`GET /api/admin/devices?series_id=1`，验证过滤生效
- [ ] 23.6 检查日志：确认无错误日志，SQL 查询使用 `series_id` 而不是 `series_allocation_id`

## 24. API 文档更新

- [x] 24.1 更新 OpenAPI 文档注释：确保路由描述中明确说明参数从 `series_allocation_id` 改为 `series_id`
- [x] 24.2 重新生成 API 文档：运行 `go run cmd/gendocs/main.go`，生成最新的 OpenAPI spec
- [x] 24.3 验证生成的文档：检查 `docs/admin-openapi.yaml` 中 `/iot-cards/series-binding` 和 `/devices/series-binding` 的参数定义
- [x] 24.4 如有前端文档，更新前端接口文档，说明 BREAKING CHANGE

## 25. 清理和最终验证

- [x] 25.1 删除所有临时代码和注释
- [x] 25.2 运行 `gofmt -w .` 格式化所有代码
- [x] 25.3 运行 `go mod tidy` 清理依赖
- [x] 25.4 再次运行全量测试：`source .env.local && go test -v ./...`，确认全部通过
- [x] 25.5 使用 `git diff` 检查所有改动，确认无遗漏，无多余修改
- [x] 25.6 更新 CHANGELOG（如有），记录 BREAKING CHANGE

## 26. 提交和归档

- [x] 26.1 提交代码：创建 Git commit，使用中文 commit message："重构: 将卡/设备的套餐系列绑定从分配ID改为系列ID"
- [ ] 26.2 运行 OpenSpec 归档：`openspec archive refactor-series-binding-to-series-id`
- [ ] 26.3 验证归档成功：检查 `openspec/changes/archive/` 目录，确认变更已归档
- [ ] 26.4 清理工作目录：删除 `openspec/changes/refactor-series-binding-to-series-id/`（已归档）
