# Requirements

**Project:** 君鸿卡管系统
**Milestone:** v1.0 — MVP 上线准备
**Date:** 2026-03-27

---

## v1 Requirements

### CRITICAL — P0 紧急 Bug 修复（方案 A）

- [x] **CRITICAL-01**: 实名状态常量统一 — `parseRealnameStatus` 写入值 2 改为常量 1，Model 注释统一，全链路使用 `constants.RealNameStatusVerified`
- [x] **CRITICAL-02**: C 端实名校验修复 — `client_order/service.go` 实名判断改用 `constants.RealNameStatusVerified`（依赖 CRITICAL-01）
- [x] **CRITICAL-03**: 实名激活任务断链修复 — `PollingHandler` 注入 `asynq.Client`，删除 RPush 降级方案，改用 `asynq.Client.EnqueueContext()`
- [x] **CRITICAL-04**: 充值回调后自动购包触发 — `recharge/service.go` 在 HandlePaymentCallback 成功后入队 `TaskTypeAutoPurchaseAfterRecharge`；注册 `AutoPurchaseHandler`
- [x] **CRITICAL-05**: 自动购包佣金链补全 — `AutoPurchaseHandler.ProcessTask()` 事务提交成功后入队 `TaskTypeCommission`
- [x] **CRITICAL-06**: 代购订单金额修正 — 场景 5（代理代购下级）`sellerCostPrice` 全部 6 处逐一审查，确保使用 `operatorCostPrice`（非 `buyerCostPrice`）
- [x] **CRITICAL-07**: 设备导入补充 IMEI 字段 — `DeviceRow` 新增 IMEI，`ParseDeviceExcel` 列名映射新增 IMEI，`device_import.go` 导入时填充
- [x] **CRITICAL-08**: 提现冻结并发校验 — `my_commission/service.go` 冻结余额后检查 `RowsAffected == 0`，余额不足并发场景返回错误而非静默继续

### PERM — 企业端权限完善（方案 B）

- [x] **PERM-01**: 移除 Resolve 接口对企业账号的错误拦截 — 企业账号应能查询被授权的资产
- [x] **PERM-02**: Refresh 接口新增企业账号拦截 — 企业只读，不允许主动触发运营商刷新

### FIN — 财务/提现流程完善（方案 C）

- [x] **FIN-01**: 提现审批人字段补全 — `Approve()` 写入 `approved_by` / `approved_at`（当前永远为空）
- [x] **FIN-02**: 提现拒绝 remark 必填校验 — `Reject()` 在 BodyParser 后补充 `validator.Validate()`
- [x] **FIN-03**: 激活配置并发保护 — 提现配置和微信配置激活时，两步 UPDATE 前加 `FOR UPDATE` 行锁

### REALNAME — 实名激活架构重构（方案 G）

- [x] **REALNAME-01**: 移除 `tb_package.enable_realname_activation` 字段，新增 `expiry_base VARCHAR(30)` 字段（`from_activation` / `from_purchase`）
- [x] **REALNAME-02**: `activateMainPackage` 改写激活决策逻辑 — 按卡类型（industry/normal）+ 购买路径（C端/后台囤货）+ `expiry_base` 三维决策
- [x] **REALNAME-03**: `client_order/service.go` 实名检查改为按卡类型判断（`card_category == "normal"`），删除 `packagesNeedRealname()` 函数
- [x] **REALNAME-04**: `ActivateByRealname()` 按 `ExpiryBase` 选择激活时间基准（`from_purchase` 用 CreatedAt，`from_activation` 用当前时刻）
- [x] **REALNAME-05**: 套餐管理 API DTO 更新 — 移除 `enable_realname_activation`，新增 `expiry_base` 枚举字段

### POLL — 轮询系统小修（方案 H）

- [x] **POLL-01**: `polling:protect` 接入调度 — `scheduler.go` 补充 processManualQueue + processTimedQueue 对 protect 任务的调度
- [x] **POLL-02**: gatewayClient=nil 时停复机返回错误 — `stopCardWithRetry` 和 `resumeCardWithRetry` 两处改为返回业务错误而非 nil
- [x] **POLL-03**: packages 接口新增分页 — `GetPackages()` 默认 page=1, pageSize=50，最大 100
- [x] **POLL-04**: Series 删除前检查关联套餐 — `package_series/service.go:Delete()` 前置 `CountBySeriesID()` 检查

