--- 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: "not_required" 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` | 字段 | 类型 | 说明 | |------|------|------| | `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 ``` 无请求体。 #### 响应示例 ```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>` | 字段 | 类型 | 说明 | |------|------|------| | `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 ``` 无请求体。 #### 响应示例 ```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` | 字段 | 类型 | 说明 | |------|------|------| | `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