# 七月迭代实现与接口对接说明

> 面向：产品、前端、测试和联调人员  
> 范围：`deliver-july-iteration-confirmed-scope` 及本期确认“后端已完成，只需前端联调”的需求  
> 接口细节：以 [`docs/admin-openapi.yaml`](../admin-openapi.yaml) 为准，本文只说明关键调用和字段变化。

## 一、先看这几个关键结论

1. **设备没有限速接口**：限速只允许对 IoT 卡 ICCID 操作，固定档位 `-1～8`，不通过设备绑定卡间接限速。
2. **企微模板在企微后台创建**：系统只配置应用、默认发起人、账号 userid 绑定、`template_id` 和控件映射，不在本系统设计审批节点。
3. **代理发起审批走默认发起人**：企微单据使用配置的默认成员发起，但退款、充值等本地业务单仍记录真实代理提交人。
4. **下架套餐续费不新增专用接口**：历史订单和资产信息返回稳定套餐 ID，前端仍调用现有 C 端创建订单接口生成一张新订单。
5. **实名流程由后端返回值决定**：前端使用 `effective_realname_policy`，不能按卡或设备自行推断；设备有任意一张有效绑定卡已实名即视为已实名。
6. **支付按钮由后端返回值决定**：前端使用 `allowed_payment_methods`，不要自行写死卡/设备的微信、支付宝或钱包规则。
7. **设备批量分配只复用导入任务外壳**：复用任务表、队列、进度和结果页；不会进入原 Excel 创建设备逻辑。
8. **系列套餐批量授权后端原本就支持多选**：前端把多选套餐组装为 `packages[]` 调现有接口即可，不需要新后端接口。

## 二、本期需求怎么实现

### 2.1 本期新增或修改后端的需求

| 需求 | 实现方式 | 关键口径检查 |
| --- | --- | --- |
| #189 换货后退款套餐未失效 | 退款处理不再只按旧资产查套餐，而是按原订单和换货迁移关系定位新资产上的对应套餐权益 | 只失效该退款订单产生的权益，不影响其他订单套餐；无前端改动 |
| #188 换货 C 端提醒 | 创建物流换货单时写可靠通知事件，继续走现有 C 端站内通知和未读弹窗 | 不做营销投放、ERP、自动创建其他单据 |
| #182/#44 提交人和审批展示 | 退款、代理充值、换货列表/详情批量解析提交人 ID 和名称；企微详情已具备将审批节点 userid 批量映射系统账号的内部能力 | 当前退款/充值业务 DTO 只稳定返回提交人和审批状态，尚未直接返回审批节点人员列表；如页面必须展示具体审批人，仍需把已有投影接入业务查询 |
| #181 订单渠道和资产标识 | C 端新订单保存正确 `purchase_role`；卡返回 ICCID，设备优先 VirtualNo、为空时返回 IMEI | 不用 SN 冒充订单设备标识；历史空数据不伪造 |
| #41 店铺 C 端登录限制 | 店铺增加 `client_login_disabled`；C 端验证资产后、签发短期令牌前检查所属店铺 | 只阻止新登录，不吊销已有 Token；平台库存保持原行为 |
| #53 卡/设备实名筛选 | 卡按自身实名状态过滤；设备通过有效绑定卡 `EXISTS` 实时判断 | 设备任意一张有效绑定卡已实名即为已实名；未建投影或 Worker |
| #57 退款中禁止换货 | 在现有换货创建入口前检查资产未终结退款 | 拒绝文案为“该资产存在退款申请”；不依赖企微实时接口 |
| #62 三种实名顺序 | 保留 `none/before_order/after_order`，补齐卡和设备最多 500 条批量修改，C 端返回生效策略 | 批量全成全败；设备和下卡冲突时以设备策略为准 |
| #97 主钱包低余额预警 | 消费主钱包扣款事实，余额首次从不少于 100 元跌破 100 元时通知店铺业务员 | 阈值以下不重复；恢复后再次跌破可再次提醒；无业务员不猜接收人 |
| #33 套餐临期提醒 | 每日计算 15/7/3 天节点，提供后台/代理临期列表及数量，并向个人客户发送站内通知 | 高亮为 8～15、4～7、0～3 天；0～3 天优先；不发企微业务员提醒 |
| #34 员工线下代充值 | 原充值创建入口在 `offline` 场景创建通用审批实例和企微提交事件；通过后幂等入主钱包 | 在线扫码充值后置；真实提交人、金额和凭证明文业务快照保留 |
| #35 退款企微审批 | 原退款申请关联唯一通用审批实例，企微标准终态驱动现有退款处理 | 不做原路退款；重复回调/轮询不会重复退款或失效套餐 |
| #37 企业微信审批 | 完成应用连接、通讯录同步、账号绑定、默认发起人、模板映射、提交、回调、详情查询和轮询恢复 | 模板/审批人规则由企微维护；超时结果未知不盲目重提 |
| #36 批量订购套餐 | 单列 CSV，整批选择一个套餐和支付方式，走独立异步任务，逐行复用现有订单和钱包规则 | 不选择代理，不在 CSV 中逐行指定套餐；部分失败不影响其他行 |
| #40 下架套餐老客户续费 | 普通可购列表排除下架套餐；当前使用者可用历史订单或当前资产返回的套餐 ID 调现有下单接口创建新订单 | 无专用续费接口；新客户和代理代购仍拒绝；历史订单不修改 |
| #42 六类导出 | 在现有导出任务框架新增 IoT 卡、套餐、钱包流水、代理充值、退款、换货 datasource | 按现有数据权限和字段来源导出，不建设新字段权限平台 |
| #47 卡固定档位限速 | 后台选择固定档位，按 ICCID 调用 Gateway，并记录操作审计和 Integration Log | 仅 IoT 卡；设备无接口；超时返回结果未知并人工核对 |
| #48 按资产类型配置支付方式 | `system_config` 分别保存卡、设备允许的 `wallet/wechat/alipay` 集合；C 端返回业务场景交集，订单端再次校验 | 至少保留一种；强充和普通充值剔除钱包；前端不可绕过 |
| #49 设备批量分配 | 扩展现有设备导入任务的 `operation_type`，新增分配代理和设置套餐系列两个 CSV 分支 | CSV 单列 VirtualNo/IMEI/SN；复用现有权限、分配、系列绑定和幂等规则 |

