# 枚举与状态字段规范

**背景**：系统历史上存在 int/string 枚举混用、description 与常量定义脱节、响应字段不统一等问题，导致前端映射错误、接口文档失真。本规范统一所有枚举相关写法。

---

## 1. 类型选择：int vs string

### 选择规则

| 场景 | 类型 | 理由 |
|------|------|------|
| **状态类**：表示生命周期阶段（待支付→已完成→已关闭） | `int` | 便于范围查询、数值比较、DB 索引优化 |
| **布尔状态**：启用/禁用、激活/未激活 | `int` | 与全局常量统一，`0=禁用, 1=启用` |
| **类型/方式类**：支付方式、订单类型、运营商、平台 | `string` | 语义更清晰，无需记忆数字含义 |
| **标识符类**：平台类型（web/h5/all）、角色来源 | `string` | 约定字符串值，自文档化 |

### 判断依据

```
这个字段表示"现在处于哪个阶段"？ → int
这个字段表示"它属于哪种类别/使用什么方式"？ → string
```

### ✅ 正确示例

```go
// 状态类 → int
Status        int    `json:"status"`          // 充值状态：1=待支付, 2=已支付...
PaymentStatus int    `json:"payment_status"`  // 订单支付状态

// 类型/方式类 → string
PaymentMethod string `json:"payment_method"`  // "wechat" | "offline"
OrderType     string `json:"order_type"`      // "single_card" | "device"
Platform      string `json:"platform"`        // "web" | "h5" | "all"
```

### ❌ 错误示例

```go
// ❌ 状态用 string（无法范围查询，数字对比失效）
Status string `json:"status"` // "pending" | "completed"

// ❌ 支付方式用 int（前端必须维护不直观的数字映射）
PaymentMethod int `json:"payment_method"` // 1=微信, 2=线下
```

---

## 2. Int 状态值约定

### 2.1 通用禁用/启用

**必须使用全局常量，禁止自定义**：

```go
// pkg/constants/constants.go
StatusDisabled = 0 // 禁用
StatusEnabled  = 1 // 启用
```

✅ 正确：

```go
Status int `gorm:"comment:状态(0-禁用 1-启用);default:1"`
```

❌ 禁止（与全局常量语义相反）：

```go
// ❌ 不可以用 1=启用 2=禁用 这种方式
Status int `gorm:"comment:状态(1-启用 2-禁用)"`
```

### 2.2 生命周期状态

从 **1** 开始递增（0 保留，避免与"未赋值/零值"混淆）：

```go
// 充值状态（标准示例）
const (
    RechargeStatusPending   = 1 // 待支付
    RechargeStatusPaid      = 2 // 已支付
    RechargeStatusCompleted = 3 // 已完成
    RechargeStatusClosed    = 4 // 已关闭
    RechargeStatusRefunded  = 5 // 已退款
)
```

### 2.3 特殊值

- 跳跃值（如 `99`）仅用于明确需要人工干预的异常状态，必须加注释说明原因
- 禁止在同一枚举中混用 0 起始和 1 起始

---

## 3. String 枚举值约定

- 全小写字母 + 下划线 `snake_case`（禁止驼峰、禁止大写）
- 必须在 `pkg/constants/` 定义常量，禁止硬编码字符串
- DTO 中必须加 `validate:"oneof=..."` 约束

```go
// pkg/constants/constants.go
const (
    PaymentMethodWechat  = "wechat"   // 微信支付
    PaymentMethodOffline = "offline"  // 线下转账
)

// DTO 中
PaymentMethod string `json:"payment_method" validate:"required,oneof=wechat offline" description:"支付方式 (wechat:微信支付, offline:线下转账)"`
```

❌ 禁止：

```go
// ❌ 硬编码字符串
if record.PaymentMethod == "wechat" { ... }

// ❌ 大写或驼峰
const PaymentMethodWechat = "Wechat"
const PaymentMethodWechat = "WECHAT"
```

---

## 4. Constants 是唯一真相来源

**DTO 的 description 枚举列表必须与 `pkg/constants/` 定义完全一致。**

### 操作规程

1. 先在 `pkg/constants/` 定义（或查找已有）枚举常量及注释
2. 将常量的注释**原文抄写**到 DTO description

