12 KiB
schema, ticket, title, consumer, author, change_type, backend_status, gateway_status, frontend_status, frontend_owner, frontend_ref, target_release, verified_at, status_note, updated_at, base
| schema | ticket | title | consumer | author | change_type | backend_status | gateway_status | frontend_status | frontend_owner | frontend_ref | target_release | verified_at | status_note | updated_at | base |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| hl-changelog/v2 | frontend | 团期产品新建订单向导创单漏传 productBatchId | admin | wx(GIT) | 前端缺陷 | not_required | not_required | verified | mmg | 03a985b6 | 2026-09-06 | 后端已随 #7135/#7159 加固并部署测试服 dev-v3、网关复测 15/15 通过(新增 581055/581056/581057、修复 581034 人数预查,合并提交 977be08e);前端可据此联调:在 order-v2/new 向导创单 payload 补 productBatchId(仅 GROUP 产品,且必须是该产品的班期),并修团期看板「新增子订单」入口带团期上下文。 | 2026-09-06 | 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,四步向导:选主题 → 选产品/档位 → 基本信息 → 确认创建)。
- Step 0 选主题:「阿斯蒂芬撒点」(GROUP 产品线
lineId=2044248925572919297) - Step 1 选产品:「冻干粉发短信给」(
productId=2044306857534636034,GROUP,tierSeq=1档位「轻奢」) - Step 2 基本信息:出发日期 2026-10-01,成人 1 名、儿童 1 名 → 系统报价 ¥5,850.00(其中单房差 +¥500.00)
- Step 3 确认创建:填客户姓名、手机号 → 点「确认创建订单」
- 结果:Toast 弹窗「团期产品必须选择团期」,订单创建失败
调用链
- hl-ui
src/views/order-v2/new/index.vue:405-450handleCreate()组装 payload →createOrder(payload) src/api/orderV2.js:79-81createOrder(data)=http.post('/v3/admin/order', data, V3)- hl-gateway
application.yml:246-249路由/v3/admin/**→lb://hl-order-service-v3 - hl-order-service-v3
OrderController.java:86-91接收 →orderService.createOrder(req, ...) OrderService.java:630直接ctx.setProductBatchId(req.getProductBatchId()),无兜底反查OrderService.java:635strategy.validate(ctx, productDetail)GroupOrderStrategy.java:46-49校验失败:if (ctx.getProductBatchId() == null) throw new BusinessException(OrderCoreErrorCode.GROUP_BATCH_REQUIRED)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 | — | — | 订单标签 |
出参 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 产品,正例)
{
"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
}
响应示例
{
"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 等。
四、前端修复要点与自测清单
修复要点
index.vue handleCreate():GROUP 时payload.productBatchId = String(pricingContext.batchId)- Step3 确认前二次校验 pricingContext 非空且与当前表单一致
- Step3 展示 batchNo/batchName
- 团期看板 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