### 2.2 后端此前已经完成，主要由前端正确调用或展示

| 需求 | 后端现状 | 前端要做什么 |
| --- | --- | --- |
| #45 换货新旧资产展示/搜索 | 换货列表已分别返回新旧资产，并支持两个独立搜索参数 | 分别提供“旧资产”和“新资产”搜索框，不要继续共用一个字段 |
| #46 预计套餐到期 | 资产详情、C 端资产信息和相关列表已返回预计最终到期字段 | 展示 `estimated_final_expires_at`；按 `is_expiring` 或剩余天数高亮，不要只显示当前套餐到期时间 |
| #55 套餐分配生效条件 | 套餐和分配接口已支持默认值、覆盖值和最终生效值 | 创建/编辑套餐传 `expiry_base`；分配时传 `expiry_base_override`，展示 `effective_expiry_base` |
| #60 店铺联系电话搜索 | 店铺列表已支持 11 位联系电话精确查询 | 将输入值作为 `contact_phone` 查询参数传给店铺列表接口 |
| #86 资产换货标识和跳转 | 资产解析接口已返回 `exchange_trace.previous_asset/next_asset` 和 `can_view` | 仅 `can_view=true` 且存在资产 ID 时允许跳转；该需求前端已对接可保持现状 |
| #38 代理信用额度 | 角色默认额度、店铺实际额度、资金概况和负可用余额均已有接口 | 使用分单位字段；更新时携带钱包 `version`；余额为负数时正常展示 |
| #94 状态同步和运营商回调 | 后端回调、定时触发和原轮询链路已装配 | 通常无前端新调用；状态页面继续读取现有资产状态字段 |
| #96 店铺业务员 | 店铺创建/更新、候选人、列表/详情和筛选都已支持业务员 | 创建/编辑店铺选择 `business_owner_account_id`；列表可按该 ID 筛选并展示名称 |
| #98 换货新资产继承旧店铺 | 换货完成时后端自动继承旧资产店铺归属 | 前端继续调用原换货完成接口，不新增分配步骤 |
| #43 系列套餐批量授权 | 创建授权和管理套餐接口均支持 `packages[]`，单次 1～100 项 | 页面实现套餐多选，一次提交整个数组；删除项使用 `remove=true` |

