# 套餐接口架构图

## 1. 整体架构

```
┌─────────────────────────────────────────────────────────────────┐
│                         HTTP 请求                                │
│              POST /api/admin/packages                            │
└────────────────────────────┬────────────────────────────────────┘
                             │
                    ┌────────▼────────┐
                    │  认证中间件      │
                    │ (AdminAuth)     │
                    └────────┬────────┘
                             │
        ┌────────────────────▼────────────────────┐
        │         路由注册 (routes/admin.go)       │
        │  registerPackageRoutes()                │
        │  ↓                                       │
        │  internal/routes/package.go             │
        └────────────────────┬────────────────────┘
                             │
        ┌────────────────────▼────────────────────┐
        │    PackageHandler                       │
        │  (internal/handler/admin/package.go)    │
        │                                         │
        │  - List()                               │
        │  - Create()                             │
        │  - Get()                                │
        │  - Update()                             │
        │  - Delete()                             │
        │  - UpdateStatus()                       │
        │  - UpdateShelfStatus()                  │
        │  - UpdateRetailPrice()                  │
        └────────────────────┬────────────────────┘
                             │
        ┌────────────────────▼────────────────────┐
        │    PackageService                       │
        │  (internal/service/package/service.go)  │
        │                                         │
        │  业务逻辑：                              │
        │  - 虚流量校验                           │
        │  - 周期类型校验                         │
        │  - 代理权限检查                         │
        │  - DTO 转换                             │
        └────────────────────┬────────────────────┘
                             │
        ┌────────────────────▼────────────────────┐
        │    PackageStore                         │
        │  (internal/store/postgres/...)          │
        │                                         │
        │  - PackageStore                         │
        │  - PackageSeriesStore                   │
        │  - ShopPackageAllocationStore           │
        │  - ShopSeriesAllocationStore            │
        └────────────────────┬────────────────────┘
                             │
        ┌────────────────────▼────────────────────┐
        │    PostgreSQL 数据库                    │
        │                                         │
        │  - tb_package                           │
        │  - tb_package_series                    │
        │  - tb_shop_package_allocation           │
        │  - tb_shop_series_allocation            │
        └─────────────────────────────────────────┘
```

---

## 2. 分层详细结构

### Handler 层
```
PackageHandler
├── List(c *fiber.Ctx)
│   └── 解析查询参数 → 调用 Service.List() → 返回分页结果
├── Create(c *fiber.Ctx)
│   └── 解析请求体 → 调用 Service.Create() → 返回创建结果
├── Get(c *fiber.Ctx)
│   └── 解析 ID 参数 → 调用 Service.Get() → 返回详情
├── Update(c *fiber.Ctx)
│   └── 解析 ID 和请求体 → 调用 Service.Update() → 返回更新结果
├── Delete(c *fiber.Ctx)
│   └── 解析 ID 参数 → 调用 Service.Delete() → 返回删除结果
├── UpdateStatus(c *fiber.Ctx)
│   └── 解析 ID 和状态 → 调用 Service.UpdateStatus() → 返回结果
├── UpdateShelfStatus(c *fiber.Ctx)
│   └── 解析 ID 和上架状态 → 调用 Service.UpdateShelfStatus() → 返回结果
└── UpdateRetailPrice(c *fiber.Ctx)
    └── 解析 ID 和零售价 → 调用 Service.UpdateRetailPrice() → 返回结果
```

