diff --git a/changelogs-v2/2026-09/08_7283_流团后班期置为不可售与下单硬闸-修改接口-管理后台.md b/changelogs-v2/2026-09/08_7283_流团后班期置为不可售与下单硬闸-修改接口-管理后台.md new file mode 100644 index 00000000..9f4f735d --- /dev/null +++ b/changelogs-v2/2026-09/08_7283_流团后班期置为不可售与下单硬闸-修改接口-管理后台.md @@ -0,0 +1,365 @@ +--- +schema: "hl-changelog/v2" +ticket: "7283" +title: "流团批准后自动把对应班期置为不可售,并给已流团团期的下单补 589551 硬闸" +consumer: "multiple" +author: "jw(GIT)" +change_type: "修改接口" +backend_status: "deployed" +gateway_status: "verified" +frontend_status: "pending" +frontend_owner: "mmg" +frontend_ref: "" +target_release: "" +verified_at: "2026-09-08" +status_note: "后端已部署测试服并逐条实测(AC-1~AC-18 全过)。前端待办两项:①错误码字典补录 589551「该团期已流团,不可下单」;②知悉流团批准后该班期在小程序日历/班期列表会变为不可选、状态文案显示「取消中/已取消」。接口出入参结构零变化,前端不改也不会报错。" +updated_at: "2026-09-08" +base: "dev-v3" +--- + +# 团期: 流团后班期自动不可售 + 下单侧 589551 硬闸 + +> **服务**: hl-order-service-v3、hl-product-service-v2(**必须一起部署**) +> **PR**: #7303 +> **Issue**: #7283 +> **日期**: 2026-09-08 +> **影响范围**: 团期流团审批、GROUP 产品下单(管理后台代客下单 + 小程序下单)、小程序班期日历 + +--- + +## ⚠️ 关键变化 + +**出入参结构一个字段都没改。** 前端不改不报错,但有两件事必须知道: + +1. **流团批准后,该班期会自动变为不可售**——小程序日历上该出发日 `isSelectable=false`, + 班期状态文案显示「取消中」或「已取消」。这是现有枚举字典的限制,不是产品下架, + **同产品的其它班期不受任何影响**。 +2. **已流团团期再下单会被拒**,新增错误码 **589551**「该团期已流团,不可下单」, + 前端错误码字典需补录。 + +改前:流团批准后班期仍停在「报名中」,客户能成功下单到一个已经解散的团,钱收得进来、退不回去。 + +--- + +## 一、背景 + +流团在 #7196 之后已是一条完整审批链:发起申请 → 管理员审批 → 通过后系统自动置团期取消、 +批量退各户定金、释放配车占用。这条链唯一漏掉的一步是「把对应班期置为不可售」。 + +后果实测(AC-5 改前对照,在部署本次改动**之前**于测试服复现):批准流团后, +`group_tour_batch.batch_status` 仍是 `ENROLLING`,对同一 `productBatchId` 再下单返回 +`code=200` 且订单创建成功(订单号 `HL20260907222635897`)。新单挂到一个终态团期上, +团期看板、配房、配车、合同保险等所有按团聚合的链路都会拿到错的事实,需要人工善后并再退一次款。 + +--- + +## 二、变更接口清单 + +| # | 接口 | 方法 | 路径 | 变更类型 | 说明 | +|---|------|------|------|----------|------| +| 1 | 同意流团申请 | POST | `/v3/admin/order/group-batch/disband/:approvalId/approve` | 行为变化 | 批准后额外把对应班期置为不可售;出入参与错误码集合**不变** | +| 2 | 管理后台代客下单 | POST | `/v3/admin/order` | 新增错误码 | 已流团团期下单被拒,返 589551 或 581028;入参出参**不变** | + +--- + +## 三、接口详情 + +### 1. 同意流团申请 `POST /v3/admin/order/group-batch/disband/:approvalId/approve` + +**VO**: `DisbandApprovalRespVO` + +#### 使用场景 + +管理后台团期「流团审批」中同意一条待审流团申请。调用方 hl-ui 团期审批页。 +权限:仅管理员角色(不满足抛 589547)。 + +#### 入参 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| approvalId | Path | Long | ✅ | — | 流团审批单 ID;**入参一个都没变** | +| remark | Body | String | ❌ | — | 批复备注;请求体整体可不传 | + +#### 出参 `Result` + +| 字段 | 类型 | 说明 | +|------|------|------| +| approvalId | Long | 审批单 ID;**响应结构完全不变** | +| approvalStatus | String | PENDING / APPROVED / REJECTED | +| affectedOrderCount | Integer | 影响子订单户数 | +| actualRefundAmount | BigDecimal | 执行后回填的实际退款合计 | +| 其余全部字段 | — | **完全不变** | + +#### 请求示例 + +```http +POST /v3/admin/order/group-batch/disband/2096968377787392001/approve HTTP/1.1 +Authorization: Bearer +Content-Type: application/json + +{"remark":"确认无法成团"} +``` + +#### 响应示例 + +```json +{ + "code": 200, + "message": "成功", + "success": true, + "data": { + "approvalId": "2096971122355458049", + "groupBatchId": "2096971120853897217", + "approvalStatus": "APPROVED", + "affectedOrderCount": 1, + "actualRefundAmount": 0.0, + "approvedByName": "admin", + "approvedAt": "2026-09-07 22:37:30" + } +} +``` + +#### 空数据 / 降级响应 + +**置班期不可售失败不影响本接口返回值**,仍返回 200 且 `approvalStatus=APPROVED`。 +失败只记 ERROR 日志并往团期状态流水写一条 `changeType=DATA` 的记录, +内容为「班期置不可售失败,请到产品后台手动取消该班期(班期 ID:…)」, +由运营到产品后台对该班期手动执行取消收尾。 + +```json +{ + "code": 200, + "message": "成功", + "success": true, + "data": { "approvalId": "2097162345619902466", "approvalStatus": "APPROVED" } +} +``` + +#### 错误响应 + +```json +{ + "code": 589546, + "message": "该流团申请已处理", + "success": false, + "data": null +} +``` + +**错误码集合无新增**。既有:589545 申请不存在、589546 已处理、589547 非管理员、 +589500 团期不存在、589501 团期已处于终态。 + +#### 业务边界 + +- **只写该班期一行**:作用域严格限定为该 `batchId` 的班期状态列,不触产品表、不影响同产品的其它班期。 +- **目标值两种**:该班期仍有活跃订单时置「取消中」,无活跃订单时置「已取消」,两者都不可售。 + 实测中流团路径**几乎恒落「取消中」**——判定读的是订单域持久计数,而扣减计数与本动作同为提交后回调、 + 先后不保证,通常本动作先跑。两者对售卖的效果一致,前端不必区分。 +- **不回滚**:跨服务同步在流团事务提交后执行,失败无从回滚,故只告警不回滚,由下单侧硬闸兜底。 +- **上线期间的预期毛刺**:流团审批执行期间(含逐户退款,秒级)该班期的下单会短暂阻塞, + 热门班期可能出现下单毛刺甚至锁等待超时。这是**预期行为不是故障**,客服口径需同步。 + +### 2. 管理后台代客下单 `POST /v3/admin/order` + +**VO**: `OrderCreateRespVO` + +#### 使用场景 + +管理后台定制师代客下单。GROUP 产品必须带 `productBatchId`。 +同一行为变化也适用于小程序下单链路(经 mp BFF 的 `/v3/internal/mp/order/create`), +错误码与文案一致。 + +#### 入参 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| productId | Body | Long | ✅ | — | **入参一个都没变** | +| productBatchId | Body | Long | GROUP 必填 | — | 产品侧班期 ID;本次新增的两道拒单都按它判定 | +| departureDate | Body | String(date) | ✅ | yyyy-MM-dd | 不变 | +| adultCount / childCount / youngChildCount / babyCount | Body | Integer | 成人必填 | — | 不变 | +| customerName / customerPhone | Body | String | ✅ | — | 不变 | +| 其余字段 | — | — | — | — | 不变 | + +#### 出参 `Result` + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | Long | 订单 ID;**成功响应结构完全不变** | +| orderNo | String | 订单号 | +| orderStatus | String | PENDING_PAY 等 | +| 其余全部字段 | — | **完全不变** | + +#### 请求示例 + +```json +{ + "productId": "2056947670512971778", + "tierSeq": 1, + "departureDate": "2026-12-18", + "adultCount": 2, + "childCount": 0, + "customerName": "张三", + "customerPhone": "13900000000", + "productBatchId": "2096971118245015554", + "roomCount": 1 +} +``` + +#### 响应示例 + +```json +{ + "code": 200, + "message": "成功", + "success": true, + "data": { + "id": "2096971120677736450", + "orderNo": "HL20260907223729183", + "orderStatus": "PENDING_PAY", + "totalAmount": "6000.00" + } +} +``` + +#### 空数据 / 降级响应 + +无空数据形态。产品域同步失败导致班期仍显示可选时,下单仍会被本地硬闸拒掉(返 589551), +不会漏出一笔挂在已解散团期上的订单。 + +#### 错误响应 + +班期已成功置为不可售时(正常路径): + +```json +{ + "code": 581028, + "message": "团期状态不允许报名", + "success": false, + "data": null +} +``` + +班期置不可售失败、产品域仍显示报名中时(兜底路径): + +```json +{ + "code": 589551, + "message": "该团期已流团,不可下单", + "success": false, + "data": null +} +``` + +**新增错误码 589551**,前端错误码字典需补录。既有 581028 语义不变。 + +#### 业务边界 + +- **两道闸,命中哪一道取决于产品域是否同步成功**:同步成功走 581028(产品域状态不允许报名), + 同步失败走 589551(订单域本地判定)。前端两者都要能展示。 +- **拒单零副作用**:整单回滚,不留脏订单、不误扣产品域名额(实测 `order_main` 无新增、名额未变动)。 +- **成团后加单不受影响**:判定只认「已流团」这一个终态,团期成团后(资源准备中及之后)的正常加单照常放行。 +- **转期入口不变**:转入已流团团期早已由 589510 拦下,本次不改。 + +--- + +## 四、契约约束与正确调用方式 + +| 场景 | 做法 | +|------|------| +| ✅ 错误码字典 | 补录 `589551 = 该团期已流团,不可下单`,与既有 `581028 = 团期状态不允许报名` 并列 | +| ✅ 下单失败提示 | 581028 与 589551 都提示「该班期已不可报名,请换一期」即可,不必向客户区分两者 | +| ✅ 班期状态展示 | 该班期会显示「取消中」或「已取消」,**两者都表示不可售**,不要只把「已取消」当不可售 | +| ✅ 流团审批结果 | 仍以接口返回的 200 为准;置班期不可售是尽力而为的后续动作,不改变审批成败 | +| ❌ 把「取消中」当成「还能报名」 | 「取消中」表示仍有活跃订单待处理,售卖上与「已取消」等价,一律不可选 | +| ❌ 把班期不可售理解成产品下架 | 只是这一条班期,同产品其它班期与产品本身状态一字未动 | +| ❌ 依赖流团后班期一定变成「已取消」 | 实际几乎恒为「取消中」,见接口 1 的业务边界 | + +--- + +## 五、数据库行为 + +- **写**:目标班期的状态列(既有列、既有取值,「取消中」或「已取消」),**单行**; + 团期状态流水表新增一条记录(正常路径为流团记录,同步失败时额外一条 DATA 类留痕记录)。 +- **读**:团期主订单状态、该班期下的子订单集合。 +- **不写**:产品表、同产品的其它班期、班期的名额与价格等任何其它列。 +- **无表结构变更、无数据迁移。** + +--- + +## 六、边界行为 + +- **小程序链路同步生效**:经 mp BFF 的下单同样返 589551 / 581028,文案一致。 +- **已付款子订单在流团时仍不会被自动取消**——这是既有缺陷(已另立工单 #7294 跟进), + 与本次改动无关,本次也未修改流团取消哪些户。 +- **团期主订单未绑定产品班期时**(历史脏数据):置不可售无处可写,按同步失败处理,记 ERROR 与流水留痕。 +- **重复批准**:审批入口本就有幂等双保险,重复触发不会重复置状态;置不可售动作本身也幂等, + 班期已是不可售状态时直接返回当前值、不重复写。 + +## 六.6、修改前后对比 + +| 维度 | 改前 | 改后 | +|------|------|------| +| 流团批准后产品域班期状态 | 停在「报名中」 | 自动置「取消中」/「已取消」 | +| 小程序日历该出发日 | 可选 | `isSelectable=false` | +| 已流团团期再下单 | **成功建单**(实测 `code=200`,订单号 `HL20260907222635897`) | 被拒:581028(正常)或 589551(同步失败兜底) | +| 运营收尾动作 | 必须另外去产品后台手动取消该班期 | 系统自动完成;仅同步失败时才需人工兜底 | +| 审批接口出入参 | — | **完全不变** | +| 下单接口出入参 | — | **完全不变**,仅多一个错误码 | + +## 六.7、影响评估 + +- **前端**:无接口结构变化,不改也不报错;两项待办是「补录 589551 文案」与「知悉班期会显示取消中/已取消」。 +- **后端**:`hl-order-service-v3` 与 `hl-product-service-v2` **必须一起部署**, + 且**上线顺序为 product-v2 先、order-v3 后**(回滚反序)——order-v3 的新调用依赖 product-v2 本次新增的内部端点。 +- **运营**:流团审批期间该班期下单会短暂阻塞(秒级),属预期行为; + 团期流水出现「班期置不可售失败」时,需到产品后台对该班期手动执行取消。 +- **数据**:存量已流团但班期仍显示报名中的团期**不会被自动修复**,需要时由运营手动取消该班期。 + +--- + +## 七、不影响范围 + +- 产品状态、产品上下架、同产品其它班期:一字未动。 +- 流团审批的响应结构、错误码集合、权限规则:全部不变。 +- 下单接口的入参与成功响应结构:全部不变。 +- 流团取消哪些子订单、退款金额口径:不变(已付款户仍不被自动取消,见 #7294)。 +- 定制师提需求、团期成团、调名额、转期等其它团期动作:不变。 +- 取消成团(资源准备中回退到招募中):不联动置班期不可售——班期本就应继续可售。 + +--- + +## 八、测试环境已验证 + +测试服 `https://api.test.1814.love:9443`,order-v3 与 product-v2 已一起部署。工单 AC-1~AC-18 全部逐条取证: + +| 项 | 结果 | +|---|---| +| 改前对照(部署本次改动前) | 流团批准后班期仍 `ENROLLING`,再下单 `code=200` 成功建单 `HL20260907222635897` | +| 流团后班期状态 | 产品域「取消中」、订单域「已取消」 | +| 作用域未越界 | 同产品 17 条班期只有目标那条变化,产品状态仍为已上架 | +| 展示层不可选 | 班期日历状态「已取消」;小程序价格日历该出发日 `isSelectable=false` | +| 改后下单 | `code=581028` | +| 硬闸兜底 | 手工把班期改回报名中后下单 → `code=589551`,无新增订单、名额未变动 | +| 幂等 | 连续两次置不可售均 200 且结果相同,第二次未产生任何写入 | +| 班期不存在 | 返 `code=200` + `data=null`,由调用方按同步失败告警 | +| 同步失败留痕 | 审批仍 200、订单域仍「已取消」、ERROR 日志含两个 ID、状态流水出现 DATA 类「班期置不可售失败…」记录 | +| 并发正确性 | 真实 MySQL 并发集成测试:流团先提交 → 创单被拒且整单回滚;创单后提交 → 被流团一并取消;相反加锁顺序产生的真实死锁下,创单侧整单回滚、无脏订单 | +| 隔离级别 | 测试库实测 `REPEATABLE-READ` | +| 全量回归 | order-v3 全量单测 9008 例 0 失败 0 错误;product-v2 1592 例 0 失败 0 错误 | + +--- + +## 十、相关文档 + +- Issue: #7283 +- PR: #7303 +- 后续工单: #7294(流团不取消已付款子订单)、#7293(库存聚合降级改写满员班期)、#7284(下单校验改判实时库存)、#7304(团期状态流水只读端点) +- 设计文档: `docs/order-v3/notes/14-group-tour-order-design.md` 流团章节 + +--- + +## 关联 / 联系人 + +- 后端: jw +- 前端: mmg(hl-ui / 小程序) +- 业务口径: wx