文件
hl-api-changelog/changelogs-v2/2026-09/28_8482_团期人员与报账人变更写进团期时间线-修改接口-管理后台.md
jw和Claude Opus 5.5 f3e200bc45
changelog-filename-gate / validate (push) Failing after 2s
docs(order-v3): 团期人员与报账人变更写进团期时间线交接件(#8482)
五条人员写路径补留痕(新增 5 个事件码)+ GB-ADM-096 同秒按 logId 排序;
PR #8490(e24be7af8)、#8492(75e182710),TEST 网关验收通过,前端零改动。

Refs wx/HL#8482

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 19:44:45 +08:00

44 KiB

schema, ticket, title, consumer, author, change_type, backend_status, gateway_status, frontend_status, frontend_owner, frontend_ref, target_release, verified_at, status_note, updated_at, base
schema ticket title consumer author change_type backend_status gateway_status frontend_status frontend_owner frontend_ref target_release verified_at status_note updated_at base
hl-changelog/v2 8482 团期人员与报账人变更写进团期时间线:配置保存、设报账人、单人删除、单人更换、取消成团回收五条写路径补留痕(新增五个事件码),时间线同一秒内按 logId 排序 admin jw(GIT) 修改接口 deployed verified not_required PR #8490 已合入 dev-v3(e24be7af8,五条写路径补留痕),PR #8492 已合入 dev-v3(75e182710,GB-ADM-096 同秒按 logId 排序);TEST 的 hl-order-service-v3 运行 dev-v3 75e182710。经网关 api.test.1814.love 用 admin token 实测:连设主报账人 A→B→C 三行可按时间还原更替、原样保存 / 只改排序 / 只改备注 / 设成已有等级零新增、单人删除与更换主(次)报账人各一行、保存移出主报账人一行、取消成团回收单独一行且排在取消成团之后、589508 / 589598 被拒零新增、CHECK 约束注入使留痕写失败时业务一起回滚、新增行无手机号,全部通过;排序修复部署后同一团期连读 5 次,同秒行全部按 logId 升序。frontend_status=not_required 的依据:#7987 时 mmg 已实证团期时间线组件 StatusLogsPanel 渲染 eventTypeName || eventType,中文名由后端直供,前端没有自建事件码映射表;2026-09-28 复核 hl-ui origin/v2.1@ce469f78 仍是这一写法,content 直接渲染,列表按接口返回顺序展示、不在前端重排,五个新事件码零改动即可正常显示。这一结论的前提是后端继续直供 eventTypeName:若后端对这些事件码返回 eventTypeName=null,页面会退化成显示原始事件码,那时本条须改回 pending。 2026-09-28 dev-v3

团期人员与报账人变更写进团期时间线(管理后台)

服务: hl-order-service-v3(端口 8086) PR: #8490(合入 dev-v3 为 e24be7af8)、#8492(合入 dev-v3 为 75e182710) Issue: #8482 日期: 2026-09-28 影响范围: 团期人员四个写接口与取消成团的副作用、团期状态流水(时间线 GB-ADM-096)


⚠️ 关键变化

  1. 五条人员写路径成功后各写一行时间线:配置人员保存、设报账人、单人删除、单人更换、取消成团回收人员。新增 5 个 eventType:BATCH_STAFF_CONFIG_SAVE「调整团期人员」、BATCH_REPORTER_CHANGE「报账人变更」、BATCH_STAFF_REMOVE「移除团期人员」、BATCH_STAFF_REPLACE「更换团期人员」、BATCH_STAFF_RECYCLED「取消成团·回收人员」,changeType 都是 DATA。
  2. 一次操作只写一行:设新主报账人时原主报账人自动降级、换人时报账人等级转给新人、被移出的人带走报账人身份,这些报账人变化都并进同一行的 content 和 extra,不另起一行。
  3. 没有实际变化不写:原样保存、只改排序或备注、只改服务日期或日薪、把人设成他已有的报账人等级,时间线都不新增。
  4. 被拒的请求不写;留痕写失败时业务一起回滚:接口返回 500,名册与子订单副本都不变。
  5. extra 里的 ID 一律是字符串;主 / 次报账人前后字段(primaryReporterBefore / primaryReporterAfter / secondaryReporterBefore / secondaryReporterAfter)只在对应归属真的变了时出现。
  6. GB-ADM-096 同一秒内的多行按 logId 升序返回。改前同秒行的先后由数据库决定,实测建团和成团会排反;改后「取消成团」与随后的回收行稳定是「取消成团」在前。
  7. 六个接口的请求、响应结构一字未改。前端无需改动,理由见第四节「前端交接清单」。

一、背景

团期缺口台账 g-062(2026-09-26 四方对账,P2,后端缺口):配置、删除、更换导游和摄影,改主 / 次报账人,以及取消成团时回收人员,这五条写路径都不写团期操作记录,运营在时间线上看不到「谁在什么时候把谁换成了谁」。

主报账人不只是一个标签:代收、垫付、报账的归属实时按当前主报账人计算,不存历史快照;团期确认逐户校验主报账人(581062),预支的领款人也限定为主报账人(589557)。主报账人一变,这些都跟着变,但改前时间线上找不到原因。

jw 2026-09-28 裁决:内容没变的操作不写;一次操作只写一行,报账人变化并入这一行;取消成团回收单独写一行。

问题 改前 改后
人员 / 报账人变更无留痕 五条写路径都不写时间线 各写一行,带前后差异
取消成团回收了谁 时间线只有「取消成团」,回收人数只在服务端日志 单独一行,列出回收的人和报账人身份
同秒行顺序 只按 changedAt(精确到秒)排,同秒先后不稳定 同秒按 logId 升序

二、变更接口清单

# 接口 方法 路径 变更类型 说明
1 保存团期人员配置 PUT /v3/admin/group-batch/{productBatchId}/staff 修改行为 名单或报账人归属有变化时写 BATCH_STAFF_CONFIG_SAVE
2 设置报账人等级 PUT /v3/admin/group-batch/{productBatchId}/staff/{staffId}/reporter-rank 修改行为 主 / 次报账人归属有变化时写 BATCH_REPORTER_CHANGE
3 团期单人删除 DELETE /v3/admin/group-batch/{productBatchId}/staff/{staffId} 修改行为 删除成功写 BATCH_STAFF_REMOVE
4 团期单人更换 PUT /v3/admin/group-batch/{productBatchId}/staff/{staffId}/replace 修改行为 更换成功写 BATCH_STAFF_REPLACE
5 取消成团 POST /v3/admin/order/group-batch/{groupBatchId}/cancel-group 修改行为 回收到人时,在「取消成团」那行之后再写一行 BATCH_STAFF_RECYCLED
6 团期状态流水(GB-ADM-096) GET /v3/admin/order/group-batch/{groupBatchId}/status-logs 取值域扩展 + 排序 eventType 新增五个值;同秒按 logId 升序

三、接口详情

1. 保存团期人员配置 PUT /v3/admin/group-batch/{productBatchId}/staff

VO: BatchStaffConfigReqVO → Result<BatchStaffConfigRespVO>

使用场景

「配置人员」弹窗保存(导游位、摄影位各一个弹窗)。请求与响应不变;保存成功后,名单或报账人归属有变化时,团期时间线多一行「调整团期人员」。

入参

字段 位置 类型 必填 约束 说明
productBatchId Path Long ✅ 雪花 ID 产品侧排期 ID,不是 groupBatchId
scopeRoles Body String[] 否 传了不能是空数组;导游位须同时传 GUIDE 与 LEADER 本次覆盖的角色范围,不传 = 整期全量覆盖;也决定时间线里的位名
staffList Body Array ✅ 可以是 [](清空该范围) 覆盖范围内的最终名单
staffList[].staffId Body Long ✅ - 员工 ID
staffList[].staffRole Body String ✅ LEADER / GUIDE / PHOTOGRAPHER 等 落库角色
其余字段 Body - - - sortOrder / remark / serviceStartDate / serviceEndDate,与现有契约一致,本次不变;这些字段的变化不写时间线

出参 Result<BatchStaffConfigRespVO>

字段 类型 说明
productBatchId String 回显,不变
groupBatchId String 团期主键,不变
staffList Array 保存后的名册,不变
affectedOrderCount Integer 扇出影响的子订单数,不变

请求示例

TEST 实测「加一人减一人」(导游位原有李雪梅、白云飞、刘大山,本次移出李雪梅、加入萨仁高娃):

{
  "staffList": [
    { "staffId": 1007, "staffRole": "LEADER", "sortOrder": 1 },
    { "staffId": 1005, "staffRole": "LEADER", "sortOrder": 2 },
    { "staffId": 1008, "staffRole": "LEADER", "sortOrder": 3 }
  ],
  "scopeRoles": ["GUIDE", "LEADER"]
}

响应示例

响应结构不变(下为 TEST 原文节选,名册每人只列部分字段,略去手机号):

{
  "code": 200,
  "message": "成功",
  "data": {
    "productBatchId": "2104527134162853890",
    "groupBatchId": "2104527135173685249",
    "staffList": [
      { "id": "2104527304799711233", "staffId": 1003, "staffRole": "PHOTOGRAPHER", "staffName": "王强", "sortOrder": 1, "reporterRank": "NONE", "reporterRankName": "非报账人" },
      { "id": "2104527526883930114", "staffId": 1007, "staffRole": "LEADER", "staffName": "白云飞", "sortOrder": 1, "reporterRank": "PRIMARY", "reporterRankName": "主报账人" },
      { "id": "2104527526888124417", "staffId": 1005, "staffRole": "LEADER", "staffName": "刘大山", "sortOrder": 2, "reporterRank": "SECONDARY", "reporterRankName": "次报账人" },
      { "id": "2104527526892318722", "staffId": 1008, "staffRole": "LEADER", "staffName": "萨仁高娃", "sortOrder": 3, "reporterRank": "NONE", "reporterRankName": "非报账人" }
    ],
    "affectedOrderCount": 1
  },
  "traceId": null,
  "success": true
}

同时 GB-ADM-096 新增的一行(原文):

{
  "logId": "2104527526900707330",
  "groupBatchId": "2104527135173685249",
  "changeType": "DATA",
  "eventType": "BATCH_STAFF_CONFIG_SAVE",
  "eventTypeName": "调整团期人员",
  "fromStatus": "RESOURCE_PREPARING",
  "toStatus": "RESOURCE_PREPARING",
  "content": "导游位:+萨仁高娃;−李雪梅",
  "operatorType": "ADMIN",
  "operatorId": "1001",
  "operatorName": "admin",
  "extra": {
    "scopeRoles": ["GUIDE", "LEADER"],
    "added": [{ "staffId": "1008", "staffName": "萨仁高娃", "staffRole": "LEADER" }],
    "removed": [{ "staffId": "1002", "staffName": "李雪梅", "staffRole": "GUIDE", "reporterRank": "NONE" }],
    "roleChanged": []
  },
  "changedAt": "2026-09-28 19:03:57"
}

空数据 / 降级响应

无空数据形态。staffList=[] 表示清空该范围:有人被移出时同样写一行,位名后只有「−」一段。

错误响应

{ "code": 589598, "message": "团期已确认,配置不可修改", "data": null, "traceId": null, "success": false }

582115 / 582116 / 582119 / 589553 等其余错误码与改前相同。所有被拒请求零写入,也不写时间线。

业务边界

  • 写的条件:名单(staffId + 落库角色)有新增、移除或角色变化,或者主 / 次报账人归属变了。排序、备注、服务日期、日薪不参与比较。
  • 原样保存(前端形态:保留行只带 staffId / staffRole / sortOrder)、只改排序、只改备注:返回 200,时间线不新增(TEST 实测三次都是 8→8)。
  • content 格式:{位名}:+新增的人;−移出的人;角色调整:某人(导游→领队),各段有内容才出现。位名:不传 scopeRoles 为「团期人员」,其余为「导游位」「摄影位」或「导游位、摄影位」。
  • 被移出的人带走报账人身份时,名字后写明去向:团期因此没有主报账人写「(原主报账人,团期现无主报账人)」,团期仍有别的主报账人写「(原主报账人,团期现主报账人为某某)」;次报账人同理。extra 同时带上报账人前后字段。
  • 报账人归属变了、却不是被移出的人带走的(少见,例如历史上同一人有重复行),content 末尾单列一段「主报账人:X → Y」。
  • content 超过 512 个字符时截断,末尾加「…」;完整名单以 extra 为准。

2. 设置报账人等级 PUT /v3/admin/group-batch/{productBatchId}/staff/{staffId}/reporter-rank

VO: SetReporterRankReqVO → Result<Void>

使用场景

名册里给某人设主报账人、次报账人,或取消报账人。团期内主、次报账人各唯一:设新的主(次)报账人时,原来那位自动降为非报账人。一次操作改动两个人,时间线只写一行。

入参

字段 位置 类型 必填 约束 说明
productBatchId Path Long ✅ 雪花 ID 产品侧排期 ID
staffId Path Long ✅ 须是本团已配人员 员工 ID
reporterRank Body String ✅ PRIMARY / SECONDARY / NONE 目标等级

出参 Result<Void>

字段 类型 说明
data null 不变

请求示例

PUT /v3/admin/group-batch/2104527134162853890/staff/1005/reporter-rank(此前主报账人是李雪梅):

{ "reporterRank": "PRIMARY" }

响应示例

{ "code": 200, "message": "成功", "data": null, "traceId": null, "success": true }

同时 GB-ADM-096 新增的一行(原文节选):

{
  "logId": "2104527347090894850",
  "eventType": "BATCH_REPORTER_CHANGE",
  "eventTypeName": "报账人变更",
  "changeType": "DATA",
  "content": "主报账人:李雪梅 → 刘大山",
  "operatorType": "ADMIN",
  "operatorId": "1001",
  "operatorName": "admin",
  "extra": {
    "staffId": "1005",
    "staffName": "刘大山",
    "staffRole": "LEADER",
    "reporterRankBefore": "NONE",
    "reporterRankAfter": "PRIMARY",
    "primaryReporterBefore": { "staffId": "1002", "staffName": "李雪梅" },
    "primaryReporterAfter": { "staffId": "1005", "staffName": "刘大山" }
  },
  "changedAt": "2026-09-28 19:03:14"
}

空数据 / 降级响应

无空数据形态。把人设成他已有的等级:返回 200,时间线不新增(TEST 实测 7→7)。

错误响应

{ "code": 589508, "message": "报账人不是该团期已派员工", "data": null, "traceId": null, "success": false }
{ "code": 589598, "message": "团期已确认,配置不可修改", "data": null, "traceId": null, "success": false }

业务边界

  • 主 / 次报账人归属都没变就不写,业务写入照常执行。
  • content:「主报账人:张三 → 李四」「次报账人:无 → 王五」「取消主报账人:张三」。把次报账人升为主报账人时两段并在一行:「主报账人:无 → 乌日娜;次报账人:乌日娜 → 无」。
  • 前任在同一事务内加锁读取,记下的前任不会是过期值。
  • 历史数据里一团有两个主报账人时,重设其中一个会把另一个降级;归属因此变了,会写一行。
  • 留痕写失败时整笔回滚,接口返回 500「系统繁忙,请稍后重试或联系客服」,团级行与子订单副本的等级都不变(TEST 用 CHECK 约束注入实测)。

3. 团期单人删除 DELETE /v3/admin/group-batch/{productBatchId}/staff/{staffId}

VO: Result<BatchStaffRemoveRespVO>(无请求体)

使用场景

名册里单独删掉一个人,其他人不动;同步删掉本团活跃子订单上此人的团期副本。本单在删除成功后写一行「移除团期人员」。

入参

字段 位置 类型 必填 约束 说明
productBatchId Path Long ✅ 雪花 ID 产品侧排期 ID
staffId Path Long ✅ 须在本团 要删除的员工 ID

出参 Result<BatchStaffRemoveRespVO>

字段 类型 说明
productBatchId / groupBatchId String 回显,不变
removedStaffId / removedStaffRole Long / String 被删的人,不变
primaryReporterRemoved Boolean 删的是否主报账人,不变
affectedOrderCount Integer 同步删除副本的子订单数,不变
staffList Array 删除后的名册,不变

请求示例

DELETE /v3/admin/group-batch/2104527134162853890/staff/1007

响应示例

响应结构不变(TEST 原文节选,略去名册):

{
  "code": 200,
  "message": "成功",
  "data": {
    "productBatchId": "2104527134162853890",
    "groupBatchId": "2104527135173685249",
    "removedStaffId": 1007,
    "removedStaffRole": "LEADER",
    "primaryReporterRemoved": true,
    "affectedOrderCount": 1
  },
  "traceId": null,
  "success": true
}

同时 GB-ADM-096 新增的一行(原文节选):

{
  "logId": "2104527571683274754",
  "eventType": "BATCH_STAFF_REMOVE",
  "eventTypeName": "移除团期人员",
  "changeType": "DATA",
  "content": "移除领队 白云飞(原主报账人,团期现无主报账人)",
  "operatorType": "ADMIN",
  "operatorId": "1001",
  "extra": {
    "staffId": "1007",
    "staffName": "白云飞",
    "staffRole": "LEADER",
    "reporterRankBefore": "PRIMARY",
    "primaryReporterBefore": { "staffId": "1007", "staffName": "白云飞" },
    "primaryReporterAfter": null
  },
  "changedAt": "2026-09-28 19:04:08"
}

空数据 / 降级响应

无空数据形态。删的不是报账人时 content 只有「移除导游 张三」,extra 不带报账人前后字段。

错误响应

{ "code": 589508, "message": "报账人不是该团期已派员工", "data": null, "traceId": null, "success": false }
{ "code": 589598, "message": "团期已确认,配置不可修改", "data": null, "traceId": null, "success": false }

业务边界

  • 每次删除成功写一行。加锁读到的名册里已经没有此人(并发下刚被别人删掉)时不写。
  • 删除不在本团的人返回 589508,message 是错误码共用的「报账人不是该团期已派员工」,与改前相同。
  • 响应里的 primaryReporterRemoved 与时间线 extra 的 primaryReporterAfter: null 是同一件事的两处体现。

4. 团期单人更换 PUT /v3/admin/group-batch/{productBatchId}/staff/{staffId}/replace

VO: BatchStaffReplaceReqVO → Result<BatchStaffReplaceRespVO>

使用场景

名册里把一个人换成另一个人;配置位、排序、备注、报账人等级、服务日期都转给新人。本单在更换成功后写一行「更换团期人员」。

入参

字段 位置 类型 必填 约束 说明
productBatchId Path Long ✅ 雪花 ID 产品侧排期 ID
staffId Path Long ✅ 须在本团 被换下的员工 ID
newStaffId Body Long ✅ 须上架、类型符合原配置位、不在本团 换上的员工 ID

出参 Result<BatchStaffReplaceRespVO>

字段 类型 说明
oldStaffId / newStaffId Long 换下 / 换上的人,不变
staffRole String 新人落库角色,不变
reporterRank String 新人继承的等级,不变
primaryReporterTransferred Boolean 主报账人是否随之转给新人,不变
affectedOrderCount / staffList - 不变

请求示例

PUT /v3/admin/group-batch/2104527134162853890/staff/1008/replace(萨仁高娃是主报账人):

{ "newStaffId": 1009 }

响应示例

响应结构不变(TEST 原文节选,略去名册):

{
  "code": 200,
  "message": "成功",
  "data": {
    "productBatchId": "2104527134162853890",
    "groupBatchId": "2104527135173685249",
    "oldStaffId": 1008,
    "newStaffId": 1009,
    "staffRole": "LEADER",
    "reporterRank": "PRIMARY",
    "primaryReporterTransferred": true,
    "affectedOrderCount": 1
  },
  "traceId": null,
  "success": true
}

同时 GB-ADM-096 新增的一行(原文节选):

{
  "logId": "2104527581611192321",
  "eventType": "BATCH_STAFF_REPLACE",
  "eventTypeName": "更换团期人员",
  "changeType": "DATA",
  "content": "领队 萨仁高娃 → 陈志远(主报账人随之转给陈志远)",
  "operatorType": "ADMIN",
  "operatorId": "1001",
  "extra": {
    "oldStaff": { "staffId": "1008", "staffName": "萨仁高娃", "staffRole": "LEADER", "reporterRank": "PRIMARY" },
    "newStaff": { "staffId": "1009", "staffName": "陈志远", "staffRole": "LEADER", "reporterRank": "PRIMARY" },
    "primaryReporterBefore": { "staffId": "1008", "staffName": "萨仁高娃" },
    "primaryReporterAfter": { "staffId": "1009", "staffName": "陈志远" }
  },
  "changedAt": "2026-09-28 19:04:10"
}

空数据 / 降级响应

无空数据形态。换下的不是报账人时 content 只有「导游 张三 → 李四」,extra 不带报账人前后字段。

错误响应

{ "code": 589508, "message": "报账人不是该团期已派员工", "data": null, "traceId": null, "success": false }
{ "code": 589598, "message": "团期已确认,配置不可修改", "data": null, "traceId": null, "success": false }

100001 / 589582 / 582103 / 582118 / 582114 / 582119 / 589553 与改前相同。

业务边界

  • 更换一定改了名单,每次成功都写一行。
  • 次报账人同理:「领队 刘大山 → 乌日娜(次报账人随之转给乌日娜)」,extra 带 secondaryReporterBefore / secondaryReporterAfter。
  • 落库角色变了时两边都带角色,如「领队 李四 → 导游 巴特尔」。

5. 取消成团 POST /v3/admin/order/group-batch/{groupBatchId}/cancel-group

VO: Result<Void>(无请求体)

使用场景

团期从「资源准备中」退回「招募中」,同时回收整团的导游 / 摄影配置。本单在回收之后单独写一行「取消成团·回收人员」,原来那行「取消成团」不动。

入参

字段 位置 类型 必填 约束 说明
groupBatchId Path Long ✅ 雪花 ID 团期主订单 ID(不是 productBatchId)

出参 Result<Void>

字段 类型 说明
data null 不变

请求示例

POST /v3/admin/order/group-batch/2104527135173685249/cancel-group

响应示例

{ "code": 200, "message": "成功", "data": null, "traceId": null, "success": true }

同时 GB-ADM-096 新增的两行(原文节选,同一秒,按 logId 排序):

[
  {
    "logId": "2104528117127360513",
    "changeType": "STATUS",
    "eventType": "BATCH_CANCEL_GROUP",
    "eventTypeName": "取消成团",
    "fromStatus": "RESOURCE_PREPARING",
    "toStatus": "RECRUITING",
    "content": "取消成团",
    "extra": null,
    "changedAt": "2026-09-28 19:06:18"
  },
  {
    "logId": "2104528117160914946",
    "changeType": "DATA",
    "eventType": "BATCH_STAFF_RECYCLED",
    "eventTypeName": "取消成团·回收人员",
    "fromStatus": "RECRUITING",
    "toStatus": "RECRUITING",
    "content": "取消成团,回收团期人员 2 人:王强、乌日娜(主报账人)",
    "operatorType": "ADMIN",
    "operatorId": "1001",
    "extra": {
      "recycledCount": 2,
      "staff": [
        { "staffId": "1003", "staffName": "王强", "staffRole": "PHOTOGRAPHER", "reporterRank": "NONE" },
        { "staffId": "1010", "staffName": "乌日娜", "staffRole": "LEADER", "reporterRank": "PRIMARY" }
      ],
      "primaryReporterBefore": { "staffId": "1010", "staffName": "乌日娜" },
      "primaryReporterAfter": null
    },
    "changedAt": "2026-09-28 19:06:18"
  }
]

空数据 / 降级响应

团期没配人员时只写「取消成团」一行,不写回收行(TEST 实测团期 Z:2→3,只多 BATCH_CANCEL_GROUP)。

错误响应

与改前相同(下为源码 GroupBatchErrorCode 定义的文案,本轮验收没有触发):

{ "code": 589502, "message": "取消成团被阻塞(有已确认子订单或已派单资源)", "data": null }
{ "code": 589501, "message": "团期状态不允许当前操作", "data": null }

业务边界

  • 回收行的 fromStatus / toStatus 都是 RECRUITING(取消成团之后的状态)。它与「取消成团」通常在同一秒,靠 logId 稳定排在后面。
  • content:「取消成团,回收团期人员 N 人:张三(主报账人)、李四、王五(次报账人)」;回收的人里没有主报账人时末尾加「;其中无主报账人」。
  • 回收名单在同一事务内、软删之前加锁读取。回收或留痕任何一步失败,取消成团整体回滚。

6. 团期状态流水 GET /v3/admin/order/group-batch/{groupBatchId}/status-logs

VO: GroupBatchStatusLogItemVO(返回 Result<List<GroupBatchStatusLogItemVO>>,无请求体)

使用场景

团期详情的时间线(GB-ADM-096)。结构不变;eventType 多了五个取值;同一秒内的行改为按 logId 升序。

入参

字段 位置 类型 必填 约束 说明
groupBatchId Path Long ✅ 雪花 ID 团期主订单 ID

出参 Result<List<GroupBatchStatusLogItemVO>>

字段 类型 说明
logId String 流水 ID;同秒排序的第二键
eventType String 新增五个值,见六.5
eventTypeName String 对应中文名,后端直供
changeType String 五个新事件都是 DATA
fromStatus / toStatus String DATA 类两者相同,取写入时的团期状态
content String 展示文本,前端直接渲染
extra Object 结构化快照,按事件类型不同,见六.5
operatorType / operatorId / operatorName String 五个新事件都是 ADMIN + 管理员 ID(字符串)+ 管理员名字
其余字段 - groupBatchId / fromStatusName / toStatusName / reason / changedAt 等,不变

请求示例

GET /v3/admin/order/group-batch/2104527135173685249/status-logs

响应示例

TEST 原文节选(团期 X,排序修复后)。前两行同为 19:02:24,按 logId 升序:

{
  "code": 200,
  "message": "成功",
  "data": [
    {
      "logId": "2104527135194656770",
      "changeType": "STATUS",
      "eventType": "BATCH_CREATE",
      "eventTypeName": "建团",
      "content": "建团:2月18日海拉尔-呼伦湖2日团",
      "changedAt": "2026-09-28 19:02:24"
    },
    {
      "logId": "2104527136431976449",
      "changeType": "STATUS",
      "eventType": "BATCH_GROUP",
      "eventTypeName": "成团",
      "content": "手动成团 · 已售 1/15 户 · 理由:客户已全款,手动成团(#8482 验收造数)",
      "changedAt": "2026-09-28 19:02:24"
    },
    {
      "logId": "2104527342187737089",
      "groupBatchId": "2104527135173685249",
      "changeType": "DATA",
      "eventType": "BATCH_REPORTER_CHANGE",
      "eventTypeName": "报账人变更",
      "fromStatus": "RESOURCE_PREPARING",
      "fromStatusName": "资源准备中",
      "toStatus": "RESOURCE_PREPARING",
      "toStatusName": "资源准备中",
      "content": "主报账人:无 → 李雪梅",
      "reason": null,
      "operatorType": "ADMIN",
      "operatorId": "1001",
      "operatorName": "admin",
      "extra": {
        "staffId": "1002",
        "staffName": "李雪梅",
        "staffRole": "GUIDE",
        "reporterRankBefore": "NONE",
        "reporterRankAfter": "PRIMARY",
        "primaryReporterBefore": null,
        "primaryReporterAfter": { "staffId": "1002", "staffName": "李雪梅" }
      },
      "changedAt": "2026-09-28 19:03:13"
    }
  ],
  "traceId": null,
  "success": true
}

空数据 / 降级响应

无流水时 data=[],与改前相同。

错误响应

与改前相同(源码 GroupBatchErrorCode 文案):

{ "code": 589507, "message": "无操作权限(当前角色未授予团期权限,或该团期不在您名下)", "data": null }

业务边界

  • 排序:changedAt 升序,同一秒内 logId 升序(#8492)。TEST 同一团期修复前连读 5 次,都是「成团」排在「建团」之前;修复后连读 5 次,两组同秒行(建团 / 成团、取消成团 / 回收)全部按 logId 升序。
  • 历史不回补:本单合入之前的人员变更查不回来。
  • 库里认不出的事件码,eventTypeName 为 null,与改前相同。

四、契约约束与正确调用方式

本节只写后端接受/拒绝的规则和前端必须跟着做的事。

✅ 正确 / ❌ 错误用法对照

场景 做法
✅ 时间线展示新事件 渲染 eventTypeName,取不到回落 eventType;正文直接渲染 content
✅ 读 extra 里的报账人前后字段 缺键按 null 处理;要判断「这一行有没有改主报账人」,看 extra 里有没有 primaryReporterAfter 这个键
❌ 把「缺键」当成「团期没有主报账人」 缺键只表示这一行没改该项归属;键在且值为 null 才表示那一端没有报账人
❌ 把 extra 里的 staffId 转成数字 extra 里的 ID 一律是字符串,按字符串比较
❌ 用 productBatchId 调时间线或取消成团 人员四个接口的路径参数是 productBatchId,时间线与取消成团是 groupBatchId,两者不能混用
❌ 前端按 changedAt 重排时间线 changedAt 只到秒,重排会打乱同秒行的先后;按接口返回顺序展示

前端交接清单

结论:无需改动。

  1. 团期时间线组件 StatusLogsPanel 渲染的是 eventTypeName || eventType || '事件',中文名由后端直供,前端没有自建事件码映射表(#7987 时 mmg 实证;2026-09-28 复核 hl-ui origin/v2.1@ce469f78 未变)。五个新事件码不改代码就显示中文名。
  2. content 由后端拼好,面板直接渲染;新事件的说明文字都在 content 里。
  3. 面板对 extra 只挑 orderNo / schemeName / kind / orderId 几个键,缺键兜住。新事件的 extra 不含这些键,不会多渲染文字,也不会报错。
  4. 面板按接口返回顺序展示,没有在前端重排,同秒按 logId 排序的修复不用前端配合。
  5. 如果要在页面上用 extra(例如单独展示主报账人更替历史):报账人前后四个字段只在对应归属变了时出现,缺键按 null 处理;ID 都是字符串。
  6. 以上结论的前提是后端继续直供 eventTypeName。

五、数据库行为

情形 order_batch_staff group_batch_status_log
保存配置:名单或报账人归属有变化 写入(与改前相同) +1 BATCH_STAFF_CONFIG_SAVE
保存配置:原样、只改排序 / 备注 / 服务日期 / 日薪 写入(与改前相同) 不写
设报账人:归属变了 更新(与改前相同) +1 BATCH_REPORTER_CHANGE
设报账人:设成已有等级 照常执行 不写
单人删除 / 单人更换成功 写入(与改前相同) +1 BATCH_STAFF_REMOVE / BATCH_STAFF_REPLACE
取消成团,团期配了人员 整团软删(与改前相同) 「取消成团」之后 +1 BATCH_STAFF_RECYCLED
取消成团,团期没配人员 不动 只有「取消成团」一行
被拒(589508 / 589598 / 582115 等) 不动 不写
留痕写失败 回滚 不写(接口 500)
  • 零 DDL,不需要 Flyway:只往 group_batch_status_log 追加行,新事件码是 event_type 列的新取值。
  • 写之前先对 order_batch_staff 做锁定读(SELECT ... FOR UPDATE)取前态:保存配置锁本次覆盖范围内的行,设报账人 / 单人删除 / 单人更换 / 取消成团回收锁整团行。时间线记下的前任不会是过期值。
  • 留痕与业务写入在同一事务:业务回滚,留痕跟着回滚;留痕写失败,业务也回滚。
  • 子订单上的团期人员副本照旧扇出,副本的变化不进订单时间线。

六、边界行为

  • 未登录 → 网关返回 code=401(HTTP 200)。
  • 无团期管理权限 → 589507,与改前相同。
  • 操作人:五种新事件都由后台请求触发,operatorType=ADMIN,operatorId 是管理员 ID(字符串),operatorName 是管理员名字。TEST 新增的 17 行全部是 ADMIN / "1001" / admin。
  • 手机号:content 和 extra 都不带手机号,明文、库列值都不带。
  • 团期当前状态是库里认不出的历史值时,这一行的 fromStatus / toStatus 为 null,业务照常写入。
  • 订单自派人员(source=ORDER)的变更不在本单范围。

六.5、枚举 / 数据字典

eventType 新增取值(GroupBatchLogEventType)

值 中文名(eventTypeName) changeType 写入接口 典型 content(TEST 原文)
BATCH_STAFF_CONFIG_SAVE 调整团期人员 DATA 保存团期人员配置 「导游位:+萨仁高娃;−李雪梅」「导游位:−陈志远(原主报账人,团期现无主报账人)」
BATCH_REPORTER_CHANGE 报账人变更 DATA 设置报账人等级 「主报账人:李雪梅 → 刘大山」「次报账人:无 → 刘大山」「主报账人:无 → 乌日娜;次报账人:乌日娜 → 无」
BATCH_STAFF_REMOVE 移除团期人员 DATA 团期单人删除 「移除领队 白云飞(原主报账人,团期现无主报账人)」
BATCH_STAFF_REPLACE 更换团期人员 DATA 团期单人更换 「领队 萨仁高娃 → 陈志远(主报账人随之转给陈志远)」「领队 刘大山 → 乌日娜(次报账人随之转给乌日娜)」
BATCH_STAFF_RECYCLED 取消成团·回收人员 DATA 取消成团 「取消成团,回收团期人员 2 人:王强、乌日娜(主报账人)」

content 里的角色中文名与名册 staffRoleName 同源(导游 / 领队 / 摄影等);报账人等级中文名为「主报账人」「次报账人」「非报账人」。

公共:报账人前后字段(五种事件都可能带)

字段 类型 说明
primaryReporterBefore Object 或 null 主报账人归属变了才有这个键;值为 {staffId, staffName}(staffId 为字符串),那一端没有主报账人时为 null
primaryReporterAfter Object 或 null 同上,变更后
secondaryReporterBefore Object 或 null 次报账人归属变了才有这个键;形态同上
secondaryReporterAfter Object 或 null 同上,变更后

BATCH_STAFF_CONFIG_SAVE 的 extra

字段 类型 说明
scopeRoles String[] 或 null 本次覆盖范围;null 表示整期全量保存
added Array(恒在,可为空) 新增的人:staffId(String)/ staffName / staffRole
removed Array(恒在,可为空) 移出的人:staffId(String)/ staffName / staffRole / reporterRank(移出前等级)
roleChanged Array(恒在,可为空) 落库角色变了的人:staffId(String)/ staffName / staffRoleBefore / staffRoleAfter(一人多角色时用「/」连接)

BATCH_REPORTER_CHANGE 的 extra

字段 类型 说明
staffId String 被设等级的人
staffName String 姓名快照
staffRole String 落库角色
reporterRankBefore String 此人原等级:PRIMARY / SECONDARY / NONE
reporterRankAfter String 此人新等级

被自动降级的原主(次)报账人体现在报账人前后字段的 Before 里。

BATCH_STAFF_REMOVE 的 extra

字段 类型 说明
staffId String 被删的人
staffName String 姓名快照
staffRole String 落库角色
reporterRankBefore String 删除前等级

BATCH_STAFF_REPLACE 的 extra

字段 类型 说明
oldStaff Object 换下的人:staffId(String)/ staffName / staffRole / reporterRank(换下前的等级)
newStaff Object 换上的人:staffId(String)/ staffName / staffRole / reporterRank(继承到的等级)

BATCH_STAFF_RECYCLED 的 extra

字段 类型 说明
recycledCount Integer 回收人数(按人计)
staff Array 每人一项:staffId(String)/ staffName / staffRole / reporterRank

回收到主(次)报账人时带报账人前后字段,After 恒为 null。


六.6、修改前后对比

字段级对比

字段 改前 改后
六个接口的请求 / 响应字段 - 不变
GB-ADM-096 eventType 取值 没有人员类事件 新增五个
GB-ADM-096 同秒行顺序 不稳定 logId 升序

行为级对比

行为 改前 改后
保存配置有增删、角色变化或报账人归属变化 时间线无记录 +1 BATCH_STAFF_CONFIG_SAVE
保存配置原样、只改排序或备注 时间线无记录 仍无记录
设报账人 时间线无记录 +1 BATCH_REPORTER_CHANGE,被降级的原报账人写在同一行
单人删除 / 单人更换 时间线无记录 +1 BATCH_STAFF_REMOVE / BATCH_STAFF_REPLACE
取消成团回收人员 只有「取消成团」一行 「取消成团」之后 +1 回收行
同秒行顺序 TEST 连读 5 次都是「成团」排在「建团」前 连读 5 次都按 logId 升序

六.7、影响评估

  • 是否破坏向后兼容: 否(结构不变;新增的是 eventType 取值,前端按 eventTypeName 渲染)
  • 前端是否必须同步上线: 否(前端零改动)
  • 前端 workaround 清理点: 无

七、不影响范围

  • 仅影响: 五个写接口成功后的时间线副作用;GB-ADM-096 的取值域和同秒顺序
  • 零影响:
    • 六个接口的请求 / 响应结构和错误码
    • 保存配置时保留人员沿用原报账人等级和备注:这一行为随 #8468 发布(见 28_8468 交接件),本篇不重复
    • 名册、候选列表、扇出到子订单的逻辑
    • 订单级时间线
    • 订单自派人员(source=ORDER)
    • 本单合入之前的历史变更(不回补)

八、测试环境已验证

部署:主轮(19:02–19:09)跑在 hl-order-service-v3 dev-v3 @ e24be7af8(#8490,18:53:31 部署),两实例经运行字节探针确认加载的是本次代码(BATCH_STAFF_CONFIG_SAVE、「取消成团·回收人员」命中,进程 jar 与磁盘 jar 同 inode)。排序复验(19:28)跑在 dev-v3 @ 75e182710(#8492)。

经网关 api.test.1814.love 用 admin token(operatorId 1001)实测。团期 X:groupBatchId 2104527135173685249(productBatchId 2104527134162853890,一户全款、手动成团);团期 Z:groupBatchId 2104528158369951745(productBatchId 2104528157774356482)。每步前后各读一次 GB-ADM-096 整段原文比对行数。

AC-B1  19:03:13~16 X 依次设主报账人 李雪梅 → 刘大山 → 白云飞
       → GB-ADM-096 4→7,新增「主报账人:无 → 李雪梅」「主报账人:李雪梅 → 刘大山」「主报账人:刘大山 → 白云飞」,
         operatorType=ADMIN、operatorId=1001;同一次读取里 BATCH_CREATE / BATCH_GROUP 为阳性对照 ✓
AC-B3  19:03:16 X 对白云飞再设 PRIMARY(已有等级)              → 200,7→7 ✓
AC-A2  19:03:31 X 设次报账人 刘大山                              → 7→8「次报账人:无 → 刘大山」✓
AC-A1  19:03:36 X 按前端形态原样保存导游位(只带 staffId/staffRole/sortOrder)
  AC-A2  → 200,8→8;团级行重新插入(白云飞 batch_staff_id 2104527299623956483 → 2104527440154095618)仍为 PRIMARY,
  AC-B3    刘大山仍为 SECONDARY;子订单 2104527134926221314 的 GROUP_BATCH 副本同为 PRIMARY / SECONDARY,update_time 19:03:37 ✓
AC-B3  19:03:54 X 只改排序 → 8→8;19:03:56 只改备注 → 8→8 ✓
AC-B3  19:03:57 X 导游位加萨仁高娃、移出李雪梅                  → 8→9「导游位:+萨仁高娃;−李雪梅」✓
AC-B2  19:04:07 X 单人删除主报账人白云飞                          → 9→10「移除领队 白云飞(原主报账人,团期现无主报账人)」,
                 响应 primaryReporterRemoved=true,extra.primaryReporterAfter=null ✓
       19:04:09 X 设主报账人 萨仁高娃                              → 10→11「主报账人:无 → 萨仁高娃」
AC-B2  19:04:10 X 单人更换主报账人 萨仁高娃 → 陈志远             → 11→12「领队 萨仁高娃 → 陈志远(主报账人随之转给陈志远)」✓
AC-B2  19:04:11 X 单人更换次报账人 刘大山 → 乌日娜               → 12→13「领队 刘大山 → 乌日娜(次报账人随之转给乌日娜)」✓
AC-A3  19:04:23 X 保存导游位时移出主报账人陈志远                  → 13→14「导游位:−陈志远(原主报账人,团期现无主报账人)」,
                 extra.primaryReporterAfter=null,导游位只剩乌日娜(SECONDARY)✓
AC-B5  19:04:24~26 X 对已不在本团的人 设报账人 / 删除 / 更换      → 三个都是 589508「报账人不是该团期已派员工」,14→14,名册不变 ✓
AC-B6  19:05:28 X 给 group_batch_status_log 临时加 CHECK 约束(只拦本团新的 BATCH_REPORTER_CHANGE,约 1.4 秒)后设主报账人乌日娜
                 → 500「系统繁忙,请稍后重试或联系客服」;服务端日志:order_batch_staff、order_staff_assignment 两条 UPDATE 已执行,
                   随后 INSERT 时间线触发 3819;回滚后团级行与子订单副本的乌日娜仍为 SECONDARY,时间线 14→14;
                   约束删除后 SHOW CREATE TABLE 与注入前逐字一致 ✓
       19:06:04 X 约束删除后再设一次                               → 14→15「主报账人:无 → 乌日娜;次报账人:乌日娜 → 无」✓
AC-B4  19:06:18 X 取消成团(已配人员)                             → 15→17,BATCH_CANCEL_GROUP「取消成团」之后
                 BATCH_STAFF_RECYCLED「取消成团,回收团期人员 2 人:王强、乌日娜(主报账人)」✓
AC-B4  19:06:28 Z 取消成团(未配人员)                             → 2→3,只多 BATCH_CANCEL_GROUP,无回收行 ✓
AC-B5  19:07:23~27 Z 团期确认后(MATERIAL_PREPARING)保存 / 设报账人 / 删除 / 更换
                 → 四个都是 589598「团期已确认,配置不可修改」,14→14,名册不变 ✓
AC-B7  19:08:21 X、Z 两团全部 31 行(新事件 17 行):11 个明文手机号逐个查、7 个库列值逐个查、掩码 **** 与 phone 键名,全部 0 命中;
                 带数字边界的 11 位手机号正则 0 命中(19:44 复查);不带边界时命中的 3 行都是既有事件 extra 里雪花 ID 的片段,新事件 0 命中 ✓
排序   19:21:19~23 排序修复部署前,X 连读 5 次:BATCH_GROUP 都排在 BATCH_CREATE 前(同为 19:02:24)
       19:28:36~40 dev-v3 75e182710,X 连读 5 次:两组同秒行(建团 / 成团、取消成团 / 回收)全部 logId 升序 ✓

所有新增行 operatorType=ADMIN、operatorId="1001"、operatorName=admin、changedAt 有值,extra 里的 ID 全部是字符串。

单元层:五处留痕调用逐一去掉,对应用例各自转红(共 12 例);#8492 的排序键去掉后 GroupBatchStatusLogMapperOrderTest 转红。


九、相关历史 PR

  • #3676:引入主 / 次报账人等级
  • #7304:GB-ADM-096 团期状态流水只读端点
  • #7987:时间线新事件由后端直供 eventTypeName,前端零改动的先例
  • #8231:取消成团回收导游 / 摄影配置
  • #8354:团期单人删除、单人更换
  • #8468:保存配置时保留人员沿用原报账人等级和备注(PR #8475)
  • #8481:团期物资留痕(同日,同一张时间线)

十、相关文档

  • 关联 Issue: wx/HL#8482
  • 关联 PR: wx/HL#8490、wx/HL#8492
  • 同日交接件:changelogs-v2/2026-09/28_8468_团期导领摄配置加服务日期与基础日薪并在选人列表显示占用-修改接口-管理后台.md、changelogs-v2/2026-09/28_8481_团期物资确认后改清单自动失效并补全物资留痕-修改接口-管理后台.md

关联 / 联系人

链接

联系人

  • 后端负责人: @jw