```go
// 步骤1：constants.go 中定义
const (
    RechargeStatusPending   = 1 // 待支付
    RechargeStatusPaid      = 2 // 已支付
    RechargeStatusCompleted = 3 // 已完成
    RechargeStatusClosed    = 4 // 已关闭
    RechargeStatusRefunded  = 5 // 已退款
)

// 步骤2：DTO description 从上面抄写，格式统一
Status int `json:"status" description:"状态 (1:待支付, 2:已支付, 3:已完成, 4:已关闭, 5:已退款)"`
```

### 禁止行为

```go
// ❌ description 中的枚举值与 constants 不一致（本次 bug 根因）
// constants: RechargeStatusCompleted=3（已完成）
// dto 写的:  3:已取消  ← 完全错误！
Status int `json:"status" description:"状态 (1:待支付, 2:已完成, 3:已取消)"`
```

---

## 5. description 格式标准

**统一格式**：`字段含义 (值1:中文含义1, 值2:中文含义2, ...)`

规则：
- 必须用**中文圆括号外**、**英文括号内**
- 值和含义之间用**冒号** `:`，不用等号 `=`
- 多个值之间用**逗号加空格** `, `
- 含义必须是**中文**，不可用英文

```go
// ✅ 统一格式
Status       int    `description:"状态 (1:待支付, 2:已支付, 3:已完成, 4:已关闭, 5:已退款)"`
Platform     string `description:"适用端口 (all:全部, web:Web后台, h5:H5端)"`
UserType     int    `description:"用户类型 (1:超级管理员, 2:平台用户, 3:代理账号, 4:企业账号)"`

// ❌ 格式混乱
Status int `description:"状态 (0=禁用, 1=启用)"`       // 用等号
Status int `description:"0=禁用 1=启用"`               // 无括号无逗号
Status int `description:"状态：0待生效 1生效中 2已用完"` // 格式不统一
```

---

## 6. Response DTO 的状态字段

**所有 Response DTO 中的 int 状态字段，必须同时提供对应的 `xxx_name` 文字字段。**

这是防止前端映射错误的关键措施。

```go
// ✅ Response DTO 标准写法
type AgentRechargeResponse struct {
    Status     int    `json:"status"      description:"状态 (1:待支付, 2:已支付, 3:已完成, 4:已关闭, 5:已退款)"`
    StatusName string `json:"status_name" description:"状态名称（中文）"`
    // ...
}

// Service 层 toResponse 函数中赋值
func statusName(status int) string {
    switch status {
    case constants.RechargeStatusPending:
        return "待支付"
    case constants.RechargeStatusPaid:
        return "已支付"
    case constants.RechargeStatusCompleted:
        return "已完成"
    case constants.RechargeStatusClosed:
        return "已关闭"
    case constants.RechargeStatusRefunded:
        return "已退款"
    default:
        return "未知"
    }
}
```

**字段命名约定**：
- `status` → `status_name`
- `payment_status` → `payment_status_name`
- `commission_status` → `commission_status_name`

**例外**：Request DTO（查询过滤、创建请求）不需要 `_name` 字段。

---

## 7. Code Review 检查项

```
□ int/string 类型选择是否符合规则？
□ 禁/启用是否用了 0=禁用, 1=启用？（禁止 1=启用, 2=禁用）
□ string 枚举常量是否在 pkg/constants/ 定义？
□ dto description 是否从 constants 注释原文抄写？
□ description 格式是否统一（冒号、逗号、括号）？
□ Response DTO 是否同时有 status int 和 status_name string？
□ 是否有遗漏的枚举值没有在 description 中列出？
```

---

## 8. 现存已知不一致（技术债，待修复）

| 文件 | 问题 | 正确值 |
|------|------|--------|
| `internal/model/shop_package_allocation.go` | `1-启用 2-禁用` | 应改为 `0-禁用 1-启用` |
| `internal/model/system.go` | `1-启用 2-禁用` | 需确认是否改 DB |
| `internal/model/dto/agent_recharge_dto.go` | description `2:已完成, 3:已取消` | 应为 `2:已支付, 3:已完成, 4:已关闭, 5:已退款` |
| `internal/model/dto/asset_wallet_dto.go` | 只有 `status_text` 无 `status int` | 需补充 `status int` |

> ⚠️ 已有数据的状态起始值（如 `1-启用 2-禁用`）改变前必须做 DB 数据迁移，改动前先评估影响。
