# 小程序"预订"按钮灰 — 后端已返正确字段，前端需排查本地判定逻辑

**日期**: 2026-04-20
**服务**: hl-product-service-v2
**类型**: 前端 bug 排查提示（后端无改动）

## 现象
产品 `游牧的森林-短途版`（productId=`2045345825172639746`，CORE 2 档 轻奢/高端，`status=PUBLISHED`）在小程序详情页"预订"按钮**始终为灰色**，即使价格日历已配置。

## 三尺实证结论（后端完全正常）

### 1. DB 价格日历已配置
| tier_seq | tierName | min_date | max_date | 条数 |
|---|---|---|---|---|
| 1 | 轻奢 | 2026-04-01 | 2026-05-10 | 32 |
| 2 | 高端 | 2026-04-01 | 2026-05-22 | 51 |

全档位都有价的日期从 `2026-04-20` 开始（查询执行日 = 2026-04-20）。

### 2. 后端接口 `/mp/product/{id}` 返回
```
isBooking : True          ← 是否预约产品（原语义：订金可不填出行人，true=允许）
startPrice : 4105         ← 起步价（未来可售最低成人售价）
tierPrices : [
  { tierSeq: 1, tierName: 轻奢, startPrice: 4105 },
  { tierSeq: 2, tierName: 高端, startPrice: 4865 }
]
productType : CORE
```
所有字段均为合法值，无 null / <=0 情况。

### 3. 新接口 `/mp/product/{id}/earliest-available-date`
（本次 PR #991 新增，已部署）
对全档位都有价的判定：`HAVING COUNT(DISTINCT tier_seq) >= tierCount`

对该产品应返回：
```json
{
  "date": "2026-04-20",
  "available": true,
  "daysFromToday": 0
}
```

## 前端需要做的

按钮灰的判定**不应**基于 isBooking（原意是"订金规则"，不是"是否可订"）。建议改为：

1. **（推荐）** 调 `GET /mp/product/{id}/earliest-available-date`，`available === true` 才点亮按钮
2. 或至少校验 `startPrice > 0 && tierPrices?.length > 0`
3. **不要**用 `status === 'PUBLISHED'`，因为 mp 详情响应体里没有 status 字段

## 参考字段语义（勿混用）

| 字段 | 原语义 | 说明 |
| --- | --- | --- |
| `isBooking` | 是否预约产品 | true=订金可不填出行人 / false=全款前必须填出行人。**不是"是否可订"** |
| `startPrice` | 起步价 | 未来可售最低成人售价 |
| `tierPrices[].startPrice` | 档位起步价 | 各档位最低成人售价 |
| （新）`earliest-available-date.available` | 全档位是否有可售日期 | true=整产品可订 |

## 已验证
- `curl https://api.test.1814.love:9443/mp/product/2045345825172639746` → `isBooking:true, startPrice:4105`
- `GET /mp/product/{id}/earliest-available-date` 已部署（PR #991）

## 建议排查前端
- 是否读取了已废弃的 `saleStatus` / `onSale` 之类字段（当前后端未返）
- 是否在打包时写死了 `bookable=false` 的本地 mock
- 是否存在小程序端 storage 缓存（`wx.clearStorage` 后重试）