> 注意：#44 的“提交人”已经可以直接对接；“审批人”目前不是退款、充值列表的稳定返回字段。后端已经能从企微详情快照解析并映射 userid，但当前前端不能把 `processor_id` 当作完整企微审批人列表。

### 2.3 本期明确不由后端处理

- #84 H5 首页隐藏设备下 ICCID：纯前端显示调整，但本期范围确认标记为“不做”。
- #63 授权列表滚动条和字段顺序：纯前端页面调整，需求已关闭。
- #73 行业卡未实名复机：后端保持原有行业卡放行逻辑，不改接口、不改前端。
- #99 原路退款、#52 聚水潭、#51 跨品类换货、#39 分销码/佣金提现：本期不做。
- #168、#90、#75、#64：已关闭，本期不重新修改。

## 三、需求与接口对接表

### 3.1 资产、店铺、订单和换货

| 需求 | 接口 | 参数或返回变化 / 前端调用说明 |
| --- | --- | --- |
| #41 登录限制 | `PUT /api/admin/shops/:id`；`GET /api/admin/shops`；`GET /api/admin/shops/:id` | 更新请求增加可选 `client_login_disabled`；列表/详情返回同名布尔值。C 端仍调 `POST /api/c/v1/auth/verify-asset`，受限时直接展示后端错误，不会返回资产令牌 |
| #53 实名筛选 | `GET /api/admin/iot-cards/standalone`；`GET /api/admin/devices` | 查询参数增加 `real_name_status=0|1`；响应已有 `real_name_status`、`real_name_status_name` |
| #62 实名顺序 | `PATCH /api/admin/assets/:identifier/realname-mode`；`POST /api/admin/iot-cards/batch-update-realname-policy`；`POST /api/admin/devices/batch-update-realname-policy` | 单条传 `realname_policy`；批量传 `asset_ids[] + realname_policy`，最多 500 条 |
| #62/#48 C 端初始化 | `GET /api/c/v1/asset/info?identifier=...` | 使用 `effective_realname_policy`、`realname_required`、`real_name_status`、`allowed_payment_methods`；前端不要自行覆盖 |
| #45 换货搜索 | `GET /api/admin/exchanges` | 使用 `old_asset_keyword`、`new_asset_keyword` 两个独立参数，可同时传并按 AND 组合 |
| #57/#98 换货 | `POST /api/admin/exchanges`；`POST /api/admin/exchanges/:id/complete` | 请求结构不变；存在退款时创建接口返回业务错误；完成后店铺归属由后端继承 |
| #188 换货提醒 | `GET /api/c/v1/notifications/unread-count`；`GET /api/c/v1/notifications`；`PUT /api/c/v1/notifications/:id/read` | 无新增通知接口；前端继续使用现有未读数、列表和已读接口弹窗展示 |
| #181 订单字段 | `GET /api/admin/orders`；`GET /api/admin/orders/:id`；`GET /api/c/v1/orders`；`GET /api/c/v1/orders/:id` | 返回正确 `purchase_role`、`asset_identifier`；设备标识为 VirtualNo 优先、IMEI 兜底 |
| #40 下架套餐续费 | `GET /api/c/v1/asset/info`；`GET /api/c/v1/orders`；`GET /api/c/v1/orders/:id`；`POST /api/c/v1/orders/create` | 资产信息返回 `current_package_id`，历史订单返回 `package_ids`；前端把选中的套餐 ID、资产 `identifier` 和当前允许的 `payment_method` 传给原创建订单接口 |
| #46/#86 资产展示 | `GET /api/admin/assets/resolve/:identifier`；`GET /api/c/v1/asset/info` | 返回预计最终到期字段；后台解析额外返回 `exchange_trace`，跳转前检查 `can_view` |

### 3.2 店铺业务员、信用和套餐授权