### Service 层
```
PackageService
├── Create(ctx, req)
│   ├── 校验套餐编码唯一性
│   ├── 校验虚流量配置
│   │   ├── 启用时虚流量 > 0
│   │   └── 虚流量 ≤ 真流量
│   ├── 校验周期类型和时长
│   │   ├── natural_month: 需要 duration_months
│   │   └── by_day: 需要 duration_days
│   ├── 查询套餐系列信息
│   ├── 创建 Package 模型
│   └── 调用 Store.Create()
├── List(ctx, req)
│   ├── 获取用户类型和店铺 ID
│   ├── 代理用户额外过滤
│   │   └── INNER JOIN tb_shop_package_allocation
│   ├── 应用其他过滤条件
│   └── 调用 Store.List()
├── Get(ctx, id)
│   └── 调用 Store.GetByID()
├── Update(ctx, id, req)
│   ├── 获取现有套餐
│   ├── 更新字段
│   └── 调用 Store.Update()
├── Delete(ctx, id)
│   └── 调用 Store.Delete()
├── UpdateStatus(ctx, id, status)
│   └── 更新状态字段
├── UpdateShelfStatus(ctx, id, shelfStatus)
│   └── 更新上架状态字段
└── UpdateRetailPrice(ctx, id, price)
    └── 更新零售价字段
```

### Store 层
```
PackageStore
├── Create(ctx, pkg)
│   └── db.Create(pkg)
├── GetByID(ctx, id)
│   └── db.First(&pkg, id)
├── GetByCode(ctx, code)
│   └── db.Where("package_code = ?", code).First(&pkg)
├── Update(ctx, pkg)
│   └── db.Save(pkg)
├── Delete(ctx, id)
│   └── db.Delete(&Package{}, id)
└── List(ctx, opts, filters)
    ├── 构建基础查询
    ├── 代理用户 JOIN 分配表
    ├── 应用过滤条件
    │   ├── package_name (模糊搜索)
    │   ├── series_id
    │   ├── status
    │   ├── shelf_status
    │   └── package_type
    ├── 计算总数
    ├── 应用分页
    └── 执行查询
```

---

## 3. 数据模型关系

```
┌──────────────────────────────────────────────────────────────┐
│                      tb_package (套餐表)                      │
├──────────────────────────────────────────────────────────────┤
│ id (PK)                                                       │
│ package_code (UNIQUE)                                         │
│ package_name                                                  │
│ series_id (FK → tb_package_series)                           │
│ package_type (formal/addon)                                   │
│ duration_months                                               │
│ duration_days ← 关键字段                                      │
│ calendar_type (natural_month/by_day)                         │
│ real_data_mb                                                  │
│ virtual_data_mb                                               │
│ enable_virtual_data                                           │
│ data_reset_cycle                                              │
│ expiry_base                                                   │
│ cost_price                                                    │
│ suggested_retail_price                                        │
│ shelf_status                                                  │
│ status                                                        │
│ created_at, updated_at, deleted_at                           │
└──────────────────────────────────────────────────────────────┘
         │
         │ 1:N
         │
         ▼
┌──────────────────────────────────────────────────────────────┐
│                  tb_package_usage (套餐使用表)                │
├──────────────────────────────────────────────────────────────┤
│ id (PK)                                                       │
│ order_id (FK)                                                 │
│ package_id (FK → tb_package)                                 │
│ usage_type (single_card/device)                              │
│ iot_card_id / device_id                                       │
│ data_limit_mb                                                 │
│ data_usage_mb                                                 │
│ activated_at, expires_at                                      │
│ status                                                        │
│ priority                                                      │
│ master_usage_id (加油包关联主套餐)                            │
└──────────────────────────────────────────────────────────────┘
         │
         │ 1:N
         │
         ▼
┌──────────────────────────────────────────────────────────────┐
│            tb_package_usage_daily_record (日记录表)           │
├──────────────────────────────────────────────────────────────┤
│ id (PK)                                                       │
│ package_usage_id (FK)                                         │
│ date                                                          │
│ daily_usage_mb                                                │
│ cumulative_usage_mb                                           │
└──────────────────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────────────┐
│                  tb_package_series (套餐系列表)               │
├──────────────────────────────────────────────────────────────┤
│ id (PK)                                                       │
│ series_code (UNIQUE)                                          │
│ series_name                                                   │
│ description                                                   │
│ enable_one_time_commission                                    │
│ one_time_commission_config (JSONB)                           │
│ status                                                        │
└──────────────────────────────────────────────────────────────┘
         │
         │ 1:N
         │
         ▼
┌──────────────────────────────────────────────────────────────┐
│            tb_shop_package_allocation (分配表)                │
├──────────────────────────────────────────────────────────────┤
│ id (PK)                                                       │
│ shop_id (FK → tb_shop)                                       │
│ package_id (FK → tb_package)                                 │
│ retail_price                                                  │
│ shelf_status                                                  │
│ status                                                        │
└──────────────────────────────────────────────────────────────┘
```

