# 【修改接口·管理后台】核单原型流迁移 (#5219) > **PR**: #5243 | **服务**: hl-order-service-v3 | **更新时间**: 2026-07-25 16:08 ## 1. 接口背景 核单流程从旧的“财务总览/优惠逐条确认后直接 Step6 提交”调整为原型流:先确认 8 个核单分类,再生成并确认主报账人报账表,再生成并确认单团核算表,最后完成核单。 ## 2. 变更清单 | # | 接口 | 方法 | 路径 | 变更类型 | 说明 | |---|------|------|------|----------|------| | 1 | 查询八分类确认状态 | GET | `/v3/admin/order/{orderId}/settlement/category-checks` | 新增 | 返回 8 个核单分类的确认状态、行数和来源指纹 | | 2 | 确认单个核单分类 | POST | `/v3/admin/order/{orderId}/settlement/category-checks/{category}/confirm` | 新增 | 用最近读取的分类指纹确认单个分类 | | 3 | 查询主报账人报账表 | GET | `/v3/admin/order/{orderId}/settlement/reports/reimbursement` | 新增 | 查询主报账表当前快照 | | 4 | 生成主报账人报账表 | POST | `/v3/admin/order/{orderId}/settlement/reports/reimbursement/generate` | 新增 | 8 分类全部确认后生成 | | 5 | 确认主报账人报账表 | POST | `/v3/admin/order/{orderId}/settlement/reports/reimbursement/confirm` | 新增 | 确认转账、预支和签字单 | | 6 | 查询单团核算表 | GET | `/v3/admin/order/{orderId}/settlement/reports/group` | 新增 | 查询单团核算表当前快照 | | 7 | 生成单团核算表 | POST | `/v3/admin/order/{orderId}/settlement/reports/group/generate` | 新增 | 主报账表确认后生成 | | 8 | 确认单团核算表 | POST | `/v3/admin/order/{orderId}/settlement/reports/group/confirm` | 新增 | 用最近读取的报告指纹确认 | | 9 | 完成核单 | POST | `/v3/admin/order/{orderId}/settlement/finalize` | 新增 | 单团核算表确认后提交核单 | | 10 | 查询核单应收财务总览 | GET | `/v3/admin/order/{orderId}/settlement/financial-overview` | 修改 | 仍保留查询,但不再返回确认状态/指纹 | | 11 | 确认财务总览 | POST | `/v3/admin/order/{orderId}/settlement/financial-overview/confirm` | 删除 | 旧 POST 契约下线 | | 12 | 确认单条优惠 | POST | `/v3/admin/order/{orderId}/settlement/financial-overview/discounts/{discountId}/confirm` | 删除 | 旧 POST 契约下线 | ## 3. 接口详情 ### 3.1 查询八分类确认状态 - **方法 + 路径**: `GET /v3/admin/order/{orderId}/settlement/category-checks` - **接口名**: 查询原型八个核单分类确认状态 - **认证**: 管理后台 JWT;房务角色不可访问 - **幂等性**: 幂等,只读 - **限流**: 无接口级特殊限流 **路径参数** | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | `orderId` | Long/String | 是 | 订单 ID,必须大于 0 | **请求体**: 无 **响应字段** | 字段 | 类型 | 说明 | |------|------|------| | `orderId` | String | 订单 ID | | `allConfirmed` | Boolean | 8 个分类是否全部已确认 | | `items` | Array | 分类确认项 | | `items[].category` | String | 分类枚举,见 §6.1 | | `items[].categoryName` | String | 分类中文名 | | `items[].rowCount` | Integer | 当前分类参与核单的行数 | | `items[].empty` | Boolean | 当前分类是否为空 | | `items[].sourceFingerprint` | String | 当前分类来源事实 SHA-256 | | `items[].confirmStatus` | String | `UNCONFIRMED` / `CONFIRMED` | | `items[].confirmedBy` | String/null | 确认人 ID | | `items[].confirmedByName` | String/null | 确认人名称 | | `items[].confirmedAt` | String/null | 确认时间,未确认时为 null | **错误码** | code | 含义 | 触发场景 | |------|------|----------| | 400 | 参数校验失败 | `orderId <= 0` | | 403 | 无访问权限 | 房务角色访问 | **典型成功示例** 请求: ```http GET /v3/admin/order/1914050000000001/settlement/category-checks Authorization: Bearer ``` 响应: ```json { "code": 200, "msg": "success", "data": { "orderId": "1914050000000001", "allConfirmed": false, "items": [ { "category": "HOTEL", "categoryName": "住宿", "rowCount": 2, "empty": false, "sourceFingerprint": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "confirmStatus": "CONFIRMED", "confirmedBy": "10001", "confirmedByName": "张三", "confirmedAt": "2026-07-25T15:20:00" }, { "category": "OTHER_EXPENSE", "categoryName": "其他支出", "rowCount": 0, "empty": true, "sourceFingerprint": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", "confirmStatus": "UNCONFIRMED", "confirmedBy": null, "confirmedByName": null, "confirmedAt": null } ] } } ``` **边界示例** 请求: ```http GET /v3/admin/order/1914050000000001/settlement/category-checks Authorization: Bearer ``` 响应: ```json { "code": 200, "msg": "success", "data": { "orderId": "1914050000000001", "allConfirmed": false, "items": [ { "category": "MEAL", "categoryName": "餐食", "rowCount": 0, "empty": true, "sourceFingerprint": "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc", "confirmStatus": "UNCONFIRMED", "confirmedBy": null, "confirmedByName": null, "confirmedAt": null } ] } } ``` **异常示例** 请求: ```http GET /v3/admin/order/0/settlement/category-checks Authorization: Bearer ``` 响应: ```json { "code": 400, "msg": "订单 ID 必须大于 0", "data": null } ``` **业务边界** - 适用:进入核单流程后读取 8 个分类状态。 - 不适用:房务角色不可查看。 - 特殊边界:空分类仍需要走显式确认,不能因为 `rowCount=0` 自动通过。 ### 3.2 确认单个核单分类 - **方法 + 路径**: `POST /v3/admin/order/{orderId}/settlement/category-checks/{category}/confirm` - **接口名**: 按最近读取指纹确认单个核单分类 - **认证**: 管理后台 JWT;需要财务写权限;房务角色不可访问 - **幂等性**: 非纯幂等;相同指纹重复确认不会改变来源事实 - **限流**: 无接口级特殊限流 **路径参数** | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | `orderId` | Long/String | 是 | 订单 ID,必须大于 0 | | `category` | String | 是 | 分类枚举,大小写不敏感,见 §6.1 | **请求体字段** | 字段 | 类型 | 必填 | 说明 | 校验规则 | |------|------|------|------|----------| | `expectedSourceFingerprint` | String | 是 | 最近读取的分类来源事实 SHA-256 | 64 位小写十六进制 | | `confirmEmpty` | Boolean | 是 | 是否明确确认空分类 | 空分类传 `true`,非空分类传 `false` | **响应字段**: 同 §3.1 `items[]` 单项。 **错误码** | code | 含义 | 触发场景 | |------|------|----------| | 400 | 参数校验失败 | 指纹不是 64 位十六进制、缺少 `confirmEmpty` | | 584315 | 核单来源数据已变化,请刷新后重新生成 | 前端传入的指纹与当前分类来源不一致 | | 584318 | 空分类必须显式确认,非空分类不得按空分类确认 | `confirmEmpty` 与当前分类是否为空不匹配 | | 584319 | 核单存在未知分类或历史迁移数据不完整 | `category` 不是 8 分类枚举 | **典型成功示例** 请求: ```http POST /v3/admin/order/1914050000000001/settlement/category-checks/HOTEL/confirm Authorization: Bearer Content-Type: application/json { "expectedSourceFingerprint": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "confirmEmpty": false } ``` 响应: ```json { "code": 200, "msg": "success", "data": { "category": "HOTEL", "categoryName": "住宿", "rowCount": 2, "empty": false, "sourceFingerprint": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "confirmStatus": "CONFIRMED", "confirmedBy": "10001", "confirmedByName": "张三", "confirmedAt": "2026-07-25T15:24:00" } } ``` **边界示例** 请求: ```http POST /v3/admin/order/1914050000000001/settlement/category-checks/MEAL/confirm Authorization: Bearer Content-Type: application/json { "expectedSourceFingerprint": "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc", "confirmEmpty": true } ``` 响应: ```json { "code": 200, "msg": "success", "data": { "category": "MEAL", "categoryName": "餐食", "rowCount": 0, "empty": true, "sourceFingerprint": "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc", "confirmStatus": "CONFIRMED", "confirmedBy": "10001", "confirmedByName": "张三", "confirmedAt": "2026-07-25T15:25:00" } } ``` **异常示例** 请求: ```http POST /v3/admin/order/1914050000000001/settlement/category-checks/UNKNOWN/confirm Authorization: Bearer Content-Type: application/json { "expectedSourceFingerprint": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "confirmEmpty": false } ``` 响应: ```json { "code": 584319, "msg": "核单存在未知分类或历史迁移数据不完整", "data": null } ``` **业务边界** - 适用:确认当前分类数据没有变化。 - 不适用:前端未先读取 `sourceFingerprint` 时不应直接确认。 - 特殊边界:`OTHER_INCOME` 确认前会校验其他收入可提交状态。 ### 3.3 主报账人报账表 - **查询**: `GET /v3/admin/order/{orderId}/settlement/reports/reimbursement` - **生成**: `POST /v3/admin/order/{orderId}/settlement/reports/reimbursement/generate` - **确认**: `POST /v3/admin/order/{orderId}/settlement/reports/reimbursement/confirm` - **接口名**: 查询主报账人报账表 / 八分类确认后生成主报账人报账表 / 完成转账、预支及签字门禁后确认主报账人报账表 - **认证**: 管理后台 JWT;房务角色不可访问 - **幂等性**: 查询幂等;生成/确认会更新报告状态和确认信息 - **限流**: 无接口级特殊限流 **路径参数** | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | `orderId` | Long/String | 是 | 订单 ID,必须大于 0 | **确认请求体字段** | 字段 | 类型 | 必填 | 说明 | 校验规则 | |------|------|------|------|----------| | `expectedSourceFingerprint` | String | 是 | 最近读取的主报账表来源 SHA-256 | 64 位小写十六进制 | | `transferStatus` | String | 否 | 转账状态 | 后端按字符串保存 | | `transferDate` | String/date | 否 | 转账日期 | `YYYY-MM-DD` | | `transferRef` | String | 否 | 转账流水/备注 | 字符串 | | `advanceSettledFlag` | Boolean | 是 | 预支是否已结清 | 不能为空 | | `signedVoucher` | Object | 是 | 签字单回收信息 | 不能为空 | | `signedVoucher.files` | Array | 否 | 文件列表 | 元素见下 | | `signedVoucher.files[].name` | String | 否 | 文件名 | 字符串 | | `signedVoucher.files[].url` | String | 否 | OSS 文件地址 | 字符串 | | `signedVoucher.note` | String | 否 | 备注 | 字符串 | **响应字段** | 字段 | 类型 | 说明 | |------|------|------| | `id` | String/null | 主报账记录 ID;未生成时可为 null | | `orderId` | String | 订单 ID | | `reportStatus` | String | 报告状态,见 §6.2 | | `sourceFingerprint` | String | 当前来源 SHA-256 | | `primaryReporterId` | String/null | 主报账人 ID | | `primaryReporterName` | String/null | 主报账人姓名 | | `primaryReporterRole` | String/null | 主报账人角色 | | `primaryReporterCollectedAmount` | Decimal | 主报账人代收金额 | | `publicPrepaidAmount` | Decimal | 公共预支金额 | | `primaryReporterDueAmount` | Decimal | 主报账人应报账金额 | | `advanceOutstandingAmount` | Decimal | 未结清预支金额 | | `reconNetAmount` | Decimal | 报账净额 | | `transferDirection` | String/null | 转账方向 | | `transferAmount` | Decimal | 转账金额 | | `incomeLines` | Array | 收入明细行 | | `expenseLines` | Array | 支出明细行 | | `advanceLines` | Array | 预支明细行 | | `transferStatus` | String/null | 转账状态 | | `transferDate` | String/date/null | 转账日期 | | `transferRef` | String/null | 转账流水/备注 | | `advanceSettledFlag` | Boolean/null | 预支是否已结清 | | `signedVoucher` | Object/null | 签字单信息 | | `generatedBy` / `generatedByName` / `generatedAt` | String/String/String | 生成信息 | | `confirmedBy` / `confirmedByName` / `confirmedAt` | String/String/String | 确认信息 | **错误码** | code | 含义 | 触发场景 | |------|------|----------| | 584310 | 八个核单分类尚未全部确认或数据已变化 | 生成主报账表前 8 分类未全部确认或指纹已失效 | | 584311 | 主报账表尚未生成 | 查询/确认时还没有可用主报账表 | | 584315 | 核单来源数据已变化,请刷新后重新生成 | 确认时传入的报告指纹已过期 | | 584316 | 核单报告发生并发变化,请刷新后重试 | 并发确认冲突 | | 584317 | 当前报告状态不允许执行该操作 | 当前状态不能生成或确认 | **典型成功示例** 请求: ```http POST /v3/admin/order/1914050000000001/settlement/reports/reimbursement/confirm Authorization: Bearer Content-Type: application/json { "expectedSourceFingerprint": "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd", "transferStatus": "TRANSFERRED", "transferDate": "2026-07-25", "transferRef": "BANK-20260725-001", "advanceSettledFlag": true, "signedVoucher": { "files": [ { "name": "签字单.pdf", "url": "https://oss.example.com/voucher.pdf" } ], "note": "签字单已回收" } } ``` 响应: ```json { "code": 200, "msg": "success", "data": { "id": "9200000000001", "orderId": "1914050000000001", "reportStatus": "CONFIRMED", "sourceFingerprint": "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd", "primaryReporterId": "3001", "primaryReporterName": "王司机", "primaryReporterRole": "DRIVER", "primaryReporterCollectedAmount": 2000.00, "publicPrepaidAmount": 500.00, "primaryReporterDueAmount": 1500.00, "advanceOutstandingAmount": 0.00, "reconNetAmount": 1500.00, "transferDirection": "COMPANY_TO_REPORTER", "transferAmount": 1500.00, "incomeLines": [], "expenseLines": [], "advanceLines": [], "transferStatus": "TRANSFERRED", "transferDate": "2026-07-25", "transferRef": "BANK-20260725-001", "advanceSettledFlag": true, "signedVoucher": { "files": [ { "name": "签字单.pdf", "url": "https://oss.example.com/voucher.pdf" } ], "note": "签字单已回收" }, "generatedBy": "10001", "generatedByName": "张三", "generatedAt": "2026-07-25T15:30:00", "confirmedBy": "10001", "confirmedByName": "张三", "confirmedAt": "2026-07-25T15:35:00" } } ``` **边界示例** 请求: ```http GET /v3/admin/order/1914050000000001/settlement/reports/reimbursement Authorization: Bearer ``` 响应: ```json { "code": 200, "msg": "success", "data": { "id": "9200000000001", "orderId": "1914050000000001", "reportStatus": "STALE", "sourceFingerprint": "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", "primaryReporterId": null, "primaryReporterName": null, "primaryReporterRole": null, "primaryReporterCollectedAmount": 0.00, "publicPrepaidAmount": 0.00, "primaryReporterDueAmount": 0.00, "advanceOutstandingAmount": 0.00, "reconNetAmount": 0.00, "transferDirection": null, "transferAmount": 0.00, "incomeLines": [], "expenseLines": [], "advanceLines": [], "transferStatus": null, "transferDate": null, "transferRef": null, "advanceSettledFlag": null, "signedVoucher": null, "generatedBy": "10001", "generatedByName": "张三", "generatedAt": "2026-07-25T15:30:00", "confirmedBy": null, "confirmedByName": null, "confirmedAt": null } } ``` **异常示例** 请求: ```http POST /v3/admin/order/1914050000000001/settlement/reports/reimbursement/generate Authorization: Bearer ``` 响应: ```json { "code": 584310, "msg": "八个核单分类尚未全部确认或数据已变化", "data": null } ``` **业务边界** - 适用:8 分类全部确认后生成主报账表。 - 不适用:8 分类未确认完成时不能生成。 - 特殊边界:查询返回 `STALE` 时表示来源已变化,应重新生成后再确认。 ### 3.4 单团核算表 - **查询**: `GET /v3/admin/order/{orderId}/settlement/reports/group` - **生成**: `POST /v3/admin/order/{orderId}/settlement/reports/group/generate` - **确认**: `POST /v3/admin/order/{orderId}/settlement/reports/group/confirm` - **接口名**: 查询单团核算表 / 主报账表确认后生成单团核算表 / 按最近读取指纹确认单团核算表 - **认证**: 管理后台 JWT;房务角色不可访问 - **幂等性**: 查询幂等;生成/确认会更新报告状态和确认信息 - **限流**: 无接口级特殊限流 **路径参数** | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | `orderId` | Long/String | 是 | 订单 ID,必须大于 0 | **确认请求体字段** | 字段 | 类型 | 必填 | 说明 | 校验规则 | |------|------|------|------|----------| | `expectedSourceFingerprint` | String | 是 | 最近读取的单团核算表来源 SHA-256 | 64 位小写十六进制 | **响应字段** | 字段 | 类型 | 说明 | |------|------|------| | `id` | String/null | 单团核算记录 ID | | `orderId` | String | 订单 ID | | `reportStatus` | String | 报告状态,见 §6.2 | | `sourceFingerprint` | String | 当前来源 SHA-256 | | `baseOrderAmount` | Decimal | 订单基础金额 | | `otherIncomeAmount` | Decimal | 其他收入金额 | | `discountAmount` | Decimal | 优惠金额 | | `adjustedReceivableAmount` | Decimal | 调整后应收 | | `paidAmount` | Decimal | 已收金额 | | `actualRefundedAmount` | Decimal | 实际退款金额 | | `netRevenueAmount` | Decimal | 净收入 | | `netReceivedAmount` | Decimal | 净已收 | | `outstandingAmount` | Decimal | 待收金额 | | `hotelCost` | Decimal | 住宿成本 | | `ticketCost` | Decimal | 门票/游玩项目成本 | | `mealCost` | Decimal | 餐食成本 | | `vehicleCost` | Decimal | 车辆成本 | | `guideCost` | Decimal | 导游成本 | | `photographerCost` | Decimal | 摄影成本 | | `otherExpenseCost` | Decimal | 其他支出成本 | | `insurancePremium` | Decimal | 保险保费 | | `totalCost` | Decimal | 总成本 | | `paidCost` | Decimal | 已付成本 | | `unpaidCost` | Decimal | 未付成本 | | `grossProfit` | Decimal | 毛利 | | `grossProfitRate` | Decimal | 毛利率 | | `travelerCount` | Integer | 出行人数 | | `perCapitaRevenue` | Decimal | 人均收入 | | `perCapitaCost` | Decimal | 人均成本 | | `perCapitaProfit` | Decimal | 人均利润 | | `incomeLines` | Array | 收入明细行 | | `costCategories` | Array | 成本分类行 | | `generatedBy` / `generatedByName` / `generatedAt` | String/String/String | 生成信息 | | `confirmedBy` / `confirmedByName` / `confirmedAt` | String/String/String | 确认信息 | **错误码** | code | 含义 | 触发场景 | |------|------|----------| | 584312 | 主报账表尚未确认或数据已变化 | 生成单团核算表前主报账表未确认或已过期 | | 584313 | 单团核算表尚未生成 | 查询/确认时还没有可用单团核算表 | | 584315 | 核单来源数据已变化,请刷新后重新生成 | 确认时传入的报告指纹已过期 | | 584316 | 核单报告发生并发变化,请刷新后重试 | 并发确认冲突 | | 584317 | 当前报告状态不允许执行该操作 | 当前状态不能生成或确认 | **典型成功示例** 请求: ```http POST /v3/admin/order/1914050000000001/settlement/reports/group/confirm Authorization: Bearer Content-Type: application/json { "expectedSourceFingerprint": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" } ``` 响应: ```json { "code": 200, "msg": "success", "data": { "id": "9300000000001", "orderId": "1914050000000001", "reportStatus": "CONFIRMED", "sourceFingerprint": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "baseOrderAmount": 24800.00, "otherIncomeAmount": 500.00, "discountAmount": 300.00, "adjustedReceivableAmount": 25000.00, "paidAmount": 25000.00, "actualRefundedAmount": 0.00, "netRevenueAmount": 25000.00, "netReceivedAmount": 25000.00, "outstandingAmount": 0.00, "hotelCost": 4280.00, "ticketCost": 3680.00, "mealCost": 860.00, "vehicleCost": 1260.00, "guideCost": 800.00, "photographerCost": 600.00, "otherExpenseCost": 300.00, "insurancePremium": 180.00, "totalCost": 11960.00, "paidCost": 11960.00, "unpaidCost": 0.00, "grossProfit": 13040.00, "grossProfitRate": 0.521600, "travelerCount": 5, "perCapitaRevenue": 5000.00, "perCapitaCost": 2392.00, "perCapitaProfit": 2608.00, "incomeLines": [], "costCategories": [], "generatedBy": "10001", "generatedByName": "张三", "generatedAt": "2026-07-25T15:40:00", "confirmedBy": "10001", "confirmedByName": "张三", "confirmedAt": "2026-07-25T15:45:00" } } ``` **边界示例** 请求: ```http GET /v3/admin/order/1914050000000001/settlement/reports/group Authorization: Bearer ``` 响应: ```json { "code": 200, "msg": "success", "data": { "id": "9300000000001", "orderId": "1914050000000001", "reportStatus": "GENERATED", "sourceFingerprint": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "baseOrderAmount": 0.00, "otherIncomeAmount": 0.00, "discountAmount": 0.00, "adjustedReceivableAmount": 0.00, "paidAmount": 0.00, "actualRefundedAmount": 0.00, "netRevenueAmount": 0.00, "netReceivedAmount": 0.00, "outstandingAmount": 0.00, "hotelCost": 0.00, "ticketCost": 0.00, "mealCost": 0.00, "vehicleCost": 0.00, "guideCost": 0.00, "photographerCost": 0.00, "otherExpenseCost": 0.00, "insurancePremium": 0.00, "totalCost": 0.00, "paidCost": 0.00, "unpaidCost": 0.00, "grossProfit": 0.00, "grossProfitRate": 0.000000, "travelerCount": 0, "perCapitaRevenue": 0.00, "perCapitaCost": 0.00, "perCapitaProfit": 0.00, "incomeLines": [], "costCategories": [], "generatedBy": "10001", "generatedByName": "张三", "generatedAt": "2026-07-25T15:40:00", "confirmedBy": null, "confirmedByName": null, "confirmedAt": null } } ``` **异常示例** 请求: ```http POST /v3/admin/order/1914050000000001/settlement/reports/group/generate Authorization: Bearer ``` 响应: ```json { "code": 584312, "msg": "主报账表尚未确认或数据已变化", "data": null } ``` **业务边界** - 适用:主报账表已确认后生成单团核算表。 - 不适用:主报账表未确认或为 `STALE` 时不能生成。 - 特殊边界:金额为 0 时仍返回完整字段,数组字段为空数组。 ### 3.5 完成核单 - **方法 + 路径**: `POST /v3/admin/order/{orderId}/settlement/finalize` - **接口名**: 单团核算表确认后完成核单 - **认证**: 管理后台 JWT;房务角色不可访问 - **幂等性**: 非幂等;完成后订单进入核单提交后的状态 - **限流**: 无接口级特殊限流 **路径参数** | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | `orderId` | Long/String | 是 | 订单 ID,必须大于 0 | **请求体**: 无 **响应字段** | 字段 | 类型 | 说明 | |------|------|------| | `summaryId` | String | 新写入的 settlement summary 主键 | | `orderId` | String | 订单 ID | | `settledAt` | String | 核单完成时间 | | `totalAmount` | Decimal | 订单总金额快照 | | `paidAmount` | Decimal | 已付金额快照 | | `balanceAmount` | Decimal | 尾款金额快照 | | `roomCost` | Decimal | 住宿实际成本 | | `ticketCost` | Decimal | 门票实际成本 | | `staffCost` | Decimal | 人员费用实际成本 | | `subsidyCost` | Decimal | 补助实际成本 | | `mealCost` | Decimal | 餐食实际成本 | | `otherExpenseCost` | Decimal | 其他支出实际成本,含车辆费用 | | `insurancePremium` | Decimal | 保险实际保费 | | `totalActualCost` | Decimal | 总实际成本 | | `driverTransferAmount` | Decimal | 给司机/主报账人转回金额 | | `profitAmount` | Decimal | 公司毛利 | | `profitRate` | Decimal | 毛利率 | | `orderStatusAfter` | String | 结算后订单状态 | | `mqTriggered` | Boolean | 结算事件是否成功触发 | | `warnings` | Array | 软预警列表 | **错误码** | code | 含义 | 触发场景 | |------|------|----------| | 584314 | 单团核算表尚未确认或数据已变化 | finalize 前单团核算表未确认或已过期 | | 584317 | 当前报告状态不允许执行该操作 | 当前流程状态不能完成核单 | **典型成功示例** 请求: ```http POST /v3/admin/order/1914050000000001/settlement/finalize Authorization: Bearer ``` 响应: ```json { "code": 200, "msg": "success", "data": { "summaryId": "9400000000001", "orderId": "1914050000000001", "settledAt": "2026-07-25T15:50:00", "totalAmount": 25000.00, "paidAmount": 25000.00, "balanceAmount": 0.00, "roomCost": 4280.00, "ticketCost": 3680.00, "staffCost": 1400.00, "subsidyCost": 0.00, "mealCost": 860.00, "otherExpenseCost": 1560.00, "insurancePremium": 180.00, "totalActualCost": 11960.00, "driverTransferAmount": 11780.00, "profitAmount": 13040.00, "profitRate": 0.5216, "orderStatusAfter": "待财务复核", "mqTriggered": true, "warnings": [] } } ``` **边界示例** 请求: ```http POST /v3/admin/order/1914050000000001/settlement/finalize Authorization: Bearer ``` 响应: ```json { "code": 200, "msg": "success", "data": { "summaryId": "9400000000002", "orderId": "1914050000000001", "settledAt": "2026-07-25T15:55:00", "totalAmount": 0.00, "paidAmount": 0.00, "balanceAmount": 0.00, "roomCost": 0.00, "ticketCost": 0.00, "staffCost": 0.00, "subsidyCost": 0.00, "mealCost": 0.00, "otherExpenseCost": 0.00, "insurancePremium": 0.00, "totalActualCost": 0.00, "driverTransferAmount": 0.00, "profitAmount": 0.00, "profitRate": 0, "orderStatusAfter": "待财务复核", "mqTriggered": true, "warnings": ["住宿 D2 现付缺凭证"] } } ``` **异常示例** 请求: ```http POST /v3/admin/order/1914050000000001/settlement/finalize Authorization: Bearer ``` 响应: ```json { "code": 584314, "msg": "单团核算表尚未确认或数据已变化", "data": null } ``` **业务边界** - 适用:单团核算表已确认后完成核单。 - 不适用:单团核算表未确认、已过期或流程状态不允许。 - 特殊边界:`warnings` 为软预警,不阻塞成功响应。 ### 3.6 查询核单应收财务总览 - **方法 + 路径**: `GET /v3/admin/order/{orderId}/settlement/financial-overview` - **接口名**: 查询核单应收财务总览 - **变更点**: 查询接口保留,但返回字段删除旧确认状态/指纹语义;确认动作迁移到 8 分类和报告流。 **响应字段** | 字段 | 类型 | 说明 | |------|------|------| | `baseOrderAmount` | Decimal | 订单基础金额 | | `otherIncomeAmount` | Decimal | 有效增费合计 | | `discountAmount` | Decimal | 有效优惠合计 | | `adjustedReceivableAmount` | Decimal | 调整后应收 | | `onlinePaidAmount` | Decimal | 成功线上支付合计 | | `offlinePaidAmount` | Decimal | 有效线下收款合计 | | `primaryReporterCollectedAmount` | Decimal | 当前主报账司机正式代收 | | `paidAmount` | Decimal | 已收合计 | | `actualRefundedAmount` | Decimal | 实际退款合计 | | `netPaidAmount` | Decimal | 净已收 | | `outstandingAmount` | Decimal | 待收 | | `surchargeMirrorMatched` | Boolean | 增费镜像是否匹配权威明细 | | `discountMirrorMatched` | Boolean | 优惠镜像是否匹配权威明细 | | `paidMirrorMatched` | Boolean | 已收镜像是否匹配权威明细 | | `refundedMirrorMatched` | Boolean | 退款镜像是否匹配权威明细 | | `discounts` | Array | 当前有效优惠 | | `discounts[].discountId` | String | 优惠 ID | | `discounts[].name` | String | 优惠名称 | | `discounts[].type` | String | 优惠类型 | | `discounts[].amount` | Decimal | 优惠金额 | **示例** 请求: ```http GET /v3/admin/order/1914050000000001/settlement/financial-overview Authorization: Bearer ``` 响应: ```json { "code": 200, "msg": "success", "data": { "baseOrderAmount": 24800.00, "otherIncomeAmount": 500.00, "discountAmount": 300.00, "adjustedReceivableAmount": 25000.00, "onlinePaidAmount": 22000.00, "offlinePaidAmount": 3000.00, "primaryReporterCollectedAmount": 2000.00, "paidAmount": 25000.00, "actualRefundedAmount": 0.00, "netPaidAmount": 25000.00, "outstandingAmount": 0.00, "surchargeMirrorMatched": true, "discountMirrorMatched": true, "paidMirrorMatched": true, "refundedMirrorMatched": true, "discounts": [ { "discountId": "9100000000001", "name": "老客优惠", "type": "CUSTOMER_DISCOUNT", "amount": 300.00 } ] } } ``` ## 6. 枚举 / 数据字典 ### 6.1 `category`(SettlementCategory) **所属字段**: 路径参数 `category`、响应 `items[].category` | **类型**: String | 值 | 中文 | 说明 | |----|------|------| | `HOTEL` | 住宿 | 住宿核单分类 | | `TICKET` | 门票/游玩项目 | 门票及游玩项目核单分类 | | `MEAL` | 餐食 | 餐食核单分类 | | `VEHICLE` | 车辆 | 车辆费用核单分类 | | `GUIDE` | 导游 | 导游费用核单分类 | | `PHOTOGRAPHER` | 摄影 | 摄影费用核单分类 | | `OTHER_INCOME` | 其他收入 | 其他收入核单分类 | | `OTHER_EXPENSE` | 其他支出 | 其他支出核单分类 | ### 6.2 `reportStatus`(SettlementReportStatus) **所属字段**: `reportStatus` | **类型**: String | 值 | 中文 | 说明 | |----|------|------| | `GENERATED` | 已生成 | 报告已生成,尚未确认 | | `CONFIRMED` | 已确认 | 报告已确认 | | `STALE` | 已过期 | 来源事实已变化,需要重新生成 | ### 6.3 `confirmStatus` **所属字段**: `items[].confirmStatus` | **类型**: String | 值 | 中文 | 说明 | |----|------|------| | `UNCONFIRMED` | 未确认 | 分类尚未确认 | | `CONFIRMED` | 已确认 | 分类已确认 | ## 7. 错误码汇总 | code | 含义 | 触发场景 | |------|------|----------| | 584310 | 八个核单分类尚未全部确认或数据已变化 | 生成主报账表前门禁失败 | | 584311 | 主报账表尚未生成 | 主报账表查询/确认前置缺失 | | 584312 | 主报账表尚未确认或数据已变化 | 生成单团核算表前门禁失败 | | 584313 | 单团核算表尚未生成 | 单团核算表查询/确认前置缺失 | | 584314 | 单团核算表尚未确认或数据已变化 | finalize 前门禁失败 | | 584315 | 核单来源数据已变化,请刷新后重新生成 | 分类或报告指纹过期 | | 584316 | 核单报告发生并发变化,请刷新后重试 | 并发确认冲突 | | 584317 | 当前报告状态不允许执行该操作 | 当前状态不能执行生成/确认/finalize | | 584318 | 空分类必须显式确认,非空分类不得按空分类确认 | `confirmEmpty` 与分类是否为空不匹配 | | 584319 | 核单存在未知分类或历史迁移数据不完整 | 分类枚举非法或历史数据缺失 | ## 10. 修改前后对比 ### 10.1 字段级对比 | 接口/字段 | 改前 | 改后 | |-----------|------|------| | `GET /settlement/financial-overview` | 返回应收总览及逐条优惠确认状态 | 只返回应收总览和优惠明细,不再承载确认流 | | `POST /settlement/financial-overview/confirm` 请求体 | `expectedOverviewFingerprint` | 接口删除,改用 `POST /settlement/category-checks/{category}/confirm` 的 `expectedSourceFingerprint` + `confirmEmpty` | | `POST /settlement/financial-overview/discounts/{discountId}/confirm` 请求体 | `expectedSourceFingerprint` | 接口删除,优惠纳入 8 分类/报告流 | | 主报账表 | 无独立响应结构 | 新增 `SettlementReimbursementReportRespVO` | | 单团核算表 | 无独立响应结构 | 新增 `SettlementGroupReportRespVO` | | 完成核单 | 旧入口为 `POST /settlement/step6/submit` | 新增原型流入口 `POST /settlement/finalize`,返回 `SettlementSubmitRespVO` | ### 10.2 行为级对比 | 行为 | 改前 | 改后 | |------|------|------| | 核单确认门禁 | 财务总览/优惠逐条确认 | 8 分类全部确认 | | 报账表 | 无独立生成/确认步骤 | 先生成并确认主报账人报账表 | | 单团核算 | 无独立生成/确认步骤 | 主报账表确认后生成并确认单团核算表 | | 最终提交 | 直接 Step6 提交 | 单团核算表确认后调用 finalize | | 旧 POST 接口 | 可调用 | 下线,调用方需迁移 | ## 11. 影响评估 / 回滚 ### 11.1 影响评估 - **是否破坏向后兼容**: 是。两个旧 POST 确认接口删除;财务总览查询响应语义收窄。 - **前端是否必须同步上线**: 是。核单页面需要按 8 分类确认、主报账表、单团核算表、finalize 的顺序对接。 - **影响已有数据**: 前端接口层不需要处理数据库迁移细节;历史订单在接口层按当前数据计算状态。 ### 11.2 回滚方案 - **回滚方式**: 回滚 PR #5243 后恢复旧确认流。 - **回滚后清理**: 前端需恢复旧 `financial-overview/confirm` 和 `discounts/{discountId}/confirm` 调用。 - **回滚耗时**: 取决于后端回滚与重新部署;前端接口调用需同步回退。 ## 12. 注意事项 - 前端不要继续调用已删除的两个旧 POST 确认接口。 - 8 分类确认必须使用刚查询到的 `sourceFingerprint`,报告确认必须使用刚查询/生成返回的 `sourceFingerprint`。 - `STALE` 表示来源已变化,不能继续确认。 - 空分类确认必须传 `confirmEmpty=true`;非空分类必须传 `false`。 ## 13. 关联 / 联系人 ### 13.1 链接 - **Issue**: [#5219](https://git.1814.love:8443/wx/HL/issues/5219) - **PR**: [#5243](https://git.1814.love:8443/wx/HL/pulls/5243) - **Merge commit**: [f64606c](https://git.1814.love:8443/wx/HL/commit/f64606c5cc65b52880aaa8985c61e62e0b90e0ea) ### 13.2 联系人 - **后端负责人**: @yst