# PRD：IoT 卡风险状态限制 / 导入任务操作人与批量失效 / 凭证多图 / 预充值备注

Status: ready-for-agent

---

## Problem Statement

运营团队在日常工作中遇到四个独立痛点：

1. **风险停机/已销户卡无法有效拦截**：运营商侧将某些卡置为"风险停机"或"已销户"状态后，系统仍允许对这些卡发起复机操作，且轮询系统仍持续轮询这些卡，浪费资源并可能产生错误数据。

2. **导入任务无操作人信息**：IoT 卡导入任务和设备导入任务的列表中没有"操作人"字段，无法追溯是谁发起的批量操作。同时缺少批量失效订单套餐的操作入口，运营只能逐条手动处理。

3. **凭证只能上传一张**：后台创建订单和发起退款时，只允许上传一张凭证图片，无法满足多张凭证的业务场景（如多笔转账记录、多页合同等）。

4. **代理预充值缺少备注字段**：代理预充值操作无法附加运营备注，导致事后无法追溯充值的业务原因。

---

## Solution

1. 在复机接口和轮询系统中增加对 `gateway_extend` 字段的检查，当独立卡处于"风险停机"或"已销户"状态时，拒绝复机并停止轮询。

2. 在所有导入任务中快照操作人姓名；新增"批量失效订单套餐"导入任务，支持通过 CSV 批量将订单下的套餐置为失效状态。

3. 将订单、退款、代理预充值的凭证字段改为支持最多5张的 jsonb 数组存储。

4. 在代理预充值记录上新增 `remark` 备注字段，供运营创建时填写，并在列表/详情接口中返回。

---

## User Stories

1. 作为运营人员，当我尝试对"风险停机"状态的独立卡发起复机时，我希望系统拒绝并提示原因，以免产生无效操作。
2. 作为运营人员，当我尝试对"已销户"状态的独立卡发起复机时，我希望系统拒绝并提示原因，以免对已注销的卡发起无意义请求。
3. 作为运营人员，我希望"风险停机"和"已销户"的独立卡不再参与系统轮询，以免浪费轮询资源并产生噪音数据。
4. 作为运营人员，当轮询系统检测到某张独立卡的网关状态变为"风险停机"或"已销户"时，我希望系统自动将该卡的轮询关闭，而不需要手动干预。
5. 作为运营人员，我希望在 IoT 卡导入任务列表中看到"操作人"姓名，以便追溯每次批量导入是谁发起的。
6. 作为运营人员，我希望在设备导入任务列表中看到"操作人"姓名，以便追溯每次批量导入是谁发起的。
7. 作为运营人员，我希望通过上传 CSV 文件批量将一批订单的套餐置为失效，以便快速处理异常订单。
8. 作为运营人员，我希望在创建批量失效任务时能填写备注和上传凭证（最多5张），以便记录操作原因。
9. 作为运营人员，我希望批量失效任务完成后能看到成功数、失败数，以及失败原因（如订单号不存在），以便核查处理结果。
10. 作为运营人员，我希望批量失效套餐只影响套餐记录本身，不触发退款和其他业务流程，让轮询系统自行根据套餐状态处理停机。
11. 作为运营人员，我希望在创建后台订单时可以上传最多5张凭证，以便完整记录线下付款证明。
12. 作为运营人员，我希望在发起退款申请时可以上传最多5张凭证，以便完整记录退款证明材料。
13. 作为运营人员，我希望在代理预充值列表中看到备注内容，以便了解每笔预充值的业务背景。
14. 作为运营人员，我希望在创建代理预充值订单时填写备注，以便记录本次充值的原因或说明。
15. 作为开放 API 调用方，当我通过 Open API 对"风险停机"或"已销户"的独立卡调用复机接口时，我希望收到明确的错误响应。

---

## Implementation Decisions

### 需求一：风险停机/已销户卡限制

- **判断字段**：`tb_iot_card.gateway_extend` 原文值 = `"风险停机"` 或 `"已销户"`。新增两个常量 `GatewayCardExtendRiskStop`、`GatewayCardExtendCancelled`。
- **判断范围**：仅 `is_standalone = true` 的独立卡（未绑定设备的卡）。绑定设备的卡不受此限制。
- **复机拦截**：在 `ManualStartCard`（后台管理员入口）和 `ResumeCard`（Open API 入口）两个函数中，调用网关前先读取 DB 中已落库的 `gateway_extend`，若命中则返回业务错误，不实时查询网关。
- **轮询停止**：轮询状态处理器（`PollingCardStatusHandler`）在将新的 `gateway_extend` 写入 DB 后，检查若命中风险状态且 `is_standalone=true`，则调用现有的 `UpdatePollingStatus(false)` 将 `enable_polling` 写为 `false`，并跳过 `requeueCard`，终止该卡的轮询循环。
- **错误码**：复机被拒绝时返回业务错误，提示信息明确说明原因（"风险停机"/"已销户"）。

