## Context

当前套餐价格在多个链路里被直接读取，后台配置、C 端展示、订单创建、自动购包和代理授权都在用同一批字段，但它们需要的其实不是同一种值。与此同时，赠送套餐已经被业务明确为独立语义，既不是普通可售套餐的别名，也不能靠 `package_type` 或数值 0 去推断。这个变更会同时影响 Handler、Service、Store、Model 和迁移脚本，必须先把语义拆开，再改各条链路。

## Goals / Non-Goals

**Goals:**
- 建立普通可售套餐的原始价与生效价分层。
- 在建议零售价未配置时，统一回退到成本价。
- 普通可售套餐不能显式配置 0 作为售卖价，0 只保留给赠送语义。
- 把赠送套餐做成独立语义，平台专属，禁止代理分配和 C 端自购。
- 保留原始配置值，避免把回退后的值写回配置页。
- 完成历史数据与存量记录的迁移回填，并输出人工复核清单。

**Non-Goals:**
- 不重做套餐类型体系，也不把 gift 语义塞进 `package_type`。
- 不新增外部依赖，不改用新的队列或存储组件。
- 不在本次变更里重构整个套餐管理域，只处理价格与赠送政策相关路径。

## Decisions

### 1. 价格状态与价格数值分开存

采用独立的价格配置状态来区分“未配置”“赠送 0”“已配置为非 0”，原始建议零售价继续保留，生效售价由服务层计算。

**原因**：普通可售套餐不能显式卖 0，但赠送套餐仍可能使用 0，不能再用数值 0 当哨兵值。

**备选方案**：
- 继续用 0 代表未配置，最简单，但会把合法 0 价和未配置混在一起，不能接受。
- 直接覆盖原始字段为生效价，查询简单，但配置页会丢失真实原值。

### 2. 赠送语义独立于价格和套餐类型

新增独立的赠送语义标记，平台专属限制和价格规则彼此独立。

**原因**：平台-only、不可分配给代理、不可自购，这些都是业务权限问题，不是价格问题。

**备选方案**：
- 仅靠 `package_type=formal/addon` 推断，已经被明确否决。
- 仅靠价格为 0 推断，无法区分合法 0 价与未配置。

### 3. 生效价格在 Service 层统一计算

Handler 只接收和返回值，不做价格推导。Store 只取原始数据，Service 统一计算生效价并下发给下游。

**原因**：价格规则会同时影响 C 端列表、下单、自动购包和授权链路，放在 Service 层更容易复用，也更符合分层职责。

**备选方案**：
- 在 Handler 里临时计算，容易散落和重复。
- 在 Store 里拼接业务规则，会把数据访问和业务逻辑混在一起。

### 4. 迁移采用“先加字段，再回填，再切流”的顺序

先把新语义字段和状态字段上线，再用迁移脚本回填历史数据，最后让读链路切到生效价。

**原因**：这样可以避免一次性改动过大导致历史数据无法解释，也方便回滚。

**备选方案**：
- 直接替换旧字段，风险太高，历史记录会失真。
- 先切读后补数据，会出现短时间内的口径不一致。

### 5. 不引入价格缓存

价格计算只依赖单条套餐和分配记录，属于 O(1) 读取，不新增 Redis 缓存层。

**原因**：这次变更的核心是语义和一致性，不是性能瓶颈。现有分页与查询边界已经足够。

**备选方案**：
- 引入缓存可以做，但会增加失效和回填复杂度，不适合作为首版方案。

## Risks / Trade-offs

- [历史数据无法准确判断是否为赠送 0 价] → 迁移脚本优先读取历史发放记录和配置快照，只有能明确识别为赠送 0 的记录才保留为赠送 0，其余旧 0 一律转为未配置并输出待人工核验清单。
- [赠送语义改动会影响多条购买链路] → 先在 Service 层统一封装判定函数，再逐步替换各入口的直接读取。
- [价格展示口径切换后前端可能误解返回值] → 接口文档明确区分原始值、生效值和赠送语义，前后端一起切换。
- [存量分配记录字段回填出错] → 迁移前后保留回滚脚本，按批次执行并校验记录数。

## Migration Plan

1. 增加价格状态字段、赠送语义字段和平台专属标记，保持原始价格字段不变。
2. 回填存量套餐的价格状态和生效价，优先使用历史配置与发放记录判断。只有能明确识别为赠送 0 的记录才保留为赠送 0，其余旧 0 一律转为未配置，并输出人工复核清单。
3. 回填分配记录、历史订单和自动购包相关快照，让展示链路能读取生效价。
4. 切换 Service 层统一价格解析与赠送校验，再切换 Handler 输出。
5. 验证完成后，清理旧的直接读取路径，保留原始值用于配置页和审计。

## Implementation Notes

- 人工复核清单只负责承接无法正向识别的旧 0 数据，不回写为赠送语义。
- 赠送套餐历史识别优先以后台发放记录为准，补录清单只作为迁移输入，不改变判定标准。
