--- schema: "hl-changelog/v2" ticket: "8468" title: "团期导领摄配置加服务日期与基础日薪,选人列表与名册显示占用状态,导摄芯片去掉逐户人员" consumer: "admin" author: "jw(GIT)" change_type: "修改接口" backend_status: "deployed" gateway_status: "verified" frontend_status: "implemented" frontend_owner: "mmg" frontend_ref: "838b80d69dd1ff5a96e6a95e886d979dac5f452c" target_release: "v2.1" verified_at: "2026-09-28" 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。前端已交付(人员画像/占用标签/服务日期三态+导摄芯片去逐户人员),详见 hl-admin v2.1 提交 838b80d6。" 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