### 需求二：导入任务操作人与批量失效

**操作人快照：**
- `tb_iot_card_import_task` 和 `tb_device_import_task` 表各新增 `creator_name varchar` 字段。
- 新的批量失效任务表同样包含此字段。
- 创建任务时从当前登录用户快照姓名写入，旧数据留空（不回填）。
- 列表响应 DTO 新增 `creator_name` 字段。

**批量失效订单套餐任务：**
- 新建模型、Store、Service、Handler，参照现有 IoT 卡导入任务的结构（CSV 上传 → 异步任务处理）。
- CSV 格式：单列 `order_no`（无表头行约定沿用现有导入任务的处理方式）。
- 创建接口额外支持：`remark`（字符串备注）和最多5张凭证（jsonb 存储）。
- 处理逻辑：按行读取 `order_no` → 查询 `Order` → 找到 `PackageUsage WHERE order_id = ? AND status NOT IN (3, 4)` → 批量更新 `status = 4`（已失效）。
- 订单不存在：记为失败（fail），继续处理后续行。
- 订单存在但旗下套餐全部已是终态（status 3 或 4）：记为成功（success）。
- 不触发退款，不直接操作 IoT 卡停机，由轮询系统根据套餐状态自行评估。
- 任务表命名：`tb_order_package_invalidate_task`。

### 需求三：凭证多图改造

- `tb_order.payment_voucher_key`（varchar 500）→ `jsonb`，存储 `[]string`，最多5个元素。
- `tb_refund.refund_voucher_key`（varchar 500）→ `jsonb`，存储 `[]string`，最多5个元素。
- `tb_agent_recharge_record.payment_voucher_key`（varchar 500）→ `jsonb`，存储 `[]string`，最多5个元素。
- **数据迁移**（同一 Migration 语句）：非空旧值 `"some_key"` → `["some_key"]`；空字符串 → `[]`。生产环境由运营手动执行 Migration。
- 所有涉及凭证的请求 DTO 字段由 `string` 改为 `[]string`，validate 标签限制 `max=5`，每项 `max=500`。
- 所有涉及凭证的响应 DTO 字段改为 `[]string`。
- 不限制文件类型（上传 URL 获取接口层面不变，仅存储 key 列表）。

### 需求四：代理预充值备注

- `tb_agent_recharge_record` 新增 `remark text` 字段（可为空）。
- `CreateAgentRechargeRequest` 新增 `remark` 字段（`omitempty`，无最大长度强限制，建议 1000 字符内）。
- `AgentRechargeResponse` 新增 `remark` 字段（列表和详情接口均返回）。
- 创建后不可修改，无需新增修改接口。

---

## Testing Decisions

本项目禁止自动化测试。验证方式：

- **PostgreSQL MCP**：核查 `tb_iot_card.enable_polling` 在风险状态卡被轮询后是否正确置为 `false`；核查 `tb_package_usage.status` 在批量失效任务执行后是否按预期更新；核查 jsonb 字段迁移结果。
- **curl / Postman**：对风险停机卡调用复机接口，验证返回正确错误码；上传多张凭证创建订单，验证存储和返回正确；创建代理预充值时附带备注，验证列表返回。

---

## Out of Scope

- 已绑定设备的卡（`is_standalone=false`）不受"风险停机/已销户"限制约束。
- 批量失效订单套餐不触发退款流程。
- 批量失效不直接调用网关停机，依赖轮询系统自行评估。
- 历史导入任务的 `creator_name` 不回填。
- 凭证文件类型校验（文件类型限制不在本次范围内）。
- 代理预充值备注创建后不可修改（无修改接口）。

---

## Further Notes

- 凭证字段改为 jsonb 后，前端需同步更新上传组件支持多选；本 PRD 只覆盖后端改造。
- 批量失效任务的凭证和备注字段，与代理预充值备注字段、以及需求三的多凭证设计保持一致的数据形态，便于后续统一处理。
- "风险停机"和"已销户"的 `gateway_extend` 原文值来自上游网关，若上游修改返回值需同步更新常量。
