--- 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` #### 使用场景 新建订单向导 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` | 字段 | 类型 | 说明 | |------|------|------| | 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