## MODIFIED Requirements ### Requirement: 同时只能有一个生效中的主套餐 系统 SHALL 确保载体(设备/卡)同一时刻只能有一个 `package_type=formal` 且 `status=1` 的套餐。 **到期检测扩展**:过期检测 SHALL 同时覆盖 `status=1`(生效中)和 `status=2`(已耗尽)的主套餐。当 `expires_at <= now` 时,无论套餐当前是生效中还是已耗尽,均视为到期,触发下一个排队套餐激活。 **数据一致性保证**: - 购买时检查:查询 `WHERE (iot_card_id/device_id)=? AND status=1 AND master_usage_id IS NULL` - 并发控制:使用数据库事务避免并发插入多个生效中主套餐 - 激活时二次检查:激活前再次查询是否有生效中主套餐,避免并发激活 #### Scenario: 首次购买主套餐立即生效 - **GIVEN** 载体无任何主套餐记录 - **WHEN** 用户购买主套餐并支付成功 - **THEN** 系统创建 PackageUsage:`status=1`,`priority=1`,`activated_at=支付完成时间`,`expires_at=根据 calendar_type 计算`,`master_usage_id=NULL` #### Scenario: 购买第二个主套餐自动排队 - **GIVEN** 载体已有 1 个生效中的主套餐(`priority=1`,`status=1`) - **WHEN** 用户购买第 2 个主套餐 - **THEN** 系统创建 PackageUsage:`status=0`,`priority=2`,无 `activated_at` 和 `expires_at` #### Scenario: 生效中主套餐到期触发下一个激活 - **GIVEN** 卡 C1 有生效中主套餐(`status=1`,`expires_at=过去时间`)和一个排队套餐(`status=0`) - **WHEN** 过期检测任务扫描到该套餐 - **THEN** 生效中套餐更新为 `status=3 Expired`,加油包级联失效,下一个排队套餐激活为 `status=1` - **AND** 如卡因流量耗尽停机(`stop_reason=traffic_exhausted`)且已实名,触发自动复机 #### Scenario: 已耗尽(Depleted)主套餐到期后触发下一个激活 - **GIVEN** 卡 C2 有已耗尽主套餐(`status=2 Depleted`,`expires_at=过去时间`)和一个预购排队套餐(`status=0`) - **WHEN** 过期检测任务扫描(查询条件:`status IN (1,2) AND expires_at <= now AND master_usage_id IS NULL`) - **THEN** 耗尽套餐更新为 `status=3 Expired`,加油包级联失效,预购排队套餐激活为 `status=1` - **AND** 如卡因流量耗尽停机且已实名,触发自动复机 #### Scenario: 已耗尽套餐到期时加油包级联失效 - **GIVEN** 卡 C3 有耗尽主套餐(`status=2`,`expires_at=过去时间`),以及绑定到该主套餐的 1 个加油包(`status=1`,尚有剩余流量) - **WHEN** 过期检测任务处理该主套餐 - **THEN** 主套餐更新为 `status=3`,加油包更新为 `status=4 Invalidated`(即使尚有剩余流量) - **AND** 下一个排队主套餐激活 #### Scenario: 已耗尽套餐到期且无下一个排队套餐 - **GIVEN** 卡 C4 有耗尽主套餐(`status=2`,`expires_at=过去时间`),无排队套餐 - **WHEN** 过期检测任务处理该主套餐 - **THEN** 耗尽套餐更新为 `status=3`,加油包级联失效,无新套餐激活 - **AND** 卡的业务状态更新为 `IotCardStatusSuspended=4`(已停用)