diff --git a/changelogs-v2/2026-09/06_7188_团期看板列表详情透出第N期batchLabel-修改接口-管理后台.md b/changelogs-v2/2026-09/06_7188_团期看板列表详情透出第N期batchLabel-修改接口-管理后台.md new file mode 100644 index 00000000..187cc4ac --- /dev/null +++ b/changelogs-v2/2026-09/06_7188_团期看板列表详情透出第N期batchLabel-修改接口-管理后台.md @@ -0,0 +1,458 @@ +--- +schema: "hl-changelog/v2" +ticket: "7188" +title: "团期看板/列表/详情透出「第N期」班期序号 batchLabel" +consumer: "admin" +author: "wx(GIT)" +change_type: "修改接口" +backend_status: "deployed" +gateway_status: "verified" +frontend_status: "pending" +frontend_owner: "" +frontend_ref: "" +target_release: "" +verified_at: "2026-09-06" +updated_at: "2026-09-06" +base: "dev-v3" +status_note: "后端已合并(PR #7209,合并提交 a5eacefd)并于 2026-09-06 21:19-21:22 部署测试服 dev-v3(product-v2 + order-v3),网关复测 6/6 通过;前端把看板行标题改成「第{batchLabel}期 {batchName}」,batchLabel 为空时退回只显 batchName。" +--- + +# 团期模块:看板 / 列表 / 详情透出「第N期」班期序号 + +## ⚠️ 关键变化 + +- 三个团期接口新增 / 修正字段 **`batchLabel`**(String,班期序号,如 `"7"`):product 侧按同产品全部班期**出发日期升序位置 = 下标 + 1** 实时重算(不信存储列,删班期后自愈;同日期同排序值按 batchId 兜底),经内部接口透传到 order-v3。 +- **前端行标题改为「第{batchLabel}期 {batchName}」**(hl-ui `src/views/order-v2/batch/components/PeriodRow.vue:164-171` `title` computed 目前只显 `batchName`),`batchLabel` 为空时退回只显 `batchName`——存量团期在下一次该班期下单前分页/详情里是 null,必须兜底。 +- 班期名口径:product 保存与 order 快照两侧 **trim**;新增/编辑班期名为空白 → 回退班期编号 `batchNo`;编辑不传 `batchName` → 保留旧名。现场「 没,那你」(前导空格)这类脏名下单后自愈。 +- 分页项(GB-ADM-001)本单取快照值;#7189 合入后分页改以产品全班期为基底、`batchLabel` 取 product 实时值,**字段名不变**。 + +--- + +## 一、背景 + +### 现象 + +wx 2026-09-06 看测试服「团期订单」看板:行标题显示班期名「没,那你」,期望「第7期 没,那你」(该班期是产品「冻干粉发短信给」按出发日的第 7 期)。序号只存在于 product 管理端班期列表,Feign 契约与团期三个响应 VO 都没有,`order_group_batch.batch_label` 列从未被写入。 + +### 调用链 + +1. hl-ui `src/stores/orderV2Batch.js:93-97` `getGroupBatchPage()` → `GET /v3/admin/order/group-batch`(GB-ADM-001)→ `records[].batchLabel`(快照) +2. 看板合并层 `GET /v3/admin/order/group-batch/board` → order-v3 Feign `GET /internal/product/group/{productId}/detail` → product `GroupTourBatchService.getBatchCalendarByProductId` 逐项 `batchLabel = 位置 + 1` → 命中/未建团行实时值、孤儿行快照 +3. 团期详情 `GET /v3/admin/order/group-batch/{groupBatchId}`(GB-ADM-002)→ `batchLabel` 快照 +4. 快照写入:GROUP 创单建团时 `GroupBatchService.buildGroupBatchDO` 从 `GET /internal/product/batch/{batchId}/info` 的 `batchLabel` 写 `batch_label`;此后每次该班期下单 `refreshSnapshot` 同步刷新序号 / 班期名 / 产品名 / 出发日 / 返团日 / 报名截止日六列 + +### 地面真相(测试服 dev-v3 a5eacefd,2026-09-06 21:2x,产品 2044306857534636034,班期 2026-10-01 productBatchId=2052935476557328386,团期 2096412454643802114) + +| 接口 | 结果 | +|---|---| +| board | 7 行按出发日升序 `batchLabel` = "1"(06-04) "2"(07-03) "3"(07-10) "4"(07-17) "5"(07-24) "6"(07-31) "7"(10-01);06-04 期存储列是 "6" 却回 "1",10-01 期回 "7" ✓ 按位置实时重算 | +| 分页 / 详情(刷新前) | `batchLabel` 字段存在、值 null(存量团期) | +| 分页 / 详情(在该班期新下一单后,测完已取消) | 详情 `batchLabel="7"`、分页项 `batchLabel="7"`、`batchName="没,那你"`(前导空格已 trim) | + +--- + +## 二、变更接口清单 + +| # | 接口 | 方法 | 路径 | 变更类型 | 说明 | +|---|------|------|------|----------|------| +| 1 | 团期看板分页(GB-ADM-001) | GET | `/v3/admin/order/group-batch` | 响应新增字段 | `records[].batchLabel`(快照;存量团期下单前为 null) | +| 2 | 团期看板合并层列表 | GET | `/v3/admin/order/group-batch/board` | 响应新增字段 | 每项 `batchLabel`:命中/未建团行取 product 实时值,孤儿行取快照 | +| 3 | 团期详情(GB-ADM-002) | GET | `/v3/admin/order/group-batch/{groupBatchId}` | 响应字段语义修正 | 既有字段 `batchLabel` 由恒 null 改为班期序号快照 | + +--- + +## 三、接口详情 + +### 1. 团期看板分页 `GET /v3/admin/order/group-batch` + +**VO**: `GroupBatchListReqVO → Result>` + +#### 使用场景 + +团期看板列表;行标题用 `batchLabel` + `batchName` 拼「第N期 名称」。 + +#### 入参 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| productId | query | Long(字符串) | 否 | 雪花 ID | 按产品筛选 | +| opsStage | query | String | 否 | RECRUIT / FORMED / PENDING_TRIP / TRAVELLING / TRIP_FINISHED / AUDITING / CHECKED / DISBANDED | 桶筛选(不变) | +| month | query | String | 否 | yyyy-MM | 出发月份 | +| keyword | query | String | 否 | 已转义 | 班期编号 / 名称模糊 | +| batchStatus | query | String | 否 | 团期状态码 | 可选 | +| deadlineFrom / deadlineTo | query | String | 否 | yyyy-MM-dd | 报名截止日区间 | +| pageNo | query | Integer | 否 | 默认 1 | 页码 | +| pageSize | query | Integer | 否 | 默认 20,最大 100 | 每页条数 | + +#### 出参 `Result>` + +| 字段 | 类型 | 说明 | +|------|------|------| +| data.records[] | Array | 团期行(分页容器 `records / total / page / pageSize`) | +| data.records[].batchLabel | String | **新增**。班期序号快照(`order_group_batch.batch_label`):建团写入、每次该班期下单刷新;存量团期在下一次下单前为 null;#7189 起改取 product 实时值 | +| data.records[].batchName | String | 班期名(快照,下单刷新时已 trim) | +| data.records[].batchNo | String | 班期编号 | +| 其余字段 | — | 不变(groupBatchId / productBatchId / productId / productName / batchStatus / batchStatusName / minGroupPeople / maxRooms / maxParticipants / enrolledPeople / enrolledRooms / remainRooms / remainParticipants / orderCount / enrollDeadline / departDate / endDate / receivableAmount / receivedAmount / chips) | + +#### 请求示例 + +```http +GET /v3/admin/order/group-batch?productId=2044306857534636034&pageNo=1&pageSize=50 HTTP/1.1 +Host: api.test.1814.love:9443 +Authorization: Bearer +``` + +#### 响应示例 + +在该班期下单刷新快照后实测(2026-09-06 21:2x,非相关字段略): + +```json +{ + "code": 200, + "message": "成功", + "data": { + "records": [ + { + "groupBatchId": "2096412454643802114", + "productBatchId": "2052935476557328386", + "productId": "2044306857534636034", + "batchNo": "Q202610012052935476548939777", + "batchName": "没,那你", + "batchLabel": "7", + "batchStatus": "RESOURCE_PREPARING", + "batchStatusName": "资源准备中", + "departDate": "2026-10-01", + "endDate": "2026-10-03" + } + ], + "total": 1, + "page": 1, + "pageSize": 50 + }, + "success": true +} +``` + +#### 空数据 / 降级响应 + +存量团期在下一次该班期下单前 `batchLabel` 为 null(不是缺字段),前端退回只显 `batchName`;无命中团期时 `records` 为空数组。 + +```json +{ + "code": 200, + "message": "成功", + "data": { + "records": [ + { "groupBatchId": "2096412454643802114", "batchName": "没,那你", "batchLabel": null } + ], + "total": 1, + "page": 1, + "pageSize": 50 + }, + "success": true +} +``` + +#### 错误响应 + +```json +{ + "code": 589507, + "message": "无操作权限(非团期管理员 / 非本定制师名下)", + "data": null, + "success": false +} +``` + +HTTP 始终 200,按 `code` 判断。 + +#### 业务边界 + +- 权限 `group-batch:view` +- `batchLabel` 是数字字符串(`"7"`),后端不拼「第N期」文案,前端拼 +- 序号会随 product 侧增删班期漂移,快照只在下单时刷新;需要绝对实时以 board / #7189 后的分页为准 + +### 2. 团期看板合并层列表 `GET /v3/admin/order/group-batch/board` + +**VO**: `Result>`(查询参数 productId,无请求 VO) + +#### 使用场景 + +以产品全部班期为基底的看板(命中 / 未建团 / 孤儿三类行);hl-ui 当前未调用,#7189 后分页也走同一合并基底。 + +#### 入参 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| productId | query | Long(字符串) | 是 | 雪花 ID | 产品 ID | + +#### 出参 `Result>` + +| 字段 | 类型 | 说明 | +|------|------|------| +| data[].batchLabel | String | **新增**。命中行 / 未建团行 = product 实时序号;孤儿行(product 已删该班期)= 快照,可能 null | +| data[].productBatchId / productId / batchNo / batchName | String | 不变 | +| data[].departureDate / endDate / enrollmentDeadline | String(yyyy-MM-dd) | 不变(product 实时) | +| data[].groupBatchId | String(Long) | 未建团行为 null(不变) | +| 其余字段 | — | 不变(maxRooms / maxParticipants / enrolledRooms / enrolledPeople / remainRooms / remainParticipants / batchStatus / batchStatusLabel / hotelReady / vehicleReady / guideReady / photographerReady / needsGuide / needsPhotographer / orderCount / productBatchRemoved / contractSignedCount / insuranceInsuredCount) | + +#### 请求示例 + +```http +GET /v3/admin/order/group-batch/board?productId=2044306857534636034 HTTP/1.1 +Host: api.test.1814.love:9443 +Authorization: Bearer +``` + +#### 响应示例 + +实测(2026-09-06 21:2x,只列序号相关字段,共 7 行): + +```json +{ + "code": 200, + "message": "成功", + "data": [ + { "productBatchId": "2045498872326717442", "departureDate": "2026-06-04", "batchName": "fadege", "batchLabel": "1", "groupBatchId": null }, + { "productBatchId": "2045401225930690562", "departureDate": "2026-07-03", "batchLabel": "2", "groupBatchId": null }, + { "productBatchId": "2045401225960050689", "departureDate": "2026-07-10", "batchLabel": "3", "groupBatchId": null }, + { "productBatchId": "2045401225964244993", "departureDate": "2026-07-17", "batchLabel": "4", "groupBatchId": null }, + { "productBatchId": "2045401225972633601", "departureDate": "2026-07-24", "batchLabel": "5", "groupBatchId": null }, + { "productBatchId": "2045401225976827906", "departureDate": "2026-07-31", "batchLabel": "6", "groupBatchId": null }, + { "productBatchId": "2052935476557328386", "departureDate": "2026-10-01", "batchName": " 没,那你", "batchLabel": "7", "groupBatchId": "2096412454643802114" } + ], + "success": true +} +``` + +#### 空数据 / 降级响应 + +产品无班期时 `data` 为空数组;孤儿行 `batchLabel` 可能为 null。 + +```json +{ + "code": 200, + "message": "成功", + "data": [], + "success": true +} +``` + +#### 错误响应 + +```json +{ + "code": 589515, + "message": "获取团期产品列表失败,请稍后重试", + "data": null, + "success": false +} +``` + +另:`589507` 无操作权限。 + +#### 业务边界 + +- 序号按 product 当前全部未删班期的出发日升序位置计算(同日期按 sortOrder、再按 batchId);软删的班期不占位 +- 未建团行的 `batchName` 取 product 侧当前值(可能未 trim,如示例第 7 行前导空格),命中行分页/详情取快照(已 trim) + +### 3. 团期详情 `GET /v3/admin/order/group-batch/{groupBatchId}` + +**VO**: `Result`(路径参数,无请求 VO) + +#### 使用场景 + +团期详情页头部;`batchLabel` 语义修正为班期序号快照。 + +#### 入参 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| groupBatchId | path | Long(字符串) | 是 | 团期主订单 ID | 不是 productBatchId | + +#### 出参 `Result` + +| 字段 | 类型 | 说明 | +|------|------|------| +| data.batchLabel | String | **语义修正**:改前恒 null(列从未写入,文案「班期标签快照 example=主推」);改后 = 班期序号快照(建团写入、下单刷新),文案「班期序号(第N期)快照 example=7」 | +| data.batchName | String | 班期名快照(下单刷新时已 trim) | +| 其余字段 | — | 不变 | + +#### 请求示例 + +```http +GET /v3/admin/order/group-batch/2096412454643802114 HTTP/1.1 +Host: api.test.1814.love:9443 +Authorization: Bearer +``` + +#### 响应示例 + +刷新快照后实测(非相关字段略): + +```json +{ + "code": 200, + "message": "成功", + "data": { + "groupBatchId": "2096412454643802114", + "productBatchId": "2052935476557328386", + "batchNo": "Q202610012052935476548939777", + "batchName": "没,那你", + "batchLabel": "7", + "batchStatus": "RESOURCE_PREPARING", + "departDate": "2026-10-01", + "endDate": "2026-10-03" + }, + "success": true +} +``` + +#### 空数据 / 降级响应 + +存量团期下单刷新前 `batchLabel` 为 null;前端退回只显 `batchName`。 + +```json +{ + "code": 200, + "message": "成功", + "data": { "groupBatchId": "2096412454643802114", "batchName": "没,那你", "batchLabel": null }, + "success": true +} +``` + +#### 错误响应 + +```json +{ + "code": 589500, + "message": "团期不存在", + "data": null, + "success": false +} +``` + +#### 业务边界 + +- 同 GB-ADM-001:数字字符串、快照语义、null 兜底 + +--- + +## 四、契约约束与正确调用方式 + +> 三个接口均为只读 GET;本节写的是**前端消费 `batchLabel` 的规则**。 + +### ✅ 正确 / ❌ 错误 payload 对照 + +| 场景 | payload / 处理 | +|------|---------| +| ✅ 行标题 | `row.batchLabel ? \`第${row.batchLabel}期 ${row.batchName}\` : row.batchName` | +| ✅ 徽标 / 导出用序号 | 直接用 `batchLabel` 字符串,不要自己按行下标算 | +| ❌ 把 `batchLabel` 当数字做算术或排序键 | 它是字符串,且随 product 增删班期漂移;排序用 `departDate` | +| ❌ 不兜底 null | 存量团期分页/详情在下单刷新前为 null,会渲染成「第undefined期」 | + +### 切换状态时的必要动作 + +无写接口。序号变化来自 product 侧班期增删/改期,看板刷新即可。 + +--- + +## 五、数据库行为 + +无表变更、无 Flyway。`order_group_batch.batch_label VARCHAR(64)` 列早已存在,本单开始写入: + +| 时点 | 写入 | +|------|------| +| GROUP 首单建团 | `batch_label` = product `GET /internal/product/batch/{batchId}/info` 的 `batchLabel`;`batch_name` trim | +| 该班期每次下单 | `refreshSnapshot` 非 null 且变化才更新:`product_name / batch_name / batch_label / depart_date / end_date / enroll_deadline` | + +product 侧 `group_tour_batch.batch_label` 存储列不再作为读源(脏值如 06-04 期存 "6" 不影响输出);`batch_name` 保存时 trim,空白回退 `batch_no`。 + +--- + +## 六、边界行为 + +- 未登录 → 网关 401;无权限 → `589507` +- 团期不存在 → `589500`;board 拉产品失败 → `589515` +- product 定位不到该班期(理论不会)→ `batchLabel` null 并记 WARN,不报错 +- 产品下只有一期 → `batchLabel="1"` +- 下单时 product 回传日期串非法 → 刷新跳过该列并记 WARN,不影响下单 + +## 六.5、枚举 / 数据字典 + +无新增枚举;`batchLabel` 为自由数字字符串。 + +## 六.6、修改前后对比 + +### 字段级对比 + +| 字段 | 修改前 | 修改后 | +|------|--------|--------| +| GB-ADM-001 `records[].batchLabel` | 无 | 新增(快照) | +| board `data[].batchLabel` | 无 | 新增(实时 / 孤儿行快照) | +| GB-ADM-002 `data.batchLabel` | 存在但恒 null | 班期序号快照 | +| product `GET /internal/product/batch/{batchId}/info` `batchLabel` | 无 | 新增(内部接口,前端不直接调) | + +### 行为级对比 + +| 场景 | 修改前 | 修改后 | +|------|--------|--------| +| 看板行标题 | 只有班期名 | 「第7期 没,那你」(前端拼) | +| 班期名带前后空格 | 原样落库、原样显示 | 保存 / 快照两侧 trim | +| 新增或编辑班期名为空白 | 新增回退 batchNo,编辑可能落 null | 两条路径都回退 `batchNo` | +| 编辑不传 batchName | 保留旧名 | 保留旧名(不变) | + +## 六.7、影响评估 + +- 向后兼容:只加字段 / 修正语义,老前端不改不报错 +- 前端是否必须同步上线:否;但不改则标题仍无序号 +- 数据:无表变更;存量 `batch_label` 不回填,随下单自愈 + +--- + +## 七、不影响范围 + +- **仅影响**: 团期看板行标题、团期详情头部、board 合并层 +- **零影响**: + - 创单 / 报价接口契约(`POST /v3/admin/order` 入参出参不变) + - product 管理端班期列表(已是同一序号口径) + - 芯片、桶、状态机 + - 小程序 + +--- + +## 八、测试环境已验证 + +真实接口输出(测试服 api.test.1814.love,2026-09-06 21:2x,登录后切 ADMIN 角色): + +``` +GET /v3/admin/order/group-batch/board?productId=2044306857534636034 → 200, 7 行 batchLabel 1..7 按出发日升序,10-01 期 "7" ✓ +GET /v3/admin/order/group-batch?productId=2044306857534636034&pageSize=50 → 200, 存量行 batchLabel 字段存在=null ✓ +GET /v3/admin/order/group-batch/2096412454643802114 → 200, batchLabel 字段存在=null ✓ +POST /v3/admin/order(该班期新下一单,测完取消) → 200, 触发 refreshSnapshot ✓ +GET /v3/admin/order/group-batch/2096412454643802114 → batchLabel="7", batchName="没,那你" ✓ +GET /v3/admin/order/group-batch?productId=2044306857534636034&pageSize=50 → 该行 batchLabel="7" ✓ +``` + +验证产品: `productId=2044306857534636034`(冻干粉发短信给),班期 `productBatchId=2052935476557328386`(2026-10-01),团期 `groupBatchId=2096412454643802114`。单测:product `GroupTourBatchServiceTest` 3/0、`GroupTourBatchMapperTest` 2/0(ORDER BY 三键,变异验证)、`GroupBatchLabelResolverTest` 6/0、`InternalProductServiceTest` 7/0、`ProductPricingServiceTest` 97/0;order `GroupBatchServiceTest` 73/0、`GroupBatchConverterTest` 37/0、`BatchInfoVODeserializationTest` 4/0、ArchTest 41/0;合入后 dev-v3 HEAD 全量 product-v2 1550/0、order-v3 8726/0。部署:Deploy Panel 21:19-21:22 product-v2 与 order-v3 双实例滚动完成,分支 dev-v3。 + +--- + +## 十、相关文档 + +- 关联 Issue: [wx/HL#7188](https://git.1814.love:8443/wx/HL/issues/7188) +- 关联 PR: [wx/HL#7209](https://git.1814.love:8443/wx/HL/pulls/7209) +- 同现场: #7189(看板以产品全班期为基底 + 班期范围筛选,分页 batchLabel 改实时值)、#7190(「出行完毕」状态与八桶)、#7204(导/摄芯片零指派口径) +- 前端缺陷 changelog: `06_frontend_团期看板展开行子订单列表恒空-前端缺陷-管理后台.md` + +## 关联 / 联系人 + +### 链接 + +- **Issue**: [#7188](https://git.1814.love:8443/wx/HL/issues/7188) +- **PR**: [#7209](https://git.1814.love:8443/wx/HL/pulls/7209) +- **Merge commit**: [a5eacefd](https://git.1814.love:8443/wx/HL/commit/a5eacefdb270b2f783db95afc790c9d0eb65a8da) + +### 联系人 + +- **后端负责人**: @wx diff --git a/changelogs-v2/2026-09/06_7190_团期状态新增出行完毕TRIP_FINISHED看板八桶-修改接口-管理后台.md b/changelogs-v2/2026-09/06_7190_团期状态新增出行完毕TRIP_FINISHED看板八桶-修改接口-管理后台.md new file mode 100644 index 00000000..e52e8f99 --- /dev/null +++ b/changelogs-v2/2026-09/06_7190_团期状态新增出行完毕TRIP_FINISHED看板八桶-修改接口-管理后台.md @@ -0,0 +1,458 @@ +--- +schema: "hl-changelog/v2" +ticket: "7190" +title: "团期状态新增「出行完毕」TRIP_FINISHED,看板七桶扩八桶" +consumer: "admin" +author: "wx(GIT)" +change_type: "修改接口" +backend_status: "deployed" +gateway_status: "verified" +frontend_status: "pending" +frontend_owner: "" +frontend_ref: "" +target_release: "" +verified_at: "2026-09-06" +updated_at: "2026-09-06" +base: "dev-v3" +status_note: "后端已合并(PR #7212,合并提交 ccb4d8fd)并于 2026-09-06 21:20-21:22 部署测试服 dev-v3(order-v3 + user-service),网关复测通过(八桶键序、opsStage=TRIP_FINISHED 筛选、job 推进);前端 batchLifecycle.js 桶表/页签/状态映射加 TRIP_FINISHED;两个自动推进 sys_job 保持 PAUSED。" +--- + +# 团期模块:状态九态(新增「出行完毕」)与看板八桶 + +## ⚠️ 关键变化 + +- **团期状态 8 → 9 态**:新增 `TRIP_FINISHED`「出行完毕」,位于 `TRAVELLING`「出行中」之后、`REVIEWING`「核单中」之前。九态与中文名:`RECRUITING` 招募中 / `RESOURCE_PREPARING` 资源准备中 / `MATERIAL_PREPARING` 物料准备中 / `PENDING_DEPARTURE` 待出发 / `TRAVELLING` 出行中 / **`TRIP_FINISHED` 出行完毕** / `REVIEWING` 核单中 / `SETTLED` 已结算 / `CANCELLED` 已取消。 +- **看板运营阶段 7 桶 → 8 桶**:新增桶 `TRIP_FINISHED`「出行完毕」,位于 `TRAVELLING` 之后、`AUDITING` 之前。八桶顺序:`RECRUIT, FORMED, PENDING_TRIP, TRAVELLING, TRIP_FINISHED, AUDITING, CHECKED, DISBANDED`。GB-ADM-009 统计条 `buckets` 固定键由 7 个变 8 个;GB-ADM-001 / GB-ADM-008 的 `opsStage` 多一个可选值。 +- **推进由两个定时任务按日自动完成**(出发日 ≤ 今天:待出发 → 出行中;返团日 < 今天:出行中 → 出行完毕;日期取 product 侧实时班期日期,不取团期快照)。**上线时两个 sys_job 为 PAUSED,「出行完毕 → 核单中」发起核单入口落地前不会 resume**,所以近期页面上不会自然出现该态,但契约已变,前端要先把桶/页签/映射补齐,否则出现时页签少一个、桶键被忽略。 +- 联动:出行完毕团期的房/车/导/摄芯片恒 `DONE`(与已返团同硬规则);对其子订单退团被拒 `589501`;预支入口在该态仍开放;转期/调容量/需求提报按既有规则在该态均拒。 + +--- + +## 一、背景 + +### 现象 + +wx 2026-09-06 要求看板状态页签在「出行中」后增加「出行完毕」。排查发现团期八态的后半段(待出发 → 出行中 → 核单中 → 已结算)此前没有任何代码推进(订单级有按日期推进的 job,团期级没有),测试库全部团期都停在已成团。本单新增正式状态并补齐团期级按日推进。 + +### 调用链 + +1. hl-user-service sys_job(Quartz)`groupBatchLifecycleJob.processDepartures() / processTripFinishes()` → 桥接 bean 经 Feign → order-v3 `POST /v3/internal/jobs/group-batch-departure/run`、`/group-batch-trip-finish/run`(内部端点,网关不开放) +2. order-v3 `GroupBatchLifecycleJobService`:取候选(`PENDING_DEPARTURE` / `TRAVELLING`)→ 按产品经 Feign 取 product 实时班期日期 → `GroupBatchLifecycleTxService.advanceOne` 同一事务 CAS 改状态 + 写时间线(`BATCH_DEPART`「系统自动发团」/ 新事件 `BATCH_TRIP_FINISH`「系统自动出行完毕」) +3. 看板读侧:`GroupBatchStageBuckets` 九态折叠八桶 → GB-ADM-001 `records[].batchStatus / batchStatusName`、GB-ADM-009 `buckets`、GB-ADM-008 导出 `opsStage` + +### 地面真相(测试服 dev-v3,2026-09-06 21:38-21:42,团期房务会话临时造数) + +| 团期 | 造数 | 触发 job 后 | 说明 | +|---|---|---|---| +| A `2096495107078328322`(#7158验收班期) | 状态 → PENDING_DEPARTURE;product 侧出发日 12-20 → 09-05;order 快照出发日仍 12-20 | 第一轮(出发日 12-20,未来)不动;第二轮(09-05)→ **TRAVELLING** | 读 product 实时日期,快照未动 | +| B `2096510069465088002`(#7178验收班期) | 状态 → TRAVELLING;product 侧返团日 12-28 → 09-05;order 快照返团日仍 12-27 | 第一轮不动;第二轮 → **TRIP_FINISHED**「出行完毕」 | 同上 | + +两轮 sys_job 1041 / 1042 执行日志均 SUCCESS(毫秒级)。造数已由团期房务会话复原。 + +--- + +## 二、变更接口清单 + +| # | 接口 | 方法 | 路径 | 变更类型 | 说明 | +|---|------|------|------|----------|------| +| 1 | 团期看板分页(GB-ADM-001) | GET | `/v3/admin/order/group-batch` | 入参枚举扩展 + 响应取值扩展 | `opsStage` / `batchStatus` 新增 `TRIP_FINISHED`;`records[].batchStatus` 可为 `TRIP_FINISHED`、`batchStatusName="出行完毕"` | +| 2 | 团期看板统计条(GB-ADM-009) | GET | `/v3/admin/order/group-batch/summary` | 响应结构扩展 | `buckets` 固定键 7 → 8,新增 `TRIP_FINISHED`,`total` = 八桶之和 | +| 3 | 团期看板导出(GB-ADM-008) | GET | `/v3/admin/order/group-batch/export` | 入参枚举扩展 | `opsStage` 新增 `TRIP_FINISHED` | + +--- + +## 三、接口详情 + +### 1. 团期看板分页 `GET /v3/admin/order/group-batch` + +**VO**: `GroupBatchListReqVO → Result>` + +#### 使用场景 + +团期看板列表(hl-ui `src/stores/orderV2Batch.js:93-97` `getGroupBatchPage()`);点「出行完毕」页签时传 `opsStage=TRIP_FINISHED`。 + +#### 入参 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| productId | query | Long(字符串) | 否 | 雪花 ID | 按产品筛选 | +| opsStage | query | String | 否 | `RECRUIT / FORMED / PENDING_TRIP / TRAVELLING / TRIP_FINISHED / AUDITING / CHECKED / DISBANDED` | **新增 `TRIP_FINISHED`**;非法值忽略不报错 | +| batchStatus | query | String | 否 | 九态之一 | **可传 `TRIP_FINISHED`** | +| month | query | String | 否 | yyyy-MM | 出发月份 | +| keyword | query | String | 否 | 已转义 | 班期编号 / 名称模糊 | +| deadlineFrom / deadlineTo | query | String | 否 | yyyy-MM-dd | 报名截止日区间 | +| pageNo | query | Integer | 否 | 默认 1 | 页码 | +| pageSize | query | Integer | 否 | 默认 20,最大 100 | 每页条数 | + +#### 出参 `Result>` + +| 字段 | 类型 | 说明 | +|------|------|------| +| data.records[] | Array | 团期行(分页容器 `records / total / page / pageSize`) | +| data.records[].groupBatchId | String(Long) | 团期主订单 ID | +| data.records[].batchStatus | String | 九态之一,**可为 `TRIP_FINISHED`** | +| data.records[].batchStatusName | String | 中文名,`TRIP_FINISHED` 对应「出行完毕」 | +| data.records[].chips | Object | 六芯片;`TRIP_FINISHED` 团期房/车/导/摄恒 `DONE`(硬规则,与已返团同) | +| 其余字段 | — | 不变(productBatchId / productId / productName / batchNo / batchName / batchLabel / maxRooms / maxParticipants / enrolledPeople / enrolledRooms / remainRooms / remainParticipants / orderCount / enrollDeadline / departDate / endDate / receivableAmount / receivedAmount) | + +#### 请求示例 + +```http +GET /v3/admin/order/group-batch?productId=2056947670512971778&opsStage=TRIP_FINISHED&pageSize=10 HTTP/1.1 +Host: api.test.1814.love:9443 +Authorization: Bearer +``` + +#### 响应示例 + +第二轮造数后实测(2026-09-06 21:42): + +```json +{ + "code": 200, + "message": "成功", + "data": { + "records": [ + { + "groupBatchId": "2096510069465088002", + "productId": "2056947670512971778", + "productName": "测试小蒙马-多档-固定金额", + "batchName": "#7178验收班期", + "batchStatus": "TRIP_FINISHED", + "batchStatusName": "出行完毕", + "orderCount": 1, + "departDate": "2026-12-27", + "endDate": "2026-12-28" + } + ], + "total": 1, + "page": 1, + "pageSize": 10 + }, + "success": true +} +``` + +#### 空数据 / 降级响应 + +`opsStage=TRIP_FINISHED` 在 sys_job 未 resume 期间通常无命中:`records` 为空数组、`total=0`,不是错误。 + +```json +{ + "code": 200, + "message": "成功", + "data": { "records": [], "total": 0, "page": 1, "pageSize": 20 }, + "success": true +} +``` + +#### 错误响应 + +```json +{ + "code": 589507, + "message": "无操作权限(非团期管理员 / 非本定制师名下)", + "data": null, + "success": false +} +``` + +#### 业务边界 + +- `opsStage` 与 `batchStatus` 的映射:`TRIP_FINISHED` 桶 = `TRIP_FINISHED` 态(单射,不是复合桶;`FORMED` 仍是 RESOURCE_PREPARING + MATERIAL_PREPARING 复合桶) +- #7189 合入后有「班期范围」筛选,默认「未结束」按返团日过滤;出行完毕/待审核/已审核的团期返团日必然已过,前端点这三个页签时应自动把范围切到「全部」 + +### 2. 团期看板统计条 `GET /v3/admin/order/group-batch/summary` + +**VO**: `Result`(查询参数 productId / month / keyword,无请求 VO) + +#### 使用场景 + +看板页签计数(hl-ui `orderV2Batch.js:74` `getGroupBatchSummary()`),与 GB-ADM-001 同筛选、不含 opsStage。 + +#### 入参 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| productId | query | Long(字符串) | 否 | 雪花 ID | 按产品 | +| month | query | String | 否 | yyyy-MM | 出发月份 | +| keyword | query | String | 否 | 已转义 | 班期编号 / 名称模糊 | + +#### 出参 `Result` + +| 字段 | 类型 | 说明 | +|------|------|------| +| data.total | Integer | 命中团期总数 = 八桶之和 | +| data.buckets | Object | **固定 8 键**,顺序 `RECRUIT, FORMED, PENDING_TRIP, TRAVELLING, TRIP_FINISHED, AUDITING, CHECKED, DISBANDED`,无命中为 0(改前 7 键,无 `TRIP_FINISHED`) | +| data.subOrderCount | Integer | 命中团期活跃子订单合计(不变) | + +#### 请求示例 + +```http +GET /v3/admin/order/group-batch/summary?productId=2056947670512971778 HTTP/1.1 +Host: api.test.1814.love:9443 +Authorization: Bearer +``` + +#### 响应示例 + +第二轮造数后实测(21:42): + +```json +{ + "code": 200, + "message": "成功", + "data": { + "total": 6, + "buckets": { + "RECRUIT": 0, + "FORMED": 2, + "PENDING_TRIP": 0, + "TRAVELLING": 1, + "TRIP_FINISHED": 1, + "AUDITING": 0, + "CHECKED": 0, + "DISBANDED": 2 + }, + "subOrderCount": 4 + }, + "success": true +} +``` + +#### 空数据 / 降级响应 + +无命中团期时 `total=0`、`subOrderCount=0`,`buckets` 仍固定返回 8 键全 0。 + +```json +{ + "code": 200, + "message": "成功", + "data": { "total": 0, "buckets": { "RECRUIT": 0, "FORMED": 0, "PENDING_TRIP": 0, "TRAVELLING": 0, "TRIP_FINISHED": 0, "AUDITING": 0, "CHECKED": 0, "DISBANDED": 0 }, "subOrderCount": 0 }, + "success": true +} +``` + +#### 错误响应 + +```json +{ + "code": 589507, + "message": "无操作权限(非团期管理员 / 非本定制师名下)", + "data": null, + "success": false +} +``` + +#### 业务边界 + +- 桶键由后端枚举顺序生成,前端按对象键顺序或按自己的页签表读取都可以,但**不要按索引位置取第 5 个当「待审核」**(改前第 5 个是 AUDITING,改后是 TRIP_FINISHED) +- 老前端未加新键时:多出的 `TRIP_FINISHED` 键被忽略,`total` 与页签计数之和会差出该桶的数量 + +### 3. 团期看板导出 `GET /v3/admin/order/group-batch/export` + +**VO**: `无请求 VO(查询参数) → CSV 文件流 text/csv` + +#### 使用场景 + +看板「导出」按钮(hl-ui `exportGroupBatch()`),筛选参数与 GB-ADM-001 同义、不含分页。 + +#### 入参 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| productId | query | Long(字符串) | 否 | 雪花 ID | 按产品 | +| month | query | String | 否 | yyyy-MM | 出发月份 | +| keyword | query | String | 否 | 已转义 | 班期编号 / 名称模糊 | +| opsStage | query | String | 否 | 八桶之一 | **新增 `TRIP_FINISHED`** | + +#### 出参 `CSV 文件流` + +| 字段 | 类型 | 说明 | +|------|------|------| +| (响应体) | text/csv | 列与改前一致;状态列可出现「出行完毕」 | + +#### 请求示例 + +```http +GET /v3/admin/order/group-batch/export?productId=2056947670512971778&opsStage=TRIP_FINISHED HTTP/1.1 +Host: api.test.1814.love:9443 +Authorization: Bearer +``` + +#### 响应示例 + +```json +{ + "code": 200, + "message": "文件流(Content-Type: text/csv; Content-Disposition: attachment),此处仅示意,实际响应体为 CSV" +} +``` + +#### 空数据 / 降级响应 + +无命中时返回只有表头的 CSV;命中 > 2000 行返回 `589517`。 + +```json +{ + "code": 589517, + "message": "导出行数超过上限,请缩小筛选范围", + "data": null, + "success": false +} +``` + +#### 错误响应 + +```json +{ + "code": 589507, + "message": "无操作权限(非团期管理员 / 非本定制师名下)", + "data": null, + "success": false +} +``` + +#### 业务边界 + +- 权限 `group-batch:export` +- `opsStage` 非法值忽略(与 GB-ADM-001 一致) + +--- + +## 四、契约约束与正确调用方式 + +> 三个接口均为只读 GET,无请求体;本节写的是**前端消费九态/八桶的规则**。 + +### ✅ 正确 / ❌ 错误 payload 对照 + +| 场景 | payload / 处理 | +|------|---------| +| ✅ 页签 → 筛选 | 点「出行完毕」页签传 `opsStage=TRIP_FINISHED`(与其它页签同一套 `opsStage`) | +| ✅ 状态 → 桶 | 八态映射表加 `TRIP_FINISHED: 'TRIP_FINISHED'`;未知状态值按灰/默认样式,不抛错 | +| ✅ 统计条 | 按键名读 `buckets.TRIP_FINISHED`,不按索引 | +| ❌ 硬编码七桶数组 | 少一个页签、`total` 对不上 | +| ❌ 把 `TRIP_FINISHED` 当「已完成」终态 | 它之后还有核单中 / 已结算;核单入口在后续单 | + +### 切换状态时的必要动作 + +无写接口。团期进入 `TRIP_FINISHED` 只由 sys_job 1042 完成(当前 PAUSED);前端不需要也不能手动推进。 + +--- + +## 五、数据库行为 + +`order_group_batch.batch_status` 为 VARCHAR(32),直接存新值 `TRIP_FINISHED`,无表变更、无 Flyway(order-v3)。hl-user-service Flyway `V20260906_005__register_group_batch_lifecycle_jobs.sql` 种子两行 `sys_job`(1041 团期出发推进 `0 10 0 * * ?`、1042 团期出行完毕推进 `0 20 0 * * ?`,`job_group=ORDER`,`status=PAUSED`)。 + +| job | 扫描条件 | 推进 | 时间线事件 | +|------|-----------------|------|------| +| 1041 | `PENDING_DEPARTURE` 且 product 侧出发日 ≤ 今天 | → `TRAVELLING` | `BATCH_DEPART` 系统自动发团 | +| 1042 | `TRAVELLING` 且 product 侧返团日 < 今天 | → `TRIP_FINISHED` | `BATCH_TRIP_FINISH` 系统自动出行完毕 | + +状态 CAS 与时间线在同一事务内原子提交;日期取不到 product 实时值时回退团期快照并记 WARN。 + +--- + +## 六、边界行为 + +- 未登录 → 网关 401;无权限 → `589507` +- `opsStage` / `batchStatus` 传未知值 → 忽略该筛选,不报错 +- 返团日当天仍算出行中(`< 今天` 才推进);出发日当天即算出行中(`≤ 今天`) +- 出行完毕团期:退团 → `589501`;转期 / 调满团名额 / 需求提报按既有阶段门拒绝;预支仍可发起 +- HTTP 始终 200,按 `code` 判断 + +## 六.5、枚举 / 数据字典 + +### batchStatus(`com.hulalv.order.groupbatch.enums.GroupBatchStatus`) + +| 值 | 中文名 | 桶(`GroupBatchStageBuckets.Bucket`) | +|---|---|---| +| `RECRUITING` | 招募中 | `RECRUIT` | +| `RESOURCE_PREPARING` | 资源准备中 | `FORMED` | +| `MATERIAL_PREPARING` | 物料准备中 | `FORMED` | +| `PENDING_DEPARTURE` | 待出发 | `PENDING_TRIP` | +| `TRAVELLING` | 出行中 | `TRAVELLING` | +| **`TRIP_FINISHED`** | **出行完毕(新增)** | **`TRIP_FINISHED`(新增)** | +| `REVIEWING` | 核单中 | `AUDITING` | +| `SETTLED` | 已结算 | `CHECKED` | +| `CANCELLED` | 已取消 | `DISBANDED` | + +### opsStage / buckets 键(`GroupBatchStageBuckets.Bucket`) + +`RECRUIT` 招募中 / `FORMED` 已成团 / `PENDING_TRIP` 待出行 / `TRAVELLING` 出行中 / **`TRIP_FINISHED` 出行完毕** / `AUDITING` 待审核 / `CHECKED` 已审核 / `DISBANDED` 流团(前端页签文案沿用现有)。 + +## 六.6、修改前后对比 + +### 字段级对比 + +| 字段 | 修改前 | 修改后 | +|------|--------|--------| +| GB-ADM-001 / 008 `opsStage` 取值 | 7 值 | 8 值(+ `TRIP_FINISHED`) | +| GB-ADM-001 `records[].batchStatus` 取值 | 8 值 | 9 值(+ `TRIP_FINISHED`,中文名「出行完毕」) | +| GB-ADM-009 `buckets` 键 | 7 键 | 8 键,`TRIP_FINISHED` 插在 `TRAVELLING` 与 `AUDITING` 之间 | + +### 行为级对比 + +| 场景 | 修改前 | 修改后 | +|------|--------|--------| +| 待出发团期到出发日 | 无人推进,永远待出发 | sys_job 1041(resume 后)每日 00:10 → 出行中 | +| 出行中团期过返团日 | 无人推进,永远出行中 | sys_job 1042(resume 后)每日 00:20 → 出行完毕 | +| 出行完毕团期芯片 | 不存在该态 | 房/车/导/摄恒 `DONE`(与已返团同) | +| 出行完毕团期退团 | 不存在该态 | `589501` | +| 原七桶各自口径 | — | 不变 | + +## 六.7、影响评估 + +- 向后兼容:只增枚举值/键,不改字段名;老前端不改代码不报错,但页签少「出行完毕」、统计条多出的键被忽略 +- 前端是否必须同步上线:否(job PAUSED 期间不会出现该态数据);但应在 job resume 前补齐 +- 数据:order-v3 无表变更;user-service 只有 sys_job 种子 + +--- + +## 七、不影响范围 + +- **仅影响**: 团期看板页签/统计条/导出的桶集合,团期状态取值 +- **零影响**: + - #7204 导/摄芯片零指派口径、房/车/约/保芯片 + - 订单(子订单)状态机与订单接口 + - 团期成团 / 取消成团 / 流团 / 物资确认 / 结算守卫(仍要求核单中) + - 小程序 + +--- + +## 八、测试环境已验证 + +真实接口输出(测试服 api.test.1814.love,2026-09-06 21:38-21:42,登录后切 ADMIN / 触发 job 切 SUPER_ADMIN): + +``` +GET /v3/admin/order/group-batch/summary?productId=2044306857534636034 → 200, buckets 键序 RECRUIT,FORMED,PENDING_TRIP,TRAVELLING,TRIP_FINISHED,AUDITING,CHECKED,DISBANDED ✓ +POST /admin/job/1041/trigger、/admin/job/1042/trigger(第一轮,造数期均为未来日期) → sys_job_log SUCCESS,A/B 状态不动 ✓ +POST /admin/job/1041/trigger、/admin/job/1042/trigger(第二轮,product 侧日期改到 09-05) → SUCCESS,A→TRAVELLING、B→TRIP_FINISHED,order 快照日期未动 ✓ +GET /v3/admin/order/group-batch?productId=2056947670512971778&opsStage=TRIP_FINISHED → 200, 1 行 B「出行完毕」 ✓ +GET /v3/admin/order/group-batch/summary?productId=2056947670512971778 → TRAVELLING=1, TRIP_FINISHED=1 ✓ +GET /v3/admin/order/group-batch/2096510069465088002/chips/hotel → aggregateStatus=DONE(硬规则) ✓ +POST /v3/admin/order/group-batch/2096510069465088002/sub-order/2096510069234401282/withdraw → 589501 ✓ +``` + +验证团期: A `2096495107078328322`、B `2096510069465088002`(产品 2056947670512971778「测试小蒙马-多档-固定金额」),造数已复原。单测:`GroupBatchStageBucketsTest` 12/0、`GroupBatchSummaryVOTest` 2/0、`GroupBatchStatusTest` 6/0、`GroupBatchLifecycleJobServiceTest` 11/0、`GroupBatchLifecycleTxServiceTest` 4/0、`GroupBatchLifecycleJobTest` 3/0、`GroupBatchMapperIT` 13/0;合入后 dev-v3 HEAD 全量 order-v3 8726/0、hl-user-service `QuartzJobExecutorTest` 16/0。部署:Deploy Panel 21:20-21:22 双实例滚动完成(order-v3 + user-service),分支 dev-v3。 + +--- + +## 十、相关文档 + +- 关联 Issue: [wx/HL#7190](https://git.1814.love:8443/wx/HL/issues/7190) +- 关联 PR: [wx/HL#7212](https://git.1814.love:8443/wx/HL/pulls/7212) +- 同现场: #7188(「第N期」序号)、#7189(看板以产品全班期为基底 + 班期范围筛选)、#7204(导/摄芯片零指派口径) +- 后续单: 「出行完毕 → 核单中」发起核单入口(落地前两个 sys_job 保持 PAUSED) + +## 关联 / 联系人 + +### 链接 + +- **Issue**: [#7190](https://git.1814.love:8443/wx/HL/issues/7190) +- **PR**: [#7212](https://git.1814.love:8443/wx/HL/pulls/7212) +- **Merge commit**: [ccb4d8fd](https://git.1814.love:8443/wx/HL/commit/ccb4d8fd5177ef816e71fadd33335f8f1dce3966) + +### 联系人 + +- **后端负责人**: @wx