# 小程序产品报价 - `/mp/product/{id}/quote` 接口补早鸟优惠字段

- **日期**: 2026-04-21
- **PR**: [#1052](https://git.1814.love:8443/wx/HL/pulls/1052) (Closes #1047)
- **类型**: FEATURE（响应 VO 新增字段，向前兼容）
- **服务**: hl-product-service-v2（路由 `/mp/product/**`）
- **前端是否需要改动**: **建议改动**（展示早鸟优惠金额 + 最终应付金额给用户）

---

## 一、背景

之前的报价接口只返回「原价合计」与「原尾款/定金」，但订单创建时后端会**自动扣早鸟优惠**（`OrderCreateService.tryApplyEarlyBirdDiscount()`），导致：

- 小程序报价页显示 `￥10,510` 尾款
- 用户提交订单实付却是 `￥10,410`（已扣 100 元早鸟）
- 用户困惑 / 客服被投诉报价不准

本次修复让报价接口**同步返回早鸟优惠信息**，与订单实扣完全一致。

---

## 二、变更接口

| # | 方法 | 路径 | 变更类型 |
|---|------|------|---------|
| 1 | POST | `/mp/product/{id}/quote` | 响应 VO 新增 5 个字段；请求参数不变 |

---

## 三、响应 VO 新增字段（`MpQuoteResultVO`）

| 字段 | 类型 | 含义 | 示例 |
|------|------|------|------|
| `earlyBirdDiscount` | BigDecimal | 早鸟优惠金额（未命中为 0） | `100.00` |
| `earlyBirdPlanId` | Long (字符串) | 命中的早鸟计划 ID（未命中为 null） | `"2046080027564318721"` |
| `earlyBirdPlanName` | String | 命中的早鸟计划名称（展示用，未命中为 null） | `"暑期早鸟"` |
| `finalBalanceAmount` | BigDecimal | **定金模式**下最终应付尾款 = `balanceAmount - earlyBirdDiscount`；全款模式为 `null` | `10410.00` |
| `finalGrandTotal` | BigDecimal | 最终应付总价 = `grandTotal - earlyBirdDiscount` | `11410.00` |

**其它字段不变**：`grandTotal` / `balanceAmount` / `depositAmount` 仍是「原始价」，仅新增的 `finalXxx` 是「扣优惠后最终应付」。

---

## 四、业务规则（与订单实扣对齐）

- 早鸟优惠**按下单日期**生效（调用报价的当下，等于订单创建时的日期）
- **定金模式 (DEPOSIT)**：定金**原价收**，早鸟优惠**从尾款扣**
  - `finalBalanceAmount = balanceAmount - earlyBirdDiscount`
  - `finalGrandTotal = grandTotal - earlyBirdDiscount`（整单一致）
- **全款模式 (FULL)**：早鸟优惠**从全款扣**
  - `finalBalanceAmount = null`
  - `finalGrandTotal = grandTotal - earlyBirdDiscount`
- **未命中 / 查询失败** → `earlyBirdDiscount = 0`，`earlyBirdPlanId = null`，`finalXxx = 原值`
- **人数门槛**：总人数（成人 + 儿童 + 小童 + 幼童）< 计划 `minPeople` → 不命中
- **金额封顶**：早鸟优惠 ≤ 对应金额池（定金模式下不超过尾款；全款模式下不超过总价），不会让尾款 / 全款为负

---

## 五、响应示例

### 定金模式 + 命中早鸟（2 成人）

```json
{
  "code": 200,
  "success": true,
  "data": {
    "adultUnitPrice": 5755.00,
    "totalAdultPrice": 11510.00,
    "grandTotal": 11510.00,
    "paymentType": "DEPOSIT",
    "depositAmount": 1000.00,
    "balanceAmount": 10510.00,

    "earlyBirdDiscount": 100.00,
    "earlyBirdPlanId": "2046080027564318721",
    "earlyBirdPlanName": "BUG20测试-2人",
    "finalBalanceAmount": 10410.00,
    "finalGrandTotal": 11410.00
  }
}
```

### 全款模式 + 命中早鸟

```json
{
  "data": {
    "grandTotal": 8000.00,
    "paymentType": "FULL",
    "balanceAmount": null,

    "earlyBirdDiscount": 500.00,
    "earlyBirdPlanId": "1234567890",
    "earlyBirdPlanName": "全款早鸟",
    "finalBalanceAmount": null,
    "finalGrandTotal": 7500.00
  }
}
```

### 未命中早鸟

```json
{
  "data": {
    "grandTotal": 5000.00,
    "balanceAmount": 4000.00,

    "earlyBirdDiscount": 0,
    "earlyBirdPlanId": null,
    "earlyBirdPlanName": null,
    "finalBalanceAmount": 4000.00,
    "finalGrandTotal": 5000.00
  }
}
```

---

## 六、前端使用建议

### 场景 A：报价页展示早鸟优惠

```tsx
{data.earlyBirdDiscount > 0 && (
  <View className="discount-row">
    <Text>早鸟优惠 {data.earlyBirdPlanName}</Text>
    <Text>-¥{data.earlyBirdDiscount}</Text>
  </View>
)}
```

### 场景 B：定金模式下拆分展示

```tsx
<View>订金（立付）：¥{data.depositAmount}</View>

{data.earlyBirdDiscount > 0 ? (
  <>
    <View className="original">原尾款：<s>¥{data.balanceAmount}</s></View>
    <View className="final">最终尾款：<b>¥{data.finalBalanceAmount}</b></View>
  </>
) : (
  <View>尾款：¥{data.balanceAmount}</View>
)}
```

### 场景 C：全款模式下拆分展示

```tsx
{data.paymentType === 'FULL' && data.earlyBirdDiscount > 0 ? (
  <>
    <View>原价：<s>¥{data.grandTotal}</s></View>
    <View className="final">最终应付：<b>¥{data.finalGrandTotal}</b></View>
  </>
) : (
  <View>应付：¥{data.grandTotal}</View>
)}
```

### ⚠️ 注意

- `earlyBirdPlanId` 是 Long，JSON 序列化为**字符串**防精度丢失（与项目其它 ID 字段一致）
- 未命中早鸟时 `finalBalanceAmount` / `finalGrandTotal` **等于**原值，可以无脑展示 `finalXxx`（无需判断）
- `earlyBirdDiscount` 一定是 `BigDecimal` 形式的数字（`0` 或 `>0`），不会是 null
- **全款模式**下 `finalBalanceAmount` 固定为 `null`（与 `balanceAmount` 语义保持一致）

---

## 七、不兼容变更

**无**。仅响应 VO 新增字段，既有字段语义与取值完全不变。旧客户端（没更新适配的）继续按原逻辑渲染，只是看不到早鸟优惠而已。

---

## 八、回归验证

```bash
# 测试产品（班期内有早鸟"BUG20测试-2人"）
curl -sk -X POST "https://api.test.1814.love:9443/mp/product/2044603865881333762/quote" \
  -H "Content-Type: application/json" \
  -d '{"departureDate":"2026-05-10","adultCount":2,"childCount":0,"tierSeq":1}' | jq .data
```

**预期**：

- `earlyBirdDiscount: 100.00`
- `earlyBirdPlanName: "BUG20测试-2人"`
- `finalBalanceAmount = balanceAmount - 100`
- `finalGrandTotal = grandTotal - 100`