### DEVICE — 设备体系完善（方案 D）

- [x] **DEVICE-01**: 设备模型字段扩展 — DB 迁移新增 `online_status / last_online_time / software_version / switch_mode / last_gateway_sync_at`
- [x] **DEVICE-02**: Gateway sync-info 同步接口对接 — `internal/gateway/device.go` 新增 `SyncDeviceInfo()` 方法
- [x] **DEVICE-03**: `tb_device_sim_binding` 新增 `is_current` 字段 — DB 迁移 + Model + 更新逻辑
- [x] **DEVICE-04**: 设备 Refresh + 详情接入 sync-info — `RefreshDevice()` 在刷新卡数据后调用 `SyncDeviceInfo()`，更新设备在线状态/固件版本/当前卡标识

### REFUND — 退款完整功能（方案 I）

- [ ] **REFUND-01**: 新建 `tb_refund_request` 数据模型 — DB 迁移 + GORM Model
- [ ] **REFUND-02**: 退款接口实现 — Handler + Service + Store + 路由注册（Create / List / Get / Approve / Reject / Return / Resubmit 共 7 个接口）
- [ ] **REFUND-03**: 审批通过后按比例扣减代理佣金钱包 — 使用整数算术（`amount * approvedRefund / actualReceived`），禁用 float64，写负向交易流水

### PAY — 支付功能补全（方案 J-1 + J-2）

- [ ] **PAY-01**: 富友支付 JSAPI 接口实现 — 替换留桩，实现预下单 + 返回前端调起支付参数
- [ ] **PAY-02**: 富友支付小程序接口实现 — 与 JSAPI 相同逻辑，tradeType 改为 `LETPAY`
- [ ] **PAY-03**: 新增 `FuiouPayJSAPIResponse` DTO，字段与微信官方 `wx.requestPayment()` 参数对齐
- [ ] **PAY-04**: 平台代理钱包代购 — `CreateLegacy` 支持 `buyerType="" && resourceShopID != nil` 场景，使用资产所属代理的成本价和钱包

### OPS — 运营逻辑修复（方案 J-3 + J-4）

- [ ] **OPS-01**: IoT 卡/设备 status 字段 3/4 业务触发 — 套餐激活成功后更新为 3，最后一个 active 套餐到期后更新为 4
- [ ] **OPS-02**: payment_status 枚举统一 — 删除 C 端 `orderStatusToClientStatus()` 映射函数，C 端直接输出与管理端一致的 1/2/3/4 枚举

### CLEAN — 代码质量清理（方案 F）

- [ ] **CLEAN-01**: 佣金链断裂改为零额待审记录 — 新增 `CommissionStatusPendingReview = 99`，断链时创建 amount=0 的待审记录，不再静默 break
- [ ] **CLEAN-02**: 废弃 sim:status:sync 任务代码删除 — 移除注册行 + 常量 + `internal/task/sim.go` + `internal/service/sync/service.go`
- [ ] **CLEAN-03**: 错误不应被吞 — `asset/service.go` 中 `cards, _ :=` 改为记录 Warn 日志
- [ ] **CLEAN-04**: C 端 Handler 迁移到 Service 层 — `handler/app/client_order.go` 订单查询改为调用 `client_order/service.go`，删除直接 DB 访问
- [ ] **CLEAN-05**: Handler/Service 权限不一致修复 — `handler/admin/order.go` 创建订单时补充钱包支付仅允许代理账号的前置校验
- [ ] **CLEAN-06**: StopResumeCallback / ResumeCallback 注入 — `internal/bootstrap/services.go` 中初始化后调用 `SetStopResumeCallback` 和 `SetResumeCallback`

### TRAFFIC — 流量体系改革（方案 E，破坏性变更）

- [ ] **TRAFFIC-01**: 新建 `tb_card_daily_usage` 日粒度流量表 — DB 迁移 + 唯一约束 `(iot_card_id, date)`
- [ ] **TRAFFIC-02**: 轮询流量写入改为 Redis 增量缓存 — `insertDataUsageRecord()` 仅在有增量时写 `RedisCardDailyTrafficKey`
- [ ] **TRAFFIC-03**: 每日落盘定时任务 — Asynq Scheduler 凌晨 2 点 SCAN Redis Key → UPSERT `tb_card_daily_usage` → 删 Key
- [ ] **TRAFFIC-04**: 流量增量累加 — `calculateFlowUpdates()` 改为 `current_month_usage_mb += increment`，检测运营商重置日，新增 `tb_carrier.data_reset_day` 和 `tb_iot_card.last_gateway_reading_mb`
- [ ] **TRAFFIC-05**: 流量查询层适配 — 新建 `TrafficQueryService.GetDailyUsage()` 兼容"今日 Redis + 历史 DB"两段数据

