308 行
13 KiB
Markdown
308 行
13 KiB
Markdown
---
|
||
schema: "hl-changelog/v2"
|
||
ticket: "7080"
|
||
title: "团期人员配置扇出补同步订单侧 staff 状态,guide_status 不再恒为 NULL"
|
||
consumer: "admin"
|
||
author: "jw(GIT)"
|
||
change_type: "修改接口"
|
||
backend_status: "deployed"
|
||
gateway_status: "verified"
|
||
frontend_status: "not_required"
|
||
frontend_owner: ""
|
||
frontend_ref: ""
|
||
target_release: ""
|
||
verified_at: "2026-09-07"
|
||
status_note: "PR #7115 合入 dev-v3(468df6fa7),2026-09-07 测试服网关实测通过(配导游后 guide 芯片 TODO(0/55)→DONE(55/55),清空回落)。出参结构一个字段没变,只改扇出副作用(guide_status/photographer_status 不再恒 NULL、补 DONE 流水、触发就绪闸门)。前端 mmg 已实查为 not_required:全仓无 guide_status/photographer_status 消费点,也无「团期配过但订单侧未配置」的兼容逻辑或提示文案可撤,无需改码。"
|
||
updated_at: "2026-09-07"
|
||
base: "dev-v3"
|
||
---
|
||
|
||
# 团期: 人员配置扇出补同步订单侧 staff 状态
|
||
|
||
> **服务**: hl-order-service-v3
|
||
> **PR**: #7115
|
||
> **Issue**: #7080
|
||
> **日期**: 2026-09-04
|
||
> **影响范围**: 管理后台团期人员配置保存后,团内各子订单的导游/摄影资源状态与时间线
|
||
|
||
---
|
||
|
||
## ⚠️ 关键变化
|
||
|
||
**出参结构一个字段没变,变的是写库之后的副作用。**
|
||
|
||
改前:经「团期详情 → 配置导游/摄影」配的人,扇出只写 `order_staff_assignment` 表,
|
||
`order_main.guide_status` / `photographer_status` **恒为 NULL**,
|
||
`GUIDE_DONE` / `PHOTOGRAPHER_DONE` 流水不产生,订单资源就绪闸门 `maybeAdvanceToConfirm` 不被触发。
|
||
只有走订单侧「订单详情 → 人员 → 新增」才会写这些。
|
||
|
||
改后:扇出末尾按配置位全量同步订单侧状态,两条路口径一致。
|
||
|
||
**前端要注意的是**:以前拿到 `guide_status=null` 不代表没配人(可能是团期配的),
|
||
现在 null 就是真的没人。原先若有「团期配过但订单侧显示未配置」的兼容逻辑或提示文案,可以撤掉。
|
||
|
||
---
|
||
|
||
## 一、背景
|
||
|
||
`GroupBatchStaffConfigService#doFanOutForOrder` 只做两件事:软删 `source=GROUP_BATCH` 旧行 + 批量 INSERT 新行。
|
||
而状态回写、DONE 流水、资源就绪闸门三件事挂在 `AssignmentService` 的 private `syncStaffStatusToOrder` 上,
|
||
扇出链路从来没调过它。
|
||
|
||
| 路径 | 写 order_staff_assignment | 写 order_main 状态 | 写 DONE 流水 | 触发就绪闸门 |
|
||
|---|---|---|---|---|
|
||
| 订单侧「订单详情 → 人员 → 新增」 | ✅ | ✅ | ✅ | ✅ |
|
||
| 团期侧「团期详情 → 配置导游」(改前) | ✅ | ❌ | ❌ | ❌ |
|
||
| 团期侧(**改后**) | ✅ | ✅ | ✅ | ✅ |
|
||
|
||
这个缺口先于 #7063 就存在,是 #7063 把「角色判定只认单一角色」和「ready 回填 ID 空间用错」修好后才浮出来的。
|
||
|
||
---
|
||
|
||
## 二、变更接口清单
|
||
|
||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||
|---|------|------|------|----------|------|
|
||
| 1 | 全量保存团期人员配置 | PUT | `/v3/admin/group-batch/:groupBatchId/staff` | 副作用变更 | 扇出后逐子订单同步 `guide_status`/`photographer_status` + DONE 流水 + 就绪闸门 |
|
||
|
||
---
|
||
|
||
## 三、接口详情
|
||
|
||
### 1. 全量保存团期人员配置 `PUT /v3/admin/group-batch/:groupBatchId/staff`
|
||
|
||
**VO**: `BatchStaffConfigReqVO`
|
||
|
||
#### 使用场景
|
||
|
||
团期详情「配置导游 / 配置摄影」弹窗点保存时调用。全量覆盖语义:传入列表即为最终配置。
|
||
保存后异步扇出到团内全部活跃子订单——本次变更就发生在扇出这一步的末尾。
|
||
|
||
#### 入参
|
||
|
||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||
|------|------|------|------|------|------|
|
||
| groupBatchId | Path | Long | ✅ | — | **产品侧团期 batchId**,非 `order_group_batch` 主键。入参未变 |
|
||
| staffList | Body | Array | ❌ | 传空则清空 | 人员配置列表,全量覆盖。入参未变 |
|
||
| staffList[].staffId | Body | Long | ✅ | — | 资源域人员 ID。入参未变 |
|
||
| staffList[].staffRole | Body | String | ✅ | LEADER / GUIDE / DRIVER / PHOTOGRAPHER / OTHER | 团期角色。入参未变 |
|
||
| staffList[].sortOrder | Body | Integer | ❌ | 缺省 0 | 展示排序。入参未变 |
|
||
|
||
#### 出参 `Result<BatchStaffConfigRespVO>`
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| staffList | Array | 落库后的人员快照,**结构未变** |
|
||
| affectedOrderCount | Integer | 扇出到的活跃子订单数,**未变** |
|
||
|
||
> 本次**不改任何出参字段**。要观察效果请查子订单的 `guide_status` / `photographer_status` 与订单时间线。
|
||
|
||
#### 请求示例
|
||
|
||
```json
|
||
{
|
||
"staffList": [
|
||
{ "staffId": 1002, "staffRole": "GUIDE", "sortOrder": 0 }
|
||
]
|
||
}
|
||
```
|
||
|
||
#### 响应示例
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"message": "成功",
|
||
"success": true,
|
||
"data": {
|
||
"staffList": [
|
||
{ "staffId": 1002, "staffRole": "GUIDE", "staffName": "李雪梅", "sortOrder": 0 }
|
||
],
|
||
"affectedOrderCount": 2
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 空数据 / 降级响应
|
||
|
||
`staffList` 传空数组即清空配置,响应照常 200:
|
||
|
||
```json
|
||
{ "code": 200, "data": { "staffList": [], "affectedOrderCount": 2 }, "success": true }
|
||
```
|
||
|
||
清空后各子订单的 `guide_status` / `photographer_status` 会被置回 `null`(该位若无订单侧手工行)。
|
||
|
||
#### 错误响应
|
||
|
||
```json
|
||
{
|
||
"code": 589507,
|
||
"message": "无操作权限(非团期管理员 / 非本定制师名下)",
|
||
"success": false,
|
||
"data": null
|
||
}
|
||
```
|
||
|
||
本次**未新增任何错误码**,错误语义与改前完全一致。
|
||
|
||
#### 业务边界
|
||
|
||
- **异步**:扇出是 `@Async`,状态同步在扇出的单订单子事务内完成,不阻塞本接口响应。
|
||
即接口返回 200 时,子订单状态可能尚未写完——前端不要在收到响应的同一刻立即读子订单状态做断言。
|
||
- **失败容忍**:单订单同步失败 → 该订单整单回滚(`REQUIRES_NEW` 子事务),记 ERROR 日志后继续下一单,
|
||
不影响其他子订单,也不影响本接口的 200。
|
||
- **清空语义**:按配置位全量同步,清空时把无人的位置回 `null` 并且**不写流水**。
|
||
- **与订单侧手工行共存**:状态按「配置位整组」计数(导游位 = GUIDE + LEADER),与 `source` 无关。
|
||
团期行被软删后若该订单仍有 `source=ORDER` 的手工导游行,`guide_status` 保持 `DONE`,两边不互相覆盖。
|
||
- **鉴权与幂等**:均未变。
|
||
|
||
---
|
||
|
||
## 四、契约约束与正确调用方式
|
||
|
||
### ✅ 正确 / ❌ 错误 payload 对照
|
||
|
||
| 场景 | payload |
|
||
|------|---------|
|
||
| ✅ 配一名导游 | `{ "staffList": [{ "staffId": 1002, "staffRole": "GUIDE" }] }` |
|
||
| ✅ 清空配置 | `{ "staffList": [] }` |
|
||
| ❌ 收到 200 后立刻断言子订单状态 | 扇出是异步的,需轮询或稍后再查 |
|
||
|
||
### 调用方式没有变化
|
||
|
||
本次是纯副作用修复,**前端不需要改任何请求**。
|
||
|
||
---
|
||
|
||
## 五、数据库行为
|
||
|
||
| 动作 | 改前 | 改后 |
|
||
|------|------|------|
|
||
| 团期配一名导游 | `order_staff_assignment` 新增 `source=GROUP_BATCH` 行;`order_main.guide_status` 保持 `NULL` | 同样新增行;**`order_main.guide_status` 置 `DONE`** |
|
||
| 同上 | `order_status_log` 无记录 | **每张子订单新增一条 `GUIDE_DONE`**(内容含脱敏姓名,如「李*」) |
|
||
| 同上 | 就绪闸门不触发 | **触发 `maybeAdvanceToConfirm`**,满足条件的订单可推进到 `PENDING_CONFIRM` |
|
||
| 团期清空配置 | 软删 `GROUP_BATCH` 行;状态字段不动 | 软删同上;**状态置回 `NULL`**(该位无手工行时),不写流水 |
|
||
| 订单侧另有手工导游行 | — | 计数按位整组,状态保持 `DONE`,不被团期清空覆盖 |
|
||
|
||
---
|
||
|
||
## 六、边界行为
|
||
|
||
- 未登录 → 401(网关拦截)
|
||
- 团期不存在 / 无权限 → 与改前一致(589500 / 589507)
|
||
- 单订单同步失败 → 该订单回滚并记 ERROR,其他订单不受影响,接口仍 200
|
||
- 无活跃子订单 → 不做任何同步,`affectedOrderCount=0`
|
||
- 已取消 / 已完成的子订单 → 本就不在扇出范围(`selectActiveOrderIdsByProductBatchId` 已过滤)
|
||
|
||
---
|
||
|
||
## 六.5、枚举 / 数据字典
|
||
|
||
### guide_status / photographer_status(order_main 状态字段)
|
||
|
||
**所属字段**: `order_main.guide_status`、`order_main.photographer_status` | **类型**: `String`
|
||
|
||
| 值 | 中文 | 说明 |
|
||
|----|------|------|
|
||
| `DONE` | 已配置 | 该配置位至少有一人(不分 source) |
|
||
| `null` | 未配置 | 该配置位无人。**改后 null 才是真的没人**,改前团期配的人也显示 null |
|
||
|
||
### 配置位与成员(`GroupBatchStaffSlot`,成员由字典决定,见 #7079)
|
||
|
||
**所属字段**: 服务端内部判定 | **类型**: `String`
|
||
|
||
| 配置位 | 默认成员 | 对应 order_main 字段 |
|
||
|----|------|------|
|
||
| `guide` | `GUIDE`、`LEADER` | `guide_status` |
|
||
| `photographer` | `PHOTOGRAPHER` | `photographer_status` |
|
||
|
||
> `DRIVER` / `OTHER` 不属于任何配置位,不关联 order_main 状态字段。
|
||
|
||
## 六.6、修改前后对比
|
||
|
||
### 字段级对比
|
||
|
||
| 字段 | 改前 | 改后 |
|
||
|------|------|------|
|
||
| `order_main.guide_status`(经团期配置) | 恒 `NULL` | 有人 `DONE` / 无人 `NULL` |
|
||
| `order_main.photographer_status`(经团期配置) | 恒 `NULL` | 有人 `DONE` / 无人 `NULL` |
|
||
| 接口出参 | — | **完全未变** |
|
||
|
||
### 行为级对比
|
||
|
||
| 行为 | 改前 | 改后 |
|
||
|------|------|------|
|
||
| 团期配人 → 订单时间线 | 无记录 | 每子订单一条 `GUIDE_DONE` / `PHOTOGRAPHER_DONE` |
|
||
| 团期配人 → 资源就绪闸门 | 不触发 | 触发 `maybeAdvanceToConfirm` |
|
||
| 团期清空 → 订单状态 | 不动(本来就是 NULL) | 置回 `NULL`(该位无手工行时) |
|
||
| 订单侧 addStaff / editStaff / deleteStaff | — | **行为完全不变**(实现搬到公共组件,断言未改) |
|
||
|
||
## 六.7、影响评估
|
||
|
||
- **是否破坏向后兼容**: 否。出参与入参均未变,只是原本恒为 NULL 的字段开始有值。
|
||
- **前端是否必须同步上线**: 否。
|
||
- **前端 workaround 清理点**: 若存在「团期已配人但订单侧 guide_status 为空,故不显示导游」这类兼容判断或提示文案,可以撤掉。
|
||
|
||
## 七、不影响范围
|
||
|
||
- **仅影响**: 团期人员配置保存后的扇出副作用
|
||
- **零影响**:
|
||
- 订单侧新增/编辑/删除人员三个端点的行为(实现搬家,断言一字未改)
|
||
- 本接口的入参、出参、错误码、鉴权、幂等
|
||
- 配车回调反写司机链路(DRIVER 不属任何配置位)
|
||
- 报账人等级设置
|
||
- C 端小程序全部接口
|
||
|
||
---
|
||
|
||
## 八、测试环境已验证
|
||
|
||
✅ **已验证。** 2026-09-07 部署 dev-v3 到测试服并经网关实测(`https://api.test.1814.love:9443`,真实管理端鉴权)。
|
||
|
||
产品 2044306857534636034 第 7 期(gbId 2096412454643802114,**55 户活跃子订单**):
|
||
|
||
| # | 用例 | 期望 | 实测 |
|
||
|---|---|---|---|
|
||
| AC-1 | 经团期配置一名导游(staffId 1002)后全部子订单 guide 状态 DONE | guide 芯片 `DONE(55/55)` | ✅ 配置前 `TODO(0/55)` → 扇出后 `DONE(55/55)`,`affectedOrderCount=55` |
|
||
| AC-5 | 清空配置(空 staffList)后状态回落 | guide 芯片回 `TODO(0)` | ✅ 撤回后经中间态 `DOING(54)` 逐单回退至 `TODO(0)`,现场已复原 |
|
||
|
||
改前对比:经团期配置扇出只写 `order_staff_assignment` 表、不碰订单侧状态,guide 芯片恒 `TODO`——
|
||
本次实测 DONE(55/55) 即修复生效的直接证据。
|
||
|
||
AC-2(GUIDE_DONE 流水)/ AC-3(摄影位)/ AC-4(就绪闸门推进)/ AC-6(手工行共存不覆盖)/
|
||
AC-7(单订单失败隔离)/ AC-8(大团期性能:52 单接口响应 0.11s、扇出落库 0.47s、均摊 9ms)
|
||
由本地端到端实测 + 单测覆盖(见工单 #7080 验收总结 comment)。
|
||
|
||
单测:全量 `mvn -o -pl hl-order-service-v3 -am test` 8167 例 Failures 0;
|
||
`OrderStaffStatusSyncerTest` 9 例、`GroupBatchStaffConfigServiceTest` 38 例、`AssignmentServiceTest` 65 例全绿。
|
||
|
||
|
||
## 九、相关历史 PR
|
||
|
||
| PR | Issue | 说明 | 是否仍有效 |
|
||
|----|-------|------|------------|
|
||
| #7085 | #7079 | 配置位成员改由字典决定 | ✅ 有效 |
|
||
| #7076 | #7063 | 导游位下游按配置位归组(第 1 波) | ✅ 有效 |
|
||
| **本 PR #7115** | **#7080** | 补上扇出链路缺失的状态同步(#7063 AC-3 在团期路径上不成立的真因) | ✅ 最新 |
|
||
|
||
---
|
||
|
||
## 十、相关文档
|
||
|
||
- 关联 Issue: [wx/HL#7080](https://git.1814.love:8443/wx/HL/issues/7080)
|
||
- 关联 PR: [wx/HL#7115](https://git.1814.love:8443/wx/HL/pulls/7115)
|
||
- 前序: [wx/HL#7063](https://git.1814.love:8443/wx/HL/issues/7063)(本单从其测试环境验收中分离)
|
||
|
||
## 关联 / 联系人
|
||
|
||
### 链接
|
||
|
||
- **Issue**: [#7080](https://git.1814.love:8443/wx/HL/issues/7080)
|
||
- **PR**: [#7115](https://git.1814.love:8443/wx/HL/pulls/7115)
|
||
- **Merge commit**: [468df6fa7](https://git.1814.love:8443/wx/HL/commit/468df6fa7)
|
||
|
||
### 联系人
|
||
|
||
- **后端负责人**: @jw
|