# card-replacement Specification ## Purpose TBD - created by archiving change add-wallet-transfer-tag-models. Update Purpose after archive. ## Requirements ### Requirement: 换卡记录实体定义 系统 SHALL 定义换卡记录(CardReplacementRecord)实体,记录老卡到新卡的完整转移过程,包括套餐权益、代理关系、所有者信息等。 **核心概念**: - **换卡场景**:老卡损坏、丢失或故障,需要更换新卡 - **权益转移**:老卡的套餐(含剩余流量)、代理关系、所有者信息等全部转移到新卡 - **套餐继续生效**:转移后套餐不作废,剩余流量继续可用 **实体字段**: - `id`:换卡记录 ID(主键,BIGINT) - `replacement_no`:换卡单号(VARCHAR(50),唯一) - `old_card_id`:老卡 ID(BIGINT,关联 tb_iot_card.id) - `old_iccid`:老卡 ICCID(VARCHAR(50),冗余存储,防止老卡被删除后无法追踪) - `new_card_id`:新卡 ID(BIGINT,关联 tb_iot_card.id) - `new_iccid`:新卡 ICCID(VARCHAR(50),冗余存储) - `old_owner_type`:老卡所有者类型(VARCHAR(20)) - `old_owner_id`:老卡所有者 ID(BIGINT) - `old_agent_id`:老卡代理 ID(BIGINT,可空) - `new_owner_type`:新卡所有者类型(VARCHAR(20)) - `new_owner_id`:新卡所有者 ID(BIGINT) - `new_agent_id`:新卡代理 ID(BIGINT,可空) - `package_snapshot`:套餐快照(JSONB,记录转移时的套餐详情) - `replacement_reason`:换卡原因(VARCHAR(20),枚举值:"damaged"-损坏 | "lost"-丢失 | "malfunction"-故障 | "upgrade"-升级 | "other"-其他) - `remark`:备注(TEXT) - `status`:换卡状态(INT,1-待审批 2-已通过 3-已拒绝 4-已完成) - `approved_by`:审批人 ID(BIGINT,可空) - `approved_at`:审批时间(TIMESTAMP,可空) - `completed_at`:完成时间(TIMESTAMP,可空) - `creator`:创建人 ID(BIGINT) - `updater`:更新人 ID(BIGINT) - `created_at`:创建时间(TIMESTAMP,自动填充) - `updated_at`:更新时间(TIMESTAMP,自动填充) - `deleted_at`:删除时间(TIMESTAMP,可空,软删除) **套餐快照 JSON 格式示例**: ```json { "package_id": 3001, "package_name": "月套餐 10GB", "package_code": "PKG-M-001", "data_limit_mb": 10240, "data_usage_mb": 5120, "real_data_usage_mb": 4000, "virtual_data_usage_mb": 1120, "data_remaining_mb": 5120, "activated_at": "2026-01-01T00:00:00Z", "expires_at": "2026-02-01T00:00:00Z", "remaining_days": 15, "order_id": 10001 } ``` #### Scenario: 创建换卡记录 - **WHEN** 用户(ID 为 2001)的老卡(ICCID 为 "8986001")损坏,需要换新卡(ICCID 为 "8986002") - **THEN** 系统创建换卡记录,`old_card_id` 为老卡 ID,`new_card_id` 为新卡 ID,`replacement_reason` 为 "damaged",`status` 为 1(待审批) #### Scenario: 审批通过换卡 - **WHEN** 运营人员(ID 为 999)审批通过换卡记录(ID 为 5001) - **THEN** 系统将换卡记录状态从 1(待审批)变更为 2(已通过),记录 `approved_by` 为 999,`approved_at` 为当前时间 #### Scenario: 完成换卡 - **WHEN** 换卡记录(ID 为 5001)状态为 2(已通过),系统执行换卡操作 - **THEN** 系统将: 1. 记录老卡和新卡的快照信息(所有者、代理、套餐) 2. 将老卡的套餐权益转移到新卡(套餐使用记录的 `iot_card_id` 更新为新卡 ID) 3. 将新卡的 `owner_type` 和 `owner_id` 更新为老卡的值 4. 将新卡的代理关系更新为老卡的值(如有) 5. 将换卡记录状态变更为 4(已完成),记录 `completed_at` 为当前时间 #### Scenario: 拒绝换卡 - **WHEN** 运营人员(ID 为 999)拒绝换卡记录(ID 为 5001),原因为"新卡不符合要求" - **THEN** 系统将换卡记录状态从 1(待审批)变更为 3(已拒绝),记录 `approved_by` 为 999,`approved_at` 为当前时间,`remark` 为拒绝原因 --- ### Requirement: 套餐权益转移 系统 SHALL 在换卡完成后,将老卡的套餐权益(包括剩余流量、过期时间等)转移到新卡,套餐继续生效。 **转移内容**: - 套餐使用记录(`tb_package_usage`) - 剩余流量(`data_limit_mb - data_usage_mb`) - 套餐过期时间(`expires_at`) - 关联的订单信息 **转移规则**: - 老卡的套餐使用记录的 `iot_card_id` 更新为新卡 ID - 剩余流量完整保留 - 套餐过期时间不变 - 如果老卡有多个套餐(正式套餐 + 加油包),全部转移 #### Scenario: 套餐转移 - **WHEN** 老卡有月套餐(剩余 5120 MB 流量,还有 15 天过期) - **THEN** 系统将套餐使用记录的 `iot_card_id` 从老卡 ID 更新为新卡 ID,流量和过期时间保持不变 #### Scenario: 多套餐转移 - **WHEN** 老卡有正式套餐和 2 个加油包 - **THEN** 系统将所有套餐使用记录的 `iot_card_id` 更新为新卡 ID,所有套餐继续生效 --- ### Requirement: 代理关系转移 系统 SHALL 在换卡完成后,将老卡的代理关系转移到新卡。 **转移内容**: - 新卡的 `owner_type` 更新为老卡的 `owner_type` - 新卡的 `owner_id` 更新为老卡的 `owner_id` - 如果老卡通过代理销售,新卡继承相同的代理关系 #### Scenario: 代理关系转移 - **WHEN** 老卡的 `owner_type` 为 "agent",`owner_id` 为 123 - **THEN** 系统将新卡的 `owner_type` 更新为 "agent",`owner_id` 更新为 123 --- ### Requirement: 换卡记录查询 系统 SHALL 支持按老卡 ID、新卡 ID、用户 ID、换卡单号等条件查询换卡记录。 **查询条件**: - 换卡单号(精确匹配) - 老卡 ID(精确匹配) - 新卡 ID(精确匹配) - 老卡 ICCID(精确匹配或模糊匹配) - 新卡 ICCID(精确匹配或模糊匹配) - 换卡状态(单选或多选) - 换卡原因(单选或多选) - 创建时间范围 - 完成时间范围 **分页**: - 默认每页 20 条,最大每页 100 条 - 返回总记录数和总页数 #### Scenario: 按老卡 ICCID 查询换卡记录 - **WHEN** 查询老卡 ICCID 为 "8986001" 的换卡记录 - **THEN** 系统返回所有 `old_iccid` 为 "8986001" 的换卡记录列表 #### Scenario: 按状态查询换卡记录 - **WHEN** 查询状态为 1(待审批)的换卡记录 - **THEN** 系统返回所有 `status` 为 1 的换卡记录列表,按创建时间倒序排列 --- ### Requirement: 换卡数据校验 系统 SHALL 对换卡数据进行校验,确保数据完整性和一致性。 **校验规则**: - `old_card_id`:必填,≥ 1,必须是有效的 IoT 卡 ID - `new_card_id`:必填,≥ 1,必须是有效的 IoT 卡 ID,不能与 `old_card_id` 相同 - `old_iccid`:必填,长度 19-20 字符 - `new_iccid`:必填,长度 19-20 字符,不能与 `old_iccid` 相同 - `replacement_reason`:必填,枚举值 "damaged" | "lost" | "malfunction" | "upgrade" | "other" - `status`:必填,枚举值 1-4 #### Scenario: 换卡时老卡和新卡相同 - **WHEN** 创建换卡记录,`old_card_id` 和 `new_card_id` 都为 1001 - **THEN** 系统拒绝创建,返回错误信息"新卡不能与老卡相同" #### Scenario: 换卡时新卡 ICCID 无效 - **WHEN** 创建换卡记录,`new_iccid` 长度为 15(小于 19) - **THEN** 系统拒绝创建,返回错误信息"ICCID 长度必须为 19-20 字符" #### Scenario: 换卡时老卡不存在 - **WHEN** 创建换卡记录,`old_card_id` 为 99999(不存在的 IoT 卡) - **THEN** 系统拒绝创建,返回错误信息"老卡不存在"