feat(product-v2): 统一价格日历接口契约 (PR #1290)

这个提交包含在:
API Changelog Bot 2026-04-23 14:52:16 +08:00
父节点 178fe3d5a4
当前提交 83d251e4f5

查看文件

@ -0,0 +1,166 @@
# 统一价格日历接口 /admin/product/item/{id}/pricing-calendar
**日期**: 2026-04-23
**PR**: #1290 (Closes #1286)
**服务**: hl-product-service-v2
**类型**: feat新增接口,零破坏性
---
## 背景
之前 admin 查价格日历需要分场景调两个不同接口:
- CORE/CUSTOM → `GET /admin/product/item/{id}/price-calendar`
- GROUP小蒙马`GET /admin/product/item/{id}/schedule/list`
**根因**GROUP 定价真相源是 `group_tour_batch.adult_price`(班期表),不走 `product_price_calendar`。前端要按 `productType` 分流调用。
本次新增**统一入口**,后端按 productType 聚合两种数据源,前端一个接口搞定。
---
## 新接口
### `GET /admin/product/item/{id}/pricing-calendar`
**Query 参数**
- `month`(可选):`yyyy-MM`,过滤月份。不传返全部
- `tierSeq`(可选):档位序号,**仅 CORE/CUSTOM 生效**,GROUP 忽略
**响应**
```json
{
"code": 200,
"data": {
"productType": "CORE | GROUP | CUSTOM",
"items": [
{ ... 统一条目 VO ... }
]
}
}
```
### 统一条目 VO 字段表
| 字段 | 类型 | CORE/CUSTOM | GROUP |
|------|------|-------------|-------|
| **通用** |
| `date` | LocalDate | 每日价格日 | 出发日departureDate|
| `adultPrice` | BigDecimal | adultSellPrice | adultPrice |
| `childPrice` | BigDecimal | childSellPrice | childPrice |
| `toddlerDiscount` | BigDecimal | ✅ | ✅ |
| `infantPrice` | BigDecimal | ✅ | ✅ |
| `stock` | Integer | dailyStock | productStockLimit无则 maxParticipants|
| `sold` | Integer | sold | enrolledCount |
| `sellable` | Boolean | dailyStock 空或 sold<stock | batchStatus∈{ENROLLING,NEARLY_FULL} |
| **CORE/CUSTOM 专属** |
| `id` | Long | 日历记录主键 | null |
| `tierSeq` | Integer | 档位序号 | null |
| `priceType` | String | NORMAL/PEAK/HOLIDAY/SPECIAL | null |
| `rangeId` | Long | 批量区间标识 | null |
| **GROUP 专属** |
| `batchId` | Long | null | 班期ID |
| `batchNo` | String | null | 班期编号 |
| `batchName` | String | null | 班期名称 |
| `batchStatus` | String | null | ENROLLING/NEARLY_FULL/FULL/FINISHED/CANCELLING/CANCELLED |
| `endDate` | LocalDate | null | 班期结束日 |
| `enrollmentDeadline` | LocalDate | null | 报名截止日 |
| `maxRooms` | Integer | null | 总房间数 |
| `bookedRooms` | Integer | null | 已预订房间数 |
---
## 前端渲染建议
```javascript
const resp = await api.getUnifiedPricingCalendar(productId, { month, tierSeq });
switch (resp.data.productType) {
case 'CORE':
case 'CUSTOM':
// 矩阵日历: 按 tierSeq × date 渲染档位 × 日期表格
renderMatrixCalendar(resp.data.items);
break;
case 'GROUP':
// 列表日历: 按出发日渲染班期卡片,显示 batchName/batchStatus/endDate
renderBatchCalendar(resp.data.items);
break;
}
```
---
## 示例
### CORE 响应
```json
{
"productType": "CORE",
"items": [
{
"date": "2026-05-01",
"adultPrice": 1999.00,
"childPrice": 999.00,
"id": 1780000000000000001,
"tierSeq": 1,
"priceType": "NORMAL",
"stock": 50,
"sold": 10,
"sellable": true,
"batchId": null
}
]
}
```
### GROUP 响应
```json
{
"productType": "GROUP",
"items": [
{
"date": "2026-07-06",
"adultPrice": 4580.00,
"childPrice": 2290.00,
"batchId": 2043697199761584129,
"batchNo": "Q202607062043697199757389826",
"batchName": "7月暑期团",
"batchStatus": "ENROLLING",
"endDate": "2026-07-10",
"stock": 25,
"sold": 0,
"sellable": true,
"tierSeq": null,
"priceType": null
}
]
}
```
---
## 向后兼容
**老接口保持不变**
- `GET /admin/product/item/{id}/price-calendar` — 不动
- `GET /admin/product/item/{id}/schedule/list` — 不动
前端可**逐步迁移**到新接口,无需一次性全量改造。
---
## 验证
测试服网关 7 场景全过:
- CORE 全量 → 92 条 tierSeq/priceType 齐 ✅
- GROUP 全量 → 4 条班期 batchId/batchStatus/endDate 齐 ✅
- CUSTOM → 62 条走 CORE 分支 ✅
- CORE/GROUP ?month=2026-05 → 正确过滤 ✅
- CORE ?tierSeq=1 → 从 92 条过滤到 79 条 ✅
- 老接口 /price-calendar → 92 条不变零回归 ✅
---
## 部署
测试服已部署: task `a1ca3b50` success。
正式环境上线前**无需任何 DDL**,纯接口聚合。