docs(changelog): 团期创单收紧+人数预查(#7135/#7159)、判团字段(#7142)、看板productId(#7143)、前端缺陷指引
changelog-filename-gate / validate (push) Successful in 2s
changelog-filename-gate / validate (push) Successful in 2s
- 06_7135:填 PR #7169 / 合并提交 977be08e / 折入 #7159(581034 改读 remainingParticipants)/ 修正单测计数 / 补部署 dev-v3 网关复测 15/15 证据 - 06_frontend:更正后端已加固并部署验证(不再是零改动),补 581055/581056/581057、581034 触发条件与影响范围 - 06_7142 / 06_7143:早前已随 PR #7155/#7157 合并,随本批提交 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N7Xpgcv9nncadAXQtWhg4P
这个提交包含在:
@@ -0,0 +1,351 @@
|
|||||||
|
---
|
||||||
|
schema: "hl-changelog/v2"
|
||||||
|
ticket: "7135"
|
||||||
|
title: "团期创单收紧 productBatchId 校验(班期归属产品 / 非 GROUP 拒收 / tierSeq 存在性 / 日期按班期)"
|
||||||
|
consumer: "admin"
|
||||||
|
author: "wx(GIT)"
|
||||||
|
change_type: "修改接口"
|
||||||
|
backend_status: "deployed"
|
||||||
|
gateway_status: "verified"
|
||||||
|
frontend_status: "pending"
|
||||||
|
frontend_owner: ""
|
||||||
|
frontend_ref: ""
|
||||||
|
target_release: ""
|
||||||
|
verified_at: ""
|
||||||
|
status_note: "服务端对 POST /v3/admin/order 新增三个校验错误码 581055/581056/581057,响应 departureDate/returnDate 改为落库值;前端新建订单向导须仅对 GROUP 产品传 productBatchId 且班期必须属于所选产品。"
|
||||||
|
updated_at: "2026-09-06"
|
||||||
|
base: "dev-v3"
|
||||||
|
---
|
||||||
|
|
||||||
|
# 订单模块:团期创单校验收紧(班期归属产品 / 非 GROUP 拒收 / tierSeq 存在性 / 日期按班期)
|
||||||
|
|
||||||
|
管理后台创单接口 `POST /v3/admin/order` 对 GROUP 产品的团期校验收紧,新增三个拒单错误码(581055/581056/581057),同时出发日期、返团日期改为班期的权威值。修复跨产品班期串号导致订单错误归团、CORE/CUSTOM 单被误命中团期逻辑等问题。
|
||||||
|
|
||||||
|
## ⚠️ 关键变化
|
||||||
|
|
||||||
|
- **仅 GROUP 产品可传 productBatchId**:CORE/CUSTOM 请求含 productBatchId → 拒单 581056「非团期产品不能指定团期」(改前静默落库并误命中团期分支)。
|
||||||
|
- **班期必须属于所选产品**:productBatchId 对应班期的 productId ≠ 请求 productId → 拒单 581055「所选团期不属于该产品」(改前会按别家班期计价并挂到别家的团)。
|
||||||
|
- **tierSeq 必须在产品配置内**(全产品类型):不在 tierPrices ∪ tiers 并集中 → 拒单 581057「所选档位不存在」;产品未配档位时不拦(改前 tier_name 落 NULL)。
|
||||||
|
- **响应 departureDate/returnDate 改为班期权威值**:GROUP 单出发日 = 班期出发日;返团日 = 班期 endDate,或 班期出发日 + 行程天数 − 1(班期无 endDate 时)。改前响应回显请求日期,落库日期与班期脱钩。
|
||||||
|
- **GROUP 单响应 tierName 现有值**:来自产品 tiers 配置,改前恒 null。
|
||||||
|
- **人数超班期剩余名额拒单(581034)**(#7159 并入本 PR):`成人+儿童+小童 > 班期剩余名额` → 581034;此前订单侧读的 `remainingSlots` 字段在产品侧「库存改造 Phase 2」后已无来源、恒 null,致该预查长期 no-op,现改读产品侧权威字段 `remainingParticipants`(null=人数不限,跳过校验)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 二、变更接口清单
|
||||||
|
|
||||||
|
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||||
|
|---|------|------|------|----------|------|
|
||||||
|
| 1 | 创建订单(管理端) | POST | `/v3/admin/order` | 请求新增校验 / 响应日期改值 | productBatchId 仅 GROUP 可传;班期归属;tierSeq 存在性;日期按班期 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 三、接口详情
|
||||||
|
|
||||||
|
### 1. 创建订单(管理端) `POST /v3/admin/order`
|
||||||
|
|
||||||
|
**VO**: `OrderCreateReqVO → OrderCreateRespVO`
|
||||||
|
|
||||||
|
#### 使用场景
|
||||||
|
|
||||||
|
管理端新建订单向导或团期看板新增子订单调用。创建跟团游、定制游、线路游订单,GROUP 产品必须指定班期,CORE/CUSTOM 产品不允许指定班期。
|
||||||
|
|
||||||
|
#### 入参
|
||||||
|
|
||||||
|
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||||
|
|------|------|------|------|------|------|
|
||||||
|
| `productId` | Body | Long(String)| ✅ | 正整数 | 产品 ID |
|
||||||
|
| `tierSeq` | Body | Integer | ✅ | ≥ 1;必须存在于产品配置档位 | 档位序号,不在产品 tierPrices ∪ tiers 配置内 → 581057 |
|
||||||
|
| `departureDate` | Body | yyyy-MM-dd | ✅ | 不早于当天;日期格式 | 出发日期;出发日早于今天 → 581011 |
|
||||||
|
| `adultCount` | Body | Integer | ✅ | ≥ 1 | 成人数 |
|
||||||
|
| `childCount` | Body | Integer | 否 | ≥ 0,默认 0 | 儿童数(5-12 岁) |
|
||||||
|
| `youngChildCount` | Body | Integer | 否 | ≥ 0,默认 0 | 小童数(3-4 岁) |
|
||||||
|
| `babyCount` | Body | Integer | 否 | ≥ 0,默认 0 | 婴儿数(0-2 岁) |
|
||||||
|
| `customerName` | Body | String | ✅ | @NotBlank | 客户姓名 |
|
||||||
|
| `customerPhone` | Body | String | ✅ | ^1[3-9]\d{9}$ | 客户手机号(明文传,DB 层 AES 加密) |
|
||||||
|
| `customerRemark` | Body | String | 否 | ≤ 500 | 客户备注 |
|
||||||
|
| `createSource` | Body | String | 否 | ≤ 20 字;枚举:CUSTOMER / CONSULTANT / OTA / WALK_IN / B2B / VIP_REPURCHASE / REFERRAL / PROMOTION / INTERNAL;默认 CONSULTANT | 创建来源 |
|
||||||
|
| `productBatchId` | Body | Long(String) | 条件必填 | 仅 GROUP 产品可传;CORE/CUSTOM 传了 → 581056;班期 productId 必须等于请求 productId,否则 → 581055 | 团期 ID(product 侧班期 batchId);**GROUP 产品必传,CORE/CUSTOM/ROUTE 禁传**;值须属于请求 productId 对应产品 |
|
||||||
|
| `roomCount` | Body | Integer | 否 | ≥ 1 | 房间数 |
|
||||||
|
| `tags` | Body | List<String> | 否 | 无约束 | 订单标签名列表 |
|
||||||
|
| `sharerOpenid` | Body | String | 否 | 无约束 | 分享人 openid(C 端裂变追踪) |
|
||||||
|
| `customizerId` | Body | Long | 否 | 无约束 | 分享归因定制师 ID |
|
||||||
|
|
||||||
|
#### 出参 `Result<OrderCreateRespVO>`
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `data.id` | Long | 订单主键(雪花 ID 序列化为 String) |
|
||||||
|
| `data.orderNo` | String | 订单号(创单瞬间生成,永不变) |
|
||||||
|
| `data.orderStatus` | String | 粗状态枚举(创单后 = PENDING_PAY) |
|
||||||
|
| `data.orderStatusName` | String | 粗状态中文名(创单后 = 待支付) |
|
||||||
|
| `data.flowStatus` | String | 细状态枚举值(创单后 = AWAITING_PAY) |
|
||||||
|
| `data.flowStatusName` | String | 细状态中文名 |
|
||||||
|
| `data.flowStep` | Integer | 线性 6 步当前步序号(创单初态 = 0) |
|
||||||
|
| `data.flowStepTotal` | Integer | 线性 6 步总步数(固定 6) |
|
||||||
|
| `data.flowStepName` | String | 线性 6 步当前步中文名 |
|
||||||
|
| `data.consultantId` | Long | 实际绑定定制师 ID(序列化为 String) |
|
||||||
|
| `data.consultantSource` | String | 定制师来源(DEFAULT_ASSIGNED / LINK_BOUND / MANUAL / SHARED) |
|
||||||
|
| `data.tags` | List<String> | 系统自动打的标签 |
|
||||||
|
| `data.createdAt` | yyyy-MM-ddTHH:mm:ss | 创单时间 |
|
||||||
|
| `data.productName` | String | 产品名称 |
|
||||||
|
| `data.tierName` | String | 档位名(v5.18 新增,**GROUP 单现有值来自产品 tiers 配置,CORE/CUSTOM/ROUTE 仍为 null**) |
|
||||||
|
| `data.groupBatchName` | String | 拼团批次名(仅供兼容,恒 null,勿读) |
|
||||||
|
| `data.departureDate` | yyyy-MM-dd | **出发日期(v5.18;改后 = 班期权威出发日,与请求值可能不同)** |
|
||||||
|
| `data.returnDate` | yyyy-MM-dd | **返团日期(v5.18;改后 = 班期 endDate 或班期出发日 + 行程天数 − 1,与请求值可能不同)** |
|
||||||
|
| `data.totalAmount` | BigDecimal(String) | 订单总价(元) |
|
||||||
|
| `data.depositAmount` | BigDecimal(String) | 建议定金金额(元) |
|
||||||
|
| `data.depositRatio` | Integer | 定金比例百分比;RATIO 模式有值,FIXED 模式为 null,FULL 模式 = 100 |
|
||||||
|
| `data.depositMode` | String | 定金计算模式(FIXED / RATIO / FULL) |
|
||||||
|
| `data.paymentMode` | String | 支付模式(DEPOSIT / FULL) |
|
||||||
|
| `data.expiryMinutes` | Integer | 支付时限分钟数(默认 1440 = 24h) |
|
||||||
|
| `data.payUrl` | String | 支付页绝对 URL |
|
||||||
|
| `data.customerName` | String | 客户姓名(回显) |
|
||||||
|
| `data.groupBatchId` | Long(String) | **运营团期 ID(order 侧),非空 = 团订单,为空 = 普通订单,这是唯一判别** |
|
||||||
|
| `data.productBatchId` | Long(String) | 团期产品排期 ID(product 侧 batchId,仅供溯源) |
|
||||||
|
| `data.groupOrder` | Boolean | 是否团订单(= groupBatchId 非空的派生值) |
|
||||||
|
|
||||||
|
#### 请求示例
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"productId": "2044306857534636034",
|
||||||
|
"tierSeq": 1,
|
||||||
|
"departureDate": "2026-10-01",
|
||||||
|
"adultCount": 1,
|
||||||
|
"childCount": 1,
|
||||||
|
"youngChildCount": 0,
|
||||||
|
"babyCount": 0,
|
||||||
|
"customerName": "张三",
|
||||||
|
"customerPhone": "13800009601",
|
||||||
|
"createSource": "CONSULTANT",
|
||||||
|
"productBatchId": "2052935476557328386"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 响应示例(成功)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "成功",
|
||||||
|
"data": {
|
||||||
|
"id": "2096414445365338113",
|
||||||
|
"orderNo": "HL20260906094527867",
|
||||||
|
"orderStatus": "PENDING_PAY",
|
||||||
|
"orderStatusName": "待支付",
|
||||||
|
"flowStatus": "AWAITING_PAY",
|
||||||
|
"flowStatusName": "待支付",
|
||||||
|
"flowStep": 0,
|
||||||
|
"flowStepTotal": 6,
|
||||||
|
"flowStepName": "待支付",
|
||||||
|
"consultantId": "50001234567890",
|
||||||
|
"consultantSource": "DEFAULT_ASSIGNED",
|
||||||
|
"tags": [],
|
||||||
|
"createdAt": "2026-09-06T09:45:27",
|
||||||
|
"productName": "冻干粉发短信给",
|
||||||
|
"tierName": "标准档",
|
||||||
|
"groupBatchName": null,
|
||||||
|
"departureDate": "2026-10-01",
|
||||||
|
"returnDate": "2026-10-03",
|
||||||
|
"totalAmount": "5850.00",
|
||||||
|
"depositAmount": "1000.00",
|
||||||
|
"depositRatio": null,
|
||||||
|
"depositMode": "FIXED",
|
||||||
|
"paymentMode": "DEPOSIT",
|
||||||
|
"expiryMinutes": 1440,
|
||||||
|
"payUrl": "https://pay.hulalv.com/pay/HL20260906094527867",
|
||||||
|
"customerName": "张三",
|
||||||
|
"groupBatchId": "2096412454643802114",
|
||||||
|
"productBatchId": "2052935476557328386",
|
||||||
|
"groupOrder": true
|
||||||
|
},
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 空数据 / 降级响应
|
||||||
|
|
||||||
|
创建接口无「空数据」场景(成功即返回订单对象)。降级路径:所依赖的产品服务 Feign 不可用时按错误码降级而非返回空——拉班期失败返 581027、拉报价失败返 581032,前端据 `code` 提示重试,不会返回 `data=null` 的成功包。
|
||||||
|
|
||||||
|
#### 错误响应
|
||||||
|
|
||||||
|
**出发日期早于今天**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 581011,
|
||||||
|
"message": "出发日期不能早于今天",
|
||||||
|
"data": null,
|
||||||
|
"success": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**档位不存在(tierSeq 未在产品配置内)**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 581057,
|
||||||
|
"message": "所选档位不存在",
|
||||||
|
"data": null,
|
||||||
|
"success": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**非团期产品不能指定团期**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 581056,
|
||||||
|
"message": "非团期产品不能指定团期",
|
||||||
|
"data": null,
|
||||||
|
"success": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**所选团期不属于该产品**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 581055,
|
||||||
|
"message": "所选团期不属于该产品",
|
||||||
|
"data": null,
|
||||||
|
"success": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**既有 GROUP 校验错误(团期缺失)**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 581026,
|
||||||
|
"message": "团期产品必须选择团期",
|
||||||
|
"data": null,
|
||||||
|
"success": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**参数校验错误(如手机号格式错误)**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 400,
|
||||||
|
"message": "手机号格式错误",
|
||||||
|
"data": null,
|
||||||
|
"success": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 业务边界
|
||||||
|
|
||||||
|
- **productBatchId 传值规则**:仅 GROUP 产品可传且必传(除非为空表示不下单);CORE/CUSTOM/ROUTE 产品绝不能带。
|
||||||
|
- **班期归属校验**:productBatchId 对应班期的 productId 必须等于请求 productId;不同则拒单 581055,不会创建订单或部分落库。
|
||||||
|
- **tierSeq 校验**:必须存在于产品的 tierPrices JSON(CORE/CUSTOM/ROUTE)或 tiers JSON(GROUP);产品未配任何档位时保持放行(存量产品兼容)。
|
||||||
|
- **出发日期与返团日期**:响应值为班期或产品的权威日期,可能与请求值不同。前端展示及后续行程渲染必须以响应值为准,不要缓存请求值。
|
||||||
|
- **错误码优先级顺序**:出发日期 581011 → 非 GROUP 拒收 581056 → tierSeq 581057 → GROUP 缺 productBatchId 581026 → 拉班期 581027 → 班期归属 581055 → 后续班期状态 / 截止 / 库存检查。
|
||||||
|
- **鉴权**:网关注入 `X-Admin-Id` 头,consultantId 无法解析返 581013。
|
||||||
|
- **幂等性**:客户端不得重试;同一 orderNo 存量幂等托管由 order-v3 内核保证。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 四、契约约束与正确调用方式
|
||||||
|
|
||||||
|
| 场景 | 正确调用 | 错误调用 | 结果 |
|
||||||
|
|------|---------|---------|------|
|
||||||
|
| GROUP 产品创单 | 传 productBatchId,值取价格日历 items[].batchId | 不传 productBatchId | 581026 团期产品必须选择团期 |
|
||||||
|
| CORE 产品创单 | 不传 productBatchId / productBatchId = null | 传 productBatchId(任何值) | 581056 非团期产品不能指定团期 |
|
||||||
|
| CUSTOM 产品创单 | 不传 productBatchId / productBatchId = null | 传 productBatchId(任何值) | 581056 非团期产品不能指定团期 |
|
||||||
|
| 档位选择 | tierSeq 必须在产品已配档位内 | tierSeq 超过产品最大档位序号 | 581057 所选档位不存在 |
|
||||||
|
| 班期串号防护 | 班期 batchId 必须属于所选 productId | 前端用不同产品的 batchId | 581055 所选团期不属于该产品 |
|
||||||
|
| 日期展示 | 使用响应 departureDate / returnDate | 使用请求的出发日期 | 行程日期与班期脱钩,退款档位错位 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 五、数据库行为
|
||||||
|
|
||||||
|
| 操作 | 落库字段 | 说明 |
|
||||||
|
|------|---------|------|
|
||||||
|
| GROUP 创单(班期出发 2026-10-01,endDate 2026-10-03,tripDays=3) | order_main.depart_date = 2026-10-01(班期值) | 请求可能为 2026-10-02,但落库为班期出发日 |
|
||||||
|
| GROUP 创单(班期无 endDate) | order_main.return_date = 班期出发日 + tripDays - 1 | 如班期 2026-10-01,tripDays=3,则 return_date=2026-10-03 |
|
||||||
|
| GROUP 创单(班期有 endDate) | order_main.return_date = 班期 endDate | endDate 为准,与 tripDays 无关 |
|
||||||
|
| GROUP 创单 | order_main.tier_name = 产品 tiers JSON 对应 tierSeq 的值 | 改前恒 null;CORE/CUSTOM 仍为 null(无 tiers JSON) |
|
||||||
|
| CORE 创单带 productBatchId | 不创建,拒单 581056 | 改前会落库 productBatchId,误命中团期逻辑 |
|
||||||
|
| 班期串号 productBatchId 错 | 不创建,拒单 581055 | 改前会按班期产品计价,订单挂错团 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 六、边界行为
|
||||||
|
|
||||||
|
- 业务失败仍 HTTP 200,**必须检查 `code` 字段判成败**。
|
||||||
|
- 存量订单不回溯修正;仅新建订单应用新规则。
|
||||||
|
- 产品未配档位时保持放行(向后兼容存量产品);即使 tierSeq 传 99 也不拦。
|
||||||
|
- 班期 productId 缺失时仅告警日志,不拦单(product 侧老数据未回填,宁缺毋滥)。
|
||||||
|
- 网关注入 `X-Admin-Id` 为 null 时返 581013,改前静默取 JWT;两者互斥。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 六.6、修改前后对比
|
||||||
|
|
||||||
|
针对 `POST /v3/admin/order`(GROUP 产品):
|
||||||
|
|
||||||
|
| 维度 | 修改前 | 修改后 |
|
||||||
|
|------|--------|--------|
|
||||||
|
| CORE/CUSTOM 带 productBatchId | 静默落库 product_batch_id,误命中团期 staff 扇出分支 | 拒单 581056「非团期产品不能指定团期」,不落库 |
|
||||||
|
| 跨产品班期(batchId 属别的产品) | 按别家班期计价,订单挂错团 | 拒单 581055「所选团期不属于该产品」(任一侧 productId 为空时仅告警放行) |
|
||||||
|
| tierSeq 不在产品档位内 | 不校验,tier_name 落 NULL | 拒单 581057「所选档位不存在」(产品未配档位时不拦) |
|
||||||
|
| GROUP 单响应/落库出发日 | 回显请求出发日,可能与班期脱钩 | = 班期出发日 |
|
||||||
|
| GROUP 单响应/落库返团日 | 按请求出发日推算 | = 班期 endDate(缺则 班期出发日 + 行程天数 − 1) |
|
||||||
|
| GROUP 单响应 tierName | 恒 null | 现有值(来自产品 tiers 配置) |
|
||||||
|
| 人数超班期剩余名额(#7159) | 预查读 remainingSlots 恒 null,整段 no-op(不拦) | 读 remainingParticipants,超额拒单 581034(人数不限的班期跳过) |
|
||||||
|
|
||||||
|
## 六.7、影响评估
|
||||||
|
|
||||||
|
- **前端必改**:新建订单向导与团期看板「新增子订单」仅对 GROUP 产品传 `productBatchId`,且取该产品价格日历的 `items[].batchId`;CORE/CUSTOM 绝不能带,否则 581056。
|
||||||
|
- **前端展示**:出发日期 / 返团日期以创单响应值为准(GROUP 单被班期覆盖),不要回显请求值。
|
||||||
|
- **向后兼容**:正常 CORE/CUSTOM 创单(不带 productBatchId)行为完全不变;错误码新增不影响既有成功路径。
|
||||||
|
- **其他端**:小程序 `POST /v3/mp/order` 共用内核,四个校验同样生效,请求契约不变。
|
||||||
|
- **数据安全**:跨产品串号单此前会把订单挂到别家团、扣错名额,本次从源头拒绝。
|
||||||
|
- **回滚**:回滚本次发布即恢复旧行为;已按新规则创建的订单不受影响。
|
||||||
|
|
||||||
|
## 七、不影响范围
|
||||||
|
|
||||||
|
- **仅影响**:管理后台「新建订单向导」和「团期看板新增子订单」流程。
|
||||||
|
- **零影响**:
|
||||||
|
- 小程序端 `POST /v3/mp/order` 共用内核,新校验同样生效但请求契约不变。
|
||||||
|
- 表结构:无 Flyway 变更、无新列新表。
|
||||||
|
- 订单列表、详情、订单编辑等读操作。
|
||||||
|
- CORE/CUSTOM 正常创单(不带 productBatchId)完全不变。
|
||||||
|
- 团期相关接口(看板、价格日历、班期详情)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 八、测试环境已验证
|
||||||
|
|
||||||
|
**测试服创单(产品「冻干粉发短信给」productId=2044306857534636034,班期 2026-10-01 productBatchId=2052935476557328386,1 成人 1 儿童,2026-09-06 测试)**
|
||||||
|
|
||||||
|
- ✓ GROUP 单正例:响应 departureDate/returnDate 与班期一致(班期 2026-10-01 出发、2026-10-03 返);groupBatchId / productBatchId / groupOrder 三字段已填值。
|
||||||
|
- ✓ CORE 产品 2056944461216100353 带 productBatchId → HTTP 200,code 581056「非团期产品不能指定团期」。
|
||||||
|
- ✓ productId=2044306857534636034 + productBatchId=2089667212070612995(属另一产品) → HTTP 200,code 581055「所选团期不属于该产品」。
|
||||||
|
- ✓ tierSeq=9(产品只配 1-3 档) → HTTP 200,code 581057「所选档位不存在」。
|
||||||
|
- ✓ GROUP 单不传 productBatchId → HTTP 200,code 581026「团期产品必须选择团期」。
|
||||||
|
|
||||||
|
**单测覆盖**(定向复测全绿):OrderServiceTest 193/193 ✓ | GroupOrderStrategyTest 30/30 ✓(含 581034 三例)| OrderCreateTransactionExecutorTest 21/21 ✓ | ProductTierResolverTest 11/11 ✓ | OrderMpCreateServiceTest 6/6 ✓ | BatchInfoVODeserializationTest 2/2 ✓ | OrderMpReadServiceTest 15/15 ✓ | InternalOrderSnapshotControllerTest 4/4 ✓ | E2eScopedOrderCreateServiceTest 19/19 ✓;ArchTest 全绿(LayerEnforcement 5 / RedLine 9 / MapperBoundary 26 / HouseModuleBoundary 4 / DashboardLayer 2 / LocalCacheVetting 1)。
|
||||||
|
|
||||||
|
**网关验证(已部署 dev-v3 复测,2026-09-06 15:00)**:合并提交 977be08e 部署测试服,双实例滚动重启完成(8086/8186 新 PID、jar 已更新)。网关实测 15/15 通过:S2 正例 200(日期=班期 10-01/10-03)、S10 跨产品班期 581055、S11 日期不一致回显班期日期、S13 tierSeq=9 581057、S14 CORE 带班期 581056、S16 GROUP tierName=轻奢;#7142 判团字段在创单响应/详情/列表三处透出、#7143 productId 在看板/分页/详情三接口透出,均已核。581034(#7159)判定逻辑经三条单测证实生效;线上端到端受测试账号对产品 schedule 无编辑权限(403)与限额班期 getBatchInfo 存量异常(581027)所限未实跑,详见 Issue #7159 评论。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 十、相关文档
|
||||||
|
|
||||||
|
- 关联 Issue: [wx/HL#7135](https://git.1814.love:8443/wx/HL/issues/7135)
|
||||||
|
- 关联 PR: [wx/HL#7169](https://git.1814.love:8443/wx/HL/pulls/7169)(Closes #7135、#7159;合并提交 977be08e)
|
||||||
|
- 关联工单 #7142(判团字段 groupBatchId / productBatchId / groupOrder)、#7143(看板 productId 显示)。
|
||||||
|
- 团期接口文档:GB-ADM-00B(OpenWiki)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 关联 / 联系人
|
||||||
|
|
||||||
|
### 链接
|
||||||
|
|
||||||
|
- **Issue**: [#7135](https://git.1814.love:8443/wx/HL/issues/7135)
|
||||||
|
- **PR**: [#7169](https://git.1814.love:8443/wx/HL/pulls/7169)(含 #7159)
|
||||||
|
- **Merge commit**: 977be08eccd0a32e27c01dce41de210a53f13e5d
|
||||||
|
|
||||||
|
### 联系人
|
||||||
|
|
||||||
|
- **后端负责人**: @wx
|
||||||
@@ -0,0 +1,509 @@
|
|||||||
|
---
|
||||||
|
schema: "hl-changelog/v2"
|
||||||
|
ticket: "7142"
|
||||||
|
title: "订单详情/列表/创单响应透出 groupBatchId·productBatchId·groupOrder 判团字段"
|
||||||
|
consumer: "admin"
|
||||||
|
author: "wx(GIT)"
|
||||||
|
change_type: "修改接口"
|
||||||
|
backend_status: "deployed"
|
||||||
|
gateway_status: "verified"
|
||||||
|
frontend_status: "pending"
|
||||||
|
frontend_owner: ""
|
||||||
|
frontend_ref: ""
|
||||||
|
target_release: ""
|
||||||
|
verified_at: ""
|
||||||
|
status_note: ""
|
||||||
|
updated_at: "2026-09-06"
|
||||||
|
base: "dev-v3"
|
||||||
|
---
|
||||||
|
|
||||||
|
# 订单模块:判团字段透出(详情·列表·创单)
|
||||||
|
|
||||||
|
三个订单读接口响应透出判团字段 `groupBatchId` / `productBatchId` / `groupOrder`,供前端判断订单是否为团订单。关键口径:**判团只读 `groupBatchId`(非空=团订单)或 `groupOrder`**;`productBatchId` 仅供溯源,不参与判团。
|
||||||
|
|
||||||
|
## ⚠️ 关键变化
|
||||||
|
|
||||||
|
- **判团唯一口径** `groupBatchId` 非空或 `groupOrder = true` → 团订单;为空/false → 普通订单。
|
||||||
|
- **不要拿 `productBatchId` 反推团单**,该字段仅供产品侧班期溯源展示,产品侧可能有多个班期映射同一团期。
|
||||||
|
- 前一版 #7083 仅涉及订单主表冻结,本次全量透出给前端消费,与主表字段同源。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 二、变更接口清单
|
||||||
|
|
||||||
|
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||||
|
|---|------|------|------|----------|------|
|
||||||
|
| 1 | 订单详情 - 主单数据 | GET | `/v3/admin/order/{id}` | 响应新增字段 | data.main 新增 4 字段 |
|
||||||
|
| 2 | 订单分页列表 | GET | `/v3/admin/order` | 响应新增字段 | data.list[] 新增 2 字段 |
|
||||||
|
| 3 | 创建订单 | POST | `/v3/admin/order` | 响应新增字段 | data 新增 3 字段 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 三、接口详情
|
||||||
|
|
||||||
|
### 1. 订单详情 - 主单数据 `GET /v3/admin/order/{id}`
|
||||||
|
|
||||||
|
**VO**: `OrderMainVO`(响应位置:`data.main`)
|
||||||
|
|
||||||
|
#### 使用场景
|
||||||
|
|
||||||
|
打开订单详情页面时,读取订单主单数据及其判团标记,供前端决定显示团期相关内容(如「团期编号」、「团期名称」等)。
|
||||||
|
|
||||||
|
#### 入参
|
||||||
|
|
||||||
|
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||||
|
|------|------|------|------|------|------|
|
||||||
|
| `id` | Path | String | 是 | 正整数 ID | 订单 ID |
|
||||||
|
|
||||||
|
#### 出参 `Result<OrderDetailRespVO>`
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `data.main.groupBatchId` | String/null | 运营团期主键(order_group_batch.group_batch_id);非空=团订单,为空=普通订单 |
|
||||||
|
| `data.main.productBatchId` | String/null | 产品侧班期 ID(product_v2.group_tour_batch.batch_id);仅供溯源,不参与判团 |
|
||||||
|
| `data.main.groupOrder` | Boolean | 派生值,= groupBatchId != null;直接用于前端判团 |
|
||||||
|
| `data.main.batchNo` | String/null | 团期编号(order_group_batch.batch_no);普通单为 null,团期软删也为 null |
|
||||||
|
| `data.main.batchName` | String/null | 团期名称(order_group_batch.batch_name);普通单为 null,团期软删也为 null |
|
||||||
|
|
||||||
|
#### 请求示例
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /v3/admin/order/2096414445365338113
|
||||||
|
Authorization: Bearer <token>
|
||||||
|
```
|
||||||
|
|
||||||
|
无请求体。
|
||||||
|
|
||||||
|
#### 响应示例
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "成功",
|
||||||
|
"success": true,
|
||||||
|
"data": {
|
||||||
|
"main": {
|
||||||
|
"id": "2096414445365338113",
|
||||||
|
"orderNo": "HL20260906094527867",
|
||||||
|
"orderStatus": "PENDING_PAY",
|
||||||
|
"orderStatusName": "待支付",
|
||||||
|
"flowStatus": "AWAITING_PAY",
|
||||||
|
"flowStatusName": "待补全信息",
|
||||||
|
"flowStep": 0,
|
||||||
|
"flowStepTotal": 6,
|
||||||
|
"totalAmount": "5850.00",
|
||||||
|
"depositAmount": "1000.00",
|
||||||
|
"departureDate": "2026-10-01",
|
||||||
|
"returnDate": "2026-10-03",
|
||||||
|
"groupBatchId": "2096412454643802114",
|
||||||
|
"productBatchId": "2052935476557328386",
|
||||||
|
"groupOrder": true,
|
||||||
|
"batchNo": "Q202610012052935476548939777",
|
||||||
|
"batchName": "10月1日长白山亲子团",
|
||||||
|
"progressStepper": []
|
||||||
|
},
|
||||||
|
"profile": {},
|
||||||
|
"resource": {},
|
||||||
|
"contract": {},
|
||||||
|
"insurance": {},
|
||||||
|
"refund": {},
|
||||||
|
"aftersale": {},
|
||||||
|
"financial": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 空数据 / 降级响应
|
||||||
|
|
||||||
|
普通订单时,`groupBatchId`、`productBatchId`、`batchNo`、`batchName` 均为 null;`groupOrder` 为 false。
|
||||||
|
|
||||||
|
#### 错误响应
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 581007,
|
||||||
|
"message": "订单不存在",
|
||||||
|
"success": false,
|
||||||
|
"data": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
示例错误码:
|
||||||
|
- 581007:订单不存在
|
||||||
|
- 581045:房务角色无权查看订单详情(HTTP 200 code)
|
||||||
|
|
||||||
|
#### 业务边界
|
||||||
|
|
||||||
|
- 沿用订单详情权限校验;业务失败仍为 HTTP 200,需检查 code。
|
||||||
|
- 普通订单与团单在出参结构上无区别,仅字段值不同(null vs 有值)。
|
||||||
|
- 团期已软删时,`groupBatchId` 存在但对应记录不可查,`batchNo` / `batchName` 回退为 null。
|
||||||
|
- `productBatchId` 与 `groupBatchId` 无必然对应,前端不做交叉校验。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. 订单分页列表 `GET /v3/admin/order`
|
||||||
|
|
||||||
|
别名接口:`GET /v3/admin/order/list`
|
||||||
|
|
||||||
|
**VO**: `OrderListItemRespVO`(响应位置:`data.list[]`)
|
||||||
|
|
||||||
|
#### 使用场景
|
||||||
|
|
||||||
|
订单列表页加载数据时,带上新增的判团标记,供前端快速判断每行是否为团订单,可用于条件展示「团期信息」列或其他团期特化功能。
|
||||||
|
|
||||||
|
#### 入参
|
||||||
|
|
||||||
|
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||||
|
|------|------|------|------|------|------|
|
||||||
|
| `orderStatus` | Query | String | 否 | 枚举多值(逗号分隔) | 粗状态过滤(PENDING_PAY、CUSTOMIZING 等) |
|
||||||
|
| `flowStatus` | Query | String | 否 | 枚举多值(逗号分隔) | 细状态过滤(AWAITING_PAY、RESOURCE_PREPARING 等) |
|
||||||
|
| `tagNames` | Query | Array | 否 | - | 按标签过滤(多标签 OR 关系) |
|
||||||
|
| `keyword` | Query | String | 否 | - | 搜索关键字(团号/客户姓名/产品名/订单号 LIKE) |
|
||||||
|
| `departureDateFrom` | Query | String | 否 | yyyy-MM-dd | 出发日期范围起始 |
|
||||||
|
| `departureDateTo` | Query | String | 否 | yyyy-MM-dd | 出发日期范围结束 |
|
||||||
|
| `createSource` | Query | String | 否 | - | 来源过滤(CONSULTANT/MP/...) |
|
||||||
|
| `cancelled` | Query | Boolean | 否 | - | 是否含已取消订单(默认 false) |
|
||||||
|
| `consultantName` | Query | String | 否 | - | 定制师姓名 LIKE 模糊匹配 |
|
||||||
|
| `statusGroup` | Query | String | 否 | 枚举单值 | 按 Tab 分组(ALL/BEFORE_TRIP/ON_TRIP/SETTLEMENT/ABNORMAL/AFTERSALE) |
|
||||||
|
| `page` | Query | Integer | 是 | ≥1 | 页码 |
|
||||||
|
| `pageSize` | Query | Integer | 是 | ≤100 | 每页条数 |
|
||||||
|
|
||||||
|
#### 出参 `Result<Page<OrderListItemRespVO>>`
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `data.list[].groupBatchId` | String/null | 运营团期主键(order_group_batch.group_batch_id);非空=团订单 |
|
||||||
|
| `data.list[].groupOrder` | Boolean | 派生值,= groupBatchId != null;直接用于前端判团 |
|
||||||
|
|
||||||
|
其他字段详见现有订单列表接口文档。
|
||||||
|
|
||||||
|
#### 请求示例
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /v3/admin/order?page=1&pageSize=20
|
||||||
|
Authorization: Bearer <token>
|
||||||
|
```
|
||||||
|
|
||||||
|
无请求体。
|
||||||
|
|
||||||
|
#### 响应示例
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "成功",
|
||||||
|
"success": true,
|
||||||
|
"data": {
|
||||||
|
"total": 150,
|
||||||
|
"list": [
|
||||||
|
{
|
||||||
|
"id": "2096414445365338113",
|
||||||
|
"orderNo": "HL20260906094527867",
|
||||||
|
"productName": "冻干粉发短信给",
|
||||||
|
"customerName": "张三",
|
||||||
|
"departureDate": "2026-10-01",
|
||||||
|
"orderStatus": "PENDING_PAY",
|
||||||
|
"flowStatus": "AWAITING_PAY",
|
||||||
|
"totalAmount": "5850.00",
|
||||||
|
"groupBatchId": "2096412454643802114",
|
||||||
|
"groupOrder": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "2096414445365338114",
|
||||||
|
"orderNo": "HL20260906094527868",
|
||||||
|
"productName": "其他产品",
|
||||||
|
"customerName": "李四",
|
||||||
|
"departureDate": "2026-10-02",
|
||||||
|
"orderStatus": "PENDING_DEPARTURE",
|
||||||
|
"flowStatus": "PENDING_DEPARTURE",
|
||||||
|
"totalAmount": "8000.00",
|
||||||
|
"groupBatchId": null,
|
||||||
|
"groupOrder": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 空数据 / 降级响应
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "成功",
|
||||||
|
"success": true,
|
||||||
|
"data": {
|
||||||
|
"total": 0,
|
||||||
|
"list": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 错误响应
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 401,
|
||||||
|
"message": "未登录或登录已过期",
|
||||||
|
"success": false,
|
||||||
|
"data": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 业务边界
|
||||||
|
|
||||||
|
- 分页字段 `page` / `pageSize` 沿用现有约束。
|
||||||
|
- 列表返回最新 50 条或 100 条时,两个新增字段保证同时返回,不存在部分返回的情况。
|
||||||
|
- `groupOrder` 是 `groupBatchId != null` 的派生布尔值,前端可二选一使用。
|
||||||
|
- 普通订单与团单混合返回,字段值直接对标订单属性。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. 创建订单 `POST /v3/admin/order`
|
||||||
|
|
||||||
|
**VO**: `OrderCreateRespVO`(响应位置:`data`)
|
||||||
|
|
||||||
|
#### 使用场景
|
||||||
|
|
||||||
|
创建订单后,管理端「订单已创建」弹窗或后续流程需判断该单是否为团单,及时显示团期相关信息(如「团期编号」、「出发日期」等)。
|
||||||
|
|
||||||
|
#### 入参
|
||||||
|
|
||||||
|
沿用现有 `OrderCreateReqVO`,**请求体不变**(本单只改响应)。字段表:
|
||||||
|
|
||||||
|
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||||
|
|------|------|------|------|------|------|
|
||||||
|
| `productId` | Body | String(Long) | ✅ | 正整数 | 产品 ID |
|
||||||
|
| `tierSeq` | Body | Integer | ✅ | ≥1 | 档位序号 |
|
||||||
|
| `departureDate` | Body | String(yyyy-MM-dd) | ✅ | 不早于今天 | 出发日期 |
|
||||||
|
| `adultCount` | Body | Integer | ✅ | ≥1 | 成人数 |
|
||||||
|
| `childCount` | Body | Integer | 否 | ≥0,默认 0 | 儿童数 |
|
||||||
|
| `youngChildCount` | Body | Integer | 否 | ≥0,默认 0 | 小童数 |
|
||||||
|
| `babyCount` | Body | Integer | 否 | ≥0,默认 0 | 婴儿数 |
|
||||||
|
| `customerName` | Body | String | ✅ | @NotBlank | 客户姓名 |
|
||||||
|
| `customerPhone` | Body | String | ✅ | ^1[3-9]\d{9}$ | 客户手机号 |
|
||||||
|
| `customerRemark` | Body | String | 否 | ≤500 | 备注 |
|
||||||
|
| `createSource` | Body | String | 否 | 默认 CONSULTANT | 创建来源 |
|
||||||
|
| `productBatchId` | Body | String(Long) | 否 | GROUP 必传、非 GROUP 禁传 | 团期 ID(见 #7135) |
|
||||||
|
| `roomCount` | Body | Integer | 否 | ≥1 | 房间数 |
|
||||||
|
| `tags` | Body | Array<String> | 否 | — | 订单标签 |
|
||||||
|
|
||||||
|
#### 出参 `Result<OrderCreateRespVO>`
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `data.id` | String | 订单主键 |
|
||||||
|
| `data.orderNo` | String | 订单号 |
|
||||||
|
| `data.orderStatus` | String | 粗状态(创单后为 PENDING_PAY) |
|
||||||
|
| `data.flowStatus` | String | 细状态(创单后为 AWAITING_PAY) |
|
||||||
|
| `data.flowStep` | Integer | 线性步序(创单初态为 0) |
|
||||||
|
| `data.flowStepTotal` | Integer | 总步数(固定 6) |
|
||||||
|
| `data.productName` | String | 产品名 |
|
||||||
|
| `data.tierName` | String | 档位名 |
|
||||||
|
| `data.departureDate` | String | 出发日期 |
|
||||||
|
| `data.returnDate` | String | 返团日期 |
|
||||||
|
| `data.totalAmount` | String | 订单总价 |
|
||||||
|
| `data.depositAmount` | String | 建议定金金额 |
|
||||||
|
| `data.depositRatio` | Integer/null | 定金比例百分比 |
|
||||||
|
| `data.depositMode` | String | 定金计算模式(FIXED/RATIO/FULL) |
|
||||||
|
| `data.paymentMode` | String | 支付模式(DEPOSIT/FULL) |
|
||||||
|
| `data.groupBatchId` | String/null | 运营团期 ID(创单同事务回写);非空=团订单 |
|
||||||
|
| `data.productBatchId` | String/null | 产品侧班期 ID(创单入参原样固化);仅供溯源 |
|
||||||
|
| `data.groupOrder` | Boolean | 派生值,= groupBatchId != null;直接用于判团 |
|
||||||
|
|
||||||
|
#### 请求示例
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"productId": 2044306857534636034,
|
||||||
|
"tierSeq": 1,
|
||||||
|
"departureDate": "2026-10-01",
|
||||||
|
"adultCount": 2,
|
||||||
|
"childCount": 1,
|
||||||
|
"customerName": "张三",
|
||||||
|
"customerPhone": "13800138000",
|
||||||
|
"productBatchId": 2052935476557328386,
|
||||||
|
"createSource": "CONSULTANT"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 响应示例
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "成功",
|
||||||
|
"success": true,
|
||||||
|
"data": {
|
||||||
|
"id": "2096414445365338113",
|
||||||
|
"orderNo": "HL20260906094527867",
|
||||||
|
"orderStatus": "PENDING_PAY",
|
||||||
|
"orderStatusName": "待支付",
|
||||||
|
"flowStatus": "AWAITING_PAY",
|
||||||
|
"flowStatusName": "待补全信息",
|
||||||
|
"flowStep": 0,
|
||||||
|
"flowStepTotal": 6,
|
||||||
|
"flowStepName": "待支付",
|
||||||
|
"productName": "冻干粉发短信给",
|
||||||
|
"tierName": "经典档",
|
||||||
|
"departureDate": "2026-10-01",
|
||||||
|
"returnDate": "2026-10-03",
|
||||||
|
"totalAmount": "5850.00",
|
||||||
|
"depositAmount": "1000.00",
|
||||||
|
"depositRatio": null,
|
||||||
|
"depositMode": "FIXED",
|
||||||
|
"paymentMode": "DEPOSIT",
|
||||||
|
"expiryMinutes": 1440,
|
||||||
|
"payUrl": "https://pay.hulalv.com/pay/HL20260906094527867",
|
||||||
|
"customerName": "张三",
|
||||||
|
"groupBatchId": "2096412454643802114",
|
||||||
|
"productBatchId": "2052935476557328386",
|
||||||
|
"groupOrder": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
实测数据示例(2026-09-06 测试服):
|
||||||
|
- 产品「冻干粉发短信给」productId=2044306857534636034
|
||||||
|
- 班期 2026-10-01 productBatchId=2052935476557328386
|
||||||
|
- 团期主键 groupBatchId=2096412454643802114
|
||||||
|
- 团期编号 batchNo=Q202610012052935476548939777
|
||||||
|
|
||||||
|
#### 空数据 / 降级响应
|
||||||
|
|
||||||
|
创建订单成功后无空数据响应。
|
||||||
|
|
||||||
|
#### 错误响应
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 400,
|
||||||
|
"message": "产品 ID 非法或产品已下架",
|
||||||
|
"success": false,
|
||||||
|
"data": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 业务边界
|
||||||
|
|
||||||
|
- 创建普通订单时,`groupBatchId` / `productBatchId` 为 null,`groupOrder` 为 false。
|
||||||
|
- 创建 GROUP 产品订单时(必须提交 `productBatchId`),后端在同事务内懒创建或命中已有团期,回写 `groupBatchId`。
|
||||||
|
- `groupBatchName` 字段当前恒为 null(仅为兼容既有前端契约),**前端不要读它**。
|
||||||
|
- 响应中 `groupBatchId` / `productBatchId` 为字符串(JSON 序列化后,避免 JS 精度丢失);前端若需数值运算应转换为字符串存储。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 四、契约约束与正确调用方式
|
||||||
|
|
||||||
|
| 场景 | 正确做法 |
|
||||||
|
|------|---------|
|
||||||
|
| 判断订单是否团单 | 读 `groupBatchId` 非空 或 `groupOrder == true`,两者等价 |
|
||||||
|
| 不要用 productBatchId 判团 | `productBatchId` 仅供溯源,可能 null(普通单)或有值(团单/非团单均可能) |
|
||||||
|
| 团单需显示团期名 | `groupBatchName` 恒为 null,读 `batchName`;团期软删时也为 null |
|
||||||
|
| 普通单与团单混合渲染 | 按 `groupOrder` 条件渲染,普通单该字段为 false;两类订单出参结构一致,仅值不同 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 五、数据库行为
|
||||||
|
|
||||||
|
| 订单类型 | groupBatchId | productBatchId | groupOrder | batchNo | batchName |
|
||||||
|
|---------|-------------|----------------|-----------|---------|-----------|
|
||||||
|
| 普通订单 | null | null | false | null | null |
|
||||||
|
| 团单(命中或懒建) | 非空 | 非空 | true | 有值 | 有值 |
|
||||||
|
| 团期已软删 | 非空 | 非空 | true | null | null |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 六、边界行为
|
||||||
|
|
||||||
|
- 业务失败可能仍为 HTTP 200,必须同时检查 `code`、`success` 和 `message`。
|
||||||
|
- 详情接口 404/权限 403 时直接返回对应 HTTP 状态码;业务类失败(如订单状态不符)返回 HTTP 200 + code。
|
||||||
|
- 列表空结果返回 `total=0, list=[]`,分页参数超界时返回空列表(无 5XX)。
|
||||||
|
- 创单失败不落库,响应 HTTP 200 + code,data 为 null。
|
||||||
|
- 新增判团字段与现有字段同源、同时刷新,无时间差。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 六.6、修改前后对比
|
||||||
|
|
||||||
|
### 字段级对比
|
||||||
|
|
||||||
|
| 字段 | 改前 | 改后 |
|
||||||
|
|------|------|------|
|
||||||
|
| `groupBatchId` | 无此字段 | 新增;运营团期主键 |
|
||||||
|
| `productBatchId` | 无此字段 | 新增;产品班期 ID(仅溯源) |
|
||||||
|
| `groupOrder` | 无此字段 | 新增;派生布尔,= groupBatchId != null |
|
||||||
|
| `batchNo` | 无此字段 | 新增;团期编号(详情/列表) |
|
||||||
|
| `batchName` | 无此字段 | 新增;团期名称(详情/列表) |
|
||||||
|
|
||||||
|
### 行为级对比
|
||||||
|
|
||||||
|
| 行为 | 改前 | 改后 |
|
||||||
|
|------|------|------|
|
||||||
|
| 前端判团依据 | 无判团字段,无法直接判别 | 读 groupBatchId 非空 或 groupOrder = true |
|
||||||
|
| 团单信息展示 | 依赖联查或额外接口 | 直接在订单响应中获得 |
|
||||||
|
| 产品班期溯源 | 不支持 | 新增 productBatchId(仅溯源展示) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 六.7、影响评估
|
||||||
|
|
||||||
|
- **是否破坏向后兼容**: 否。新增字段对旧客户端透明,非必需字段缺失时前端框架可靠 null 处理。
|
||||||
|
- **前端是否必须同步上线**: 是。前端需接入新增四个字段至详情/列表/创建成功弹窗模板,判团逻辑改用 groupBatchId 或 groupOrder。
|
||||||
|
- **前端 workaround 清理点**:
|
||||||
|
- 删除旧的"通过产品 ID 推断团单"逻辑,改用 groupBatchId 判别
|
||||||
|
- 不要硬编码团期编号/名称,改用响应中的 batchNo / batchName
|
||||||
|
- groupBatchName 恒为 null,勿读之;用 batchName 替代
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 七、不影响范围
|
||||||
|
|
||||||
|
- **仅影响**: 管理后台订单详情、列表和创建流程的前端渲染
|
||||||
|
- **零影响**:
|
||||||
|
- 订单创建/编辑/取消接口
|
||||||
|
- 小程序端(MpOrderDetailVO 不变)
|
||||||
|
- 数据库结构(新字段冻结在 order_main 表,无表改动)
|
||||||
|
- 订单写操作和业务流程
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 八、测试环境已验证
|
||||||
|
|
||||||
|
- **单测**: 559 条测试用例绿✓(新增判团字段相关的 UT 已覆盖普通单/团单双路径)
|
||||||
|
- **ArchTest**: 45 条架构测试绿✓
|
||||||
|
- **测试服网关实测**: 单测 + CR 通过;测试服网关实测见管理者补充
|
||||||
|
|
||||||
|
实测产品: `productId=2044306857534636034`(冻干粉发短信给),班期 `2026-10-01`(productBatchId=2052935476557328386),团期主键 `groupBatchId=2096412454643802114`,批号 `batchNo=Q202610012052935476548939777`。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 九、相关历史 PR(功能演进)
|
||||||
|
|
||||||
|
| PR | Issue | 说明 | 是否仍有效 |
|
||||||
|
|----|-------|------|------------|
|
||||||
|
| #7083 | - | order_group_batch 一跳直连,订单主表冻结 groupBatchId/productBatchId | ✅ 有效 |
|
||||||
|
| #7135 | - | GROUP 产品创单校验与重整 | ✅ 有效 |
|
||||||
|
| #7143 | - | 团期看板 VO 补 productId(配合本 PR) | ✅ 有效 |
|
||||||
|
| **本 PR #7155** | **#7142** | **订单详情/列表/创单响应透出判团字段** | ✅ 最新 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 十、相关文档
|
||||||
|
|
||||||
|
- 关联 Issue: [wx/HL#7142](https://git.1814.love:8443/wx/HL/issues/7142)
|
||||||
|
- 关联 PR: [wx/HL#7155](https://git.1814.love:8443/wx/HL/pulls/7155)
|
||||||
|
- 团期接口文档: `docs/ARCHITECTURE.md` §0A.2.2(团单冻结口径)
|
||||||
|
- 团单业务规范: 见 #7135 changelog 中的团期创建规则
|
||||||
|
|
||||||
|
## 关联 / 联系人
|
||||||
|
|
||||||
|
### 链接
|
||||||
|
|
||||||
|
- **Issue**: [#7142](https://git.1814.love:8443/wx/HL/issues/7142)
|
||||||
|
- **PR**: [#7155](https://git.1814.love:8443/wx/HL/pulls/7155)
|
||||||
|
- **Merge commit**: [21d3d00de](https://git.1814.love:8443/wx/HL/commit/21d3d00de)
|
||||||
|
|
||||||
|
### 联系人
|
||||||
|
|
||||||
|
- **后端负责人**: @wx
|
||||||
@@ -0,0 +1,573 @@
|
|||||||
|
---
|
||||||
|
schema: "hl-changelog/v2"
|
||||||
|
ticket: "7143"
|
||||||
|
title: "团期看板分页项/详情/看板 VO 补 productId(供新增子订单深链预填产品)"
|
||||||
|
consumer: "admin"
|
||||||
|
author: "wx(GIT)"
|
||||||
|
change_type: "修改接口"
|
||||||
|
backend_status: "deployed"
|
||||||
|
gateway_status: "verified"
|
||||||
|
frontend_status: "pending"
|
||||||
|
frontend_owner: ""
|
||||||
|
frontend_ref: ""
|
||||||
|
target_release: ""
|
||||||
|
verified_at: ""
|
||||||
|
status_note: ""
|
||||||
|
updated_at: "2026-09-06"
|
||||||
|
base: "dev-v3"
|
||||||
|
---
|
||||||
|
|
||||||
|
# 团期模块:看板 VO 补 productId
|
||||||
|
|
||||||
|
三个团期看板读接口响应新增 `productId` 字段,供前端「新增子订单」深链到订单创建向导时预填产品和班期 ID,锁定出发日期。前端逻辑:**仅 GROUP 产品创单时才在 payload 中带 productBatchId**;非 GROUP 产品忽略 productBatchId。
|
||||||
|
|
||||||
|
## ⚠️ 关键变化
|
||||||
|
|
||||||
|
- 新增 `productId` 是产品主键,用于深链向导 `/order-v2/new?productId={productId}&productBatchId={productBatchId}&departureDate={date}` 的预填参数。
|
||||||
|
- 向导跳转后,**仅 GROUP 产品**应将 productBatchId 放入订单创建 POST payload;其他产品类型忽略该参数。
|
||||||
|
- 前端缺陷单已发,说明旧建单向导漏传 productBatchId,新版本补全(见关联链接)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 二、变更接口清单
|
||||||
|
|
||||||
|
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||||
|
|---|------|------|------|----------|------|
|
||||||
|
| 1 | 团期分页列表 | GET | `/v3/admin/order/group-batch` | 响应新增字段 | data.list[] 各项新增 productId |
|
||||||
|
| 2 | 团期详情 | GET | `/v3/admin/order/group-batch/{groupBatchId}` | 响应新增字段 | data 新增 productId |
|
||||||
|
| 3 | 团期看板列表 | GET | `/v3/admin/order/group-batch/board?productId=` | 响应新增字段 | data[] 各项新增 productId |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 三、接口详情
|
||||||
|
|
||||||
|
### 1. 团期分页列表 `GET /v3/admin/order/group-batch`
|
||||||
|
|
||||||
|
**VO**: `GroupBatchPageItemRespVO`(响应位置:`data.list[]`)
|
||||||
|
|
||||||
|
#### 使用场景
|
||||||
|
|
||||||
|
加载团期分页列表时,每一行包含产品 ID,前端点击「新增子订单」时取该行的 productId / productBatchId / departureDate 拼接深链,跳转到订单创建向导。
|
||||||
|
|
||||||
|
#### 入参
|
||||||
|
|
||||||
|
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||||
|
|------|------|------|------|------|------|
|
||||||
|
| `productId` | Query | String | 否 | 正整数 ID | 按产品筛选 |
|
||||||
|
| `batchStatus` | Query | String | 否 | 枚举值 | 按团期状态码筛选(RECRUITING/RESOURCE_PREPARING/...) |
|
||||||
|
| `deadlineFrom` | Query | String | 否 | yyyy-MM-dd | 报名截止日起 |
|
||||||
|
| `deadlineTo` | Query | String | 否 | yyyy-MM-dd | 报名截止日止 |
|
||||||
|
| `opsStage` | Query | String | 否 | 枚举值 | 按运营阶段筛选(RECRUIT/FORMED/PENDING_TRIP/TRAVELLING/AUDITING/CHECKED/DISBANDED) |
|
||||||
|
| `month` | Query | String | 否 | yyyy-MM | 按出发月份筛选 |
|
||||||
|
| `keyword` | Query | String | 否 | - | 班期编号/班期名称模糊关键词 |
|
||||||
|
| `pageNo` | Query | Integer | 是 | ≥1 | 页码,从 1 开始 |
|
||||||
|
| `pageSize` | Query | Integer | 是 | ≤100 | 每页条数,默认 20 |
|
||||||
|
|
||||||
|
#### 出参 `Result<Page<GroupBatchPageItemRespVO>>`
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `data.list[].groupBatchId` | String | 团期聚合主键 |
|
||||||
|
| `data.list[].productBatchId` | String | product 侧班期 ID |
|
||||||
|
| `data.list[].productId` | String | **【新增】** 产品 ID,供深链预填 |
|
||||||
|
| `data.list[].productName` | String | 产品名称 |
|
||||||
|
| `data.list[].batchNo` | String | 班期编号 |
|
||||||
|
| `data.list[].batchName` | String | 班期名称 |
|
||||||
|
| `data.list[].batchStatus` | String | 团期状态码 |
|
||||||
|
| `data.list[].batchStatusName` | String | 状态中文名 |
|
||||||
|
| `data.list[].minGroupPeople` | Integer | 最低成团人数 |
|
||||||
|
| `data.list[].maxRooms` | Integer | 房间容量 |
|
||||||
|
| `data.list[].maxParticipants` | Integer | 人数容量 |
|
||||||
|
| `data.list[].enrolledPeople` | Integer | 已报名人数 |
|
||||||
|
| `data.list[].enrolledRooms` | Integer | 已用房间数 |
|
||||||
|
| `data.list[].remainRooms` | Integer | 剩余房间数 |
|
||||||
|
| `data.list[].remainParticipants` | Integer | 剩余人数 |
|
||||||
|
| `data.list[].orderCount` | Integer | 子订单数 |
|
||||||
|
| `data.list[].enrollDeadline` | String | 报名截止日 |
|
||||||
|
| `data.list[].departDate` | String | 出发日期 |
|
||||||
|
| `data.list[].endDate` | String | 结束日期 |
|
||||||
|
| `data.list[].receivableAmount` | String | 整团应收合计 |
|
||||||
|
| `data.list[].receivedAmount` | String | 整团已收合计 |
|
||||||
|
| `data.list[].chips` | Object | 六芯片整团聚合态(hotel/vehicle/guide/photo/contract/insurance,各为字符串状态:全部完成为 `DONE`,存在未完成项为待办态;完整取值见六芯片文档 GB-ADM-090~095) |
|
||||||
|
|
||||||
|
#### 请求示例
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /v3/admin/order/group-batch?pageNo=1&pageSize=20
|
||||||
|
Authorization: Bearer <token>
|
||||||
|
```
|
||||||
|
|
||||||
|
无请求体。
|
||||||
|
|
||||||
|
#### 响应示例
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "成功",
|
||||||
|
"success": true,
|
||||||
|
"data": {
|
||||||
|
"total": 5,
|
||||||
|
"list": [
|
||||||
|
{
|
||||||
|
"groupBatchId": "2096412454643802114",
|
||||||
|
"productBatchId": "2052935476557328386",
|
||||||
|
"productId": "2044306857534636034",
|
||||||
|
"productName": "冻干粉发短信给",
|
||||||
|
"batchNo": "Q202610012052935476548939777",
|
||||||
|
"batchName": "10月1日长白山亲子团",
|
||||||
|
"batchStatus": "RECRUITING",
|
||||||
|
"batchStatusName": "招募中",
|
||||||
|
"minGroupPeople": 6,
|
||||||
|
"maxRooms": 4,
|
||||||
|
"maxParticipants": 10,
|
||||||
|
"enrolledPeople": 4,
|
||||||
|
"enrolledRooms": 2,
|
||||||
|
"remainRooms": 2,
|
||||||
|
"remainParticipants": 6,
|
||||||
|
"orderCount": 2,
|
||||||
|
"enrollDeadline": "2026-09-25",
|
||||||
|
"departDate": "2026-10-01",
|
||||||
|
"endDate": "2026-10-03",
|
||||||
|
"receivableAmount": "48000.00",
|
||||||
|
"receivedAmount": "36000.00",
|
||||||
|
"chips": {
|
||||||
|
"hotel": "DONE",
|
||||||
|
"vehicle": "DONE",
|
||||||
|
"guide": "DONE",
|
||||||
|
"photo": "DONE",
|
||||||
|
"contract": "DONE",
|
||||||
|
"insurance": "DONE"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 空数据 / 降级响应
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "成功",
|
||||||
|
"success": true,
|
||||||
|
"data": {
|
||||||
|
"total": 0,
|
||||||
|
"list": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 错误响应
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 589507,
|
||||||
|
"message": "无操作权限",
|
||||||
|
"success": false,
|
||||||
|
"data": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 业务边界
|
||||||
|
|
||||||
|
- 沿用团期列表权限校验(团期管理员/运营/定制师等);无权返回 589507。
|
||||||
|
- `productId` 与其他字段同时生效,始终非空(命中行/未命中行/孤儿行均返回)。
|
||||||
|
- 分页参数超界时返回空列表。
|
||||||
|
- 业务失败仍为 HTTP 200,需检查 code。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. 团期详情 `GET /v3/admin/order/group-batch/{groupBatchId}`
|
||||||
|
|
||||||
|
**VO**: `GroupBatchDetailRespVO`(响应位置:`data`)
|
||||||
|
|
||||||
|
#### 使用场景
|
||||||
|
|
||||||
|
打开团期详情页时,读取产品 ID,供「新增子订单」按钮拼接深链,跳转订单创建向导并预填产品及班期。
|
||||||
|
|
||||||
|
#### 入参
|
||||||
|
|
||||||
|
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||||
|
|------|------|------|------|------|------|
|
||||||
|
| `groupBatchId` | Path | String | 是 | 正整数 ID | 团期主键 |
|
||||||
|
|
||||||
|
#### 出参 `Result<GroupBatchDetailRespVO>`
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `data.groupBatchId` | String | 团期聚合主键 |
|
||||||
|
| `data.productBatchId` | String | product 侧班期 ID |
|
||||||
|
| `data.productId` | String | **【新增】** 产品 ID,供深链预填 |
|
||||||
|
| `data.productName` | String | 产品名称 |
|
||||||
|
| `data.batchNo` | String | 班期编号 |
|
||||||
|
| `data.batchName` | String | 班期名称 |
|
||||||
|
| `data.batchLabel` | String/null | 班期标签快照 |
|
||||||
|
| `data.batchStatus` | String | 团期状态码 |
|
||||||
|
| `data.batchStatusName` | String | 状态中文名 |
|
||||||
|
| `data.minGroupPeople` | Integer | 最低成团人数 |
|
||||||
|
| `data.maxRooms` | Integer | 房间容量 |
|
||||||
|
| `data.maxParticipants` | Integer | 人数容量 |
|
||||||
|
| `data.enrolledPeople` | Integer | 已报名人数 |
|
||||||
|
| `data.enrolledRooms` | Integer | 已用房间数 |
|
||||||
|
| `data.remainRooms` | Integer | 剩余房间数 |
|
||||||
|
| `data.remainParticipants` | Integer | 剩余人数 |
|
||||||
|
| `data.hotelReady` | Boolean | 配房完成标志 |
|
||||||
|
| `data.vehicleReady` | Boolean | 配车完成标志 |
|
||||||
|
| `data.guideReady` | Boolean | 导游完成标志 |
|
||||||
|
| `data.photographerReady` | Boolean | 摄影完成标志 |
|
||||||
|
| `data.materialConfirmed` | Boolean | 物资确认标志 |
|
||||||
|
| `data.requirementConfirmed` | Boolean | 需求整体确认标志 |
|
||||||
|
| `data.departDate` | String | 出发日期 |
|
||||||
|
| `data.endDate` | String | 结束日期 |
|
||||||
|
| `data.enrollDeadline` | String | 报名截止日 |
|
||||||
|
| `data.totalReceivable` | String | 整团应收合计 |
|
||||||
|
| `data.totalReceived` | String | 整团已收合计 |
|
||||||
|
| `data.remark` | String/null | 备注 |
|
||||||
|
|
||||||
|
#### 请求示例
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /v3/admin/order/group-batch/2096412454643802114
|
||||||
|
Authorization: Bearer <token>
|
||||||
|
```
|
||||||
|
|
||||||
|
无请求体。
|
||||||
|
|
||||||
|
#### 响应示例
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "成功",
|
||||||
|
"success": true,
|
||||||
|
"data": {
|
||||||
|
"groupBatchId": "2096412454643802114",
|
||||||
|
"productBatchId": "2052935476557328386",
|
||||||
|
"productId": "2044306857534636034",
|
||||||
|
"productName": "冻干粉发短信给",
|
||||||
|
"batchNo": "Q202610012052935476548939777",
|
||||||
|
"batchName": "10月1日长白山亲子团",
|
||||||
|
"batchLabel": null,
|
||||||
|
"batchStatus": "RESOURCE_PREPARING",
|
||||||
|
"batchStatusName": "资源准备中",
|
||||||
|
"minGroupPeople": 6,
|
||||||
|
"maxRooms": 4,
|
||||||
|
"maxParticipants": 10,
|
||||||
|
"enrolledPeople": 10,
|
||||||
|
"enrolledRooms": 4,
|
||||||
|
"remainRooms": 0,
|
||||||
|
"remainParticipants": 0,
|
||||||
|
"hotelReady": false,
|
||||||
|
"vehicleReady": false,
|
||||||
|
"guideReady": false,
|
||||||
|
"photographerReady": false,
|
||||||
|
"materialConfirmed": false,
|
||||||
|
"requirementConfirmed": false,
|
||||||
|
"departDate": "2026-10-01",
|
||||||
|
"endDate": "2026-10-03",
|
||||||
|
"enrollDeadline": "2026-09-25",
|
||||||
|
"totalReceivable": "48000.00",
|
||||||
|
"totalReceived": "36000.00",
|
||||||
|
"remark": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 空数据 / 降级响应
|
||||||
|
|
||||||
|
详情接口不存在空数据响应。
|
||||||
|
|
||||||
|
#### 错误响应
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 589501,
|
||||||
|
"message": "团期不存在",
|
||||||
|
"success": false,
|
||||||
|
"data": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
业务失败错误码沿用 GroupBatchErrorCode(权限/不存在等)。
|
||||||
|
|
||||||
|
#### 业务边界
|
||||||
|
|
||||||
|
- 沿用团期详情权限校验;无权返回 589507。
|
||||||
|
- `productId` 与其他字段同时返回,始终非空。
|
||||||
|
- 团期不存在返回业务码 589501(HTTP 200)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. 团期看板列表 `GET /v3/admin/order/group-batch/board?productId=`
|
||||||
|
|
||||||
|
**VO**: `GroupBatchBoardItemRespVO`(响应位置:`data[]`)
|
||||||
|
|
||||||
|
#### 使用场景
|
||||||
|
|
||||||
|
打开团期看板时,按产品维度加载该产品下的所有班期(产品侧班期为基底,左连运营侧团期数据),每一行携带 productId,供「新增子订单」深链按行数据拼接预填参数。
|
||||||
|
|
||||||
|
#### 入参
|
||||||
|
|
||||||
|
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||||
|
|------|------|------|------|------|------|
|
||||||
|
| `productId` | Query | String | 是 | 正整数 ID | 按产品筛选(必填;缺参返回 400) |
|
||||||
|
|
||||||
|
#### 出参 `Result<List<GroupBatchBoardItemRespVO>>`
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `data[].productBatchId` | String | product 侧班期 ID |
|
||||||
|
| `data[].productId` | String | **【新增】** 产品 ID(= 请求入参;命中/未命中/孤儿行均非空) |
|
||||||
|
| `data[].batchNo` | String | 班期编号 |
|
||||||
|
| `data[].batchName` | String | 班期名称 |
|
||||||
|
| `data[].departureDate` | String | 出发日期 |
|
||||||
|
| `data[].endDate` | String | 结束日期 |
|
||||||
|
| `data[].enrollmentDeadline` | String | 报名截止日 |
|
||||||
|
| `data[].maxRooms` | Integer | 房间容量 |
|
||||||
|
| `data[].maxParticipants` | Integer | 人数容量 |
|
||||||
|
| `data[].enrolledRooms` | Integer | 已报名房数 |
|
||||||
|
| `data[].enrolledPeople` | Integer | 已报名人数 |
|
||||||
|
| `data[].remainRooms` | Integer | 剩余房间数 |
|
||||||
|
| `data[].remainParticipants` | Integer | 剩余人数 |
|
||||||
|
| `data[].batchStatus` | String | 团期状态码 |
|
||||||
|
| `data[].batchStatusLabel` | String | 状态中文名 |
|
||||||
|
| `data[].hotelReady` | Boolean | 配房完成标志 |
|
||||||
|
| `data[].vehicleReady` | Boolean | 配车完成标志 |
|
||||||
|
| `data[].guideReady` | Boolean | 导游完成标志 |
|
||||||
|
| `data[].photographerReady` | Boolean | 摄影完成标志 |
|
||||||
|
| `data[].needsGuide` | Boolean | 是否需领队 |
|
||||||
|
| `data[].needsPhotographer` | Boolean | 是否需摄影 |
|
||||||
|
| `data[].orderCount` | Integer | 活跃子订单数 |
|
||||||
|
| `data[].groupBatchId` | String/null | 团期聚合主键(未成团时为 null) |
|
||||||
|
| `data[].productBatchRemoved` | Boolean | 是否孤儿行(product 侧已删除该班期) |
|
||||||
|
| `data[].contractSignedCount` | Integer | 合同已签子订单数 |
|
||||||
|
| `data[].insuranceInsuredCount` | Integer | 保险已出子订单数 |
|
||||||
|
|
||||||
|
#### 请求示例
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /v3/admin/order/group-batch/board?productId=2044306857534636034
|
||||||
|
Authorization: Bearer <token>
|
||||||
|
```
|
||||||
|
|
||||||
|
无请求体。
|
||||||
|
|
||||||
|
#### 响应示例
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "成功",
|
||||||
|
"success": true,
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"productBatchId": "2052935476557328386",
|
||||||
|
"productId": "2044306857534636034",
|
||||||
|
"batchNo": "Q202610012052935476548939777",
|
||||||
|
"batchName": "10月1日长白山亲子团",
|
||||||
|
"departureDate": "2026-10-01",
|
||||||
|
"endDate": "2026-10-03",
|
||||||
|
"enrollmentDeadline": "2026-09-25",
|
||||||
|
"maxRooms": 4,
|
||||||
|
"maxParticipants": 10,
|
||||||
|
"enrolledRooms": 2,
|
||||||
|
"enrolledPeople": 4,
|
||||||
|
"remainRooms": 2,
|
||||||
|
"remainParticipants": 6,
|
||||||
|
"batchStatus": "RECRUITING",
|
||||||
|
"batchStatusLabel": "招募中",
|
||||||
|
"hotelReady": false,
|
||||||
|
"vehicleReady": false,
|
||||||
|
"guideReady": false,
|
||||||
|
"photographerReady": false,
|
||||||
|
"needsGuide": true,
|
||||||
|
"needsPhotographer": false,
|
||||||
|
"orderCount": 2,
|
||||||
|
"groupBatchId": "2096412454643802114",
|
||||||
|
"productBatchRemoved": false,
|
||||||
|
"contractSignedCount": 1,
|
||||||
|
"insuranceInsuredCount": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"productBatchId": "2052935476557328387",
|
||||||
|
"productId": "2044306857534636034",
|
||||||
|
"batchNo": "Q202610022052935476548939778",
|
||||||
|
"batchName": "10月2日长白山亲子团",
|
||||||
|
"departureDate": "2026-10-02",
|
||||||
|
"endDate": "2026-10-04",
|
||||||
|
"enrollmentDeadline": "2026-09-26",
|
||||||
|
"maxRooms": 4,
|
||||||
|
"maxParticipants": 10,
|
||||||
|
"enrolledRooms": 0,
|
||||||
|
"enrolledPeople": 0,
|
||||||
|
"remainRooms": 4,
|
||||||
|
"remainParticipants": 10,
|
||||||
|
"batchStatus": "RECRUITING",
|
||||||
|
"batchStatusLabel": "招募中",
|
||||||
|
"hotelReady": false,
|
||||||
|
"vehicleReady": false,
|
||||||
|
"guideReady": false,
|
||||||
|
"photographerReady": false,
|
||||||
|
"needsGuide": true,
|
||||||
|
"needsPhotographer": false,
|
||||||
|
"orderCount": 0,
|
||||||
|
"groupBatchId": null,
|
||||||
|
"productBatchRemoved": false,
|
||||||
|
"contractSignedCount": 0,
|
||||||
|
"insuranceInsuredCount": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 空数据 / 降级响应
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "成功",
|
||||||
|
"success": true,
|
||||||
|
"data": []
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 错误响应
|
||||||
|
|
||||||
|
缺参 productId:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 400,
|
||||||
|
"message": "productId 参数缺失",
|
||||||
|
"success": false,
|
||||||
|
"data": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 业务边界
|
||||||
|
|
||||||
|
- `productId` 必填,缺参返回 HTTP 200 + code 400;不要用 GET 参数默认值。
|
||||||
|
- 返回结构按产品侧班期为基底(左连团期数据):
|
||||||
|
- **命中行**(班期有对应团期):团期状态/容量/ready 取订单侧值;batchStatus ≠ RECRUITING
|
||||||
|
- **未命中行**(班期无团期):batchStatus 固定 RECRUITING;groupBatchId = null;容量取产品侧 maxRooms/maxParticipants
|
||||||
|
- **孤儿行**(团期但班期已删):productBatchRemoved = true;仅 orderCount > 0 时出现
|
||||||
|
- `productId` 在命中行、未命中行、孤儿行中均等于请求入参,始终非空。
|
||||||
|
- 表合并按 productBatchId 左连 order_group_batch;无团期记录时新增一行(未命中)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 四、契约约束与正确调用方式
|
||||||
|
|
||||||
|
| 场景 | 正确做法 |
|
||||||
|
|------|---------|
|
||||||
|
| 新增子订单深链 | 按行数据拼接 `/order-v2/new?productId={productId}&productBatchId={productBatchId}&departureDate={departureDate}` |
|
||||||
|
| 向导内 GROUP 产品创单 | 检查产品类型,仅 GROUP 类型在 POST payload 中带 productBatchId;其他类型忽略 |
|
||||||
|
| 非 GROUP 产品创单 | 忽略 productBatchId 参数,后端根据 productId 与 departureDate 自行逻辑 |
|
||||||
|
| 孤儿行处理 | 若 productBatchRemoved = true,需向用户提示"班期已下架,不可创建子订单"或禁用按钮 |
|
||||||
|
| 未成团行处理 | groupBatchId = null 时无团期记录;前端可选择隐藏"团期信息"列或显示"待成团" |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 五、数据库行为
|
||||||
|
|
||||||
|
| 班期状态 | productBatchId | groupBatchId | batchStatus | 数据来源 |
|
||||||
|
|---------|----------------|-------------|------------|---------|
|
||||||
|
| 命中(有团期) | 产品侧 | 订单侧 | 订单侧 | order_group_batch 存在 |
|
||||||
|
| 未命中(无团期) | 产品侧 | null | RECRUITING | 无 order_group_batch 行 |
|
||||||
|
| 孤儿(班期删)| 产品侧 | 订单侧 | 订单侧 | order_group_batch 存在但班期无 |
|
||||||
|
|
||||||
|
新增 productId 字段在 productBatchId 后(响应顺序一致)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 六、边界行为
|
||||||
|
|
||||||
|
- 业务失败可能仍为 HTTP 200,必须同时检查 `code`、`success` 和 `message`。
|
||||||
|
- 看板接口 `productId` 缺参返回 400(HTTP 200);不要依赖前端参数校验。
|
||||||
|
- 分页/列表接口分页参数超界时返回空列表(无 5XX)。
|
||||||
|
- `productId` 在所有三个接口的所有行中均非空,无特殊情况返回 null。
|
||||||
|
- Long 型 ID 在 JSON 字符串化后,前端若需数值运算应保持字符串存储。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 六.6、修改前后对比
|
||||||
|
|
||||||
|
### 字段级对比
|
||||||
|
|
||||||
|
| 字段 | 改前 | 改后 |
|
||||||
|
|------|------|------|
|
||||||
|
| `productId`(分页项) | 无此字段 | 新增;产品 ID |
|
||||||
|
| `productId`(详情) | 无此字段 | 新增;产品 ID |
|
||||||
|
| `productId`(看板项) | 无此字段 | 新增;产品 ID |
|
||||||
|
|
||||||
|
### 行为级对比
|
||||||
|
|
||||||
|
| 行为 | 改前 | 改后 |
|
||||||
|
|------|------|------|
|
||||||
|
| 新增子订单跳转 | 无法从响应直接拼深链参数 | 新增 productId,与 productBatchId/departureDate 配合直接拼链 |
|
||||||
|
| 向导内产品预填 | 依赖约定俗成或外部 context | 直接从深链 query string 传入 |
|
||||||
|
| GROUP 产品识别 | 向导内自行判别 | 向导根据产品类型自动识别是否需 productBatchId |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 六.7、影响评估
|
||||||
|
|
||||||
|
- **是否破坏向后兼容**: 否。新增字段对旧客户端透明。
|
||||||
|
- **前端是否必须同步上线**: 否(从旧口径讲);但为完整支持团期看板「新增子订单」功能,前端应同步接入新增 productId 于深链拼接(仅一处改动)。
|
||||||
|
- **前端 workaround 清理点**:
|
||||||
|
- 新增子订单按钮处,改用响应中的 productId 拼深链,而非硬编码产品 ID
|
||||||
|
- 向导内创建 GROUP 订单时,检查产品类型再决定是否传 productBatchId(无需前端重构,只需补一个类型判断)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 七、不影响范围
|
||||||
|
|
||||||
|
- **仅影响**: 管理后台团期看板的「新增子订单」功能入口
|
||||||
|
- **零影响**:
|
||||||
|
- 团期创建、编辑、审批、资源配置等写接口
|
||||||
|
- 团期与订单间的关联关系和业务流程
|
||||||
|
- 产品侧班期相关接口和定义
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 八、测试环境已验证
|
||||||
|
|
||||||
|
- **单测**: 79 条测试用例绿✓(新增 productId 相关的 UT 已覆盖命中/未命中/孤儿行三路径)
|
||||||
|
- **ArchTest**: 45 条架构测试绿✓
|
||||||
|
- **测试服网关实测**: 单测 + CR 通过;测试服网关实测见管理者补充
|
||||||
|
|
||||||
|
实测产品: `productId=2044306857534636034`(冻干粉发短信给),班期 `2026-10-01`(productBatchId=2052935476557328386),团期主键 `groupBatchId=2096412454643802114`,团期名 `batchName=10月1日长白山亲子团`。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 九、相关历史 PR(功能演进)
|
||||||
|
|
||||||
|
| PR | Issue | 说明 | 是否仍有效 |
|
||||||
|
|----|-------|------|------------|
|
||||||
|
| #7135 | - | GROUP 产品创单校验与重整(定义 productBatchId 入参) | ✅ 有效 |
|
||||||
|
| #7142 | - | 订单详情/列表/创单响应透出判团字段(groupBatchId/productBatchId/groupOrder)| ✅ 有效 |
|
||||||
|
| **本 PR #7157** | **#7143** | **团期看板 VO 补 productId(配合深链预填)** | ✅ 最新 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 十、相关文档
|
||||||
|
|
||||||
|
- 关联 Issue: [wx/HL#7143](https://git.1814.love:8443/wx/HL/issues/7143)
|
||||||
|
- 关联 PR: [wx/HL#7157](https://git.1814.love:8443/wx/HL/pulls/7157)
|
||||||
|
- 相关工单: #7142(订单判团字段)、#7135(GROUP 产品规范)
|
||||||
|
- 前端缺陷: 新建订单向导漏传 productBatchId(另发前端 changelog)
|
||||||
|
|
||||||
|
## 关联 / 联系人
|
||||||
|
|
||||||
|
### 链接
|
||||||
|
|
||||||
|
- **Issue**: [#7143](https://git.1814.love:8443/wx/HL/issues/7143)
|
||||||
|
- **PR**: [#7157](https://git.1814.love:8443/wx/HL/pulls/7157)
|
||||||
|
- **Merge commit**: [ce2809c15](https://git.1814.love:8443/wx/HL/commit/ce2809c15)
|
||||||
|
|
||||||
|
### 联系人
|
||||||
|
|
||||||
|
- **后端负责人**: @wx
|
||||||
@@ -0,0 +1,249 @@
|
|||||||
|
---
|
||||||
|
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: "pending"
|
||||||
|
frontend_owner: ""
|
||||||
|
frontend_ref: ""
|
||||||
|
target_release: ""
|
||||||
|
verified_at: ""
|
||||||
|
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
|
||||||
在新工单中引用
屏蔽一个用户