---

## v2 Requirements（延后）

- 卡/资产批量操作（停/复/注销）— 运营场景必须，当前仅有单卡接口
- 卡/订单批量导出（Excel/CSV）— 财务对账标配，已有导入缺导出
- 套餐到期前微信模板消息提醒 — 用户体验，竞品标配
- 代理商完整资金流水账单（按时间倒序，标注来源类型）
- 下级代理业绩报表（订单数/金额/活跃卡数）
- 卡状态变更历史记录（溯源谁什么时候停了哪张卡）
- SaaS 多租户架构 — v2+ 规划

---

## Out of Scope

- 自动化测试（unit/integration/e2e）— 项目明确不做
- 设备定期轮询（P1-9）— 伪需求，改为按需实时拉取
- P1-2 企业认证中间件 — 复用 AdminAuth，无需新中间件
- P1-3 提现"已到账"确认接口 — 线下打款，系统无法感知
- P2-6 告警通知渠道 — 优先级低，暂不做
- P2-9 asset_type 校验 — Service 层已有兜底
- P2-24 Carrier→Series 强绑定 — 系列可跨运营商
- P2-30 轮询配置通用化 — 设备轮询取消后此问题不存在

---

## Traceability

*（由 roadmapper 在创建 ROADMAP.md 时填充 — 2026-03-27）*

| REQ-ID | Phase | Status |
|--------|-------|--------|
| CRITICAL-01 | Phase 1 | Complete |
| CRITICAL-02 | Phase 1 | Complete |
| CRITICAL-03 | Phase 1 | Complete |
| CRITICAL-04 | Phase 1 | Complete |
| CRITICAL-05 | Phase 1 | Complete |
| CRITICAL-06 | Phase 1 | Complete |
| CRITICAL-07 | Phase 1 | Complete |
| CRITICAL-08 | Phase 1 | Complete |
| PERM-01 | Phase 2 | Complete |
| PERM-02 | Phase 2 | Complete |
| FIN-01 | Phase 2 | Complete |
| FIN-02 | Phase 2 | Complete |
| FIN-03 | Phase 2 | Complete |
| REALNAME-01 | Phase 2 | Complete |
| REALNAME-02 | Phase 2 | Complete |
| REALNAME-03 | Phase 2 | Complete |
| REALNAME-04 | Phase 2 | Complete |
| REALNAME-05 | Phase 2 | Complete |
| POLL-01 | Phase 2 | Complete |
| POLL-02 | Phase 2 | Complete |
| POLL-03 | Phase 2 | Complete |
| POLL-04 | Phase 2 | Complete |
| DEVICE-01 | Phase 3 | Complete |
| DEVICE-02 | Phase 3 | Complete |
| DEVICE-03 | Phase 3 | Complete |
| DEVICE-04 | Phase 3 | Complete |
| REFUND-01 | Phase 4 | Pending |
| REFUND-02 | Phase 4 | Pending |
| REFUND-03 | Phase 4 | Pending |
| PAY-01 | Phase 4 | Pending |
| PAY-02 | Phase 4 | Pending |
| PAY-03 | Phase 4 | Pending |
| PAY-04 | Phase 4 | Pending |
| OPS-01 | Phase 4 | Pending |
| OPS-02 | Phase 4 | Pending |
| CLEAN-01 | Phase 5 | Pending |
| CLEAN-02 | Phase 5 | Pending |
| CLEAN-03 | Phase 5 | Pending |
| CLEAN-04 | Phase 5 | Pending |
| CLEAN-05 | Phase 5 | Pending |
| CLEAN-06 | Phase 5 | Pending |
| TRAFFIC-01 | Phase 6 | Pending |
| TRAFFIC-02 | Phase 6 | Pending |
| TRAFFIC-03 | Phase 6 | Pending |
| TRAFFIC-04 | Phase 6 | Pending |
| TRAFFIC-05 | Phase 6 | Pending |
