# UR46 资产预计最终到期时间

## 实现范围

后台资产解析、卡列表、设备列表和 C 端资产信息统一返回“预计套餐到期时间”。结果由 `internal/query/packageexpiry` 实时计算，不维护资产级到期快照。

## 计算口径

- 只纳入未软删除、未退款、状态为待生效/生效中/已用完的主套餐；加油包、已过期和已失效记录不参与。
- 按 `priority ASC, created_at ASC, id ASC` 稳定排序，当前套餐真实到期时间为起点，排队套餐从前段结束后的下一时刻接续。
- 新记录只使用购买时计时快照；完整空快照的历史记录才回退套餐当前条款，部分或非法快照返回数据异常。
- 按上海自然日返回剩余天数，只有精确结果且剩余 0 至 15 天时标记临期。

## 接口字段

以下响应统一包含 `estimated_final_expires_at`、`days_until_final_expiry`、`expiry_estimate_status`、`expiry_estimate_status_name` 和 `is_expiring`：

- `GET /api/admin/assets/resolve/:identifier`
- `GET /api/admin/iot-cards`
- `GET /api/admin/devices`
- `GET /api/c/v1/asset/info`

非精确状态的日期与剩余天数字段固定返回 `null`。状态分别为 `exact`、`waiting_activation`、`none`、`invalid_data`。

## 性能与迁移

列表使用每页资产 ID 的一次批量 Query，不逐资产查询套餐。迁移 `000164_add_package_expiry_query_indexes` 为卡和设备主套餐队列读取增加部分索引，可通过对应 down 迁移回滚。

开发测试库在 2026-07-23 执行 `EXPLAIN ANALYZE`：当前 `tb_package_usage` 仅约 30 行，优化器选择顺序扫描，卡查询执行时间约 0.24ms、设备查询约 0.07ms，均低于 50ms 目标；`TestQueryIndexesExplainAnalyze` 固化了该验证命令。
