From cc8292ea1ed05d281bf68f5c0398fe90e1396bf5 Mon Sep 17 00:00:00 2001 From: jw Date: Sat, 3 Oct 2026 23:18:14 +0800 Subject: [PATCH] =?UTF-8?q?docs(changelog):=20#8752=20=E5=9B=A2=E6=9C=9F?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E5=91=98=E4=B8=80=E9=94=AE=E5=82=AC=E5=8A=9E?= =?UTF-8?q?=E6=9C=AA=E6=8F=90=E4=BA=A4=E6=88=BF=20/=20=E8=BD=A6=E9=9C=80?= =?UTF-8?q?=E6=B1=82=E7=9A=84=E5=AE=9A=E5=88=B6=E5=B8=88=EF=BC=88=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E6=8E=A5=E5=8F=A3=C2=B7=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 Co-Authored-By: Claude Opus 5.5 --- ...键催办定制师提交房车需求-新增接口-管理后台.md | 394 ++++++++++++++++++ 1 file changed, 394 insertions(+) create mode 100644 changelogs-v2/2026-10/03_8752_团期一键催办定制师提交房车需求-新增接口-管理后台.md diff --git a/changelogs-v2/2026-10/03_8752_团期一键催办定制师提交房车需求-新增接口-管理后台.md b/changelogs-v2/2026-10/03_8752_团期一键催办定制师提交房车需求-新增接口-管理后台.md new file mode 100644 index 00000000..a9396f59 --- /dev/null +++ b/changelogs-v2/2026-10/03_8752_团期一键催办定制师提交房车需求-新增接口-管理后台.md @@ -0,0 +1,394 @@ +--- +schema: "hl-changelog/v2" +ticket: "8752" +title: "团期管理员一键催办未提交房 / 车需求的定制师:新增 POST requirement/nudge,按定制师每人一条站内信 + 企微,无定制师的户列为跳过" +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: "新增 POST /v3/admin/order/group-batch/{groupBatchId}/requirement/nudge(权限码 group-batch:manage:团期管理员 / ADMIN / SUPER_ADMIN,定制师 589507)。圈本团「该定制师动手」的户(与定制师待办同源:资源准备中、房 / 车需求未提交或被打回定制师),按定制师聚合每人一条通知(管理端站内信 + 企微,站内信点开到「定制师待办」/order/todos),只投员工不触达客户;无定制师的户在 skipped 里列出。同团 300 秒冷却(589852 带剩余秒数),一条都没发出时不占冷却。已合并 dev-v3(5b204b9d2)并部署 TEST(user-service、order-v3),经网关真实鉴权验收 AC-1~AC-10 通过,工单 #8752 已关。前端待做:团期详情需求 Tab「企微催需求」按钮、看板铃铛接本接口;成功后按 recipients / skipped 提示,wecomBound=false 的定制师提示「未绑企微,只收到站内信」,按钮按 cooldownSeconds 置灰倒计时。" +updated_at: "2026-10-03" +base: "dev-v3" +--- + +# 团期催办:新增一键催办定制师提交房 / 车需求(管理后台) + +> **服务**: hl-order-service-v3(端口 8086/8186)+ hl-user-service(通知配置迁移) +> **PR**: #8777 +> **Issue**: #8752 +> **日期**: 2026-10-03 +> **影响范围**: 管理后台团期详情「需求」Tab 的「企微催需求」按钮、团期看板铃铛(原型 `batchDetail.jsx:889` / `batchBoard.jsx:495`);既有接口零变化 + +--- + +## ⚠️ 关键变化 + +1. **新增写端点** `POST .../{groupBatchId}/requirement/nudge`:团期管理员一键催本团所有「房型 / 用车需求未提交或被打回」户的定制师。此前前端写明「企微催需求无接口契约,不做」,底栏「去催需求」只跳需求 Tab;现在有接口了。 +2. **一位定制师一条**:同一定制师名下多户合成一条通知,正文分列房型、用车待提交户数并列出订单号(最多 10 个,多的写「等 N 户」)。 +3. **只发员工**:管理端站内信 + 企微,不发短信、不进客户收件箱。站内信链接是 `/order/todos`(定制师待办),**不是团期详情**——定制师角色没有「出团详情」菜单。 +4. **同一团 300 秒冷却**:冷却期内再点返回 589852,`message` 里带剩余秒数;前端拿 `cooldownSeconds` 做倒计时置灰即可。 + +--- + +## 一、背景 + +成团后系统按户给定制师开了「房型需求 · 待提交」「用车需求 · 待提交」待办,但待办只在后台列表里,**不推送**;团期管理员发现有户没提交,只能线下去找定制师。原型团期详情有「企微催需求」按钮、看板有铃铛,本单给它补后端。 + +--- + +## 二、变更接口清单 + +| # | 接口 | 方法 | 路径 | 变更类型 | 说明 | +|---|------|------|------|----------|------| +| 1 | 一键催办定制师提交房 / 车需求 | POST | `/v3/admin/order/group-batch/{groupBatchId}/requirement/nudge` | 新增接口 | 无请求体;按定制师每人一条站内信 + 企微;同团 300 秒冷却 | + +网关无改动(在既有 `/v3/admin/**` → order-service-v3 通配下)。 + +--- + +## 三、接口详情 + +### 1. 一键催办定制师提交房 / 车需求 `POST /v3/admin/order/group-batch/{groupBatchId}/requirement/nudge` + +**VO**: `GroupBatchRequirementNudgeRespVO` + +#### 使用场景 + +团期详情「需求」Tab 的「企微催需求」按钮、团期看板铃铛。团期已成团(资源准备中)、还有户没提交房 / 车需求时,团期管理员点一下,系统按定制师聚合、每人发一条站内信 + 企微。成功后用 `recipients` 提示「已催 N 位定制师(M 户)」,`skipped` 非空时提示「K 户未指派定制师,未催」,`wecomBound=false` 的定制师提示「未绑企微,只收到站内信」;按 `cooldownSeconds` 倒计时置灰按钮。 + +#### 入参 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| groupBatchId | Path | Long | ✅ | 团期主键 | 不存在返 589500 | + +无请求体、无查询参数。 + +#### 出参 + +| 字段 | 类型 | 说明 | +|------|------|------| +| groupBatchId | String | 团期主键(雪花 id,按字符串返回) | +| groupBatchNo | String | 团号,如 `T26-0938` | +| departDate | String | 出发日期 `yyyy-MM-dd` | +| eventCode | String | 固定 `GROUP_BATCH_REQUIREMENT_NUDGE` | +| pendingHouseholdCount | Integer | 待定制师提交的户数(含 skipped 里无定制师的户) | +| consultantCount | Integer | 本次催到的定制师人数(= recipients 条数) | +| enqueuedCount | Integer | 通知成功投进通知中心的定制师人数(≤ consultantCount) | +| skippedHouseholdCount | Integer | 因无定制师跳过的户数(= skipped 条数) | +| recipients | Array | 逐定制师结果,按首个待提交户的顺序 | +| recipients[].consultantId | String | 定制师(员工)id | +| recipients[].consultantName | String | 定制师姓名(企微名,缺省登录名);员工信息查不到时为 null | +| recipients[].wecomBound | Boolean | 是否绑企微:true 同时收到企微;false 只收站内信;查不到时为 null(未知) | +| recipients[].householdCount | Integer | 该定制师名下待提交户数 | +| recipients[].hotelPendingCount | Integer | 其中房型需求待提交 / 被打回的户数 | +| recipients[].vehiclePendingCount | Integer | 其中用车需求待提交 / 被打回的户数 | +| recipients[].households | Array | 该定制师名下待提交户明细(结构同下 `skipped[]`,skipReason 为 null) | +| recipients[].enqueued | Boolean | 通知是否已投进通知中心 | +| recipients[].message | String | 未投进的原因(如「通知中心投递失败」);成功为 null | +| skipped | Array | 无定制师、没人可催的户 | +| skipped[].orderId | String | 子订单 id | +| skipped[].orderNo | String | 订单号 | +| skipped[].teamNo | String | 团号(子订单级) | +| skipped[].customerName | String | 客户姓名 | +| skipped[].hotelPending | Boolean | 房型需求是否待提交 / 被打回 | +| skipped[].vehiclePending | Boolean | 用车需求是否待提交 / 被打回 | +| skipped[].skipReason | String | 跳过原因,现为「订单未指派定制师」 | +| notifiedAt | String | 本次催办时间 `yyyy-MM-dd HH:mm:ss`;一条都没发时为 null | +| cooldownSeconds | Integer | 下次可催前的冷却秒数:有通知发出时 300;一条都没发(全部户无定制师)时 0 | + +#### 请求示例 + +```http +POST /v3/admin/order/group-batch/2106397492469985281/requirement/nudge +Authorization: Bearer <团期管理员 token> +``` + +#### 响应示例 + +TEST 实测(团期 T26-0938,两位定制师 + 一户无定制师): + +```json +{ + "code": 200, + "message": "成功", + "data": { + "groupBatchId": "2106397492469985281", + "groupBatchNo": "T26-0938", + "departDate": "2026-11-18", + "eventCode": "GROUP_BATCH_REQUIREMENT_NUDGE", + "pendingHouseholdCount": 4, + "consultantCount": 2, + "enqueuedCount": 2, + "skippedHouseholdCount": 1, + "recipients": [ + { + "consultantId": "1002", + "consultantName": "test_admin", + "wecomBound": true, + "householdCount": 2, + "hotelPendingCount": 2, + "vehiclePendingCount": 1, + "households": [ + { + "orderId": "2106397492268658689", + "orderNo": "HL20261003225431608", + "teamNo": "26-2132", + "customerName": "乌云毕力格", + "hotelPending": true, + "vehiclePending": true, + "skipReason": null + }, + { + "orderId": "2106397493581516801", + "orderNo": "HL20261003225431974", + "teamNo": "26-9255", + "customerName": "王淑芬", + "hotelPending": true, + "vehiclePending": false, + "skipReason": null + } + ], + "enqueued": true, + "message": null + }, + { + "consultantId": "2073973862738944001", + "consultantName": "designer_4760", + "wecomBound": false, + "householdCount": 1, + "hotelPendingCount": 1, + "vehiclePendingCount": 1, + "households": [ + { + "orderId": "2106397494344839169", + "orderNo": "HL20261003225432223", + "teamNo": "26-6378", + "customerName": "刘志强", + "hotelPending": true, + "vehiclePending": true, + "skipReason": null + } + ], + "enqueued": true, + "message": null + } + ], + "skipped": [ + { + "orderId": "2106397494781087745", + "orderNo": "HL20261003225432341", + "teamNo": "26-3501", + "customerName": "高玉梅", + "hotelPending": true, + "vehiclePending": true, + "skipReason": "订单未指派定制师" + } + ], + "notifiedAt": "2026-10-03 22:57:38", + "cooldownSeconds": 300 + } +} +``` + +#### 空数据 / 降级响应 + +待提交的户全部没有定制师(TEST 实测团期 T26-7327):返回 200,不发任何通知、不写时间线、不占冷却,`cooldownSeconds=0`、`notifiedAt=null`,可立即再点: + +```json +{ + "code": 200, + "message": "成功", + "data": { + "groupBatchId": "2106397514322350082", + "groupBatchNo": "T26-7327", + "departDate": "2026-11-24", + "eventCode": "GROUP_BATCH_REQUIREMENT_NUDGE", + "pendingHouseholdCount": 1, + "consultantCount": 0, + "enqueuedCount": 0, + "skippedHouseholdCount": 1, + "recipients": [], + "skipped": [ + { + "orderId": "2106397514263629826", + "orderNo": "HL20261003225436973", + "teamNo": "26-0624", + "customerName": "孙德胜", + "hotelPending": true, + "vehiclePending": true, + "skipReason": "订单未指派定制师" + } + ], + "notifiedAt": null, + "cooldownSeconds": 0 + } +} +``` + +员工信息查询降级(user-service 不可用)时照常催办,只是 `consultantName`、`wecomBound` 为 null(未知),不要显示成「未绑企微」。 + +#### 错误响应 + +冷却期内重复催办(`{0}` 为剩余秒数,TEST 实测): + +```json +{ "code": 589852, "message": "催办过于频繁,请 298 秒后再试", "success": false, "data": null } +``` + +没有可催的户(在团户的房 / 车需求都已提交): + +```json +{ "code": 589850, "message": "本团期在团户的房型 / 用车需求均已提交,无需催办", "success": false, "data": null } +``` + +团期还在招募中: + +```json +{ "code": 589552, "message": "团期尚未成团,请先完成成团后再操作", "success": false, "data": null } +``` + +团期已过资源准备中(物料准备中及以后、已取消),定制师已不能提交需求: + +```json +{ "code": 589851, "message": "团期当前状态为「已取消」,定制师已不能提交房型 / 用车需求,无法催办", "success": false, "data": null } +``` + +无权限(如定制师): + +```json +{ "code": 589507, "message": "无操作权限(当前角色未授予团期权限,或该团期不在您名下)", "success": false, "data": null } +``` + +| code | 含义 | 前端处理 | +|---|---|---| +| 589500 | 团期不存在 | 提示后返回列表 | +| 589507 | 当前角色无 `group-batch:manage`(定制师、财务等) | 不展示按钮 | +| 589552 | 团期还在招募中 | 按钮置灰「成团后可催」 | +| 589850 | 没有可催的户 | 提示「需求已全部提交」 | +| 589851 | 团期已过资源准备中,需求已冻结 | 不展示按钮 | +| 589852 | 冷却中,`message` 带剩余秒数 | 按剩余秒数倒计时置灰 | +| 589853 | 通知中心暂不可用,一条都没发出 | 提示稍后重试(冷却已释放,可立即重试) | + +#### 业务边界 + +- 权限码 `group-batch:manage`(授 `GROUP_BATCH_MANAGER` / `ADMIN` / `SUPER_ADMIN`),定制师不能点。 +- 「待提交」的口径与定制师待办同源:在团户中订单处于资源准备中,且需要房(或车)、对应需求**从未提交**或**被打回定制师**;车侧整团已声明免车的户不算。 +- 被房务 / 车队**退回团期管理员重审**(`REJECTED_TO_ADMIN`)的户**不催**——球在管理员手里,不在定制师。需求 Tab 车侧逐户页把这类户显示为「待重提」,与催办户数可能差这一类。 +- 一次催全团,不支持按户 / 按定制师勾选;同一定制师名下多户合成一条。 +- 只发员工:管理端站内信 + 企微,不发短信 / 小程序 / 公众号,客户收件箱不会出现。 +- 冷却按团计 300 秒;一条都没发出(无人可催、通知中心不可用)不占冷却。部分定制师投递失败时冷却照常生效,失败的那几位也要等冷却结束才能再催(看 `recipients[].enqueued`)。 +- 「已投进通知中心」不等于「已送达」:站内信 / 企微的实际结果由通知中心异步记录;未绑企微的定制师只收到站内信。 +- 停用 / 离职的定制师照样会被催;订单需要改派定制师的请先改派。 +- 每次点击都会写一条团期时间线(GB-ADM-096 事件类型 `BATCH_REQUIREMENT_NUDGE`「催办定制师提交需求」),不要轮询调用。 + +--- + +## 四、契约约束与正确调用方式 + +### ✅ 正确 / ❌ 错误调用顺序 + +- ✅ 进入需求 Tab 时按团期状态决定按钮:资源准备中才展示可点;招募中置灰;之后的阶段不展示。 +- ✅ 点击后用响应里的 `cooldownSeconds` 做倒计时;收到 589852 时从 `message` 解析剩余秒数或直接展示 `message`。 +- ✅ 用 `recipients` / `skipped` 组提示文案,`wecomBound` 为 null 时不要提示「未绑企微」。 +- ❌ 不要把 `enqueued=true` 说成「已送达」。 +- ❌ 不要拿响应里的户数去和需求 Tab 车侧「待重提」户数强行对齐(见业务边界第 3 条)。 + +--- + +## 五、数据库行为 + +- **order-v3**:不建表、不改表;每次成功催办在 `group_batch_status_log` 追加一条 DATA 流水(`event_type=BATCH_REQUIREMENT_NUDGE`,content「催办定制师提交需求(N 位定制师 · M 户)」,extra 记定制师 / 订单 / 跳过户 id 与房车户数)。冷却只在 Redis(键 `order:gb:requirement-nudge:{groupBatchId}`,TTL 300 秒),不落库。 +- **user-service**:Flyway `V20261003_8752__group_batch_requirement_nudge_notification.sql` 往 `notification_event_config` 插(或按 `uk_event_code` 收敛)一行 `GROUP_BATCH_REQUIREMENT_NUDGE`:站内信 + 企微开、短信 / 小程序 / 公众号关、接收人 `ORDER_CONSULTANT`、站内信链接 `/order/todos`。通知中心消费后写 `admin_message` 与 `notification_send_log`,不写 `user_message`。 + +--- + +## 六、边界行为 + +| 场景 | 行为 | +|---|---| +| 团期不存在 | 589500 | +| 招募中 | 589552 | +| 物料准备中 / 待出发 / 出行中 / 核单 / 已结算 / 已取消 | 589851 | +| 资源准备中但全部户已提交 | 589850 | +| 全部待提交户都没有定制师 | 200,`consultantCount=0`、`cooldownSeconds=0`,不发不留痕 | +| 部分户无定制师 | 有定制师的照发,无定制师的列入 `skipped` | +| 300 秒内再点 | 589852,带剩余秒数 | +| 通知中心不可用(一条都没投进) | 589853,冷却释放可立即重试 | +| 部分定制师投递失败 | 200,失败者 `enqueued=false`、`message` 有原因;冷却生效 | +| 员工信息查不到 | 照常催办,`consultantName` / `wecomBound` 为 null | + +--- + +## 六.5、枚举 / 数据字典 + +- `skipped[].skipReason`:目前只有「订单未指派定制师」。 +- 时间线事件类型 `BATCH_REQUIREMENT_NUDGE`,中文名「催办定制师提交需求」,变更类型 DATA。 +- 新错误码段 589850-589899:589850 无可催的户、589851 阶段已冻结、589852 冷却中、589853 通知中心不可用。 + +--- + +## 七、不影响范围 + +- **仅影响**: 新增一个写端点 + 一条通知事件配置。 +- **零影响**: + - 需求 Tab 既有接口(requirement-summary、hotel-households、vehicle-households、confirm、reject 等)的入参、出参与错误码 + - 定制师待办的开单 / 关单逻辑(判定抽成单源后行为不变) + - 补发成团通知 `notify-formed` 及其冷却 + - 小程序端、客户通知 +- 零表结构变更、零网关变更、零权限种子变更(复用 `group-batch:manage`)。 + +--- + +## 八、测试环境已验证 + +部署:hl-user-service 与 hl-order-service-v3 = dev-v3 @ 5b204b9d2(2026-10-03 22:31 / 22:34,user-service 先上);TEST `flyway_schema_history` 20261003.8752 success=1;四批取证前各打一次构建身份探针(不存在的团期 → 589500),经网关 `https://api.test.1814.love` 真实鉴权实测(2026-10-03 22:40–23:05);工单 #8752 已验收关单。 + +| # | 场景 | 结果 | +|---|---|---| +| 1 | 团期管理员(gbm8154test,单角色)催两位定制师 + 一户无定制师的团 | 200;每位定制师恰好一条;无定制师户进 `skipped`;户数与定制师待办逐户一致 | +| 2 | 落库:`admin_message` / `notification_send_log` / `user_message` | 站内信 2 行(link `/order/todos`)、ADMIN_INAPP 两行 status=0;`user_message` 前后 0 行 | +| 3 | 企微 | 未绑定的定制师 `wecomBound=false`、WEWORK status=2「无企微接收人」;已绑定的测试号调到真实企微接口、因测试假 userid 被拒(errcode 81013),**未真实送达** | +| 4 | 冷却 | 1 秒后再点 589852「298 秒」、13 秒后「286 秒」;300 秒后再点 200 | +| 5 | ADMIN / 定制师 | ADMIN 200;定制师 589507 | +| 6 | 招募中 / 全部已提交 / 已取消 / 核单中 / 不存在 | 589552 / 589850 / 589851 / 589851 / 589500 | +| 7 | 全部户无定制师的团 | 200、`cooldownSeconds=0`,不写站内信、不写发送记录、不写时间线 | +| 8 | 时间线 GB-ADM-096 | 读到 `BATCH_REQUIREMENT_NUDGE`「催办定制师提交需求(2 位定制师 · 3 户)」 | +| 9 | 投递失败释放冷却 | 本机真组件注入(真 Redis + 不可达的真 RocketMQ):589853、冷却键已释放、立即重试仍 589853 | + +--- + +## 九、相关历史 PR + +| PR | Issue | 说明 | 是否仍有效 | +|----|-------|------|------------| +| — | #7534 | 补发成团通知(300 秒冷却先例) | ✅ | +| — | #7105 | 团期批量催签合同(批量触达 + 逐户结果先例) | ✅ | +| — | #8562 | 用车逐户区分从未提交 / 被打回 | ✅ | +| — | #8228 | 站内信 link 留空成死链(本单 link 不留空的原因) | ✅ | +| **本 PR #8777** | **#8752** | 一键催办定制师提交房 / 车需求 | ✅ 最新 | + +--- + +## 十、相关文档 + +- 关联 Issue: [wx/HL#8752](https://git.1814.love/wx/HL/issues/8752) +- 关联 PR: [wx/HL#8777](https://git.1814.love/wx/HL/pulls/8777) + +## 关联 / 联系人 + +### 链接 + +- **Issue**: [#8752](https://git.1814.love/wx/HL/issues/8752) +- **PR**: [#8777](https://git.1814.love/wx/HL/pulls/8777) +- **Merge commit**: [5b204b9d2](https://git.1814.love/wx/HL/commit/5b204b9d2cb2459238283646c65eff41fd07a3f0) + +### 联系人 + +- **后端负责人**: @jw