17 KiB
schema, ticket, title, consumer, author, change_type, backend_status, gateway_status, frontend_status, frontend_owner, frontend_ref, target_release, verified_at, status_note, updated_at, base
| schema | ticket | title | consumer | author | change_type | backend_status | gateway_status | frontend_status | frontend_owner | frontend_ref | target_release | verified_at | status_note | updated_at | base |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| hl-changelog/v2 | 7105 | 团期合同保险面板与逐户出具 | admin | jw(GIT) | 新增接口 | deployed | verified | verified | mmg | 838e77da | 2026-09-08 | 新增团期合同保险面板与逐户手动开/作废重开/批量催签四端点,后端已部署 TEST 并实测。注意:出具门依赖房车导摄四项 ready,而 hotel_ready 线上无写入方(#4132 未完成),故面板 issuable 恒 false、手动开/作废重开恒 589548;面板读与批量催签不受影响现在就能用。前端待接:团期详情新增合同保险 Tab(汇总计数+逐户表格+三态徽标用后端 contractStateText/insuranceStateText 不自映射),手动开/作废重开弹窗(target+可选户+逐户结果),批量催签,出具类按钮按 issuable 置灰;权限读 group-batch:view、写 group-batch:contract:issue(需先部署用户服务)。决策:整个 Tab 入 backlog 排在 #7067 U3-U7 闭环后统一汇总审派发,接入时按 issuable 置灰即可、不因 #4132 单独挂起。本条保持 pending。 | 2026-09-07 | dev-v3 |
团期「合同保险」面板:逐户出具、作废重开、批量催签
影响范围:管理后台「团期详情 → 合同保险」Tab。 当前状态:后端已部署 TEST 并实测;前端待接入。 请先读下面的「当前不可用的部分」再排期。
⚠️ 当前不可用的部分(重要)
出具的开闸条件是房 / 车 / 导 / 摄四项资源全部就绪。这四项里:
| 项 | 线上有没有写入方 |
|---|---|
| 导游 ready | ✅ 成团时若该团不需要导游即自动置位,需要时由人员配置置位 |
| 摄影 ready | ✅ 同上 |
| 车辆 ready | ✅ 车队服务整团配车完成后回填 |
| 酒店 ready | ❌ 没有。回填它的那条链路挂在一个从未被发出的事件上,见 HL#4132 |
结果:现网任何团期的四项都集不齐,所以
- 面板的
issuable恒为 false - 手动开 / 作废重开 恒返 589548
- 团期单的合同与保险不会自动出具
前端可以照本文档接入,但在 HL#4132 落地前,出具相关的按钮点下去只会拿到 589548。 面板读取与批量催签不受此限制,现在就能用。
这一条是产品链路的既有缺口,不是本次交付的缺陷,但它决定了本功能何时真正可用,故写在最前面。
一、背景
此前团期没有合同保险的集中视图,管理员要逐户点进订单才能看到谁的合同出了、谁签了、谁的保险还没投; 开错了也只能逐户处理,催签只有单张接口。本次补齐面板与三个批量动作。
二、变更接口清单
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|---|---|---|---|---|---|
| 1 | 合同保险面板 | GET | /v3/admin/order/group-batch/{groupBatchId}/contracts |
新增接口 | 汇总计数 + 逐户三态明细;权限码 group-batch:view |
| 2 | 手动开合同/保险 | POST | /v3/admin/order/group-batch/{groupBatchId}/contracts/issue |
新增接口 | 逐户补开;权限码 group-batch:contract:issue |
| 3 | 作废重开 | POST | /v3/admin/order/group-batch/{groupBatchId}/contracts/reissue |
新增接口 | 先作废后重开;权限码 group-batch:contract:issue |
| 4 | 批量催签合同 | POST | /v3/admin/order/group-batch/{groupBatchId}/contracts/remind-sign |
新增接口 | 只对已出未签的户发短信;权限码 group-batch:contract:issue |
三、接口详情
1. 合同保险面板 GET /v3/admin/order/group-batch/{groupBatchId}/contracts
VO: GroupBatchContractBoardVO
使用场景
进入团期详情的「合同保险」Tab 时调用,一次拿到整团的汇总计数与逐户明细。 整页只发 3 次底层查询,不随户数放大。
入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
| groupBatchId | path | Long | 是 | 正整数 | 团期 ID |
出参
| 字段 | 类型 | 说明 |
|---|---|---|
| issuable | Boolean | 当前是否允许出具(四项已配齐)。现网恒为 false,见文首说明 |
| batchStatus | String | 团期状态 |
| totalCount | Integer | 活跃子订单户数(已取消的户不计) |
| contractIssuedCount | Integer | 合同已出户数 |
| contractSignedCount | Integer | 合同已签户数 |
| insuranceIssuedCount | Integer | 保险已出户数 |
| contractWrongCount | Integer | 合同开错户数 |
| insuranceWrongCount | Integer | 保险开错户数 |
| items[].contractState | String | 三态:NOT_ISSUED / ISSUED / WRONG |
| items[].contractStateText | String | 三态中文名,服务端给出,前端直接显示 |
| items[].awaitingSign | Boolean | 已出未签,即催签的目标 |
| items[].contractUrl | String | 合同文件地址,未出为 null |
| items[].policyNo | String | 保单号,未出为 null |
请求示例
GET /v3/admin/order/group-batch/2096412454643802114/contracts
Authorization: Bearer <token>
响应示例
{
"code": 200,
"message": "成功",
"data": {
"batchId": "2096412454643802114",
"issuable": false,
"batchStatus": "RESOURCE_PREPARING",
"totalCount": 55,
"contractIssuedCount": 0,
"contractSignedCount": 0,
"insuranceIssuedCount": 0,
"contractWrongCount": 0,
"insuranceWrongCount": 0,
"items": [
{
"orderId": "2096633951715033089",
"orderNo": "HL20260907001741975",
"contactName": "蒋岚妍",
"contractState": "NOT_ISSUED",
"contractStateText": "未出",
"contractStatus": null,
"contractPlanName": null,
"contractSignedAt": null,
"contractUrl": null,
"awaitingSign": false,
"insuranceState": "NOT_ISSUED",
"insuranceStateText": "未出",
"insuranceStatus": null,
"insuranceProductName": null,
"policyNo": null
}
]
},
"success": true
}
空数据 / 降级响应
团期尚无子订单时 totalCount 为 0、items 为空数组,各计数器均为 0,页面展示空态。
错误响应
{
"code": 589507,
"message": "无操作权限(非团期管理员 / 非本定制师名下)",
"data": null,
"success": false
}
业务边界
- 已取消的户不出现在面板里,也不计入任何计数器。
- 三态不落库,全部由既有的合同状态与保险状态派生;后端给出中文名,前端不要自行映射。
- 读面板走只读权限,不需要出具权限。
2. 手动开合同/保险 POST /v3/admin/order/group-batch/{groupBatchId}/contracts/issue
VO: GroupBatchContractIssueReqVO
使用场景
四项资源配齐后,为漏开的户补开合同与旅行意外险。正常情况下系统会自动出具, 本接口是补救通路。
入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
| groupBatchId | path | Long | 是 | 正整数 | 团期 ID |
| target | body | String | 是 | CONTRACT / INSURANCE / BOTH | 本次要开什么 |
| orderIds | body | String[] | 否 | 子订单 ID 列表 | 不传表示整团所有活跃户 |
出参
| 字段 | 类型 | 说明 |
|---|---|---|
| totalCount | Integer | 本次处理户数 |
| successCount | Integer | 成功户数 |
| failCount | Integer | 失败户数 |
| skipCount | Integer | 跳过户数(已出具无需重复开) |
| results[].outcome | String | SUCCESS / FAILED / SKIPPED |
| results[].message | String | 失败或跳过的原因,成功为 null |
请求示例
{
"target": "BOTH",
"orderIds": ["2096633951715033089", "2096633958908264449"]
}
响应示例
{
"code": 200,
"message": "成功",
"data": {
"totalCount": 2,
"successCount": 1,
"failCount": 1,
"skipCount": 0,
"results": [
{ "orderId": "2096633951715033089", "target": "CONTRACT", "outcome": "SUCCESS", "message": null },
{ "orderId": "2096633958908264449", "target": "INSURANCE", "outcome": "FAILED", "message": "保险下单失败,请稍后重试" }
]
},
"success": true
}
空数据 / 降级响应
指定的户都已出具时全部记 SKIPPED,successCount 为 0,属正常情况不是错误。
错误响应
{
"code": 589548,
"message": "房/车/导/摄四项配齐后才能出具合同与保险",
"data": null,
"success": false
}
业务边界
- 逐户独立事务:单户失败不回滚已成功的户,也不中断后续户,前端按
results逐行展示。 - 四项未配齐整单拒绝,返回 589548 且零写入——不是部分成功。
- 五秒内重复提交会被幂等窗口拦下,返回「出具处理中,请勿重复提交」。
- 同一团期的出具动作串行执行,并发点击不会重复开。
3. 作废重开 POST /v3/admin/order/group-batch/{groupBatchId}/contracts/reissue
VO: GroupBatchContractIssueReqVO
使用场景
合同方案选错、保险投保信息有误等「开错」情形:先作废原件,再按同一条链路重新开具。
入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
| groupBatchId | path | Long | 是 | 正整数 | 团期 ID |
| target | body | String | 是 | CONTRACT / INSURANCE / BOTH | 本次要重开什么 |
| orderIds | body | String[] | 否 | 子订单 ID 列表 | 不传表示整团所有活跃户 |
| reason | body | String | 否 | 最长 512 字 | 作废原因,写入操作记录 |
出参
| 字段 | 类型 | 说明 |
|---|---|---|
| totalCount | Integer | 本次处理户数 |
| successCount | Integer | 作废并重开成功的户数 |
| failCount | Integer | 失败户数(含作废失败与重开失败,message 区分) |
| skipCount | Integer | 跳过户数(该户没有可作废的单据) |
| results[].orderId | String | 子订单 ID |
| results[].target | String | CONTRACT / INSURANCE |
| results[].outcome | String | SUCCESS / FAILED / SKIPPED |
| results[].message | String | 失败或跳过的原因,成功为 null |
请求示例
{
"target": "CONTRACT",
"orderIds": ["2096633951715033089"],
"reason": "合同方案选错"
}
响应示例
{
"code": 200,
"message": "成功",
"data": {
"totalCount": 1,
"successCount": 1,
"failCount": 0,
"skipCount": 0,
"results": [
{ "orderId": "2096633951715033089", "target": "CONTRACT", "outcome": "SUCCESS", "message": null }
]
},
"success": true
}
空数据 / 降级响应
指定的户没有可作废的单据时记 SKIPPED,不产生任何写入。
错误响应
{
"code": 589550,
"message": "无可用合同方案,无法出具合同",
"data": null,
"success": false
}
业务边界
- 作废失败不进重开:该户直接记 FAILED,绝不会出现「原件作废了但新件没开出来」的空档。
- 与「手动开」共用同一道四项配齐门,未配齐同样返 589548。
- 幂等窗口与串行锁同「手动开」。
4. 批量催签合同 POST /v3/admin/order/group-batch/{groupBatchId}/contracts/remind-sign
VO: GroupBatchContractRemindReqVO
使用场景
合同已出但客户迟迟不签,管理员一次性给这些户重发签署短信。 本接口不受四项配齐门限制,现在就能用。
入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
| groupBatchId | path | Long | 是 | 正整数 | 团期 ID |
| orderIds | body | String[] | 否 | 子订单 ID 列表 | 不传表示整团所有活跃户 |
出参
| 字段 | 类型 | 说明 |
|---|---|---|
| totalCount | Integer | 本次处理户数 |
| successCount | Integer | 实际发出短信的户数 |
| skipCount | Integer | 跳过户数 |
| results[].message | String | 跳过原因 |
请求示例
{
"orderIds": ["2096633951715033089", "2096633958908264449"]
}
响应示例
{
"code": 200,
"message": "成功",
"data": {
"totalCount": 2,
"successCount": 0,
"failCount": 0,
"skipCount": 2,
"results": [
{ "orderId": "2096633951715033089", "target": "CONTRACT", "outcome": "SKIPPED", "message": "该户合同未出具或已签署,无需催签" },
{ "orderId": "2096633958908264449", "target": "CONTRACT", "outcome": "SKIPPED", "message": "该户合同未出具或已签署,无需催签" }
]
},
"success": true
}
空数据 / 降级响应
无「已出未签」的户时全部 SKIPPED,successCount 为 0,不发任何短信。这是正常结果。
错误响应
{
"code": 589507,
"message": "无操作权限(非团期管理员 / 非本定制师名下)",
"data": null,
"success": false
}
业务边界
- 只对「已出未签」的户发:未出具的、已签署的一律跳过并说明原因。
- 单户发送失败不影响其他户,逐户记 FAILED。
- 只要真发出了至少一条,就写一条团期操作记录。
四、契约约束与正确调用方式
- 出具按钮的可用性以面板返回的
issuable为准,不要前端自行按团期状态推断。 - 三个写动作共用一个权限码
group-batch:contract:issue,读面板走group-batch:view。 orderIds不传即整团,传了就只处理这几户;两种用法都返回逐户结果。- 逐户结果必须逐行展示:这三个接口都可能部分成功,只看
code=200会误导操作者。 - 三态文案用后端返回的
contractStateText/insuranceStateText,不要前端自己映射枚举。 - 出具与重开有五秒幂等窗口,前端做防抖即可。
五、数据库行为
- 面板读取零写入。
- 手动开 / 重开逐户独立事务:每户单独提交,失败的户不回滚成功的户。
- 四项未配齐时在任何写操作之前拒绝,零写入。
- 重开时先作废后开具,作废失败即停在该户,不会留下「已作废未重开」的中间态。
- 出具、重开、催签各写一条团期操作记录;催签仅在真发出短信时才写。
- 无权限、幂等拦截、参数非法一律零写入。
六、边界行为
- 已取消的户不参与任何动作,也不出现在面板。
- 指定的户不属于本团期时按跳过处理,不整单失败。
- 同团期的出具动作串行,避免并发重复开具。
六.7、影响评估
- 自动出具链路新增了同一道门:团期单在四项配齐前不再自动出具合同与保险, 未配齐时静默跳过(不建待办、不发失败通知)。散客单不受影响。
- 上线依赖部署顺序:
group-batch:contract:issue权限码由用户服务的迁移注册并授予 管理员与超级管理员,必须先部署用户服务再部署订单服务,否则三个写动作除超级管理员外全返 589507。 - 本功能当前实际不可用,原因见文首:酒店就绪标志线上无写入方(HL#4132)。
- 面板读与批量催签不受上述限制。
七、不影响范围
- 散客单(非团期单)的合同保险出具完全不变。
- 单张合同的出具、签署、作废接口不变,本次只加团期级的批量入口。
- 小程序端不受影响。
- 团期成团、名额调整、流团审批等其他动作不受影响。
八、测试环境已验证
TEST 环境已部署用户服务与订单服务并实测:
- 面板读取:55 户的团期返回 55 行逐户明细,计数器与
issuable齐全。 - 空团期读面板:户数 0、明细空数组,不报错。
- 四项未配齐时手动开 → 589548,零写入。
- 四项未配齐时作废重开 → 589548,与手动开同一道门。
- 出具目标非法时同样先被四项配齐门拦下(门先于目标校验)。
- 批量催签指定两户 → 均跳过并给出「该户合同未出具或已签署,无需催签」,零副作用。
- 权限码已生效:读面板通过、三个写动作走到业务校验而非 589507, 说明权限已注册并授予管理员。
尚未验证
- 出具成功的正向路径未在网关侧实测:需要四项配齐的团期,而酒店就绪标志线上无写入方(HL#4132), 该状态在测试环境造不出来。出具成功、逐户独立事务、作废失败不进重开等行为由单元测试覆盖(60 例全绿), 但未经真实环境验证,前端联调时请以实际返回为准。
- 出具目标非法的错误码 589549 因被四项配齐门先拦,同样未在真实环境命中。
十、相关文档
- 工单:HL#7105
- 合并:HL PR#7243
- 后续修正:HL#7248 / PR#7249——出具门原先按团期状态判定,与「合同全签才能进入物资准备」 这道既有门形成死锁,已改为直接判四项就绪标志。本文档描述的是修正后的口径。
- 阻塞项:HL#4132——团期基准变更事件未发出,导致酒店就绪标志无写入方
关联 / 联系人
- 后端:jw
- 前端待接:合同保险 Tab(汇总计数 + 逐户表格 + 三态徽标)、
手动开与作废重开弹窗(目标选择、可选择户、逐户结果展示)、批量催签、
出具类按钮按
issuable置灰并提示原因