# 套餐系统升级 - 功能总结

## 概述

本次升级实现了完整的套餐生命周期管理，支持主套餐排队激活、加油包绑定主套餐、囤货待实名激活、流量按优先级扣减等核心功能。

## 核心功能

### 1. 套餐有效期计算

| 类型 | 计算方式 | 示例 |
|------|---------|------|
| 自然月 | 激活月份 + N 个月，月末 23:59:59 | 2月15日激活3个月 → 5月31日 23:59:59 过期 |
| 按天 | 激活日期 + N 天，当天 23:59:59 | 2月15日激活30天 → 3月16日 23:59:59 过期 |

### 2. 主套餐排队机制

```
卡/设备 购买主套餐 A → 立即激活（status=1, priority=1）
       购买主套餐 B → 排队等待（status=0, priority=2）
       主套餐 A 过期 → 自动激活主套餐 B
```

- 同一卡/设备同时只能有一个生效中的主套餐
- 新购买的主套餐自动进入排队状态
- 过期检查每 10 秒执行一次

### 3. 加油包绑定主套餐

```
加油包必须绑定到当前生效的主套餐（master_usage_id）
├── 加油包与主套餐同时生效
├── 主套餐过期时，加油包自动失效（status=4）
└── 流量扣减时，先扣加油包，再扣主套餐
```

- 购买加油包时必须有生效中或待生效的主套餐
- 加油包可设置独立有效期（`has_independent_expiry=true`）

### 4. 囤货待实名激活

```
后台为未实名卡/设备购买套餐
├── 套餐 status=0, pending_realname_activation=true
├── 用户完成实名
├── 轮询系统检测到实名状态变更
└── 自动激活套餐（status=1）
```

- 仅当套餐 `enable_realname_activation=true` 时触发此机制
- H5 端未实名用户无法直接购买套餐

### 5. 流量扣减优先级

扣减顺序：**加油包（按 priority ASC）→ 主套餐**

```go
// 示例：卡有 3 个生效套餐
主套餐：1000MB，已用 500MB
加油包1：100MB，已用 0MB，priority=2
加油包2：200MB，已用 50MB，priority=3

// 本次使用 180MB
扣减顺序：
1. 加油包1 扣 100MB（用完，status=2）
2. 加油包2 扣 80MB
3. 主套餐不变
```

### 6. 流量重置周期

| 周期 | 套餐类型 | 重置时间 | 说明 |
|------|---------|---------|------|
| 日重置 | 所有 | 每天 00:00:00 | `data_reset_cycle=daily` |
| 月重置 | 自然月 | 每月1号 00:00:00 | `calendar_type=natural_month` |
| 月重置 | 按天 | 从激活日期起每30天 | `calendar_type=by_day` |
| 年重置 | 所有 | 每年1月1日 00:00:00 | `data_reset_cycle=yearly` |

### 7. 停复机机制

- **停机条件**：所有生效套餐流量用完（主套餐 + 所有加油包 status=2）
- **复机条件**：购买新套餐或套餐激活后自动复机

## 数据库变更

### 新增表

| 表名 | 说明 |
|------|------|
| `tb_package_usage_daily_record` | 套餐流量日记录 |
| `tb_card_daily_usage` | 卡每日流量使用汇总 |

### 扩展字段

**tb_package 表**：
- `calendar_type`: 有效期类型（natural_month/by_day）
- `data_reset_cycle`: 流量重置周期（daily/monthly/yearly/none）
- `enable_realname_activation`: 是否需要实名后激活
- `duration_days`: 按天套餐的有效天数

**tb_package_usage 表**：
- `priority`: 套餐优先级
- `master_usage_id`: 主套餐 ID（加油包使用）
- `has_independent_expiry`: 加油包是否有独立有效期
- `pending_realname_activation`: 是否待实名激活
- `data_reset_cycle`: 流量重置周期
- `last_reset_at`: 上次重置时间
- `next_reset_at`: 下次重置时间

**tb_iot_card 表**：
- `stopped_at`: 停机时间
- `resumed_at`: 复机时间
- `stop_reason`: 停机原因

**tb_carrier 表**：
- `billing_day`: 运营商计费日（用于流量查询接口的计费周期计算，联通=27，其他=1）

## API 端点

### 新增端点

| 端点 | 方法 | 说明 |
|------|------|------|
| `/api/h5/packages/my-usage` | GET | 客户端查询我的流量使用情况 |
| `/api/admin/package-usage/:id/daily-records` | GET | 查询套餐流量详单 |

### 扩展端点

套餐管理 API 支持新字段：
- `calendar_type`: 有效期类型
- `duration_days`: 有效天数
- `data_reset_cycle`: 重置周期
- `enable_realname_activation`: 实名激活开关

## 轮询任务

| 任务 | 调度频率 | 说明 |
|------|---------|------|
| 套餐激活检查 | 每 10 秒 | 检查过期主套餐，激活排队套餐 |
| 流量重置调度 | 每 10 秒 | 执行日/月/年流量重置 |
| 实名状态检查 | 配置化 | 检测首次实名，触发套餐激活 |

## Asynq 任务

| 任务类型 | 说明 |
|---------|------|
| `task:package:first_activation` | 首次实名激活套餐 |
| `task:package:queue_activation` | 排队主套餐激活 |

## 错误码

| 错误码 | 说明 |
|-------|------|
| `CodePackageActivationConflict` | 套餐正在激活中 |
| `CodeNoMainPackage` | 必须有主套餐才能购买加油包 |
| `CodeRealnameRequired` | 必须先完成实名认证才能购买套餐 |
| `CodeMixedOrderForbidden` | 同订单不能同时购买正式套餐和加油包 |

## 技术实现

### Service 层

| 服务 | 文件 | 职责 |
|------|------|------|
| ActivationService | `activation_service.go` | 套餐激活（实名激活、排队激活） |
| UsageService | `usage_service.go` | 流量扣减、停机检查 |
| ResetService | `reset_service.go` | 流量重置（日/月/年） |
| CustomerViewService | `customer_view_service.go` | 客户端流量查询 |
| DailyRecordService | `daily_record_service.go` | 套餐流量详单 |
| StopResumeService | `stop_resume_service.go` | 停复机操作 |

### 工具函数

| 函数 | 说明 |
|------|------|
| `CalculateExpiryTime()` | 计算套餐过期时间 |
| `CalculateNextResetTime()` | 计算下次重置时间 |

## 性能优化

- 流量重置分批处理：每批最多 10000 条
- 使用 Redis 分布式锁避免套餐激活并发问题
- Asynq 任务重试策略：MaxRetry(3), Timeout(30s)