| 需求 | 接口 | 参数或返回变化 / 前端调用说明 |
| --- | --- | --- |
| #60 联系电话搜索 | `GET /api/admin/shops?contact_phone=11位号码` | 精确查询；可与店铺名称、编号等条件组合 |
| #96 店铺业务员 | `GET /api/admin/shops/business-owner-candidates`；`POST /api/admin/shops`；`PUT /api/admin/shops/:id`；`GET /api/admin/shops` | 创建/更新传 `business_owner_account_id`；列表可用同名参数筛选，响应展示账号 ID、名称和可用状态 |
| #38 信用额度 | `PUT /api/admin/roles/:id/default-credit`；`PUT /api/admin/shops/:id/credit-limit`；`GET /api/admin/shops/fund-summary` | 角色接口配置新建代理默认值；店铺接口传 `credit_enabled + credit_limit + version`；金额单位均为分 |
| #55 套餐默认生效条件 | `POST /api/admin/packages`；`PUT /api/admin/packages/:id`；`GET /api/admin/packages/:id` | 请求使用 `expiry_base=from_activation|from_purchase`；响应展示默认生效条件名称 |
| #55 分配覆盖 | `POST /api/admin/shop-package-batch-allocations`；`PATCH /api/admin/shop-package-allocations/:id/expiry-base` | 分配时使用 `expiry_base_override`；`null` 表示跟随套餐默认值 |
| #43 系列套餐多选 | `POST /api/admin/shop-series-grants`；`PUT /api/admin/shop-series-grants/:id/packages` | `packages` 是 1～100 项数组，每项包含 `package_id`、`cost_price`，删除时传 `remove=true` |

### 3.3 审批、退款和员工线下代充值

| 需求 | 接口 | 参数或返回变化 / 前端调用说明 |
| --- | --- | --- |
| #182/#44 退款 | `GET /api/admin/refunds`；`GET /api/admin/refunds/:id` | 新增/补齐 `submitter_id`、`submitter_name`、`approval_provider`、`approval_status`、`approval_status_name` |
| #182/#44 充值 | `GET /api/admin/agent-recharges`；`GET /api/admin/agent-recharges/:id` | 同上；线下代充值的审批状态只读展示 |
| #44 换货 | `GET /api/admin/exchanges`；`GET /api/admin/exchanges/:id` | 新增/补齐 `submitter_id`、`submitter_name` |
| #34 员工线下代充 | `POST /api/admin/agent-recharges` | 仍用原入口；`payment_method=offline` 时传目标 `shop_id`、金额、1～5 个 `payment_voucher_key` 和备注，创建后等待企微审批 |
| #35 退款申请 | `POST /api/admin/refunds` | 仍用原入口；请求结构保持订单、实收金额、申请金额、凭证、原因等业务字段，创建后等待企微审批 |
| #34/#35 旧按钮 | `POST /api/admin/agent-recharges/:id/offline-pay`；`POST /api/admin/refunds/:id/approve|reject` | 仅兼容存量旧审批记录；企微记录前端不得显示这些操作按钮，最终按发布配置停用 |
| #37 企微应用 | `POST/GET /api/admin/wecom/applications`；`POST /api/admin/wecom/applications/:id/test` | 配置 corp_id、agent_id、Secret、回调 Token、EncodingAESKey；管理端按明文填写 |
| #37 默认发起人和成员 | `POST /api/admin/wecom/applications/:id/members/sync`；`GET /api/admin/wecom/applications/:id/members`；`PUT /api/admin/wecom/applications/:id/default-creator` | 先同步成员，再从可见成员中选择默认 `userid` |
| #37 账号绑定 | `PUT /api/admin/accounts/:id/wecom-binding` | 管理员选择应用可见成员，绑定 `(corp_id,userid)`；不做扫码绑定 |
| #37 场景模板 | `PUT /api/admin/wecom/scenes/:business_type`；`GET /api/admin/wecom/scenes` | `business_type` 为 `refund_approval` 或 `offline_recharge_approval`；传 `application_id`、`template_id`、`control_mapping[]` |
| #37 回调 | `GET/POST /api/callback/wecom/approval/:application_id` | 由企微服务器调用，前端无需调用 |

### 3.4 通知、批量任务、导出和 Gateway