---

## 4. 请求流程示例

### 创建套餐（按天）

```
1. HTTP 请求
   POST /api/admin/packages
   {
     "package_code": "PKG_DAY_30",
     "package_name": "30天套餐",
     "calendar_type": "by_day",
     "duration_days": 30,
     ...
   }

2. Handler.Create()
   ├─ 解析请求体 → CreatePackageRequest
   └─ 调用 Service.Create(ctx, req)

3. Service.Create()
   ├─ 校验 package_code 唯一性
   │  └─ Store.GetByCode(ctx, "PKG_DAY_30") → nil ✓
   ├─ 校验虚流量配置 ✓
   ├─ 校验周期类型
   │  ├─ calendar_type = "by_day" ✓
   │  ├─ duration_days = 30 ✓
   │  └─ 校验通过 ✓
   ├─ 查询套餐系列 (可选)
   ├─ 创建 Package 模型
   └─ Store.Create(ctx, pkg)

4. Store.Create()
   └─ db.Create(pkg)
      └─ INSERT INTO tb_package (...)

5. Service 返回 PackageResponse
   └─ Handler 返回 HTTP 200

6. HTTP 响应
   {
     "code": 0,
     "data": {
       "id": 1,
       "package_code": "PKG_DAY_30",
       "duration_days": 30,
       ...
     },
     "msg": "success"
   }
```

---

## 5. 代理用户套餐查询流程

```
1. 代理用户请求
   GET /api/admin/packages?status=1

2. Handler.List()
   └─ Service.List(ctx, req)

3. Service.List()
   ├─ 获取用户类型: UserTypeAgent ✓
   ├─ 获取店铺 ID: 10 ✓
   └─ Store.List(ctx, opts, filters)

4. Store.List()
   ├─ 构建基础查询
   │  └─ SELECT * FROM tb_package
   ├─ 检测代理用户
   │  └─ isAgent = true ✓
   ├─ 添加 JOIN 条件
   │  └─ INNER JOIN tb_shop_package_allocation
   │     ON tb_shop_package_allocation.package_id = tb_package.id
   │     AND tb_shop_package_allocation.deleted_at IS NULL
   ├─ 添加 WHERE 条件
   │  └─ WHERE tb_shop_package_allocation.shop_id = 10
   │     AND tb_shop_package_allocation.status = 1
   ├─ 应用其他过滤
   │  └─ AND tb_package.status = 1
   ├─ 计算总数
   ├─ 应用分页
   └─ 执行查询

5. 返回结果
   └─ 只返回已分配给店铺 10 的套餐
```

---

## 6. 字段验证流程

```
CreatePackageRequest
├─ package_code
│  └─ validate: required, min=1, max=100
│     └─ Service: 检查唯一性
├─ package_name
│  └─ validate: required, min=1, max=255
├─ calendar_type
│  └─ validate: oneof=natural_month by_day
│     └─ Service: 根据类型校验时长字段
├─ duration_months
│  └─ validate: required, min=1, max=120
│     └─ Service: 当 calendar_type=natural_month 时必填
├─ duration_days
│  └─ validate: omitempty, min=1, max=3650
│     └─ Service: 当 calendar_type=by_day 时必填
├─ real_data_mb
│  └─ validate: omitempty, min=0
├─ virtual_data_mb
│  └─ validate: omitempty, min=0
│     └─ Service: 启用虚流量时必须 > 0 且 ≤ real_data_mb
├─ enable_virtual_data
│  └─ Service: 虚流量配置校验
├─ cost_price
│  └─ validate: required, min=0
└─ suggested_retail_price
   └─ validate: omitempty, min=0
```

