# UR#33 套餐临期提醒功能总结

## 交付范围

- 新增 `GET /api/admin/expiring-assets`，平台与代理共用当前后台认证和店铺层级权限。
- 仅返回预计最终到期状态为 `exact`、剩余 0 至 15 个上海自然日的卡和设备。
- 返回卡、设备及合计数量，提供 `pink/purple/red` 高亮等级和 0 至 3 天优先标识。
- 每日上海时间 03:00 扫描 15、7、3 天节点，向资产关联个人客户写入站内通知 Outbox。
- C 端继续复用既有通知列表、未读数和弹窗机制，不新增营销、短信或企业微信提醒。

## 数据与幂等

- 临期时间复用 `internal/query/packageexpiry` 的最终到期算法，不建立临期状态快照。
- 列表候选使用当前主套餐到期时间预筛，再批量计算当前与排队主套餐的最终到期时间。
- 通知事件 ID 固化资产类型、资产 ID、到期日、节点和个人客户 ID；定时任务、Outbox Relay 和通知消费者重复执行均不会重复通知。
- `000183_add_package_expiry_scan_indexes` 仅增加卡、设备主套餐到期扫描索引，不新增外键。

## 权限和异常边界

- 平台与超级管理员读取全局范围；代理只读取当前认证上下文的下级店铺范围。
- 代理权限范围为空时查询固定返回空集，不能因空切片退化为全局读取。
- `shop_id` 只缩小既有权限范围；企业账号禁止调用。
- 无关联个人客户的资产正常跳过通知，不影响临期列表事实。

## 联调与回滚

- 前端按 `expiry_level` 渲染颜色，按 `is_priority` 置顶，不自行重算阈值。
- 回滚应用版本后可删除迁移新增的两个扫描索引；已产生的通知和 Outbox 事实不得清表。
- 本 Change 未运行测试、完整构建、LSP 和迁移；按用户后续明确要求已运行 OpenAPI 生成器，另执行 `gofmt`、只读一致性检查和 `git diff --check`。