| 需求 | 接口 | 参数或返回变化 / 前端调用说明 |
| --- | --- | --- |
| #97 后台低余额通知 | `GET /api/admin/notifications/unread-count`；`GET /api/admin/notifications`；`PUT /api/admin/notifications/:id/read` | 复用现有后台通知接口；业务员账号正常展示低余额通知 |
| #33 临期列表 | `GET /api/admin/expiring-assets` | 支持资产类型、关键词、店铺、套餐、剩余天数和日期范围；响应含 `summary`、`expiry_level`、`is_priority` |
| #33 C 端提醒 | `GET /api/c/v1/notifications/unread-count`；`GET /api/c/v1/notifications` | 复用现有站内通知，前端在 15/7/3 天节点按未读通知弹窗 |
| #36 批量订购上传 | `POST /api/admin/storage/upload-url` | `purpose=batch_purchase`，上传 UTF-8 单列 CSV 后取得 `file_key` |
| #36 批量订购任务 | `POST /api/admin/asset-package-batch-orders`；`GET /api/admin/asset-package-batch-orders`；`GET /api/admin/asset-package-batch-orders/:id` | 创建传 `file_key + package_id + payment_method`，线下支付另传 `voucher_keys[]`；详情返回逐行结果 |
| #42 业务导出 | `POST /api/admin/export-tasks`；`GET /api/admin/export-tasks`；`GET /api/admin/export-tasks/:id` | `scene` 使用 `iot_card/package/agent_wallet_transaction/agent_recharge/refund/exchange`，筛选条件放 `query` |
| #47 卡限速 | `PUT /api/admin/iot-cards/:iccid/speed-tier` | 请求只传 `code=-1..8`；设备页面不要展示限速入口 |
| #48 后台支付配置 | `GET /api/admin/system-configs`；`PUT /api/admin/system-configs/:key` | Key 为 `c2b.payment.card_allowed_methods`、`c2b.payment.device_allowed_methods`；更新请求的 `value` 是 JSON 数组字符串 |
| #48 C 端支付 | `GET /api/c/v1/asset/info`；`GET /api/c/v1/wallet/recharge-check`；`POST /api/c/v1/orders/create`；`POST /api/c/v1/wallet/recharge` | 展示和提交都使用后端返回的 `allowed_payment_methods`；创建订单时 `payment_method` 必传，微信场景按接口要求传 `app_type` |
| #49 分配 CSV 上传 | `POST /api/admin/storage/upload-url` | `purpose=device_batch_allocation`，上传单列 CSV 后取得 `file_key` |
| #49 创建设备分配任务 | `POST /api/admin/devices/import/allocations` | 传 `file_key + operation_type + target_id`；`operation_type=assign_shop|assign_series` |
| #49 查询任务 | `GET /api/admin/devices/import/tasks`；`GET /api/admin/devices/import/tasks/:id` | 复用原设备导入任务页面，新增展示 `operation_type`、`operation_name`、`target_id`、`status_name` |

## 四、前端本期最容易漏掉的工作

- 换货列表拆成新、旧资产两个搜索参数。
- 资产预计到期展示使用“预计最终到期”，并按临期字段高亮。
- 套餐分配页面传递默认/覆盖生效条件。
- 店铺页面接入联系电话查询、业务员选择和 C 端登录限制开关。
- 系列套餐授权页面真正使用现有 `packages[]` 做多选提交。
- C 端实名流程只读取 `effective_realname_policy`。
- C 端支付按钮只读取 `allowed_payment_methods`。
- 历史订单续费继续调用原创建订单接口，不等待新续费接口。
- 企微退款和线下代充值只读展示审批状态，隐藏本地人工审批按钮。
- 卡页面提供固定档位限速；设备页面不得出现限速入口。
- 设备批量分配继续复用原设备导入任务列表/详情页面，但根据 `operation_type` 改标题和结果说明。

## 五、联调和验收边界

- 当前有两个需要在联调时明确的接口边界：
  - #44：退款/充值列表已经返回提交人和审批状态，但没有直接返回企微审批节点人员列表；现有 userid 映射能力尚未接入这两个业务 DTO。
  - #49：设备批量分配创建服务允许平台和代理账号，但复用的设备导入任务列表/详情 Handler 目前仍沿用“仅平台用户可查看”的旧限制。若本期只允许平台操作则前端应隐藏代理入口；若要求代理自行查看任务，需要再统一后端权限。
- OpenAPI 已生成，新增路径和 DTO 已检查；设备限速旧契约不存在。
- 本次没有执行数据库迁移、完整构建、自动化测试、LSP 或真实企微/Gateway 闭环。
- 企微、Gateway、Redis/Asynq、对象存储配置和回滚步骤见 [`七月迭代联调交付说明.md`](七月迭代联调交付说明.md)。
- 涉及金额的字段默认单位为分；前端展示时统一转换，提交时不要传浮点元金额。
