文件
hl-api-changelog/changelogs-v2/2026-09/06_frontend_团期产品新建订单向导创单漏传productBatchId-前端缺陷-管理后台.md
T
2026-09-06 16:33:44 +08:00

249 行
12 KiB
Markdown
原始文件 Blame 文件历史

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
---
schema: "hl-changelog/v2"
ticket: "frontend"
title: "团期产品新建订单向导创单漏传 productBatchId"
consumer: "admin"
author: "wx(GIT)"
change_type: "前端缺陷"
backend_status: "not_required"
gateway_status: "not_required"
frontend_status: "verified"
frontend_owner: "mmg"
frontend_ref: "03a985b6"
target_release: ""
verified_at: "2026-09-06"
status_note: "后端已随 #7135/#7159 加固并部署测试服 dev-v3、网关复测 15/15 通过(新增 581055/581056/581057、修复 581034 人数预查,合并提交 977be08e);前端可据此联调:在 order-v2/new 向导创单 payload 补 productBatchId(仅 GROUP 产品,且必须是该产品的班期),并修团期看板「新增子订单」入口带团期上下文。"
updated_at: "2026-09-06"
base: "dev-v3"
---
# 团期订单:新建订单向导对 GROUP 产品创单漏传 productBatchId(前端缺陷)
## ⚠️ 关键变化
**现象**:团期(GROUP)产品在管理后台新建订单向导完成表单、点"确认创建"后报 HTTP 200 `code=581026 message='团期产品必须选择团期'`,无法创建。
**根因**:前端 `src/views/order-v2/new/index.vue` `handleCreate()` 组装的创单 payload 漏传 `productBatchId` 字段。而向导其实已在报价阶段成功拿到班期 ID(存于 `pricingContext.batchId`),报价后端响应 ¥5,850 正确,但创建时没把它放入请求体。
**结论**:前端补 `productBatchId` 是主修复。**后端已随 #7135/#7159 同步加固**(不再是「零改动」):现在仅 GROUP 产品可传 `productBatchId`,CORE/CUSTOM 传了会被拒(581056);班期必须属于所选产品(否则 581055);`tierSeq` 必须在产品配置内(否则 581057);人数超班期剩余名额会被拒(581034,此前因字段名漂移长期失效,#7159 修复)。因此前端务必**只对 GROUP 产品**传 `productBatchId`,且取自该产品价格日历的 `items[].batchId`。
---
## 一、背景
### 复现步骤
页面:管理后台 `192.168.100.219:9527/order-v2/new`(hl-ui 路由 `orderV2NewRoute`,`src/router/routes.js:166-175`,四步向导:选主题 → 选产品/档位 → 基本信息 → 确认创建)。
1. **Step 0 选主题**:「阿斯蒂芬撒点」(GROUP 产品线 `lineId=2044248925572919297`)
2. **Step 1 选产品**:「冻干粉发短信给」(`productId=2044306857534636034`,GROUP,`tierSeq=1` 档位「轻奢」)
3. **Step 2 基本信息**:出发日期 2026-10-01,成人 1 名、儿童 1 名 → 系统报价 ¥5,850.00(其中单房差 +¥500.00)
4. **Step 3 确认创建**:填客户姓名、手机号 → 点「确认创建订单」
5. **结果**:Toast 弹窗「团期产品必须选择团期」,订单创建失败
### 调用链
1. hl-ui `src/views/order-v2/new/index.vue:405-450` `handleCreate()` 组装 payload → `createOrder(payload)`
2. `src/api/orderV2.js:79-81` `createOrder(data)` = `http.post('/v3/admin/order', data, V3)`
3. hl-gateway `application.yml:246-249` 路由 `/v3/admin/**` → `lb://hl-order-service-v3`
4. hl-order-service-v3 `OrderController.java:86-91` 接收 → `orderService.createOrder(req, ...)`
5. `OrderService.java:630` 直接 `ctx.setProductBatchId(req.getProductBatchId())`,无兜底反查
6. `OrderService.java:635` `strategy.validate(ctx, productDetail)`
7. `GroupOrderStrategy.java:46-49` 校验失败:`if (ctx.getProductBatchId() == null) throw new BusinessException(OrderCoreErrorCode.GROUP_BATCH_REQUIRED)`
8. `OrderCoreErrorCode.java:102-103` 返回 `581026`「团期产品必须选择团期」
### 地面真相(测试库验证)
| 属性 | 值 |
|------|-----|
| 主题(product_line) | 阿斯蒂芬撒点 `2044248925572919297` |
| 产品(product) | 冻干粉发短信给 `2044306857534636034`,`product_type=GROUP` |
| 档位(tier) | `tierSeq=1` 轻奢 |
| 班期(group_tour_batch) | `batch_id=2052935476557328386`,`batch_no=Q202610012052935476548939777`,`departure_date=2026-10-01`,`batch_status=ENROLLING`,`end_date=2026-10-03`,`adult_price=2925`,`child_price=2425`,`single_room_diff=500` |
| 预计金额 | 2925 + 2425 = 5,350,加单房差 500 = **5,850** ✓ (与前端报价一致,证明向导已成功拿到班期信息计价) |
---
## 二、变更接口清单
| # | 接口名 | 方法 | 网关路径 | 前端函数 | 变更 | 说明 |
|---|--------|------|---------|---------|------|------|
| 1 | 创建订单 | POST | `/v3/admin/order` | `createOrder()` | **调用方式修正** | GROUP 产品**必传** `productBatchId`;CORE/CUSTOM 必须**不传** |
| 2 | 统一价格日历 | GET | `/admin/product/item/{productId}/pricing-calendar` | `getUnifiedPricingCalendar()` | 无变更 | GROUP 时返回班期列表,`items[].batchId` 即为所需班期 ID |
| 3 | 报价 | POST | `/admin/product/item/{productId}/quote` | `quoteProduct()` | 无变更 | GROUP 时现已传 `batchId`,保持即可 |
---
## 三、接口详情
### 1. 创建订单 `POST /v3/admin/order`
**VO**: `OrderCreateReqVO → Result<OrderCreateRespVO>`
#### 使用场景
新建订单向导 Step 3 确认创建时调用,后端落库并触发团期聚合等副作用。
#### 入参
| 字段 | 类型 | 必填 | 约束 | 说明 |
|------|------|------|------|------|
| productId | String(Long 值) | ✓ | 正整数 | 产品 ID |
| tierSeq | Integer | ✓ | 1~N,且必须在产品已配档位内 | 档位序号;不在产品 tierPrices ∪ tiers 配置内 → 581057(#7135 起全类型校验;产品未配任何档位时不拦) |
| departureDate | String (yyyy-MM-dd) | ✓ | 不早于今天 | 出发日期;GROUP 下服务端会用班期权威出发日落库,但仍必填;CORE/CUSTOM 按字面值 |
| adultCount | Integer | ✓ | ≥1 | 成人数 |
| childCount | Integer | — | ≥0,默认 0 | 儿童数(6~12 岁) |
| youngChildCount | Integer | — | ≥0,默认 0 | 小童数(2~5 岁) |
| babyCount | Integer | — | ≥0,默认 0 | 婴儿数;GROUP 下服务端强制置为 0,不计入名额和价格 |
| customerName | String | ✓ | 非空,≤50 | 客户姓名 |
| customerPhone | String | ✓ | 格式 `^1[3-9]\d{9}$` | 手机号明文 |
| customerRemark | String | — | ≤500 | 订单备注 |
| createSource | String | — | ≤20,默认 CONSULTANT | 创建来源标记 |
| **productBatchId** | **String(Long 值)** | **GROUP ✓ / CORE、CUSTOM ✗** | 仅 GROUP 可传且必传;班期须属于本 productId | **GROUP 产品必传班期 ID**(来自价格日历 `items[].batchId`),**非 GROUP 产品禁止传递**(CORE/CUSTOM 带了 → 581056);班期 productId 须等于请求 productId(否则 581055);建议用字符串如 `"2052935476557328386"`(JSON Number 亦可,后端接受,但字符串防前端精度丢失) |
| roomCount | Integer | — | ≥1,默认 1 | 房间数;超过班期剩余房间数报 `581031` |
| tags | Array<String> | — | — | 订单标签 |
#### 出参 `Result<OrderCreateRespVO>`
| 字段 | 类型 | 说明 |
|------|------|------|
| code | Integer | 200 = 成功,否则为业务错误码 |
| data.id | String | 订单 ID(雪花 Long)|
| data.orderNo | String | 订单编号(HL+时间+序号,如 HL20260906093733163) |
| data.orderStatus | String | 订单状态(创建初态 = PENDING_PAY) |
| data.totalAmount | String | 订单总金额,格式 decimal(12,2) |
| data.depositAmount | String | 订金额 |
| data.departureDate | String | 出发日期(yyyy-MM-dd);GROUP 以班期为准,回显班期出发日 |
| data.returnDate | String | 归程日期(yyyy-MM-dd);根据 tripDays = endDate - departureDate + 1 推算 |
| data.groupBatchName | String/null | 团批次名称(当前实现为 null) |
| data.tierName | String/null | 档位名称(#7135 起 GROUP 也回显来自 tiers 配置的档位名;产品未配档位时为 null) |
| data.consultantId | String | 发单定制师 ID(创建时落 1001) |
#### 请求示例(GROUP 产品,正例)
```json
{
"productId": "2044306857534636034",
"tierSeq": 1,
"departureDate": "2026-10-01",
"adultCount": 1,
"childCount": 1,
"youngChildCount": 0,
"babyCount": 0,
"customerName": "张三",
"customerPhone": "13800009601",
"createSource": "CONSULTANT",
"customerRemark": "团期测试订单",
"productBatchId": "2052935476557328386",
"roomCount": 1
}
```
#### 响应示例
```json
{
"code": 200,
"message": "成功",
"data": {
"id": "2096412454488612866",
"orderNo": "HL20260906093733163",
"orderStatus": "PENDING_PAY",
"totalAmount": "5850.00",
"depositAmount": "1000.00",
"departureDate": "2026-10-01",
"returnDate": "2026-10-03",
"groupBatchName": null,
"tierName": null,
"consultantId": "1001"
},
"success": true
}
```
#### 错误响应
| code | message | 触发条件 |
|------|---------|---------|
| 581026 | 团期产品必须选择团期 | productBatchId 为 null 且 productType = GROUP |
| 581027 | 团期信息获取失败 | Feign 调班期服务异常 |
| 581028 | 团期状态不允许报名 | 班期状态不在可订范围 |
| 581029 | 团期已过报名截止日 | 当前日期 > enrollment_deadline |
| 581031 | 团期剩余房间不足 | roomCount > 班期剩余房间数 |
| 581034 | 团期剩余名额不足 | 成人+儿童+小童 > 班期剩余名额(#7159 修复:此前字段名漂移致此校验长期失效;人数不限的班期不拦) |
| 581055 | 所选团期不属于该产品 | 班期 productId ≠ 请求 productId(跨产品串号;#7135 新增) |
| 581056 | 非团期产品不能指定团期 | CORE/CUSTOM 请求带了 productBatchId(#7135 新增) |
| 581057 | 所选档位不存在 | tierSeq 不在产品 tierPrices ∪ tiers 配置内(#7135 新增;产品未配档位时不拦) |
#### 业务边界
- 后端已校验 tierSeq 存在性(581057)与班期归属产品(581055);GROUP 出发日/返团日以班期为准回显(#7135)
- babyCount 强制 0,不参与计价
- returnDate 按班期 tripDays 计算
- 创建后触发 staff 分配、group_batch 懒建
---
### 2. 统一价格日历 `GET /admin/product/item/{productId}/pricing-calendar`
GROUP 时返回班期列表,items[] 每条含:
- date、batchId(JSON Number,**前端必须 String() 转换**)
- batchNo、batchName、batchStatus、endDate、enrollmentDeadline
- maxRooms、bookedRooms、remainParticipants(null 表不限)
- 价格字段、sellable(仅 ENROLLING 为 true)
---
### 3. 报价 `POST /admin/product/item/{productId}/quote`
GROUP 时入参需 batchId,出参 grandTotal、singleRoomSurcharge 等。
---
## 四、前端修复要点与自测清单
### 修复要点
1. `index.vue handleCreate()`:GROUP 时 `payload.productBatchId = String(pricingContext.batchId)`
2. Step3 确认前二次校验 pricingContext 非空且与当前表单一致
3. Step3 展示 batchNo/batchName
4. 团期看板 onAddSub() 带深链 ?productBatchId&departureDate
### 自测清单
- ✓ GROUP 选日期 → 报价 → 创建成功
- ✓ CORE 创建不含 productBatchId
- ✓ 日期未选时按钮拦截
---
## 五、验证证据
### 场景矩阵(测试服 2026-09-06)
14 场景通过,1 场景失败(S8),10 单已清理。
### DB 落库
orderNo HL20260906093733163,product_batch_id=2052935476557328386
---
## 六、影响与不影响范围
**不影响**:
- 正常 CORE/CUSTOM 创单(不带 productBatchId):完全不变
- 订单读接口(列表 / 详情 / 编辑):不变
- 前端改动只涉及 `order-v2/new` 向导创单 payload 与团期看板「新增子订单」入口
**受后端加固影响(前端需知晓)**:
- 小程序端 `POST /v3/mp/order` 与管理端共用内核,581055/581056/581057/581034 同样生效,但小程序请求契约不变(`groupBatchId` 语义不变)
- CORE/CUSTOM 若误带 productBatchId:此前静默落库,现在直接 581056 拒单
---
## 七、关联
- 前端向导既有工单 #7083(已合);后端判团字段透出 #7142、看板 productId 深链 #7143(均已合 dev-v3)
- 后端加固 #7135(收紧 productBatchId 校验:581055/581056/581057、日期按班期)+ #7159(人数预查改读 remainingParticipants 使 581034 生效),随 PR #7169 合入 dev-v3
- 证据:gateway-verify.txt、gateway-matrix.txt