--- schema: "hl-changelog/v2" ticket: "8341" title: "团期核单 / 结算 / 反结算同步子订单:进入核单时待核单户进核单中;团期结算即整团财务复核(逐户结算 + 报账单),有逐户核单未提交的户返 589568 并列出订单 ID;反结算时已结算户退回待结算" consumer: "admin" author: "jw(GIT)" change_type: "修改接口" backend_status: "deployed" gateway_status: "verified" frontend_status: "verified" frontend_owner: "mmg" frontend_ref: "d2585f3c766c9ad7e102f7409f1a7419792636dd" target_release: "v2.1" verified_at: "2026-09-24" status_note: "六节点定案(SRS §0.27.7):团期单子订单的核单结算状态由团期驱动,与团期状态变更同一事务、全有全无。① 团期进入核单(POST .../review/start 或首次 POST .../settlement/cost 把团期从 TRIP_FINISHED 推到 REVIEWING):在团户中 flow=PENDING_REVIEW 的户同步为 review IN_PROGRESS / flow REVIEWING,已在核单中或已提交的户不动,幂等命中(alreadyStarted=true / 第二笔成本)不同步。② 团期结算 POST .../settle = 整团财务复核:待结算户逐户做与「财务复核确认结算」相同的写入(settlement COMPLETED / flow SETTLED / settled_at、SETTLEMENT_CONFIRM 日志、推送 BZ 报账单),已结算户跳过;有逐户核单未提交(或定稿后被逐户反确认)的户,整团拒绝 589568「整团核单当前状态不允许该操作:结算需要所有在团订单逐户核单已提交,未提交订单:{订单 ID,「、」分隔}」,零写入。③ 团期反结算 POST .../settle/reopen:已结算户退回 flow PENDING_SETTLE / settlement PENDING、清 settled_at,review 与逐户核单快照不动,已生成的报账单不撤(重新结算按 settlementId 幂等命中)。路径、入参、成功出参均不变;已取消户不受影响。前端需:处理团期结算的 589568 新原因(提示先去对应订单提交逐户核单);团期结算后不必再引导运营逐户点「结算确认」。 前端已交付并验证:实证对 589568 零按码分支(核团域同码不同场景均透 message),验团成功文案无「引导逐户结算确认」残留,透 message 即达标;settle/reopen/review-start JSDoc 补同步语义与 589568 新原因(禁解析订单 ID 自建链接),AuditTab 注释订正。纯文档零行为改动,hl-admin@d2585f3c。" updated_at: "2026-09-24" base: "dev-v3" --- # 团期结算: 核单 / 结算 / 反结算同步子订单(管理后台) > **服务**: hl-order-service-v3(端口 8086/8186) > **PR**: #8345 > **Issue**: #8341 > **日期**: 2026-09-24 > **影响范围**: 管理后台团期详情「核单 / 结算」节点的发起核单、录共享成本、结算、反结算按钮;子订单列表与订单详情里的核单 / 结算状态展示 --- ## ⚠️ 关键变化 1. **团期结算 = 整团财务复核**。点团期「结算」后,在团待结算的户一并变为「已结算」并各生成一张报账单,运营**不再逐户点结算确认**。 2. **团期结算新增一种 589568 拒绝原因**:有户的逐户核单还没提交时,整团拒绝,message 列出这些户的**订单 ID**。 3. **团期反结算会把子订单一并退回待结算**;已生成的报账单保留。 4. **团期进入核单时,待核单的户自动进入核单中**,不必等逐户第一次录核单明细。 以上四个接口的路径、入参、成功出参都不变。 --- ## 一、背景 改前团期的核单、结算、反结算只改团期自身,子订单仍要逐户推进:团期「已结算」而子订单还「待结算」、财务侧没有报账单的情况会出现。SRS §0.27.7 定案子订单跟随团期: | 团期动作 | 子订单同步(同一事务) | |---|---| | 进入核单(`TRIP_FINISHED → REVIEWING`) | `flow PENDING_REVIEW` 的户 → review `IN_PROGRESS` / flow `REVIEWING` | | 逐户核单明细录入与提交 | 照旧(提交后 flow `PENDING_SETTLE`、review `COMPLETED`、settlement `PENDING`) | | 结算(`REVIEWING → SETTLED`) | 待结算户逐户财务复核 → settlement `COMPLETED` / flow `SETTLED` / `settled_at` + 报账单 | | 反结算(`SETTLED → REVIEWING`) | 已结算户 → flow `PENDING_SETTLE` / settlement `PENDING`,清 `settled_at` | --- ## 二、变更接口清单 | # | 接口 | 方法 | 路径 | 变更类型 | 说明 | |---|------|------|------|----------|------| | 1 | 结算归档 | POST | `/v3/admin/order/group-batch/{groupBatchId}/settle` | 修改(新增拒绝原因 + 副作用) | 逐户结算 + 报账单;有逐户核单未提交的户 589568 | | 2 | 结算反确认 | POST | `/v3/admin/order/group-batch/{groupBatchId}/settle/reopen` | 修改(副作用) | 已结算户退回待结算 | | 3 | 发起核单 | POST | `/v3/admin/order/group-batch/{groupBatchId}/review/start` | 修改(副作用) | 首次推进时待核单户进核单中 | | 4 | 录入团期共享成本 | POST | `/v3/admin/order/group-batch/{groupBatchId}/settlement/cost` | 修改(副作用 + 事务) | 首笔成本推进团期时同上同步;整方法改为单事务 | 网关无改动;无新增权限码;无新增错误码;无 DB schema 变更。 --- ## 三、接口详情 ### 1. 结算归档 `POST /v3/admin/order/group-batch/{groupBatchId}/settle` **VO**: `GroupBatchSettleReqVO` → `Result` #### 使用场景 团期详情「核单」节点的「结算」按钮(`REVIEWING → SETTLED`)。本次起同时完成全团子订单的财务复核,逐户生成报账单。 #### 入参 | 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | |------|------|------|------|------|------| | groupBatchId | Path | Long | ✅ | 团期主键 | 不变 | | checkNote | Body | String | 否 | - | 结算意见(请求体整体可省略,不变);本次起也写进逐户财务复核日志的备注 | #### 出参 | 字段 | 类型 | 说明 | |------|------|------| | data | Void | 成功返回 null(不变) | #### 请求示例 ```json { "checkNote": "成本已逐项核对" } ``` #### 响应示例 ```json { "code": 200, "message": "成功", "data": null, "success": true } ``` #### 空数据 / 降级响应 无列表出参。团期没有在团子订单时跳过子订单同步,团期照常结算: ```json { "code": 200, "message": "成功", "data": null, "success": true } ``` #### 错误响应 有在团户逐户核单未提交(本次新增的原因,码值与前缀不变): ```json { "code": 589568, "message": "整团核单当前状态不允许该操作:结算需要所有在团订单逐户核单已提交,未提交订单:2103023889187799042", "success": false, "data": null } ``` 已结算归档(不变): ```json { "code": 589555, "message": "该团期已结算归档,不可重复结算", "success": false, "data": null } ``` #### 业务边界 - 判定顺序:判权(589507)→ 团期状态(589555 / 589501)→ 整团核单前置(589567 / 589568,不变)→ 团期 CAS → **子订单同步** → 时间线。 - 在团户分三类:待结算(flow `PENDING_SETTLE` + settlement `PENDING` + review `COMPLETED`)逐户复核;已结算(flow `SETTLED` + settlement `COMPLETED`)跳过、不重复推报账单;其余一律视为「逐户核单未提交」→ 589568。 - 589568 在任何逐户写入之前判,拒绝时零写入;列出的是**订单 ID**(不是订单号),以「、」分隔。 - 逐户复核的写入与副作用与 `POST /v3/admin/order/{orderId}/settlement/confirm`(财务复核)同源;任一户复核失败,其业务码原样返回,整团回滚(含已推的报账单)。 - 团期结算不对每户再做订单数据范围判定:财务角色结算整团不会被逐户 581008 挡住;判权仍是团期结算原有的角色判据。 - 已取消户不在「在团户」内,全程不受影响。 --- ### 2. 结算反确认 `POST /v3/admin/order/group-batch/{groupBatchId}/settle/reopen` **VO**: `Result`(无请求体) #### 使用场景 团期详情「结算」节点的「反结算」按钮(`SETTLED → REVIEWING`)。本次起已结算的子订单一并退回待结算。 #### 入参 | 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | |------|------|------|------|------|------| | groupBatchId | Path | Long | ✅ | 团期主键 | 不变 | 无请求体(不变)。 #### 出参 | 字段 | 类型 | 说明 | |------|------|------| | data | Void | 成功返回 null(不变) | #### 请求示例 ```http POST /v3/admin/order/group-batch/2097250563497385985/settle/reopen HTTP/1.1 Host: api.test.1814.love:9443 Authorization: Bearer ``` #### 响应示例 ```json { "code": 200, "message": "成功", "data": null, "success": true } ``` #### 空数据 / 降级响应 无列表出参;没有已结算子订单时只退团期,不报错: ```json { "code": 200, "message": "成功", "data": null, "success": true } ``` #### 错误响应 团期当前不是已结算(不变): ```json { "code": 589501, "message": "团期状态不允许当前操作", "success": false, "data": null } ``` #### 业务边界 - 只退「财务复核」这一步:flow `SETTLED → PENDING_SETTLE`、settlement `COMPLETED → PENDING`、清 `settled_at`;review 保持 `COMPLETED`,逐户核单终态快照与核单汇总不动。 - 不在已结算的户(未结算 / 已被逐户反确认)不命中,不写。 - **已生成的报账单不撤**;重新结算时按核单汇总 ID 幂等命中既有报账单,不重复生成。反结算期间出纳仍可能处理这些报账单。 - 本接口未新增错误码。 --- ### 3. 发起核单 `POST /v3/admin/order/group-batch/{groupBatchId}/review/start` **VO**: `GroupBatchReviewStartRespVO` #### 使用场景 团期详情「出行完毕」节点的「发起核单」按钮(`TRIP_FINISHED → REVIEWING`)。本次起首次推进时同事务把待核单的户带进核单中。 #### 入参 | 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | |------|------|------|------|------|------| | groupBatchId | Path | Long | ✅ | 团期主键 | 不变 | 无请求体(不变)。 #### 出参 | 字段 | 类型 | 说明 | |------|------|------| | groupBatchId | String | 团期主订单 ID(不变) | | batchStatus | String | 恒为 `REVIEWING`(不变) | | batchStatusDesc | String | 恒为「核单中」(不变) | | alreadyStarted | Boolean | true = 幂等命中,零写入(不变;本次起幂等命中也不同步子订单) | #### 请求示例 ```http POST /v3/admin/order/group-batch/2097250563497385985/review/start HTTP/1.1 Host: api.test.1814.love:9443 Authorization: Bearer ``` #### 响应示例 ```json { "code": 200, "message": "成功", "data": { "groupBatchId": "2097250563497385985", "batchStatus": "REVIEWING", "batchStatusDesc": "核单中", "alreadyStarted": false }, "success": true } ``` #### 空数据 / 降级响应 重复点击返回 `alreadyStarted=true`,团期与子订单都不写(不变): ```json { "code": 200, "message": "成功", "data": { "groupBatchId": "2097250563497385985", "batchStatus": "REVIEWING", "batchStatusDesc": "核单中", "alreadyStarted": true }, "success": true } ``` #### 错误响应 团期不在出行完毕(不变): ```json { "code": 589564, "message": "当前团期状态不可发起核单(须为「出行完毕」)", "success": false, "data": null } ``` #### 业务边界 - 只推 flow `PENDING_REVIEW` 的户(review `PENDING` / `IN_PROGRESS`、settlement 为空或 `NONE`);已在核单中、已提交、已结算的户不动。 - flow 仍早于待核单的户(如未完成出行同步的存量户)跳过并记日志,不阻断团期进入核单;这些户日后第一次录核单明细时仍会自己进入核单中。 - 权限码 `group-batch:finance:advance` 与各错误码(589507 / 589564 / 589565 / 589566)不变。 --- ### 4. 录入团期共享成本 `POST /v3/admin/order/group-batch/{groupBatchId}/settlement/cost` **VO**: `RecordBatchCostReqVO` → `Result` #### 使用场景 团期核单页录大巴 / 领队 / 摄影等共享成本。团期在出行完毕时录第一笔会顺带把团期推进核单中;本次起这一推进同时同步子订单(与接口 3 同一逻辑)。 #### 入参 | 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | |------|------|------|------|------|------| | groupBatchId | Path | Long | ✅ | 团期主键 | 不变 | | costType | Body | String | ✅ | `BUS` / `LEADER` / `PHOTOGRAPHER` / `OTHER` | 不变 | | amount | Body | BigDecimal | ✅ | ≥ 0 | 不变 | | source | Body | String | 否 | `MANUAL` / `FLEET_CALLBACK` | 默认 `MANUAL`(不变) | | sourceRefNo | Body | String | 否 | `FLEET_CALLBACK` 时必填 | 幂等键(不变) | | remark | Body | String | 否 | ≤ 255 字符 | 不变 | #### 出参 | 字段 | 类型 | 说明 | |------|------|------| | data | Long | 新成本明细 ID(不变) | #### 请求示例 ```json { "costType": "BUS", "amount": 1200.00, "source": "MANUAL", "remark": "大巴费用" } ``` #### 响应示例 ```json { "code": 200, "message": "成功", "data": 90412, "success": true } ``` #### 空数据 / 降级响应 第二笔及以后的成本(团期已在核单中)只插明细,不再同步子订单(不变的推进口径): ```json { "code": 200, "message": "成功", "data": 90413, "success": true } ``` #### 错误响应 团期不在可录成本的状态(不变): ```json { "code": 589501, "message": "团期状态不允许当前操作", "success": false, "data": null } ``` #### 业务边界 - 本接口由「无事务」改为整方法单事务:团期推进、子订单同步、明细插入全有全无(改前 CAS 与 INSERT 各自提交,插入失败时团期可能已被推进)。 - 权限码 `group-batch:finance:advance` 与错误码不变。 --- ## 四、契约约束与正确调用方式 ### ✅ 正确 / ❌ 错误调用对照 | 场景 | 调用 | |------|------| | ✅ 团期结算 | 各户提交逐户核单 → 整团核单提交核算(ALLOCATED)→ `settle`,子订单一并结算 | | ❌ 有户逐户核单未提交就结算 | `settle` → 589568「…未提交订单:{订单 ID}」 | | ❌ 团期结算后再逐户点「结算确认」 | 不再需要;已结算户逐户复核会被既有状态门拒绝 | | ✅ 结算后发现问题 | `settle/reopen` → 改整团核单 → 重新 `settle`(报账单幂等,不重复) | ### 589568 的区分 589568 码值同时用于「整团核单状态不对」与本次新增的「逐户核单未提交」。前端可直接展示 message;如要分支,message 含「未提交订单:」即为本次新增原因,出路是去对应订单提交逐户核单。 --- ## 五、数据库行为 | 前端动作 | 外部可观察的写入(同一事务) | |----------|------------------| | `review/start` 首次成功 / 首笔 `settlement/cost` | 团期 `REVIEWING` + 待核单户 review `IN_PROGRESS` / flow `REVIEWING`(CAS,按行) | | `review/start` 幂等命中 | 零写入 | | `settle` 成功 | 核单 `CHECKED` + 团期 `SETTLED` + 待结算户 settlement `COMPLETED` / flow `SETTLED` / `settled_at`、订单日志 `SETTLEMENT_CONFIRM`(备注「团期结算(整团财务复核)[:结算意见]」)、每户一张 BZ 报账单 | | `settle` 被 589568 拒 | 零写入 | | `settle/reopen` 成功 | 团期 `REVIEWING` + 核单退回 + 已结算户 flow `PENDING_SETTLE` / settlement `PENDING` / 清 `settled_at`;报账单保留 | 无 schema 变更。 --- ## 六、边界行为 - 未登录 → 401(网关拦截);团期不存在 → 589500。 - 已取消户全程不受影响;重复触发不重复写(各写入均带状态 CAS)。 - 二次结算(反结算后再结算)报账单不重复、`settlement_id` 不变。 - 首次结算后逐户反确认会被 584330(报账单已进入出纳)拒绝(既有行为,不变)。 - 逐户财务复核入口对团期单仍开放;已被逐户复核过的户在团期结算时跳过。 --- ## 六.6、修改前后对比 ### 字段级对比 | 字段 | 改前 | 改后 | |------|------|------| | `settle` 589568 message | 仅整团核单状态类原因 | 新增「结算需要所有在团订单逐户核单已提交,未提交订单:{订单 ID}」 | | 子订单 `settlement_status` / `flow_status` / `settled_at`(团期结算后) | 不变(待结算) | `COMPLETED` / `SETTLED` / 结算时间 | | 子订单 review / flow(团期进入核单后) | `PENDING` / `PENDING_REVIEW` | `IN_PROGRESS` / `REVIEWING` | ### 行为级对比 | 行为 | 改前 | 改后 | |------|------|------| | 团期结算 | 只改团期与整团核单 | 另做整团财务复核,逐户结算 + 报账单 | | 团期反结算 | 只改团期与整团核单 | 另把已结算户退回待结算 | | 团期进入核单 | 不碰子订单 | 待核单户进核单中 | | 录共享成本事务 | 无事务 | 单事务 | ## 六.7、影响评估 - **是否破坏向后兼容**: 部分。路径、入参、成功出参不变;`settle` 多一种 589568 原因,逐户核单未提交的团原先能结算、现在被拒。 - **前端是否必须同步上线**: 否。后端已自行完成子订单同步;前端未改时 589568 按原样弹 message 即可理解。 - **前端 workaround 清理点**: 团期结算后引导运营逐户「结算确认」的提示可移除;子订单列表刷新即可看到已结算。 --- ## 七、不影响范围 - **仅影响**: 团期发起核单、首笔共享成本、结算、反结算四个动作对子订单的联动。 - **零影响**: - 逐户核单明细录入与提交(`/v3/admin/order/{orderId}/settlement/**`) - 逐户财务复核 `POST /v3/admin/order/{orderId}/settlement/confirm` 的入参出参与判权 - 整团核单(核算、分摊)各端点 - 散客单的核单结算 - 小程序端 --- ## 八、测试环境已验证 部署:hl-order-service-v3 = dev-v3 @ f55840cb5(含 PR #8345),TEST 环境实测 2026-09-24 16:05–16:11(证据见工单 #8341 验收评论);工单 #8341 已验收关单。 | # | 场景 | 结果 | |---|---|---| | 1 | 团期进入核单 | 活跃户 review `IN_PROGRESS` / flow `REVIEWING`;重复调用状态不变 | | 2 | 逐户核单定稿 + 整团核单 ALLOCATED 后团期结算 | 四户 settlement `COMPLETED` / flow `SETTLED` / `settled_at`;报账单每户 1 张(BZ-202609240001~0004) | | 3 | 团期反结算 | 四户回 flow `PENDING_SETTLE` / settlement `PENDING`,`settled_at` 清空;报账单保留 | | 4 | 已取消户 / 二次结算 | 已取消户全程未动;二次结算报账单不重复、`settlement_id` 不变 | | 5 | 逐户核单未提交时整团结算(SQL 构造) | 589568「…未提交订单:2103023889187799042」,整团回滚 | 本地:相关单测 / H2 IT / ArchUnit 314 例 0 失败;Docker IT `GroupBatchAuditVersionCasMySqlIT` 10 例、`FlowStatusP1cIntegrationTest` 4 例 0 失败。 --- ## 九、相关历史 PR | PR | Issue | 说明 | 是否仍有效 | |----|-------|------|------------| | — | #7456 | 团期结算归档 / 反确认端点 | ✅ 端点不变,本单加子订单联动 | | — | #7527 | 发起核单端点 | ✅ 端点不变,本单加子订单联动 | | **本 PR #8345** | **#8341** | 核单 / 结算 / 反结算同步子订单 | ✅ 最新 | --- ## 十、相关文档 - 关联 Issue: [wx/HL#8341](https://git.1814.love/wx/HL/issues/8341) - 关联 PR: [wx/HL#8345](https://git.1814.love/wx/HL/pulls/8345) - 定案文档:SRS §0.27.7(PR #8338) - 同批条目:团期确认联动子订单确认行程(#8339) ## 关联 / 联系人 ### 链接 - **Issue**: [#8341](https://git.1814.love/wx/HL/issues/8341) - **PR**: [#8345](https://git.1814.love/wx/HL/pulls/8345) - **Merge commit**: [454e2da4b](https://git.1814.love/wx/HL/commit/454e2da4ba347d45c25834f12d290d7689b85a3a) ### 联系人 - **后端负责人**: @jw