## Context

当前 `tb_order` 仅存储 `buyer_id`（个人客户 ID 或店铺 ID），售后需验证订单归属时必须跨表关联 `tb_personal_customer_phone` 查询手机号，增加了查询复杂度且在客户数据变更后可能出现不一致。

`tb_order_item` 已有 `package_name` 快照字段，但缺少 `package_type` 快照。套餐删除后 `tb_package.package_type` 不可查，导致历史订单的套餐类型信息永久丢失，影响报表统计和套餐组合校验。

相关 Store 方法已存在且可直接复用：
- `PersonalCustomerStore.GetByID(ctx, id)` → 返回 `model.PersonalCustomer`，含 `Nickname` 字段
- `PersonalCustomerPhoneStore.GetPrimaryPhone(ctx, customerID)` → 返回 `model.PersonalCustomerPhone`，含 `Phone` 字段

## Goals / Non-Goals

**Goals:**
- `tb_order` 新增 `buyer_phone`、`buyer_nickname` 字段，下单时快照买家信息
- `tb_order_item` 新增 `package_type` 字段，下单时快照套餐类型
- C 端下单（`client_order.Service.CreateOrder`）自动填充三个快照字段
- 后台下单（`order.Service`）在个人客户代购场景填充 `buyer_phone`、`buyer_nickname`；所有场景填充 `package_type`
- 后台订单列表支持按 `buyer_phone` 精确过滤
- 后台订单 DTO（`OrderResponse`）新增 `buyer_phone`、`buyer_nickname` 字段
- 历史数据：`package_type` 从 `tb_package` 回填；`buyer_phone`/`buyer_nickname` 留空

**Non-Goals:**
- 不修改代理商订单的 `buyer_phone`/`buyer_nickname`（代理商无个人手机号概念，留空）
- 不修改 C 端订单详情 DTO（C 端用户无需看到自己的手机号）
- 不实现手机号模糊搜索（仅精确匹配，避免全表扫描）
- 不同步历史订单的 `buyer_phone`/`buyer_nickname`（历史数据留空，不回填）

## Decisions

### 决策 1：快照字段允许为空

`buyer_phone`、`buyer_nickname` 设为 `VARCHAR NOT NULL DEFAULT ''` 或 `VARCHAR NULL`。

**选择**：`VARCHAR NULL`，不设默认值。

**理由**：代理商订单无手机号，强制默认空字符串会导致按手机号过滤时干扰结果（空字符串会匹配到 `buyer_phone = ''` 的查询）。NULL 值语义更清晰，过滤时 `buyer_phone = ?` 自动跳过 NULL 行。

### 决策 2：买家信息的查询策略（不阻塞下单）

`GetByID` 和 `GetPrimaryPhone` 查询失败时仅记录日志，不返回错误，下单流程继续。

**理由**：买家快照是辅助信息，不影响核心业务（套餐激活、支付、佣金）。若因网络抖动或数据缺失导致查询失败而拒绝下单，损失远大于快照字段为空的代价。

### 决策 3：package_type 从已有套餐对象直接读取

`buildOrderItems` 中的 `pkg` 参数已是 `*model.Package` 对象，含 `PackageType` 字段，无需额外查询。

**理由**：零额外数据库查询，直接复用已加载的套餐数据。

### 决策 4：买家信息注入位置

在 `buildPendingOrder` 函数或其调用处注入，而非在 `buildOrderItems` 里。

**理由**：`buyer_phone`/`buyer_nickname` 属于 `Order` 表字段，与 `OrderItem` 无关，职责分离清晰。

### 决策 5：buyer_phone 普通索引

`buyer_phone` 建普通 B-tree 索引（允许 NULL 的列 PostgreSQL 默认跳过 NULL 值入索引，稀疏索引天然节省空间）。

**理由**：售后场景按手机号过滤是低频辅助查询，普通索引足够，无需唯一索引（同一手机号可有多个订单）。

## Risks / Trade-offs

- **[风险] 历史订单 buyer_phone/buyer_nickname 为空** → 缓解：明确文档说明历史数据为空，售后系统在展示时标注"下单时未记录"
- **[风险] package_type 回填脚本执行期间锁表** → 缓解：使用 `UPDATE ... WHERE package_type IS NULL LIMIT 500` 分批回填，或在低峰期执行
- **[权衡] C 端 Service 新增两个 Store 依赖** → 接受：Store 注入是项目标准模式，结构体字段注入不影响测试性

## Migration Plan

### 部署步骤

1. 执行数据库迁移（新增字段，不修改现有列）
2. 部署新版本代码（包含 package_type 回填逻辑或手动执行 SQL）
3. 验证：新订单的三个快照字段是否正确填充
4. 可选：验证 buyer_phone 过滤接口是否返回正确结果

### 历史数据回填 SQL

```sql
-- 回填 package_type（仅回填能匹配到套餐的记录）
UPDATE tb_order_item oi
SET package_type = p.package_type
FROM tb_package p
WHERE oi.package_id = p.id
  AND oi.package_type IS NULL
  AND oi.deleted_at IS NULL;
```

### 回滚策略

新增字段均为 NULL，回滚代码后字段保留但不被读写，无副作用。迁移本身可通过 `ALTER TABLE DROP COLUMN` 回滚（数据丢失，但新字段为快照信息，回滚成本低）。

## Open Questions

- 无
