# PRD:UR#45 换货资产标识与新旧资产独立搜索 Status: ready-for-agent --- ## Problem Statement 换货单中的卡资产标识没有统一:旧资产可能保存虚拟号,新资产可能原样保存操作员输入的 ICCID、接入号或虚拟号,导致同类记录展示不一致。现有列表又只有一个同时匹配新旧资产快照的 `identifier` 参数,无法明确查“旧资产”还是“新资产”,也无法稳定通过卡的其他标识找到对应换货单。 ## Solution 新建换货单时,将卡的新旧资产标识统一规范为 ICCID;设备使用稳定的设备号。换货列表用 `old_asset_keyword` 和 `new_asset_keyword` 替代通用 `identifier`,先将关键词解析为候选卡/设备 ID,再按换货单的新旧资产主键筛选。两个参数可独立使用,同时提供时按 AND 组合。 ## User Stories 1. 作为运营人员,我希望换货列表分别展示旧资产和新资产,避免混淆换出和换入对象。 2. 作为运营人员,我希望通过 ICCID、接入号或虚拟号搜索卡对应的旧资产换货记录。 3. 作为运营人员,我希望用相同标识搜索新资产,但不会误命中旧资产。 4. 作为运营人员,我希望同时填写新旧资产条件,以定位一条明确的换货关系。 5. 作为维护人员,我希望新换货单保存规范化快照,历史展示不再取决于操作员当时输入了哪种标识。 6. 作为维护人员,我希望大结果集查询不逐条反查资产,也不绕过换货单数据权限。 ## Implementation Decisions ### 快照规范 - 卡资产的 `old_asset_identifier` 和 `new_asset_identifier` 均保存已解析卡记录的完整 ICCID,不保存请求原文、接入号或虚拟号。 - 设备资产优先保存虚拟号;虚拟号为空时依次使用 IMEI、SN,确保保存稳定的设备标识。 - 规范化覆盖物流换货和直接换货:创建旧资产快照、直接换货的新资产快照、物流发货时的新资产快照均使用同一解析能力。 - 请求仍可使用接口当前支持的任一资产标识定位资产,但保存快照必须取解析后的权威字段。 - 历史换货单不回填、不改写;历史列表继续按原快照展示,新的搜索能力通过资产主键关联命中历史记录。 ### 列表接口 - 复用 `GET /api/admin/exchanges`。 - 新增可选 `old_asset_keyword` 和 `new_asset_keyword`,各自最长 100 个字符。 - 移除新契约中的通用 `identifier`;前后端在同一维护窗口切换,不再把一个参数解释为“新资产或旧资产”。 - 单独提供旧资产关键词时只过滤 `old_asset_type + old_asset_id`;单独提供新资产关键词时只过滤 `new_asset_type + new_asset_id`。 - 两个关键词同时提供时按 AND 组合,并继续与状态、流程类型、创建时间和分页条件按 AND 组合。 - 空参数不增加对应条件;无候选资产或无换货单命中时返回成功空分页,不返回 404。 - 卡关键词对 ICCID、接入号、虚拟号做包含匹配;设备关键词对虚拟号、IMEI、SN 做包含匹配。 - 候选资产查询必须排除软删除资产;换货单查询继续排除软删除记录并应用现有店铺数据范围。 - 候选 ID 解析和换货单过滤使用固定次数的批量查询或数据库子查询,不允许按换货单逐行读取卡或设备。 - `total` 与 `items` 必须使用完全相同的过滤条件,排序继续使用创建时间倒序。 - 响应继续分别返回新旧资产类型、ID、快照标识、状态及状态名称,不改变统一响应外层结构。 ### 校验、权限与架构 - 列表 Handler 对整个请求 DTO 执行校验;非法长度、分页、状态、流程类型或时间参数统一返回 HTTP 400、`code=1001`、`msg=参数验证失败`,详细原因仅写日志。 - 代理账号只能查询其现有店铺范围内的换货单;关键词解析不得扩大最终换货单范围。 - 无权限和不存在的换货单继续使用现有防枚举错误策略,不向客户端暴露候选资产数量或主键。 - 快照规范属于换货写用例的一部分,应由统一资产解析/规范化能力提供,不在多个 Handler 或 Service 分支复制。 - 列表属于读取用例,可在现有 Store 上做简单增量;若候选解析和分页组合已达到复杂查询程度,则收口到 Exchange Query,但不得为此迁移整个换货模块。 - 数据库或候选查询失败返回脱敏的统一内部错误,不得降级为“无结果”。 ### 前端与文档 - 换货列表筛选区将单一资产搜索拆为“旧资产”和“新资产”两个输入框。 - 空值不提交;两个非空值同时提交并展示 AND 查询结果。 - 表格分别显示旧资产类型/标识和新资产类型/标识;卡直接展示后端 ICCID,设备展示后端设备号。 - 前端不把接入号或虚拟号转换为 ICCID,也不在本地过滤当前页。 - 沿用列表加载、空态、失败反馈和分页交互。 - 更新 OpenAPI 参数及描述,重新生成文档;新增 UR#45 中文总结并更新 README 索引。 ## Testing Decisions - 写用例测试分别用 ICCID、接入号、虚拟号创建卡换货,验证新旧快照始终为数据库 ICCID。 - 设备测试覆盖虚拟号、IMEI、SN 输入以及标识优先级。 - 列表 HTTP 集成测试覆盖仅旧关键词、仅新关键词、两个关键词 AND、与状态/时间组合、空参数、无匹配和非法参数。 - 覆盖卡 ICCID、接入号、虚拟号以及设备虚拟号、IMEI、SN 的候选映射。 - 构造历史非规范快照,验证不回填但仍可通过资产主键搜索命中。 - 代理、平台和超级管理员测试验证现有换货单数据范围不被关键词绕过。 - 使用真实 PostgreSQL 执行代表性大结果集测试,验证查询次数固定、无逐行反查,计数和分页结果一致并满足项目性能目标。 - 验证数据库错误返回脱敏 500,不能返回空列表掩盖故障。 - 前端人工验收新旧字段不混列、组合搜索、清空、分页和错误状态。 ## Out of Scope - 不回填或清洗历史换货快照。 - 不新增独立搜索接口或换货关系表。 - 不改变换货状态机、归属继承、资料迁移或完成规则。 - 不做跨新旧资产 OR 搜索;旧通用 `identifier` 不保留为第二套长期语义。 - 不由前端解析或规范化资产标识。 ## Further Notes - UR#45 负责“快照规范与列表检索”;资产详情前代/后代关系由 UR#86 提供,店铺继承由 UR#98 提供。 - UR#86 应直接使用这里形成的不可变快照;历史非规范快照保持原样。