From 7281031d04c9c8640741ccdc6cefd09d82598d84 Mon Sep 17 00:00:00 2001 From: jw Date: Mon, 28 Sep 2026 15:01:04 +0800 Subject: [PATCH] =?UTF-8?q?docs(changelog):=20#8468=20=E5=9B=A2=E6=9C=9F?= =?UTF-8?q?=E5=AF=BC=E9=A2=86=E6=91=84=E9=85=8D=E7=BD=AE=E5=8A=A0=E6=9C=8D?= =?UTF-8?q?=E5=8A=A1=E6=97=A5=E6=9C=9F=E4=B8=8E=E5=9F=BA=E7=A1=80=E6=97=A5?= =?UTF-8?q?=E8=96=AA=EF=BC=8C=E9=80=89=E4=BA=BA=E5=88=97=E8=A1=A8=E4=B8=8E?= =?UTF-8?q?=E5=90=8D=E5=86=8C=E6=98=BE=E7=A4=BA=E5=8D=A0=E7=94=A8=E7=8A=B6?= =?UTF-8?q?=E6=80=81=EF=BC=8C=E5=AF=BC=E6=91=84=E8=8A=AF=E7=89=87=E5=8E=BB?= =?UTF-8?q?=E6=8E=89=E9=80=90=E6=88=B7=E4=BA=BA=E5=91=98=EF=BC=88=E4=BF=AE?= =?UTF-8?q?=E6=94=B9=E6=8E=A5=E5=8F=A3-=E7=AE=A1=E7=90=86=E5=90=8E?= =?UTF-8?q?=E5=8F=B0=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR #8475(bf65803b7)+ #8476(623c932bd)已合入 dev-v3 并部署 TEST,经网关实测; frontend_status=pending,前端交接清单见第四节。 Co-Authored-By: Claude Opus 5.5 --- ...期与基础日薪并在选人列表显示占用-修改接口-管理后台.md | 1790 +++++++++++++++++ 1 file changed, 1790 insertions(+) create mode 100644 changelogs-v2/2026-09/28_8468_团期导领摄配置加服务日期与基础日薪并在选人列表显示占用-修改接口-管理后台.md diff --git a/changelogs-v2/2026-09/28_8468_团期导领摄配置加服务日期与基础日薪并在选人列表显示占用-修改接口-管理后台.md b/changelogs-v2/2026-09/28_8468_团期导领摄配置加服务日期与基础日薪并在选人列表显示占用-修改接口-管理后台.md new file mode 100644 index 00000000..c6fc3c3b --- /dev/null +++ b/changelogs-v2/2026-09/28_8468_团期导领摄配置加服务日期与基础日薪并在选人列表显示占用-修改接口-管理后台.md @@ -0,0 +1,1790 @@ +--- +schema: "hl-changelog/v2" +ticket: "8468" +title: "团期导领摄配置加服务日期与基础日薪,选人列表与名册显示占用状态,导摄芯片去掉逐户人员" +consumer: "admin" +author: "jw(GIT)" +change_type: "修改接口" +backend_status: "deployed" +gateway_status: "verified" +frontend_status: "pending" +frontend_owner: "" +frontend_ref: "" +target_release: "" +verified_at: "" +status_note: "PR #8475(bf65803b7)与 #8476(623c932bd)已合入 dev-v3,TEST 的 hl-resource-service 与 hl-order-service-v3 均运行 dev-v3 623c932bd;经网关 api.test.1814.love 用真实 admin token 实测 AC-1、3、6~13、15、16 主路径全部通过,金额字段字符串形态已复验。前端需接:选人弹窗展示等级、服务区域、擅长、基础日薪与占用标签,占用明细弹层,保存弹窗编辑服务日期,服务人员管理基础日薪回显与列表列,导摄页签与看板芯片弹层去掉逐户人员表(见第四节交接清单),故 frontend_status 记 pending。" +updated_at: "2026-09-28" +base: "dev-v3" +--- + +# 团期人员配置:服务日期、基础日薪与占用状态(管理后台) + +> **服务**: hl-order-service-v3(团期人员配置、导/摄芯片明细)、hl-resource-service(服务人员管理) +> **PR**: #8475(主体,合入 dev-v3 为 `bf65803b7`)、#8476(金额字段改字符串输出,合入 dev-v3 为 `623c932bd`) +> **Issue**: #8468 +> **日期**: 2026-09-28 +> **影响范围**: 团期详情「配导游」「配摄影」的选人弹窗、已配置人员名册与保存弹窗,看板导/摄芯片弹层,服务人员管理新建/编辑/详情/列表 + +--- + +## ⚠️ 关键变化 + +1. **名册与候选新增占用**:已配置人员名册(以及保存、更换、删除响应里的 `staffList[]`)和两个候选接口的每一项都新增 `occupancyStatus`(`FREE` 空闲 / `PARTIAL` 部分占用 / `FULL` 全程占用)、`occupiedDays`、`freeRanges`、`occupancies[]`。占用**只提示、不拦截**,有占用照样能保存。 +2. **名册新增服务日期与基础日薪**:`serviceStartDate`、`serviceEndDate`、`serviceDateCustom`、`baseDailyWage`。保存接口每人可选传 `serviceStartDate` / `serviceEndDate`;日期不合法返回**新错误码 `582119`**(文案带人名,整批不写入)。 +3. **候选新增人员画像并改为全局排序**:候选项新增 `level`、`serviceAreas`、`specialties`、`baseDailyWage`;排序改为「本团本位已选 → 空闲 → 部分占用 → 全程占用 → 等级从高到低 → `sortOrder` 升序 → `staffId` 升序」,分页版逐页翻完不重不漏。 +4. **金额字段是 JSON 字符串**:order-v3 的 `baseDailyWage` 与服务人员管理的 `basePrice` 都以字符串输出(如 `"600.00"`)。做数值运算、比较大小或回填数字输入框前先 `Number()`。 +5. **导/摄芯片不再逐户返回人员**:`GET .../chips/guide`、`GET .../chips/photo` 的 `items` 恒为 `[]`,只看 `staffList`(团期名册)。`aggregateStatus` / `totalCount` / `doneCount` 口径不变。 +6. **修复:保存导游位后主报账人与备注被重置**。保留下来的人(即使请求只带 `staffId`、`staffRole`、`sortOrder`)沿用原来的报账人等级、备注、服务日期与日薪。随之 **`remark` 口径变化**:不传(`null`)= 沿用原备注;要清空请传空串 `""`。 + +--- + +## 一、背景 + +选人弹窗原来只有姓名、手机号、类型,看不到等级、服务区域、擅长和日薪,也看不出这个人同一时段是否已派到别的团或订单;导游/摄影页签下的逐户表把团期名册在每个子订单上重复列一遍;每保存一次导游位,主报账人被重置、备注被清空;服务人员管理的「基础日薪」输入框填了不保存。 + +jw 2026-09-28 定案口径: + +| # | 口径 | +|---|---| +| D1 | 基础日薪在选人时从服务人员管理的「基础日薪」带出,团期里不能改。更换人员取新人的档案值。档案值为 0 或空时返回 null。快照为空时,下次保存补取一次档案当前值;已有值不刷新 | +| D2 | 服务日期不填就跟随团期:读的时候换算成团期出发日和结束日,团期改期后自动跟着变。提交的日期和团期起止相同也按跟随团期处理 | +| D3 | 招募中(还没成团)的团期里派了的人,也算占用 | +| D4 | 日期段两端都算:前一个团结束当天、后一个团出发,这种首尾相接也算占用 | +| D5 | 占用只提示,不拦截。有占用照样能保存,也不要求二次确认 | +| D6 | 保存时,保留下来的人员沿用原来那一行的报账人等级、备注、服务日期和日薪;请求里传了的以请求为准 | +| D7 | 占用有两个来源:①其他团期的人员配置;②普通订单(不属于任何团期)上定制师直接派的人。团期同步到子订单的人员副本不重复计算。已取消的订单不算,其余订单状态都算 | +| D8 | 占用分三档:被占 0 天为空闲,被占满整段为全程占用,其余为部分占用 | +| D9 | 本单不做「只看空闲」筛选 | +| D10 | 导游/摄影不再按子订单逐户展示配置人员,看团期整体名册即可 | + +--- + +## 二、变更接口清单 + +| # | 接口 | 方法 | 路径 | 变更类型 | 说明 | +|---|------|------|------|----------|------| +| 1 | 保存团期人员配置 | PUT | `/v3/admin/group-batch/{productBatchId}/staff` | 修改:入参加字段、新错误码、出参加字段 | 每人可选 `serviceStartDate`、`serviceEndDate`;日薪由服务端带出;新错误码 582119;保留人员沿用原等级、备注、日期、日薪 | +| 2 | 查询团期人员名册 | GET | `/v3/admin/group-batch/{productBatchId}/staff` | 修改:出参加字段 | 每人加服务日期、是否自定义、基础日薪与四个占用字段 | +| 3 | 团期单人更换 | PUT | `/v3/admin/group-batch/{productBatchId}/staff/{staffId}/replace` | 修改:行为、新错误码、出参加字段 | 新人沿用原人服务日期,日薪取新人档案;可能返回 582119 | +| 4 | 团期单人删除 | DELETE | `/v3/admin/group-batch/{productBatchId}/staff/{staffId}` | 修改:出参加字段 | 响应 `staffList[]` 加字段 | +| 5 | 团期人员候选分页 | GET | `/v3/admin/group-batch/{productBatchId}/staff/candidates/page` | 修改:出参加字段、排序变化 | 加等级、区域、擅长、日薪与占用;全局排序后分页 | +| 6 | 团期人员候选列表 | GET | `/v3/admin/group-batch/{productBatchId}/staff/candidates` | 修改:出参加字段、排序变化 | 与 5 相同(全量版) | +| 7 | 配导游芯片明细 | GET | `/v3/admin/order/group-batch/{groupBatchId}/chips/guide` | 修改:出参取值变化 | `items` 恒为 `[]`,只看 `staffList` | +| 8 | 配摄影芯片明细 | GET | `/v3/admin/order/group-batch/{groupBatchId}/chips/photo` | 修改:出参取值变化 | `items` 恒为 `[]`,只看 `staffList` | +| 9 | 新建服务人员 | POST | `/admin/staff` | 修改:入参加字段、出参加字段 | 可传 `basePrice`;响应 `basePrice` 为字符串 | +| 10 | 编辑服务人员 | PUT | `/admin/staff/{staffId}` | 修改:入参加字段、出参加字段 | `basePrice` 可改,不传不改;负数或超过 2 位小数返回 400 | +| 11 | 服务人员详情 | GET | `/admin/staff/{staffId}` | 修改:出参加字段 | 返回字符串 `basePrice` | +| 12 | 服务人员列表 | GET | `/admin/staff/list` | 修改:出参加字段 | 每条返回字符串 `basePrice` | + +--- + +## 三、接口详情 + +### 1. 保存团期人员配置 `PUT /v3/admin/group-batch/{productBatchId}/staff` + +**VO**: `BatchStaffConfigReqVO` → `BatchStaffConfigRespVO` + +#### 使用场景 + +「配导游」「配摄影」保存弹窗点「保存」时调用。本单起弹窗里可以给每个人填服务开始 / 结束日期(不填 = 跟随团期);基础日薪只读展示,不提交。 + +#### 入参 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| productBatchId | Path | Long | 是 | 产品侧排期 ID | 取 `detail.productBatchId`,不是团期 ID(既有) | +| scopeRoles | Body | String[] | 否 | 传了就不能是 `[]`;导游位必须同时含 `GUIDE` 与 `LEADER` | 本次覆盖的角色范围;不传 = 整期覆盖(既有) | +| staffList | Body | Object[] | 是 | 显式传 `[]` = 清空覆盖范围 | 覆盖范围内的最终名单(既有) | +| staffList[].staffId | Body | Long | 是 | - | 人员 ID(既有) | +| staffList[].staffRole | Body | String | 是 | 取值域不变 | 角色,如 `LEADER`、`GUIDE`、`PHOTOGRAPHER`(既有) | +| staffList[].sortOrder | Body | Integer | 否 | 缺省 0 | 展示排序(既有;不沿用原值) | +| staffList[].remark | Body | String | 否 | ≤500 字 | **口径变化**:不传(`null`)时,保留下来的人沿用原备注;传空串 `""` 才是清空;新选的人不传即无备注 | +| staffList[].serviceStartDate | Body | String(yyyy-MM-dd) | 否 | 有效开始日不晚于有效结束日,且在团期出发日到结束日之间,否则 582119 | **新增**。服务开始日。不传:保留下来的人沿用原值,新选的人跟随团期。与团期出发日相同按跟随团期处理。要把自定义日期改回跟随团期,传团期出发日本身 | +| staffList[].serviceEndDate | Body | String(yyyy-MM-dd) | 否 | 同上 | **新增**。服务结束日,口径同上,对照团期结束日 | +| staffList[].baseDailyWage | Body | - | 否 | 不接收 | 请求里带了会被忽略(不报错、不落库);日薪一律由服务端从服务人员档案带出 | + +#### 出参 `Result` + +| 字段 | 类型 | 说明 | +|------|------|------| +| productBatchId | String | 回显路径参数(既有) | +| groupBatchId | String | 运营团期 ID(既有) | +| affectedOrderCount | Integer | 同步到的活跃子订单数(既有) | +| staffList | Object[] | 保存后的整期名单(既有)。既有字段 `id`、`staffId`、`staffRole`、`staffRoleName`、`staffName`、`staffPhone`、`avatarUrl`、`sortOrder`、`remark`、`reporterRank`、`reporterRankName` 不变;`staffStatus`、`liveInfoDegraded` 在本接口为 null(既有) | +| staffList[].serviceStartDate | String(yyyy-MM-dd) | **新增**。有效服务开始日:没自定义 = 团期出发日(团期改期后跟着变);自定义值原样显示;只有团期出发日为空时为 null | +| staffList[].serviceEndDate | String(yyyy-MM-dd) | **新增**。有效服务结束日,口径同上,对照团期结束日 | +| staffList[].serviceDateCustom | Boolean | **新增**。开始或结束任一自定义过为 `true`;都跟随团期为 `false`;恒非 null | +| staffList[].baseDailyWage | String | **新增**。基础日薪快照,字符串如 `"1000.00"`;档案未填或为 0 时为 null;前端运算前 `Number()` | +| staffList[].occupancyStatus | String | **新增**。`FREE` / `PARTIAL` / `FULL`,按这个人在本团的服务日期段比对;本团期未建或团期起止不全时为 null | +| staffList[].occupiedDays | Integer | **新增**。服务日期段内被占的天数(两端都算);`occupancyStatus` 为 null 时为 0 | +| staffList[].freeRanges | String[] | **新增**。可派日期段,形如 `2026-10-28~2026-10-28`,按日期升序;全程占用或 `occupancyStatus` 为 null 时为 `[]` | +| staffList[].occupancies | Object[] | **新增**。占用明细,按 `startDate` 升序;空闲或 `occupancyStatus` 为 null 时为 `[]` | +| staffList[].occupancies[].sourceType | String | 来源:`GROUP_BATCH` 其他团期的人员配置 / `ORDER` 普通订单上定制师直派 | +| staffList[].occupancies[].sourceId | String | 来源 ID:`GROUP_BATCH` 为运营团期 ID,`ORDER` 为订单 ID | +| staffList[].occupancies[].refNo | String | 团期号 / 订单号;源数据为空时为 null | +| staffList[].occupancies[].title | String | 团期名称(为空时取产品名)/ 订单的产品名;都为空时为 null | +| staffList[].occupancies[].statusName | String | 来源当前状态中文名,如「招募中」「待出行」 | +| staffList[].occupancies[].staffRole / staffRoleName | String | 他在那边的角色及中文名(跨角色也计入占用) | +| staffList[].occupancies[].startDate / endDate | String(yyyy-MM-dd) | 他在那边的服务起止:团期为他在那个团的有效服务日期(自定义日期先夹进那个团当前起止);订单为出发日到返回日(返回日为空按出发日当天) | +| staffList[].occupancies[].overlapStartDate / overlapEndDate | String(yyyy-MM-dd) | 与本团服务日期段重叠的起止,两端都算,恒在本团服务日期段内 | + +#### 请求示例 + +```http +PUT /v3/admin/group-batch/2104455374549721090/staff +Authorization: Bearer +Content-Type: application/json + +{ + "scopeRoles": ["GUIDE", "LEADER"], + "staffList": [ + { + "staffId": 1005, + "staffRole": "LEADER", + "sortOrder": 1, + "remark": "负责额尔古纳段", + "serviceStartDate": "2026-10-27", + "serviceEndDate": "2026-10-28" + } + ] +} +``` + +#### 响应示例 + +2026-09-28 测试服实测(班期 10-26 ~ 10-28,刘大山 10-27 已被一张普通订单派为领队;金额按当前契约写成字符串): + +```json +{ + "code": 200, + "message": "成功", + "data": { + "productBatchId": "2104455374549721090", + "groupBatchId": "2104455377657724930", + "staffList": [ + { + "id": "2104456232708509698", + "staffId": 1005, + "staffRole": "LEADER", + "staffRoleName": "领队", + "staffName": "刘大山", + "staffPhone": "138****1005", + "avatarUrl": "https://hlgl-test.oss-cn-beijing.aliyuncs.com/test/material/2026/03/26/45d9abebc569ca7b7ab13cac212ca38a.png", + "sortOrder": 1, + "remark": "负责额尔古纳段", + "reporterRank": "NONE", + "reporterRankName": "非报账人", + "staffStatus": null, + "liveInfoDegraded": null, + "serviceStartDate": "2026-10-27", + "serviceEndDate": "2026-10-28", + "serviceDateCustom": true, + "baseDailyWage": "1000.00", + "occupancyStatus": "PARTIAL", + "occupiedDays": 1, + "freeRanges": ["2026-10-28~2026-10-28"], + "occupancies": [ + { + "sourceType": "ORDER", + "sourceId": "2100743225424621570", + "refNo": "HL20260918082629372", + "title": "测试核心产品-单档-固定比例", + "statusName": "待出行", + "staffRole": "LEADER", + "staffRoleName": "领队", + "startDate": "2026-10-25", + "endDate": "2026-10-27", + "overlapStartDate": "2026-10-27", + "overlapEndDate": "2026-10-27" + } + ] + } + ], + "affectedOrderCount": 1 + }, + "traceId": null, + "success": true +} +``` + +#### 空数据 / 降级响应 + +- `staffList: []`(清空覆盖范围):照常 200,响应 `staffList` 是保存后的整期名单,范围内的人已移除(既有)。 +- 团期起止任一端为空时:名册项 `serviceStartDate` / `serviceEndDate` 可能为 null,占用四字段为 `null` / `0` / `[]` / `[]`。 +- 占用不影响保存结果:有占用、全程占用都照样 200。 + +#### 错误响应 + +```json +{ + "code": 582119, + "message": "萨仁高娃 的服务日期(2026-11-16 至 2026-11-19)不合法:开始日期不能晚于结束日期,且须在团期日期(2026-11-17 至 2026-11-19)之内", + "data": null, + "traceId": null, + "success": false +} +``` + +| code | 触发条件 | +|---|---| +| `582119` | **新增**。某人的有效服务开始日晚于结束日,或越出团期出发日到结束日;文案里是人名和他的有效起止、团期起止(团期日期不全时写「未定」);整批不写入 | +| `582115` / `582116` | 角色超出 `scopeRoles` / 只声明了半个配置位(既有;同时触犯时先报这两个) | +| `589553` / `589598` | 团期尚未创建 / 团期已确认或已取消(既有) | +| `400` | 缺 `staffList`、`remark` 超 500 字、日期格式不对等参数校验(既有机制) | + +#### 业务边界 + +- 判权未改:需要团期管理权限 `group-batch:manage`。 +- 「保留下来的人」= 本次提交里、且在本次覆盖范围内原本就有记录的人(按 `staffId` 认)。他们的报账人等级一律沿用(本接口改不了等级,改等级走 `PUT .../staff/{staffId}/reporter-rank`);备注、服务日期请求没传时沿用;日薪原快照非空时沿用。 +- 日薪(D1):新选的人取服务人员档案当前的基础日薪(0 或空 → null);保留下来的人原快照非空不刷新,原快照为空时补取一次档案当前值;请求里的 `baseDailyWage` 一律忽略。 +- 服务日期(D2):与团期起止相同按跟随团期处理,名册 `serviceDateCustom=false`;自定义才算 `true`。开始、结束两端各自判断。 +- 582119 校验的是本次所有要写入的人,**包括沿用来的旧日期**:团期改期后旧的自定义日期可能已落在新团期外,这时保存同样返回 582119,文案点名,先把那个人的日期改到团期内再保存。 +- 返回 582119 时整批不写入:原名册一行不变。 +- 占用只提示不拦截(D5)。 + +### 2. 查询团期人员名册 `GET /v3/admin/group-batch/{productBatchId}/staff` + +**VO**: `List` + +#### 使用场景 + +「配导游」「配摄影」页签打开时拉已配置人员,前端按 `staffRole` 分到导游位(`GUIDE` / `LEADER`)与摄影位(`PHOTOGRAPHER`)两张表。本单起每行可展示服务日期、基础日薪与占用标签。 + +#### 入参 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| productBatchId | Path | Long | 是 | 产品侧排期 ID | 取 `detail.productBatchId`,不是团期 ID(既有) | + +#### 出参 `Result>` + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | String | 团期人员记录 ID(既有;整位保存后会变,单人操作用 `staffId`) | +| staffId | Long | 人员 ID(既有) | +| staffRole / staffRoleName | String | 角色及中文名(既有) | +| staffName / staffPhone / avatarUrl | String | 姓名、脱敏手机号、头像(既有) | +| sortOrder | Integer | 排序(既有) | +| remark | String | 备注(既有) | +| reporterRank / reporterRankName | String | 报账人等级及中文名(既有) | +| staffStatus / liveInfoDegraded | String / Boolean | 人员在资源库的状态、是否降级为快照(既有,仅本接口填充) | +| serviceStartDate | String(yyyy-MM-dd) | **新增**。有效服务开始日:没自定义 = 团期出发日(团期改期后跟着变);自定义值原样显示;只有团期出发日为空时为 null | +| serviceEndDate | String(yyyy-MM-dd) | **新增**。有效服务结束日,口径同上,对照团期结束日 | +| serviceDateCustom | Boolean | **新增**。开始或结束任一自定义过为 `true`;恒非 null | +| baseDailyWage | String | **新增**。基础日薪快照,字符串如 `"1000.00"`;档案未填或为 0 时为 null;前端运算前 `Number()` | +| occupancyStatus | String | **新增**。`FREE` / `PARTIAL` / `FULL`,按这个人在本团的服务日期段比对;本团期未建或团期起止不全时为 null | +| occupiedDays | Integer | **新增**。服务日期段内被占天数(两端都算);`occupancyStatus` 为 null 时为 0 | +| freeRanges | String[] | **新增**。可派日期段 `yyyy-MM-dd~yyyy-MM-dd`,升序;全程占用或 `occupancyStatus` 为 null 时为 `[]` | +| occupancies | Object[] | **新增**。占用明细,按 `startDate` 升序;空闲或 `occupancyStatus` 为 null 时为 `[]` | +| occupancies[].sourceType | String | `GROUP_BATCH` 其他团期的人员配置 / `ORDER` 普通订单上定制师直派 | +| occupancies[].sourceId | String | `GROUP_BATCH` 为运营团期 ID,`ORDER` 为订单 ID | +| occupancies[].refNo | String | 团期号 / 订单号;源数据为空时为 null | +| occupancies[].title | String | 团期名称(为空时取产品名)/ 订单的产品名;都为空时为 null | +| occupancies[].statusName | String | 来源当前状态中文名,如「招募中」「待出行」 | +| occupancies[].staffRole / staffRoleName | String | 他在那边的角色及中文名(跨角色也计入占用) | +| occupancies[].startDate / endDate | String(yyyy-MM-dd) | 他在那边的服务起止:团期为他在那个团的有效服务日期(自定义日期先夹进那个团当前起止);订单为出发日到返回日(返回日为空按出发日当天) | +| occupancies[].overlapStartDate / overlapEndDate | String(yyyy-MM-dd) | 与本团服务日期段重叠的起止,两端都算 | + +#### 请求示例 + +```http +GET /v3/admin/group-batch/2104455359768993795/staff +Authorization: Bearer +``` + +无请求体。 + +#### 响应示例 + +2026-09-28 测试服实测(班期 11-17 ~ 11-19;白云飞跟随团期,萨仁高娃自定义为 11-18 单日;金额按当前契约写成字符串): + +```json +{ + "code": 200, + "message": "成功", + "data": [ + { + "id": "2104455839127633921", + "staffId": 1007, + "staffRole": "LEADER", + "staffRoleName": "领队", + "staffName": "白云飞", + "staffPhone": "139****1007", + "avatarUrl": null, + "sortOrder": 1, + "remark": null, + "reporterRank": "NONE", + "reporterRankName": "非报账人", + "staffStatus": "ON_SHELF", + "liveInfoDegraded": false, + "serviceStartDate": "2026-11-17", + "serviceEndDate": "2026-11-19", + "serviceDateCustom": false, + "baseDailyWage": "800.00", + "occupancyStatus": "FREE", + "occupiedDays": 0, + "freeRanges": ["2026-11-17~2026-11-19"], + "occupancies": [] + }, + { + "id": "2104455839131828226", + "staffId": 1008, + "staffRole": "LEADER", + "staffRoleName": "领队", + "staffName": "萨仁高娃", + "staffPhone": "139****1008", + "avatarUrl": null, + "sortOrder": 2, + "remark": null, + "reporterRank": "NONE", + "reporterRankName": "非报账人", + "staffStatus": "ON_SHELF", + "liveInfoDegraded": false, + "serviceStartDate": "2026-11-18", + "serviceEndDate": "2026-11-18", + "serviceDateCustom": true, + "baseDailyWage": null, + "occupancyStatus": "FREE", + "occupiedDays": 0, + "freeRanges": ["2026-11-18~2026-11-18"], + "occupancies": [] + } + ], + "traceId": null, + "success": true +} +``` + +#### 空数据 / 降级响应 + +- 未配置人员:`data: []`(测试服未建团期的班期实测即为 `[]`)。 +- 本团期未建或团期起止不全:每行 `occupancyStatus=null`、`occupiedDays=0`、`freeRanges=[]`、`occupancies=[]`。 +- 资源服务不可用:姓名手机回落快照、`liveInfoDegraded=true`(既有);本单新增的服务日期、日薪、占用字段照常返回,不依赖资源服务。 + +#### 错误响应 + +判权未改动(`group-batch:view`)。 + +```json +{ + "code": 589507, + "message": "无操作权限(当前角色未授予团期权限,或该团期不在您名下)", + "data": null, + "success": false +} +``` + +#### 业务边界 + +- 名册项**不含**等级、服务区域、擅长(这几列只在两个候选接口返回);名册要展示时从候选接口按 `staffId` 取。 +- 占用比对用的日期段 = 这个人在本团的有效服务日期,先夹进本团当前起止;展示的 `serviceStartDate` / `serviceEndDate` 不夹。团期改期后旧的自定义日期越出团期时,名册照实显示越界日期,保存时会 582119。 +- 占用来源口径见第六节;占用只提示不拦截。 + +### 3. 团期单人更换 `PUT /v3/admin/group-batch/{productBatchId}/staff/{staffId}/replace` + +**VO**: `BatchStaffReplaceReqVO` → `BatchStaffReplaceRespVO` + +#### 使用场景 + +名册表格某一行点「更换」,从候选里选一个新人替换原人。 + +#### 入参 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| productBatchId | Path | Long | 是 | 产品侧排期 ID | 同名册接口(既有) | +| staffId | Path | Long | 是 | 须在本团名册中 | 被换下的人(既有) | +| newStaffId | Body | Long | 是 | 上架、类型符合原配置位、不在本团、不等于 `staffId` | 换上的人(既有) | + +#### 出参 `Result` + +| 字段 | 类型 | 说明 | +|------|------|------| +| productBatchId / groupBatchId | String | 回显 / 运营团期 ID(既有) | +| oldStaffId / newStaffId | Long | 被换下 / 换上的人员 ID(既有) | +| staffRole | String | 新人落库角色(既有) | +| reporterRank | String | 新人继承的报账人等级(既有) | +| primaryReporterTransferred | Boolean | 主报账人是否转给了新人(既有) | +| affectedOrderCount | Integer | 同步替换的活跃子订单数(既有) | +| staffList | Object[] | 更换后的整期名单(既有字段不变,`staffStatus`、`liveInfoDegraded` 为 null) | +| staffList[].serviceStartDate / serviceEndDate | String(yyyy-MM-dd) | **新增**。有效服务起止;新人沿用原人的服务日期 | +| staffList[].serviceDateCustom | Boolean | **新增**。是否自定义过服务日期 | +| staffList[].baseDailyWage | String | **新增**。基础日薪快照(新人取新人档案值),字符串;0 或空为 null | +| staffList[].occupancyStatus | String | **新增**。`FREE` / `PARTIAL` / `FULL`;本团期未建或团期起止不全时为 null | +| staffList[].occupiedDays | Integer | **新增**。被占天数 | +| staffList[].freeRanges | String[] | **新增**。可派日期段 `yyyy-MM-dd~yyyy-MM-dd` | +| staffList[].occupancies | Object[] | **新增**。占用明细,按 `startDate` 升序 | +| staffList[].occupancies[].sourceType / sourceId | String | `GROUP_BATCH` + 运营团期 ID,或 `ORDER` + 订单 ID | +| staffList[].occupancies[].refNo / title / statusName | String | 团期号或订单号 / 团期名称或产品名 / 来源状态中文名 | +| staffList[].occupancies[].staffRole / staffRoleName | String | 他在那边的角色及中文名 | +| staffList[].occupancies[].startDate / endDate | String(yyyy-MM-dd) | 他在那边的服务起止 | +| staffList[].occupancies[].overlapStartDate / overlapEndDate | String(yyyy-MM-dd) | 与本团服务日期段重叠的起止 | + +#### 请求示例 + +```http +PUT /v3/admin/group-batch/2104455374549721090/staff/1005/replace +Authorization: Bearer +Content-Type: application/json + +{ "newStaffId": 1007 } +``` + +#### 响应示例 + +2026-09-28 测试服实测:原人刘大山(主报账人,服务日期 10-27 ~ 10-28,日薪 1000)换成白云飞(档案日薪 800)。等级、备注、服务日期随位置转给新人,日薪取新人档案: + +```json +{ + "code": 200, + "message": "成功", + "data": { + "productBatchId": "2104455374549721090", + "groupBatchId": "2104455377657724930", + "oldStaffId": 1005, + "newStaffId": 1007, + "staffRole": "LEADER", + "reporterRank": "PRIMARY", + "primaryReporterTransferred": true, + "affectedOrderCount": 1, + "staffList": [ + { + "id": "2104456292880023553", + "staffId": 1007, + "staffRole": "LEADER", + "staffRoleName": "领队", + "staffName": "白云飞", + "staffPhone": "139****1007", + "avatarUrl": null, + "sortOrder": 1, + "remark": "负责额尔古纳段", + "reporterRank": "PRIMARY", + "reporterRankName": "主报账人", + "staffStatus": null, + "liveInfoDegraded": null, + "serviceStartDate": "2026-10-27", + "serviceEndDate": "2026-10-28", + "serviceDateCustom": true, + "baseDailyWage": "800.00", + "occupancyStatus": "FREE", + "occupiedDays": 0, + "freeRanges": ["2026-10-27~2026-10-28"], + "occupancies": [] + } + ] + }, + "traceId": null, + "success": true +} +``` + +#### 空数据 / 降级响应 + +写接口,无空数据形态;任何校验失败都不写入。新人档案日薪为 0 或空时,新行 `baseDailyWage` 为 null。 + +#### 错误响应 + +以下 582119 按源码文案模板拼出(示意,测试服未造「团期改期后旧日期越界再更换」的数据): + +```json +{ + "code": 582119, + "message": "白云飞 的服务日期(2026-10-25 至 2026-10-28)不合法:开始日期不能晚于结束日期,且须在团期日期(2026-10-26 至 2026-10-28)之内", + "data": null, + "success": false +} +``` + +| code | 触发条件 | +|---|---| +| `582119` | **新增**。原人的自定义服务日期已不在团期起止内(团期改期后没重新保存);文案里是**新人**的名字;不写入。先在保存弹窗把这个位置的日期改到团期内,再换人 | +| `100001` / `589508` / `589582` | 新旧相同 / 原人不在本团 / 新人已在本团(既有) | +| `582103` / `582118` / `582114` | 资源库取不到新人 / 新人已下架 / 新人类型不符合配置位(既有) | +| `589553` / `589598` | 未建团 / 团期已确认或已取消(既有) | + +#### 业务边界 + +- 服务日期跟位置走:新人原样沿用原人的服务日期(包括「跟随团期」)。 +- 日薪跟人走:取新人服务人员档案的基础日薪(D1),不沿用原人的日薪。 +- 排序、备注、报账人等级转给新人(既有)。 + +### 4. 团期单人删除 `DELETE /v3/admin/group-batch/{productBatchId}/staff/{staffId}` + +**VO**: `BatchStaffRemoveRespVO` + +#### 使用场景 + +名册表格某一行点「删除」,只移除这一个人。本单只给响应的 `staffList[]` 加字段,删除行为不变。 + +#### 入参 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| productBatchId | Path | Long | 是 | 产品侧排期 ID | 同名册接口(既有) | +| staffId | Path | Long | 是 | 须在本团名册中 | 要删除的人(既有) | + +#### 出参 `Result` + +| 字段 | 类型 | 说明 | +|------|------|------| +| productBatchId / groupBatchId | String | 回显 / 运营团期 ID(既有) | +| removedStaffId | Long | 被删除的人员 ID(既有) | +| removedStaffRole | String | 被删除人员的角色(既有) | +| primaryReporterRemoved | Boolean | 删的是否是主报账人(既有) | +| affectedOrderCount | Integer | 同步删除副本的活跃子订单数(既有) | +| staffList | Object[] | 删除后的整期名单(既有字段不变,`staffStatus`、`liveInfoDegraded` 为 null) | +| staffList[].serviceStartDate / serviceEndDate | String(yyyy-MM-dd) | **新增**。有效服务起止(没自定义 = 团期起止) | +| staffList[].serviceDateCustom | Boolean | **新增**。是否自定义过服务日期 | +| staffList[].baseDailyWage | String | **新增**。基础日薪快照,字符串;0 或空为 null | +| staffList[].occupancyStatus | String | **新增**。`FREE` / `PARTIAL` / `FULL`;本团期未建或团期起止不全时为 null | +| staffList[].occupiedDays | Integer | **新增**。被占天数 | +| staffList[].freeRanges | String[] | **新增**。可派日期段 `yyyy-MM-dd~yyyy-MM-dd` | +| staffList[].occupancies | Object[] | **新增**。占用明细,按 `startDate` 升序 | +| staffList[].occupancies[].sourceType / sourceId | String | `GROUP_BATCH` + 运营团期 ID,或 `ORDER` + 订单 ID | +| staffList[].occupancies[].refNo / title / statusName | String | 团期号或订单号 / 团期名称或产品名 / 来源状态中文名 | +| staffList[].occupancies[].staffRole / staffRoleName | String | 他在那边的角色及中文名 | +| staffList[].occupancies[].startDate / endDate | String(yyyy-MM-dd) | 他在那边的服务起止 | +| staffList[].occupancies[].overlapStartDate / overlapEndDate | String(yyyy-MM-dd) | 与本团服务日期段重叠的起止 | + +#### 请求示例 + +```http +DELETE /v3/admin/group-batch/2104455356057034754/staff/1009 +Authorization: Bearer +``` + +无请求体。 + +#### 响应示例 + +2026-09-28 测试服实测节选(班期 11-16 ~ 11-18;`staffList` 只保留两行;金额按当前契约写成字符串): + +```json +{ + "code": 200, + "message": "成功", + "data": { + "productBatchId": "2104455356057034754", + "groupBatchId": "2104455358355509249", + "removedStaffId": 1009, + "removedStaffRole": "LEADER", + "primaryReporterRemoved": false, + "affectedOrderCount": 1, + "staffList": [ + { + "id": "2104456120838062081", + "staffId": 1005, + "staffRole": "LEADER", + "staffRoleName": "领队", + "staffName": "刘大山", + "staffPhone": "138****1005", + "sortOrder": 1, + "remark": "负责草原段带队", + "reporterRank": "PRIMARY", + "reporterRankName": "主报账人", + "staffStatus": null, + "liveInfoDegraded": null, + "serviceStartDate": "2026-11-16", + "serviceEndDate": "2026-11-16", + "serviceDateCustom": true, + "baseDailyWage": "1000.00", + "occupancyStatus": "FREE", + "occupiedDays": 0, + "freeRanges": ["2026-11-16~2026-11-16"], + "occupancies": [] + }, + { + "id": "2104456120838062082", + "staffId": 1002, + "staffRole": "GUIDE", + "staffRoleName": "导游", + "staffName": "李雪梅", + "staffPhone": "138****1002", + "sortOrder": 2, + "remark": null, + "reporterRank": "NONE", + "reporterRankName": "非报账人", + "staffStatus": null, + "liveInfoDegraded": null, + "serviceStartDate": "2026-11-16", + "serviceEndDate": "2026-11-18", + "serviceDateCustom": false, + "baseDailyWage": "600.00", + "occupancyStatus": "PARTIAL", + "occupiedDays": 1, + "freeRanges": ["2026-11-16~2026-11-17"], + "occupancies": [ + { + "sourceType": "GROUP_BATCH", + "sourceId": "2104455369311031298", + "refNo": "Q202611182104455365389361153", + "title": "11月18日海拉尔-额尔古纳3日团", + "statusName": "招募中", + "staffRole": "GUIDE", + "staffRoleName": "导游", + "startDate": "2026-11-18", + "endDate": "2026-11-20", + "overlapStartDate": "2026-11-18", + "overlapEndDate": "2026-11-18" + } + ] + } + ] + }, + "traceId": null, + "success": true +} +``` + +#### 空数据 / 降级响应 + +删光后 `staffList` 为 `[]`(既有)。本团期未建或团期起止不全时,名单项占用四字段为 `null` / `0` / `[]` / `[]`。 + +#### 错误响应 + +错误码未改动。 + +```json +{ + "code": 589508, + "message": "报账人不是该团期已派员工", + "data": null, + "success": false +} +``` + +#### 业务边界 + +- 删除行为、错误码与判权(`group-batch:manage`)都未改,只是响应 `staffList[]` 多了字段。 +- 删掉的人在其他团的占用计算里立即消失(已删除的配置行不算占用)。 + +### 5. 团期人员候选分页 `GET /v3/admin/group-batch/{productBatchId}/staff/candidates/page` + +**VO**: `PageResult` + +#### 使用场景 + +「配导游」「配摄影」选人弹窗的人员列表(带关键词搜索、分页)。本单起每行可展示等级、服务区域、擅长、基础日薪和占用标签,点占用标签看 `occupancies` 明细与 `freeRanges`。 + +#### 入参 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| productBatchId | Path | Long | 是 | 产品侧排期 ID | 取 `detail.productBatchId`(既有) | +| role | Query | String | 是 | `GUIDE` / `PHOTOGRAPHER` | 配置位;导游位并收导游与领队(既有) | +| keyword | Query | String | 否 | ≤50 字 | 姓名包含;纯数字另按手机号尾号匹配(既有) | +| page | Query | Integer | 否 | 缺省 1 | 页码(既有) | +| pageSize | Query | Integer | 否 | 缺省 20,最大 100 | 每页条数(既有) | + +#### 出参 `Result>` + +| 字段 | 类型 | 说明 | +|------|------|------| +| total / page / pageSize | Integer | 分页信息(既有) | +| records | Object[] | 候选项(既有字段 `productBatchId`、`groupBatchId`、`staffId`、`staffName`、`staffPhone`、`staffType`、`staffTypeName`、`avatarUrl`、`assigned`、`assignedRole`、`assignedRoleName`、`reporterRank`、`reporterRankName` 不变) | +| records[].level | String | **新增**。导游等级:`初级` / `中级` / `高级` / `特级`,档案里认不出的写法原样返回;未填为 null | +| records[].serviceAreas | String[] | **新增**。服务区域;未填为 `[]`,恒非 null | +| records[].specialties | String[] | **新增**。擅长;未填为 `[]`,恒非 null | +| records[].baseDailyWage | String | **新增**。服务人员档案的基础日薪,字符串如 `"600.00"`,仅供参考;未填或为 0 时为 null;前端运算前 `Number()` | +| records[].occupancyStatus | String | **新增**。`FREE` / `PARTIAL` / `FULL`,按**本团出发日到结束日**比对;本团期未建(`groupBatchId` 为 null)或团期起止不全时为 null | +| records[].occupiedDays | Integer | **新增**。本团日期段内被占天数(两端都算);`occupancyStatus` 为 null 时为 0 | +| records[].freeRanges | String[] | **新增**。可派日期段 `yyyy-MM-dd~yyyy-MM-dd`,升序;全程占用或 `occupancyStatus` 为 null 时为 `[]` | +| records[].occupancies | Object[] | **新增**。占用明细,按 `startDate` 升序;空闲或 `occupancyStatus` 为 null 时为 `[]` | +| records[].occupancies[].sourceType | String | `GROUP_BATCH` 其他团期的人员配置 / `ORDER` 普通订单上定制师直派 | +| records[].occupancies[].sourceId | String | `GROUP_BATCH` 为运营团期 ID,`ORDER` 为订单 ID | +| records[].occupancies[].refNo | String | 团期号 / 订单号;源数据为空时为 null | +| records[].occupancies[].title | String | 团期名称(为空时取产品名)/ 订单的产品名;都为空时为 null | +| records[].occupancies[].statusName | String | 来源当前状态中文名,如「招募中」「待支付」 | +| records[].occupancies[].staffRole / staffRoleName | String | 他在那边的角色及中文名(跨角色也计入占用) | +| records[].occupancies[].startDate / endDate | String(yyyy-MM-dd) | 他在那边的服务起止:团期为他在那个团的有效服务日期(自定义日期先夹进那个团当前起止);订单为出发日到返回日(返回日为空按出发日当天) | +| records[].occupancies[].overlapStartDate / overlapEndDate | String(yyyy-MM-dd) | 与本团日期段重叠的起止,两端都算,恒在本团日期段内 | + +#### 请求示例 + +```http +GET /v3/admin/group-batch/2104455359768993795/staff/candidates/page?role=GUIDE&page=1&pageSize=20 +Authorization: Bearer +``` + +无请求体。 + +#### 响应示例 + +2026-09-28 测试服实测节选(班期 11-17 ~ 11-19,共 7 人,只保留三行:本团已选 → 空闲 → 部分占用;金额按当前契约写成字符串): + +```json +{ + "code": 200, + "message": "成功", + "data": { + "records": [ + { + "productBatchId": "2104455359768993795", + "groupBatchId": "2104455363380285441", + "staffId": 1007, + "staffName": "白云飞", + "staffPhone": "139****1007", + "staffType": "LEADER", + "staffTypeName": "领队", + "avatarUrl": null, + "assigned": true, + "assignedRole": "LEADER", + "assignedRoleName": "领队", + "reporterRank": "NONE", + "reporterRankName": "非报账人", + "level": null, + "serviceAreas": ["内蒙古", "新疆"], + "specialties": ["户外探险", "亲子活动", "草原穿越"], + "baseDailyWage": "800.00", + "occupancyStatus": "FREE", + "occupiedDays": 0, + "freeRanges": ["2026-11-17~2026-11-19"], + "occupancies": [] + }, + { + "productBatchId": "2104455359768993795", + "groupBatchId": "2104455363380285441", + "staffId": 1005, + "staffName": "刘大山", + "staffPhone": "138****1005", + "staffType": "LEADER", + "staffTypeName": "领队", + "avatarUrl": "https://hlgl-test.oss-cn-beijing.aliyuncs.com/test/material/2026/03/26/45d9abebc569ca7b7ab13cac212ca38a.png", + "assigned": false, + "assignedRole": null, + "assignedRoleName": null, + "reporterRank": null, + "reporterRankName": null, + "level": null, + "serviceAreas": ["内蒙古", "新疆", "青海", "甘肃", "西藏"], + "specialties": ["户外探险", "高原旅行", "沙漠旅行", "自驾越野"], + "baseDailyWage": "1000.00", + "occupancyStatus": "FREE", + "occupiedDays": 0, + "freeRanges": ["2026-11-17~2026-11-19"], + "occupancies": [] + }, + { + "productBatchId": "2104455359768993795", + "groupBatchId": "2104455363380285441", + "staffId": 1002, + "staffName": "李雪梅", + "staffPhone": "138****1002", + "staffType": "GUIDE", + "staffTypeName": "导游", + "avatarUrl": null, + "assigned": false, + "assignedRole": null, + "assignedRoleName": null, + "reporterRank": null, + "reporterRankName": null, + "level": "中级", + "serviceAreas": ["云南", "四川", "贵州"], + "specialties": ["民俗风情", "美食体验", "自然风光"], + "baseDailyWage": "600.00", + "occupancyStatus": "PARTIAL", + "occupiedDays": 2, + "freeRanges": ["2026-11-19~2026-11-19"], + "occupancies": [ + { + "sourceType": "GROUP_BATCH", + "sourceId": "2104455358355509249", + "refNo": "Q202611162104455356057034753", + "title": "11月16日海拉尔-额尔古纳3日团", + "statusName": "招募中", + "staffRole": "GUIDE", + "staffRoleName": "导游", + "startDate": "2026-11-16", + "endDate": "2026-11-18", + "overlapStartDate": "2026-11-17", + "overlapEndDate": "2026-11-18" + } + ] + } + ], + "total": 7, + "page": 1, + "pageSize": 20 + }, + "traceId": null, + "success": true +} +``` + +#### 空数据 / 降级响应 + +本团期还没建(班期有、团期无)时,`groupBatchId` 为 null,占用四字段为空值(测试服班期 11-26 实测节选): + +```json +{ + "productBatchId": "2104455372507095042", + "groupBatchId": null, + "staffId": 1002, + "staffName": "李雪梅", + "level": "中级", + "baseDailyWage": "600.00", + "occupancyStatus": null, + "occupiedDays": 0, + "freeRanges": [], + "occupancies": [] +} +``` + +- 页码越界:`records` 为 `[]`,`total` 为真实总数(既有)。 +- 资源服务不可用:返回 582103(见下)。 + +#### 错误响应 + +```json +{ + "code": 582103, + "message": "员工信息查询失败,请稍后重试", + "data": null, + "success": false +} +``` + +| code | 触发条件 | +|---|---| +| `582103` | 资源服务查询候选失败(既有码,本单起候选分页经资源服务新的排序分页查询取数) | +| `582113` | `role` 不是 `GUIDE` / `PHOTOGRAPHER`(既有) | +| `589507` | 无团期查看权限(既有) | +| `400` | `keyword` 超 50 字(既有) | + +#### 业务边界 + +- **全局排序**:本团本位已选(即 `assigned=true`)→ 空闲 → 部分占用 → 全程占用;同组内按等级从高到低(特级 → 高级 → 中级 → 初级 → 未填)→ 人员 `sortOrder` 升序 → `staffId` 升序。先按同一筛选条件(类型、关键词)取全体候选排好再切页,逐页翻完不重不漏,与第 6 个接口的顺序一致。 +- 本团期未建时没有占用分组,只有「本团本位已选」排最前,其余人按等级 → `sortOrder` → `staffId` 排(改前只按 `sortOrder`、`staffId`)。 +- 本团其他配置位上的人(例如在导游位看摄影师)不算占用(本团自己不算),`assigned=false`,照常用 `assignedRole` 提示「已在其他位」。 +- 占用只提示不拦截;本单不提供「只看空闲」筛选参数(D9)。 +- 保存仍以 `GET .../staff` 为已选名单来源,不能只凭当前页勾选拼保存请求(既有)。 + +### 6. 团期人员候选列表 `GET /v3/admin/group-batch/{productBatchId}/staff/candidates` + +**VO**: `List` + +#### 使用场景 + +旧的全量候选接口(不分页)。条目结构、占用口径、排序规则与候选分页完全一致。 + +#### 入参 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| productBatchId | Path | Long | 是 | 产品侧排期 ID | 取 `detail.productBatchId`(既有) | +| role | Query | String | 是 | `GUIDE` / `PHOTOGRAPHER` | 配置位;导游位并收导游与领队(既有) | + +#### 出参 `Result>` + +| 字段 | 类型 | 说明 | +|------|------|------| +| 既有字段 | - | `productBatchId`、`groupBatchId`、`staffId`、`staffName`、`staffPhone`、`staffType`、`staffTypeName`、`avatarUrl`、`assigned`、`assignedRole`、`assignedRoleName`、`reporterRank`、`reporterRankName` 不变 | +| level | String | **新增**。导游等级:`初级` / `中级` / `高级` / `特级`,认不出的写法原样返回;未填为 null | +| serviceAreas | String[] | **新增**。服务区域;未填为 `[]` | +| specialties | String[] | **新增**。擅长;未填为 `[]` | +| baseDailyWage | String | **新增**。档案基础日薪,字符串,仅供参考;未填或为 0 时为 null | +| occupancyStatus | String | **新增**。`FREE` / `PARTIAL` / `FULL`,按本团出发日到结束日比对;本团期未建或团期起止不全时为 null | +| occupiedDays | Integer | **新增**。被占天数;`occupancyStatus` 为 null 时为 0 | +| freeRanges | String[] | **新增**。可派日期段 `yyyy-MM-dd~yyyy-MM-dd`;全程占用或 null 状态时为 `[]` | +| occupancies | Object[] | **新增**。占用明细,按 `startDate` 升序 | +| occupancies[].sourceType / sourceId | String | `GROUP_BATCH` + 运营团期 ID,或 `ORDER` + 订单 ID | +| occupancies[].refNo / title / statusName | String | 团期号或订单号 / 团期名称或产品名 / 来源状态中文名 | +| occupancies[].staffRole / staffRoleName | String | 他在那边的角色及中文名 | +| occupancies[].startDate / endDate | String(yyyy-MM-dd) | 他在那边的服务起止 | +| occupancies[].overlapStartDate / overlapEndDate | String(yyyy-MM-dd) | 与本团日期段重叠的起止 | + +#### 请求示例 + +```http +GET /v3/admin/group-batch/2104455359768993795/staff/candidates?role=GUIDE +Authorization: Bearer +``` + +无请求体。 + +#### 响应示例 + +2026-09-28 测试服实测节选(班期 11-17 ~ 11-19;陈志远只有 11-18 被占,可派日期被切成两段;李雪梅被两个招募中的团占满整段): + +```json +{ + "code": 200, + "message": "成功", + "data": [ + { + "productBatchId": "2104455359768993795", + "groupBatchId": "2104455363380285441", + "staffId": 1009, + "staffName": "陈志远", + "staffPhone": "139****1009", + "staffType": "LEADER", + "staffTypeName": "领队", + "avatarUrl": null, + "assigned": false, + "assignedRole": null, + "assignedRoleName": null, + "reporterRank": null, + "reporterRankName": null, + "level": null, + "serviceAreas": ["内蒙古", "黑龙江", "吉林"], + "specialties": ["户外探险", "高原旅行", "冬季活动"], + "baseDailyWage": null, + "occupancyStatus": "PARTIAL", + "occupiedDays": 1, + "freeRanges": ["2026-11-17~2026-11-17", "2026-11-19~2026-11-19"], + "occupancies": [ + { + "sourceType": "GROUP_BATCH", + "sourceId": "2104455358355509249", + "refNo": "Q202611162104455356057034753", + "title": "11月16日海拉尔-额尔古纳3日团", + "statusName": "招募中", + "staffRole": "LEADER", + "staffRoleName": "领队", + "startDate": "2026-11-18", + "endDate": "2026-11-18", + "overlapStartDate": "2026-11-18", + "overlapEndDate": "2026-11-18" + } + ] + }, + { + "productBatchId": "2104455359768993795", + "groupBatchId": "2104455363380285441", + "staffId": 1002, + "staffName": "李雪梅", + "staffPhone": "138****1002", + "staffType": "GUIDE", + "staffTypeName": "导游", + "avatarUrl": null, + "assigned": false, + "assignedRole": null, + "assignedRoleName": null, + "reporterRank": null, + "reporterRankName": null, + "level": "中级", + "serviceAreas": ["云南", "四川", "贵州"], + "specialties": ["民俗风情", "美食体验", "自然风光"], + "baseDailyWage": "600.00", + "occupancyStatus": "FULL", + "occupiedDays": 3, + "freeRanges": [], + "occupancies": [ + { + "sourceType": "GROUP_BATCH", + "sourceId": "2104455358355509249", + "refNo": "Q202611162104455356057034753", + "title": "11月16日海拉尔-额尔古纳3日团", + "statusName": "招募中", + "staffRole": "GUIDE", + "staffRoleName": "导游", + "startDate": "2026-11-16", + "endDate": "2026-11-18", + "overlapStartDate": "2026-11-17", + "overlapEndDate": "2026-11-18" + }, + { + "sourceType": "GROUP_BATCH", + "sourceId": "2104455369311031298", + "refNo": "Q202611182104455365389361153", + "title": "11月18日海拉尔-额尔古纳3日团", + "statusName": "招募中", + "staffRole": "GUIDE", + "staffRoleName": "导游", + "startDate": "2026-11-18", + "endDate": "2026-11-20", + "overlapStartDate": "2026-11-18", + "overlapEndDate": "2026-11-19" + } + ] + } + ], + "traceId": null, + "success": true +} +``` + +#### 空数据 / 降级响应 + +- 资源库没有可用人员:`data: []`(既有)。 +- 本团期未建:`groupBatchId` 为 null,每人 `occupancyStatus=null`、`occupiedDays=0`、`freeRanges=[]`、`occupancies=[]`。 + +#### 错误响应 + +```json +{ + "code": 582113, + "message": "人员配置位不合法,请检查配置位标识", + "data": null, + "success": false +} +``` + +| code | 触发条件 | +|---|---| +| `582113` | `role` 不是 `GUIDE` / `PHOTOGRAPHER`(既有) | +| `582103` | 资源服务查询候选失败(既有) | +| `589507` | 无团期查看权限(既有) | + +#### 业务边界 + +- 排序与候选分页同一规则:本团本位已选 → 空闲 → 部分占用 → 全程占用 → 等级从高到低 → `sortOrder` 升序 → `staffId` 升序。 +- 同一来源(同一个团期或同一张订单)即使有多行也只出一条明细;`occupiedDays` 按实际被占的日子算,重叠部分只算一次。 +- 占用只提示不拦截。 + +### 7. 配导游芯片明细 `GET /v3/admin/order/group-batch/{groupBatchId}/chips/guide` + +**VO**: `GroupBatchChipDetailVO` + +#### 使用场景 + +团期详情「配导游」页签、看板点导游芯片弹出的明细。本单起只看团期名册 `staffList`,不再有逐户人员表。 + +#### 入参 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| groupBatchId | Path | Long | 是 | 运营团期 ID | 路由上的团期 ID(既有) | + +#### 出参 `Result` + +| 字段 | 类型 | 说明 | +|------|------|------| +| batchId / groupBatchId | String | 团期 ID(`batchId` 已废弃,同值;既有) | +| chipLabel | String | 芯片中文名(既有) | +| aggregateStatus / aggregateStatusName | String | 整团聚合态及中文名(既有,口径不变,仍按户计) | +| totalCount / doneCount | Integer | 计入统计的户数 / 已完成户数(既有,口径不变) | +| staffList | Object[] | 本团导游位(导游 + 领队)已配置人员(既有,内容不变);元素字段 `staffId`、`staffRole`、`staffRoleName`、`staffName`、`staffPhone`、`reporterRank`、`reporterRankName`、`source`、`sourceName` | +| items | Object[] | **取值变化**:恒为 `[]`,不再逐户返回(改前每户一行并带 `items[].staffs`) | + +#### 请求示例 + +```http +GET /v3/admin/order/group-batch/2104455358355509249/chips/guide +Authorization: Bearer +``` + +无请求体。 + +#### 响应示例 + +2026-09-28 测试服实测: + +```json +{ + "code": 200, + "message": "成功", + "data": { + "batchId": "2104455358355509249", + "groupBatchId": "2104455358355509249", + "chipLabel": "配导游", + "aggregateStatus": "TODO", + "aggregateStatusName": "待开始", + "totalCount": 0, + "doneCount": 0, + "staffList": [ + { + "staffId": "1005", + "staffRole": "LEADER", + "staffRoleName": "领队", + "staffName": "刘大山", + "staffPhone": "138****1005", + "reporterRank": "PRIMARY", + "reporterRankName": "主报账人", + "source": null, + "sourceName": null + }, + { + "staffId": "1002", + "staffRole": "GUIDE", + "staffRoleName": "导游", + "staffName": "李雪梅", + "staffPhone": "138****1002", + "reporterRank": "NONE", + "reporterRankName": "非报账人", + "source": null, + "sourceName": null + } + ], + "items": [] + }, + "traceId": null, + "success": true +} +``` + +#### 空数据 / 降级响应 + +未配置导游位人员时 `staffList: []`、`items: []`(改前 `items` 会列出每户,本单起恒为 `[]`)。 + +#### 错误响应 + +错误码未改动。 + +```json +{ + "code": 589500, + "message": "团期不存在", + "data": null, + "success": false +} +``` + +#### 业务边界 + +- 判权未改(`group-batch:view`,无权限 589507)。 +- `staffList` 不带服务日期、日薪、占用;需要这些时调 `GET /v3/admin/group-batch/{productBatchId}/staff`。 +- 芯片聚合态(`aggregateStatus`、`totalCount`、`doneCount`)仍按户计算,本单不改;改按团期名册计算的口径见 #8469。 +- 配房、配车、合同、保险四个芯片完全不变。 + +### 8. 配摄影芯片明细 `GET /v3/admin/order/group-batch/{groupBatchId}/chips/photo` + +**VO**: `GroupBatchChipDetailVO` + +#### 使用场景 + +团期详情「配摄影」页签、看板点摄影芯片弹出的明细。本单起只看团期名册 `staffList`,不再有逐户人员表。 + +#### 入参 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| groupBatchId | Path | Long | 是 | 运营团期 ID | 路由上的团期 ID(既有) | + +#### 出参 `Result` + +| 字段 | 类型 | 说明 | +|------|------|------| +| batchId / groupBatchId | String | 团期 ID(`batchId` 已废弃,同值;既有) | +| chipLabel | String | 芯片中文名(既有) | +| aggregateStatus / aggregateStatusName | String | 整团聚合态及中文名(既有,口径不变,仍按户计) | +| totalCount / doneCount | Integer | 计入统计的户数 / 已完成户数(既有,口径不变) | +| staffList | Object[] | 本团摄影位已配置人员(既有,内容不变);元素字段 `staffId`、`staffRole`、`staffRoleName`、`staffName`、`staffPhone`、`reporterRank`、`reporterRankName`、`source`、`sourceName` | +| items | Object[] | **取值变化**:恒为 `[]`,不再逐户返回(改前每户一行并带 `items[].staffs`) | + +#### 请求示例 + +```http +GET /v3/admin/order/group-batch/2104455358355509249/chips/photo +Authorization: Bearer +``` + +无请求体。 + +#### 响应示例 + +2026-09-28 测试服实测: + +```json +{ + "code": 200, + "message": "成功", + "data": { + "batchId": "2104455358355509249", + "groupBatchId": "2104455358355509249", + "chipLabel": "配摄影", + "aggregateStatus": "TODO", + "aggregateStatusName": "待开始", + "totalCount": 0, + "doneCount": 0, + "staffList": [ + { + "staffId": "1003", + "staffRole": "PHOTOGRAPHER", + "staffRoleName": "摄影", + "staffName": "王强", + "staffPhone": "138****1003", + "reporterRank": "NONE", + "reporterRankName": "非报账人", + "source": null, + "sourceName": null + } + ], + "items": [] + }, + "traceId": null, + "success": true +} +``` + +#### 空数据 / 降级响应 + +未配置摄影位人员时 `staffList: []`、`items: []`(改前 `items` 会列出每户,本单起恒为 `[]`)。 + +#### 错误响应 + +错误码未改动。 + +```json +{ + "code": 589507, + "message": "无操作权限(当前角色未授予团期权限,或该团期不在您名下)", + "data": null, + "success": false +} +``` + +#### 业务边界 + +- 判权未改(`group-batch:view`);团期不存在 589500(既有)。 +- `staffList` 不带服务日期、日薪、占用;需要这些时调 `GET /v3/admin/group-batch/{productBatchId}/staff`。 +- 芯片聚合态仍按户计算,本单不改;改按团期名册计算的口径见 #8469。 + +### 9. 新建服务人员 `POST /admin/staff` + +**VO**: `StaffCreateRequest` → `StaffVO` + +#### 使用场景 + +服务人员管理「新建」弹窗保存。本单起「基础日薪」输入框的值会保存(改前填了不保存)。 + +#### 入参 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| name | Body | String | 是 | ≤100 字 | 姓名(既有) | +| staffType | Body | String | 是 | 人员类型字典 | 人员类型(既有) | +| basePrice | Body | Number | 否 | ≥0;最多 8 位整数、2 位小数 | **新增**。基础日薪(元/天);不传按 0 保存(= 没填) | + +其余字段与改前一致。 + +#### 出参 `Result` + +| 字段 | 类型 | 说明 | +|------|------|------| +| basePrice | String | **新增**。基础日薪,按库值原样返回的字符串,如 `"650.00"`;没填为 `"0.00"`(不是 null)。回填数字输入框用 `Number(basePrice)` | +| 其余字段 | - | 与改前一致 | + +#### 请求示例 + +```json +{ + "name": "孟和巴雅尔", + "staffType": "LEADER", + "gender": 1, + "serviceAreas": ["内蒙古"], + "specialties": ["草原穿越"], + "basePrice": 650, + "description": "熟悉呼伦贝尔草原线路,常带亲子团" +} +``` + +#### 响应示例 + +2026-09-28 测试服实测节选(验收后已软删;金额按当前契约写成字符串): + +```json +{ + "code": 200, + "message": "成功", + "data": { + "staffId": "2104454928988823553", + "name": "孟和巴雅尔", + "staffType": "LEADER", + "specialties": ["草原穿越"], + "serviceAreas": ["内蒙古"], + "basePrice": "650.00", + "sortOrder": 0, + "status": 0 + }, + "traceId": null, + "success": true +} +``` + +#### 空数据 / 降级响应 + +写接口,无空数据形态。不传 `basePrice` 时按 0 保存,详情与列表返回 `"0.00"`。 + +#### 错误响应 + +与编辑接口同一组校验(测试服在编辑接口实测): + +```json +{ + "code": 400, + "message": "基础日薪不能为负数", + "data": null, + "traceId": null, + "success": false +} +``` + +| code | 触发条件 | +|---|---| +| `400` | `basePrice` 为负数(「基础日薪不能为负数」);超过 8 位整数或 2 位小数(「基础日薪最多8位整数、2位小数」) | + +#### 业务边界 + +- 判权与审批上架流程未改。 +- 校验失败不写入。 +- 团期选人时取的就是这里保存的基础日薪(0 视为没填,团期侧显示 null)。 + +### 10. 编辑服务人员 `PUT /admin/staff/{staffId}` + +**VO**: `StaffUpdateRequest` → `StaffVO` + +#### 使用场景 + +服务人员管理「编辑」弹窗保存。弹窗打开时用详情接口回显 `Number(basePrice)`,保存时把数值传回。 + +#### 入参 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| staffId | Path | Long | 是 | - | 人员 ID(既有) | +| basePrice | Body | Number | 否 | ≥0;最多 8 位整数、2 位小数 | **新增**。基础日薪(元/天);**不传不改**原值;传 0 = 清成没填 | + +其余字段与改前一致(均为不传不改)。 + +#### 出参 `Result` + +| 字段 | 类型 | 说明 | +|------|------|------| +| basePrice | String | **新增**。保存后的基础日薪字符串,如 `"800.00"`;没填为 `"0.00"` | +| 其余字段 | - | 与改前一致 | + +#### 请求示例 + +```json +{ "basePrice": 800 } +``` + +#### 响应示例 + +2026-09-28 测试服实测节选(金额按当前契约写成字符串): + +```json +{ + "code": 200, + "message": "成功", + "data": { + "staffId": "1007", + "name": "白云飞", + "phone": "139****1007", + "staffType": "LEADER", + "basePrice": "800.00", + "sortOrder": 90, + "status": 1 + }, + "traceId": null, + "success": true +} +``` + +#### 空数据 / 降级响应 + +写接口,无空数据形态。请求不带 `basePrice`(如 `{ "sortOrder": 90 }`)时原值不变,测试服实测保持 800。 + +#### 错误响应 + +测试服实测: + +```json +{ + "code": 400, + "message": "基础日薪最多8位整数、2位小数", + "data": null, + "traceId": null, + "success": false +} +``` + +| code | 触发条件 | +|---|---| +| `400` | `basePrice=-1` →「基础日薪不能为负数」;`basePrice=12.345` →「基础日薪最多8位整数、2位小数」;均不写入 | + +#### 业务边界 + +- 不传不改:只提交部分字段的调用方不会把日薪清掉。 +- 改了档案日薪后,已经选进团期的人日薪快照**不刷新**;之后新选、或更换进来的人才取新值(D1)。 +- 判权未改。 + +### 11. 服务人员详情 `GET /admin/staff/{staffId}` + +**VO**: `StaffVO` + +#### 使用场景 + +服务人员详情页、编辑弹窗回显。 + +#### 入参 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| staffId | Path | Long | 是 | - | 人员 ID(既有) | + +#### 出参 `Result` + +| 字段 | 类型 | 说明 | +|------|------|------| +| basePrice | String | **新增**。基础日薪字符串,按库值原样返回,如 `"600.00"`;没填为 `"0.00"`(不是 null) | +| 其余字段 | - | 与改前一致 | + +#### 请求示例 + +```http +GET /admin/staff/1007 +Authorization: Bearer +``` + +无请求体。 + +#### 响应示例 + +测试服实测节选(1007 验收后已恢复为没填;金额为字符串): + +```json +{ + "code": 200, + "message": "成功", + "data": { + "staffId": "1007", + "name": "白云飞", + "phone": "139****1007", + "staffType": "LEADER", + "guideLevel": null, + "serviceAreas": ["内蒙古", "新疆"], + "specialties": ["户外探险", "亲子活动", "草原穿越"], + "basePrice": "0.00", + "sortOrder": 90, + "status": 1 + }, + "traceId": null, + "success": true +} +``` + +#### 空数据 / 降级响应 + +没填日薪的人 `basePrice` 为 `"0.00"`,前端按「未填」展示即可。 + +#### 错误响应 + +错误码未改动(人员不存在时 HTTP 200 + 业务码 404): + +```json +{ + "code": 404, + "message": "服务人员不存在", + "data": null, + "success": false +} +``` + +#### 业务边界 + +- 详情返回库里的原值(0 就是 `"0.00"`);团期侧把 0 视为没填、返回 null,两处口径不同是有意的。 +- 判权未改。 + +### 12. 服务人员列表 `GET /admin/staff/list` + +**VO**: `PageResult` + +#### 使用场景 + +服务人员管理列表页。本单起每条记录带 `basePrice`,列表可恢复「基础日薪」列。 + +#### 入参 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| keyword | Query | String | 否 | - | 姓名 / 描述模糊匹配(既有) | +| staffType | Query | String | 否 | - | 人员类型筛选(既有) | +| status | Query | Integer | 否 | 0 / 1 | 下架 / 上架(既有) | +| page | Query | Integer | 否 | ≥1,缺省 1 | 页码(既有) | +| pageSize | Query | Integer | 否 | 1~100,缺省 20 | 每页条数(既有) | + +#### 出参 `Result>` + +| 字段 | 类型 | 说明 | +|------|------|------| +| records[].basePrice | String | **新增**。基础日薪字符串,按库值原样返回,如 `"1000.00"`;没填为 `"0.00"` | +| records[] 其余字段 | - | 与改前一致 | + +#### 请求示例 + +```http +GET /admin/staff/list?staffType=LEADER&page=1&pageSize=20 +Authorization: Bearer +``` + +无请求体。 + +#### 响应示例 + +2026-09-28 测试服实测节选: + +```json +{ + "code": 200, + "message": "成功", + "data": { + "records": [ + { + "staffId": "1005", + "name": "刘大山", + "staffType": "LEADER", + "phone": "138****1005", + "basePrice": "1000.00", + "sortOrder": 95, + "status": 1, + "supplierFullName": "陈巴尔虎旗天下草原旅游服务有限" + }, + { + "staffId": "1007", + "name": "白云飞", + "staffType": "LEADER", + "phone": "139****1007", + "basePrice": "0.00", + "sortOrder": 90, + "status": 1 + } + ] + }, + "traceId": null, + "success": true +} +``` + +#### 空数据 / 降级响应 + +无匹配记录时 `records: []`(既有)。没填日薪的人 `basePrice` 为 `"0.00"`。 + +#### 错误响应 + +参数校验错误码未改动: + +```json +{ + "code": 400, + "message": "页码不能小于1", + "data": null, + "success": false +} +``` + +#### 业务边界 + +- 列表排序、筛选、判权都未改,只多了 `basePrice`。 +- `basePrice` 是字符串,排序或求和前先 `Number()`。 + +--- + +## 四、契约约束与正确调用方式 + +### ✅ 正确 / ❌ 错误 payload 对照(保存团期人员配置) + +| 场景 | payload(`staffList[]` 单项) | 结果 | +|------|---------|------| +| ✅ 前端现有写法,保留的人只传三项 | `{ "staffId": 1005, "staffRole": "LEADER", "sortOrder": 1 }` | 沿用原报账人等级、备注、服务日期、日薪 | +| ✅ 自定义服务日期 | `{ "staffId": 1008, "staffRole": "LEADER", "sortOrder": 2, "serviceStartDate": "2026-11-18", "serviceEndDate": "2026-11-18" }` | 落为自定义,`serviceDateCustom=true` | +| ✅ 改回跟随团期 | 传团期起止本身,如团期 11-17 ~ 11-19 时 `"serviceStartDate": "2026-11-17", "serviceEndDate": "2026-11-19"` | 按跟随团期处理,`serviceDateCustom=false` | +| ✅ 清空备注 | `{ ..., "remark": "" }` | 备注清空 | +| ⚠️ 想清空备注却传 null 或不传 | `{ ..., "remark": null }` | 保留下来的人**沿用原备注**(不会清空) | +| ⚠️ 想改回跟随团期却不传日期 | `{ "staffId": 1008, "staffRole": "LEADER" }` | 保留下来的人**沿用原自定义日期** | +| ❌ 开始晚于结束 | `"serviceStartDate": "2026-11-19", "serviceEndDate": "2026-11-18"` | 582119,整批不写入 | +| ❌ 越出团期 | 团期 11-17 ~ 11-19 时 `"serviceStartDate": "2026-11-16"` | 582119,整批不写入 | +| ➖ 带日薪 | `{ ..., "baseDailyWage": 9999 }` | 不报错,但被忽略;日薪取档案值 | + +### 金额字段 + +- `baseDailyWage`(团期名册、候选)与 `basePrice`(服务人员管理详情、列表、新建与编辑响应)都是 JSON 字符串,如 `"600.00"`。展示可直接用;运算、比较、回填数字输入框前 `Number()`。 +- 团期侧 `baseDailyWage` 为 null 表示「档案没填日薪」;服务人员管理侧 `basePrice` 为 `"0.00"` 表示同一件事。 +- 服务人员新建 / 编辑的请求体里 `basePrice` 传数字即可(如 `800`)。 + +### 前端交接清单 + +1. **选人弹窗(候选分页 / 候选列表)**:每行展示等级 `level`、服务区域 `serviceAreas`、擅长 `specialties`、基础日薪 `baseDailyWage`(标「参考」),以及占用标签(`FREE` 空闲 / `PARTIAL` 部分占用 / `FULL` 全程占用)和被占天数 `occupiedDays`;`occupancyStatus` 为 null(团期未建)时不显示占用标签。列表顺序直接用接口顺序,不要在前端重排。 +2. **占用明细弹层**:点占用标签弹出 `occupancies[]`(来源类型 `sourceType`、团期号或订单号 `refNo`、名称 `title`、状态 `statusName`、角色 `staffRoleName`、他那边的服务起止 `startDate`~`endDate`、与本团重叠的起止 `overlapStartDate`~`overlapEndDate`)和可派日期段 `freeRanges`。 +3. **保存弹窗**:每个人可编辑服务开始 / 结束日期(不填 = 跟随团期),基础日薪只读展示。回显用名册的 `serviceStartDate` / `serviceEndDate` / `serviceDateCustom`;把自定义日期改回跟随团期时提交团期起止本身(不传会沿用原值);收到 582119 直接展示接口文案(带人名)。有占用不需要二次确认(D5)。 +4. **已配置人员名册**:可加服务日期、基础日薪、占用标签三列(字段见接口 2)。 +5. **服务人员管理**:编辑框回显用 `Number(row.basePrice)`(出参是字符串);列表恢复「基础日薪」列,`"0.00"` 按未填展示。 +6. **团期详情导游 / 摄影页签、看板导 / 摄芯片弹层**:去掉逐户人员表(`items` 恒为 `[]`)及其「共 N 户计入」汇总行,只展示团期名册 `staffList`。 +7. **本单不做**:「只看空闲」筛选(D9);导 / 摄芯片状态颜色口径另见 #8469,本单不改。 + +### 服务间内部接口(前端不可调) + +资源服务的 `/internal/staff/...` 系列接口也随本单加了字段,并新增 `POST /internal/staff/page-available`(候选分页的全局排序由它完成)。这些接口不经网关,前端无需关注;候选分页在资源服务不可用时返回 582103。 + +--- + +## 五、数据库行为 + +| 前端动作 | 可观察到的存储结果 | +|----------|---------------------| +| 保存时服务日期不传 / 与团期起止相同 | 按「跟随团期」存,名册返回团期起止,`serviceDateCustom=false`,团期改期后跟着变 | +| 保存时服务日期自定义 | 原样保存,`serviceDateCustom=true`,团期改期后不变 | +| 保存 / 更换选人 | 基础日薪快照在选人那一刻从服务人员档案带出并保存;档案之后再改,快照不变 | +| 保存返回 582119 | 整批不写入,名册前后一致(测试服前后比对) | +| 服务人员新建 / 编辑带 `basePrice` | 档案基础日薪保存;编辑不传不改;新建不传为 0 | + +- 存量团期人员配置:服务日期全部视为「跟随团期」,基础日薪快照为 null;下次保存这些人时会补取一次档案当前日薪。 +- 查询接口只读。 + +--- + +## 六、边界行为 + +- **占用来源(D3 / D7)**: + - 计入:其他团期里这个人的有效配置(含招募中团期;在那个团当任何角色都算);普通订单(不属于任何团期)上定制师直接派的人(未取消的订单,任何订单状态都算)。 + - 不计:本团自己;已取消的团期(含流团);出发日或结束日为空的团期;已删除的配置;团期同步到子订单的人员副本;车务派车的司机;已取消的订单;没有出发日的订单。 +- **日期段**:候选接口用本团出发日到结束日;名册(及保存、更换、删除响应)用这个人在本团的有效服务日期,先夹进本团当前起止。其他团的自定义服务日期同样先夹进那个团当前起止,完全越界时按那个团整段算。订单用出发日到返回日,返回日为空按出发日一天。 +- **两端都算(D4)**:首尾相接那一天算占用。测试服实测:本团 11-18 ~ 11-20,另一团 11-16 ~ 11-18,重叠 1 天(11-18)。 +- **三档(D8)**:被占 0 天 → `FREE`;被占满整段 → `FULL`;其余 → `PARTIAL`。守恒:`FREE` ⇔ `occupiedDays=0` ⇔ `occupancies` 为空;`FULL` ⇔ `freeRanges` 为空;`occupiedDays` + 可派天数 = 日期段天数。 +- **多段**:多条占用重叠部分只算一次;被占日子在中间时可派日期切成两段。 +- **只提示不拦截(D5)**:有占用照样能保存。 +- **未建团期**:候选能查、团期还没有是正常态,占用四字段为 `null` / `0` / `[]` / `[]`。 +- **未登录**:网关拦截返回 401(既有)。 +- **向后兼容**:团期侧全部为新增字段,旧前端不读也不报错;例外见六.6 的 `items` 与 `remark` 两处行为变化。 + +--- + +## 六.5、枚举 / 数据字典 + +### occupancyStatus(常量定义于 `com.hulalv.order.assignment.helper.StaffOccupancyCalculator`) + +**所属字段**: `BatchStaffItemVO.occupancyStatus`、`StaffCandidateRespVO.occupancyStatus` | **类型**: `String` + +| 值 | 中文 | 说明 | +|----|------|------| +| `FREE` | 空闲 | 日期段内被占 0 天 | +| `PARTIAL` | 部分占用 | 被占天数大于 0 且小于日期段天数 | +| `FULL` | 全程占用 | 日期段每一天都被占 | +| `null` | - | 本团期未建或团期起止不全,算不出 | + +### occupancies[].sourceType(常量定义于 `com.hulalv.order.assignment.helper.StaffOccupancyCalculator`) + +**所属字段**: `StaffOccupancyItemVO.sourceType` | **类型**: `String` + +| 值 | 中文 | 说明 | +|----|------|------| +| `GROUP_BATCH` | 团期 | 其他团期的人员配置;`sourceId` 为运营团期 ID,`refNo` 为团期号 | +| `ORDER` | 订单 | 普通订单上定制师直派;`sourceId` 为订单 ID,`refNo` 为订单号 | + +### level(`com.hulalv.resource.staff.constant.StaffGuideLevel`) + +**所属字段**: `StaffCandidateRespVO.level` | **类型**: `String` + +| 值 | 中文 | 说明 | +|----|------|------| +| `特级` | 特级 | 排序最高 | +| `高级` | 高级 | - | +| `中级` | 中级 | - | +| `初级` | 初级 | 已知等级中排最后 | +| 其他原样值 / null | - | 档案写法认不出时原样返回;未填为 null;都排在已知等级之后 | + +### occupancies[].statusName + +**所属字段**: `StaffOccupancyItemVO.statusName` | **类型**: `String`(中文名,不是编码) + +| 值 | 中文 | 说明 | +|----|------|------| +| 团期来源 | 招募中 / 资源准备中 / 物料准备中 / 待出发 / 出行中 / 出行完毕 / 核单中 / 已结算 | 团期当前状态(已取消的团期不计入,不会出现) | +| 订单来源 | 待支付 / 定制中 / 待出行 / 出行中 / 已完成 | 订单当前状态(已取消的订单不计入,不会出现) | + +--- + +## 六.6、修改前后对比 + +### 字段级对比 + +| 字段 | 改前 | 改后 | +|------|------|------| +| 名册 / 保存 / 更换 / 删除响应的人员项 | 无服务日期、日薪、占用 | 新增 `serviceStartDate`、`serviceEndDate`、`serviceDateCustom`、`baseDailyWage`、`occupancyStatus`、`occupiedDays`、`freeRanges`、`occupancies` | +| 保存请求 `staffList[]` | 无日期字段 | 新增可选 `serviceStartDate`、`serviceEndDate` | +| 候选项 | 只有姓名、手机、类型等 | 新增 `level`、`serviceAreas`、`specialties`、`baseDailyWage` 与四个占用字段 | +| 导 / 摄芯片 `items` | 每户一行,带 `items[].staffs` | 恒为 `[]` | +| 服务人员 `basePrice` | 请求传了不保存,响应不返回 | 可保存;详情、列表、新建与编辑响应返回字符串 | + +### 行为级对比 + +| 行为 | 改前 | 改后 | +|------|------|------| +| 保存导游位(前端只传三项) | 主报账人被打回非报账人、备注被清空 | 沿用原等级、备注、服务日期、日薪 | +| 保存时 `remark` 不传 | 清空备注 | 保留下来的人沿用原备注;要清空传 `""` | +| 候选排序 | 资源库原顺序(`sortOrder`、`staffId`) | 本团本位已选 → 空闲 → 部分占用 → 全程占用 → 等级 → `sortOrder` → `staffId` | +| 服务日期不合法 | 无此校验 | 582119,整批不写入 | +| 更换人员 | 新人不带服务日期、日薪 | 新人沿用原人服务日期,日薪取新人档案 | +| 导 / 摄页签逐户表 | 每户显示分到的人 | 不再提供,只看团期名册 | + +--- + +## 六.7、影响评估 + +- **是否破坏向后兼容**: 部分。团期侧与服务人员管理都是新增字段;有两处行为变化:①导 / 摄芯片 `items` 恒为 `[]`,旧页面的逐户人员表会变空;②保存时 `remark` 不传不再清空备注。候选顺序也变了(接口顺序即展示顺序)。 +- **前端是否必须同步上线**: 否。新字段不读不会报错;逐户表变空只影响展示,按交接清单第 6 项去掉即可。 +- **前端 workaround 清理点**: 如果前端为规避「保存后主报账人丢失」在保存后补调过设置报账人接口,可以删掉;导 / 摄页签里按 `items[].staffs` 渲染的「人员」列与「共 N 户计入」汇总行可以删掉。 + +--- + +## 七、不影响范围 + +- **仅影响**: 团期人员配置的保存 / 名册 / 更换 / 删除 / 两个候选接口,导 / 摄两个芯片明细,服务人员管理新建 / 编辑 / 详情 / 列表。 +- **零影响**: + - 设置报账人等级 `PUT /v3/admin/group-batch/{productBatchId}/staff/{staffId}/reporter-rank` + - 配房、配车、合同、保险四个芯片明细与看板聚合 + - 订单侧人员接口(`/v3/admin/order/{orderId}/staff`)与团期同步到子订单的人员副本 + - 出团通知书里的人员默认值 + - 核团费用(本单不用「日薪 × 服务天数」预填) + - 服务人员价格日历 + +--- + +## 八、测试环境已验证 + +- **环境**:TEST,`hl-resource-service` 与 `hl-order-service-v3` 均为 dev-v3 `623c932bd`(含 `bf65803b7` 与 `623c932bd` 两次合入;resource 14:28、order-v3 14:31 双实例滚动完成,先 resource 后 order-v3)。 +- **方式**:经网关 `https://api.test.1814.love`,真实 admin token;每批取证前做构建身份探针(候选响应含 `occupancyStatus` 等新键),5 次全部命中。手机号均为接口脱敏值。 +- **造数**:五个班期(A 11-16、B 11-17、C 11-18、D 11-26 未建团期、E 10-26)与两张普通订单;验收后配置清空、团期流团、订单取消,新建人员已软删,1007 基础日薪已恢复为 0。 + +| AC | 检验点 | 结果 | 证据 | +|----|--------|------|------| +| 1 | `basePrice` 编辑 800 后详情 / 列表都为 800;再次编辑不带 `basePrice` 仍 800;-1、12.345 返回 400 且库值不变;新建带 650 → 650 | ✅ | 会话证据 accept/AC-1.json | +| 3 | 候选带等级、服务区域、擅长、日薪;档案为 0 返回 null,1005 为 1000 | ✅ | accept/AC-3.json | +| 6 | 新选 1007 日薪 800、1008 为 null;请求夹 `baseDailyWage=9999` 不生效;档案改回 0 后再保存,已有 800 快照不刷新 | ✅ | accept/AC-6.json | +| 7 | 1005 设为主报账人、备注「负责草原段带队」,用前端现有请求体(只含 `staffId`/`staffRole`/`sortOrder`)重存导游位:仍为主报账人,备注、日薪、1009 的自定义日期都保留 | ✅ | accept/AC-7.json | +| 8 | 不传存为跟随团期且名册返回团期起止、`serviceDateCustom=false`;与团期起止相同同样跟随;自定义 → `true`;开始晚于结束 / 早于团期 / 晚于团期 → 582119,文案含「萨仁高娃」,名册前后一致 | ✅ | accept/AC-8.json | +| 9 | E 团 1005 → 1007:沿用 10-27 ~ 10-28,日薪 800,等级与备注随之转移 | ✅ | accept/AC-9.json | +| 10 | 产品侧把 C 改期到 11-19 后,团期变为 11-19 ~ 11-21,1002 名册日期随之变化 | ✅ | accept/AC-10.json | +| 11 | 名册与两个候选接口字段齐全,占用明细 11 个字段齐全;未建团期的班期 D 候选占用为 null / 0 / [] / [] | ✅ | accept/AC-11.json | +| 12 | 重叠 2 天、首尾相接 1 天、招募中算、本团不算、缩短日期后不算、删除后不算、流团后不算、多段合并、中间切两段、普通订单直派算、取消订单后不算、跨角色(订单上当摄影)算、团期副本不重复算 | ✅ | accept/AC-12.json | +| 13 | E 团派已被普通订单占用的 1005,保存成功,名册显示部分占用 | ✅ | accept/AC-13.json | +| 15 | A / B / C / E 四团每页 2 条翻完:本团已选 → 空闲 → 部分 → 全程 → 等级 → `sortOrder`,与全量接口一致,`total` 不变 | ✅ | accept/AC-15.json | +| 16 | A 团导游 / 摄影芯片 `items=[]`、`staffList` 有人;配房、配车芯片照常逐户返回 | ✅ | accept/AC-16.json | +| 18 | #8476 合入后复验:候选 `baseDailyWage` 为 `"600.00"` / `"1000.00"`;人员详情与列表 `basePrice` 为 `"600.00"` / `"0.00"`(字符串) | ✅ | accept/AC-18-wage-string.json | + +说明:本文各响应示例取自 AC-6 ~ AC-16 的实测响应(取证时金额字段仍为数字),示例里的金额已按 AC-18 复验过的字符串形态书写,其余字段为原值。「司机行不算」在测试服造不出司机来源数据,由真库单测覆盖。 + +--- + +## 九、相关历史 PR + +| PR | Issue | 说明 | 是否仍有效 | +|----|-------|------|------------| +| #7831 | #7827 | 团期人员候选分页与模糊搜索 | ✅ 有效(本单改了排序与条目字段) | +| #7856 | #7853 | 导 / 摄芯片带出 `staffList` 与逐户 `items[].staffs` | ⚠️ `staffList` 有效;逐户人员已被本单撤掉 | +| #8357 | #8354 | 名册实时姓名手机、单人删除 / 更换 | ✅ 有效(本单给响应加字段、更换沿用服务日期) | +| **#8475** | **#8468** | 本单主体 | ✅ 最新 | +| **#8476** | **#8468** | 金额字段改字符串输出 | ✅ 最新 | + +--- + +## 十、相关文档 + +- 工单 #8468: https://git.1814.love/wx/HL/issues/8468 +- 导 / 摄芯片状态改按团期名册计算:https://git.1814.love/wx/HL/issues/8469 +- 团期人员配置二期改表:https://git.1814.love/wx/HL/issues/8258 + +--- + +## 关联 / 联系人 + +### 链接 + +- **Issue**: [#8468](https://git.1814.love/wx/HL/issues/8468) +- **PR**: [#8475](https://git.1814.love/wx/HL/pulls/8475)、[#8476](https://git.1814.love/wx/HL/pulls/8476) +- **Merge commit**: [bf65803b7](https://git.1814.love/wx/HL/commit/bf65803b7)、[623c932bd](https://git.1814.love/wx/HL/commit/623c932bd) + +### 联系人 + +- **后端负责人**: @jw