diff --git a/changelogs-v2/2026-07/30_5356_核单八类来源确认状态与车辆Step3接口统一-修改接口-管理后台.md b/changelogs-v2/2026-07/30_5356_核单八类来源确认状态与车辆Step3接口统一-修改接口-管理后台.md new file mode 100644 index 0000000..7c575ab --- /dev/null +++ b/changelogs-v2/2026-07/30_5356_核单八类来源确认状态与车辆Step3接口统一-修改接口-管理后台.md @@ -0,0 +1,911 @@ +--- +schema: "hl-changelog/v2" +ticket: "5356" +title: "核单八类来源确认状态与车辆 Step3 接口统一" +consumer: "admin" +change_type: "修改接口" +backend_status: "deployed" +gateway_status: "verified" +frontend_status: "pending" +frontend_owner: "" +frontend_ref: "" +target_release: "" +verified_at: "" +status_note: "部署任务 80f1695a 已完成 order-v3 主副实例滚动;测试网关已验证新车辆 Step3 路由进入新代码并返回业务前置码 584102,旧车辆费用接口返回 404。车辆 DTO 正向数据与 Full E2E 尚未验证。" +updated_at: "2026-07-30" +base: "dev-v3" +--- + +# ⚠️【修改接口·管理后台】核单八类来源确认状态与车辆 Step3 接口统一 (#5356) + +> **PR**: #5362 | **服务**: hl-order-service-v3 | **更新时间**: 2026-07-30 15:46 + +## 1. 接口背景 + +核单页面需要用同一套规则识别“手工行”和“系统来源行”,并逐行完成确认。此前各 Tab 的 `sourceType`、确认状态和车辆费用入口不一致,车辆数据还残留过已下线接口的字段口径。本次统一八类核单来源与逐行确认语义,并新增 Order 侧车辆 Step3 草稿查询、全量保存接口。 + +管理后台应以本文列出的 Order 侧接口为准;已删除的 +`GET /v3/admin/order/:orderId/settlement/vehicle-fees` 和 +`POST /v3/admin/order/:orderId/settlement/vehicle-fees/confirm` +继续保持下线,不得恢复调用。 + +## 变更接口(2. 变更清单) + +| # | 接口 | 方法 | 路径 | 变更类型 | 说明 | +|---|---|---|---|---|---| +| 1 | Step 1 查询住宿核单明细 | GET | `/v3/admin/order/:orderId/settlement/step1` | 修改 | 来源值统一;系统派生行初始为未确认;ID 按字符串返回 | +| 2 | Step 1 保存住宿核单明细 | PUT | `/v3/admin/order/:orderId/settlement/step1` | 修改 | 支持逐行 `UNCONFIRMED/CONFIRMED`;手工新行必须先未确认 | +| 3 | Step 2 查询门票核单明细 | GET | `/v3/admin/order/:orderId/settlement/step2` | 修改 | 响应新增逐行确认状态;ID 按字符串返回 | +| 4 | Step 2 保存门票核单明细 | PUT | `/v3/admin/order/:orderId/settlement/step2` | 修改 | 请求新增逐行确认状态;手工新行必须先未确认 | +| 5 | 查询车辆核单草稿 | GET | `/v3/admin/order/:orderId/settlement/step3/vehicles` | 新增 | Order 侧车辆 Step3 唯一查询入口 | +| 6 | 全量保存车辆核单草稿 | PUT | `/v3/admin/order/:orderId/settlement/step3/vehicles` | 新增 | 带 `version` 全量保存,支持车务行确认和手工行维护 | +| 7 | 查询领队人员费用 | GET | `/v3/admin/order/:orderId/settlement/staff-fees/leaders` | 修改 | 新增来源与确认状态名称 | +| 8 | 保存领队人员费用 | PUT | `/v3/admin/order/:orderId/settlement/staff-fees/leaders` | 修改 | 请求行新增 `id/sourceType/settlementConfirmStatus` | +| 9 | 查询司机人员费用 | GET | `/v3/admin/order/:orderId/settlement/staff-fees/drivers` | 修改 | 新增来源与确认状态名称 | +| 10 | 保存司机人员费用 | PUT | `/v3/admin/order/:orderId/settlement/staff-fees/drivers` | 修改 | 请求行新增 `id/sourceType/settlementConfirmStatus` | +| 11 | 查询导游人员费用 | GET | `/v3/admin/order/:orderId/settlement/staff-fees/guides` | 修改 | 新增来源与确认状态名称 | +| 12 | 保存导游人员费用 | PUT | `/v3/admin/order/:orderId/settlement/staff-fees/guides` | 修改 | 请求行新增 `id/sourceType/settlementConfirmStatus` | +| 13 | 查询摄影师人员费用 | GET | `/v3/admin/order/:orderId/settlement/staff-fees/photographers` | 修改 | 新增来源与确认状态名称 | +| 14 | 保存摄影师人员费用 | PUT | `/v3/admin/order/:orderId/settlement/staff-fees/photographers` | 修改 | 请求行新增 `id/sourceType/settlementConfirmStatus` | +| 15 | 查询其他人员费用 | GET | `/v3/admin/order/:orderId/settlement/staff-fees/others` | 修改 | 新增来源与确认状态名称 | +| 16 | 保存其他人员费用 | PUT | `/v3/admin/order/:orderId/settlement/staff-fees/others` | 修改 | 请求行新增 `id/sourceType/settlementConfirmStatus` | +| 17 | 查询餐食费用 | GET | `/v3/admin/order/:orderId/settlement/meals` | 修改 | 响应新增来源、来源名称、确认状态名称 | +| 18 | 新增餐食费用 | POST | `/v3/admin/order/:orderId/settlement/meals` | 修改 | 请求新增来源和确认状态;新行必须先未确认 | +| 19 | 修改餐食费用 | PUT | `/v3/admin/order/:orderId/settlement/meals/:settlementId` | 修改 | 可把已存在未确认行保存为已确认 | +| 20 | 查询其他支出 | GET | `/v3/admin/order/:orderId/settlement/other-expenses` | 修改 | 响应新增来源、来源名称、确认状态名称 | +| 21 | 新增其他支出 | POST | `/v3/admin/order/:orderId/settlement/other-expenses` | 修改 | 手工新行必须先未确认 | +| 22 | 修改其他支出 | PUT | `/v3/admin/order/:orderId/settlement/other-expenses/:settlementId` | 修改 | 可把已存在未确认行保存为已确认 | +| 23 | 查询其他收入 | GET | `/v3/admin/order/:orderId/settlement/other-incomes` | 修改 | `sourceType` 从内部来源值改为 `MANUAL/SYSTEM` | +| 24 | 新增其他收入 | POST | `/v3/admin/order/:orderId/settlement/other-incomes` | 修改 | 手工新行必须先未确认 | +| 25 | 修改其他收入 | PUT | `/v3/admin/order/:orderId/settlement/other-incomes/:incomeId` | 修改 | 可把已存在未确认行保存为已确认 | +| 26 | 完成核单 | POST | `/v3/admin/order/:orderId/settlement/finalize` | 修改 | 只接受八类来源同步完成且所有非空明细均已确认的数据 | + +## 3. 接口详情 + +以下接口均需登录态 JWT 和现有订单查看/核单权限;无接口级特殊限流。GET 为只读幂等;PUT 为全量替换幂等;POST 新增其他收入使用 `requestId` 保证同订单幂等。 + +### 3.1 住宿 Step 1 + +**接口** + +| 方法 | 路径 | 使用场景 | +|---|---|---| +| GET | `/v3/admin/order/:orderId/settlement/step1` | 打开住宿 Tab、刷新系统配房来源 | +| PUT | `/v3/admin/order/:orderId/settlement/step1` | 全量保存住宿行及逐行确认状态 | + +**路径参数** + +| 字段 | 类型 | 必填 | 说明 | +|---|---|---|---| +| `orderId` | String(Long) | 是 | 订单 ID,必须大于 0 | + +**PUT 请求体** + +| 字段 | 类型 | 必填 | 说明 | +|---|---|---|---| +| `items` | HotelItem[] | 是 | 全量数组;缺少的手工现存行按删除处理 | +| `items[].id` | String(Long) | 更新时是 | 已保存行 ID;新行为空 | +| `items[].hotelAssignmentId` | String(Long) | 否 | 配房来源行 ID;手工行为空 | +| `items[].hotelId` | String(Long) | 否 | 酒店 ID | +| `items[].roomTypeId` | String(Long) | 否 | 房型 ID | +| `items[].stayDate` | Date | 是 | `yyyy-MM-dd` | +| `items[].hotelName` | String | 是 | 最长 200 字符 | +| `items[].roomType` | String | 否 | 房型摘要,最长 64 字符 | +| `items[].roomTypeName` | String | 否 | 房型/规格名称,最长 128 字符 | +| `items[].roomCount` | Integer | 是 | 总间数 | +| `items[].unitPrice` | Decimal | 否 | 核算单价,必须大于等于 0 | +| `items[].plannedCost` | Decimal | 是 | 计划成本,必须大于等于 0 | +| `items[].actualCost` | Decimal | 是 | 实际成本,必须大于等于 0 | +| `items[].paymentMethod` | String | 条件必填 | `SIGNED/COMPANY_PAID/CASH_PAID`;手工行必填 | +| `items[].settleType` | String | 否 | `cash/sign/company`,兼容配房来源付款口径 | +| `items[].sourceType` | String | 是 | `HOUSE_ASSIGNMENT/MANUAL/SYSTEM`;`TEMPLATE` 仅兼容旧入参 | +| `items[].sourceId` | String(Long) | 否 | 系统来源业务 ID | +| `items[].settlementConfirmStatus` | String | 是 | `UNCONFIRMED/CONFIRMED` | +| `items[].remark` | String | 否 | 最长 500 字符 | +| `items[].voucherUrls` | String[] | 否 | 凭证 URL | + +**GET 响应 `data[]`** + +除上述行字段外,还返回: + +| 字段 | 类型 | 说明 | +|---|---|---| +| `paymentMethodName` | String | 付款方式名称 | +| `sourceTypeName` | String | 来源名称:配房结果/手工/系统 | +| `settlementConfirmStatusName` | String | 未确认/已确认 | + +**PUT 响应 `data`** + +| 字段 | 类型 | 说明 | +|---|---|---| +| `addedIds` | Long[] | 新增行 ID | +| `updatedIds` | Long[] | 更新行 ID | +| `deletedIds` | Long[] | 删除行 ID | +| `totalActualCost` | String(Decimal) | 保存后住宿实际成本合计 | + +**业务边界** + +- 配房来源行首次进入草稿返回 `UNCONFIRMED`;带已有 `id` 保存时可改为 `CONFIRMED`。 +- 手工新行 `id=null` 时只允许 `UNCONFIRMED`;保存取得 ID 后,下一次 PUT 才可改为 `CONFIRMED`。 +- `TEMPLATE` 只兼容旧请求,响应统一为 `SYSTEM`。 + +### 3.2 门票 Step 2 + +**接口** + +| 方法 | 路径 | 使用场景 | +|---|---|---| +| GET | `/v3/admin/order/:orderId/settlement/step2` | 打开门票/游玩项目 Tab、刷新行程来源 | +| PUT | `/v3/admin/order/:orderId/settlement/step2` | 全量保存门票行及逐行确认状态 | + +**PUT 请求体** + +| 字段 | 类型 | 必填 | 说明 | +|---|---|---|---| +| `items` | TicketItem[] | 是 | 全量数组 | +| `items[].id` | String(Long) | 更新时是 | 已保存行 ID;新行为空 | +| `items[].sourceType` | String | 是 | `SCENIC_ASSIGNMENT/ACTIVITY_ASSIGNMENT/MANUAL`;`CUSTOM_ASSIGNMENT` 仅兼容旧入参 | +| `items[].scenicAssignmentId` | String(Long) | 系统行是 | 景区或活动来源 ID | +| `items[].dayNumber` | Integer | 否 | 行程第几天,响应派生 | +| `items[].dayDate` | Date | 是 | 行程日 | +| `items[].scenicName` | String | 是 | 项目名,最长 200 字符 | +| `items[].specName` | String | 否 | 票型/规格,最长 128 字符 | +| `items[].ticketCount` | Integer | 是 | 实际购票数量,可为 0 | +| `items[].ticketUnitPrice` | Decimal | 否 | 参考单价 | +| `items[].sellPrice` | Decimal | 否 | 客户成交单价,必须大于等于 0 | +| `items[].totalAmount` | Decimal | 否 | 客户成交小计,必须大于等于 0 | +| `items[].plannedCost` | Decimal | 是 | 计划成本,必须大于等于 0 | +| `items[].actualCost` | Decimal | 是 | 实际成本,必须大于等于 0 | +| `items[].paymentMethod` | String | 否 | `SIGNED/COMPANY_PAID/CASH_PAID` | +| `items[].voucherUrls` | String[] | 否 | 凭证 URL | +| `items[].remark` | String | 否 | 最长 500 字符 | +| `items[].settlementConfirmStatus` | String | 是 | `UNCONFIRMED/CONFIRMED` | + +**GET 响应 `data[]`** + +返回完整 `TicketItem`,并增加: + +| 字段 | 类型 | 说明 | +|---|---|---| +| `sourceTypeName` | String | 景区/游玩项目/手工 | +| `paymentMethodName` | String | 付款方式名称 | +| `settlementConfirmStatusName` | String | 未确认/已确认 | + +**PUT 响应**与住宿 Step 1 相同:`addedIds/updatedIds/deletedIds/totalActualCost`。 + +**业务边界** + +- 系统来源首次同步为 `UNCONFIRMED`,来源事实变化后会重新变为 `UNCONFIRMED`。 +- 手工新行必须先保存为 `UNCONFIRMED`,已有 ID 后可保存为 `CONFIRMED`。 +- 响应不再返回 `CUSTOM_ASSIGNMENT`,历史自定义值统一返回 `MANUAL`。 + +### 3.3 车辆 Step 3 + +**接口** + +| 方法 | 路径 | 使用场景 | +|---|---|---| +| GET | `/v3/admin/order/:orderId/settlement/step3/vehicles` | 查询 Order 侧车辆核单草稿 | +| PUT | `/v3/admin/order/:orderId/settlement/step3/vehicles` | 带版本全量保存车辆行 | + +**PUT 请求体** + +| 字段 | 类型 | 必填 | 说明 | +|---|---|---|---| +| `version` | Long | 是 | GET 返回的草稿版本,最小 0 | +| `items` | VehicleItem[] | 是 | 全量明细;所有现存 `FLEET` 行必须原样带回 | +| `items[].id` | String(Long) | FLEET/更新时是 | 行 ID;手工新行为空 | +| `items[].sourceType` | String | 是 | `FLEET/MANUAL` | +| `items[].serviceDate` | Date | 是 | 服务日期 | +| `items[].vehicleId` | String(Long) | 否 | 车辆 ID | +| `items[].vehiclePlate` | String | 否 | 车牌,最长 64 字符 | +| `items[].vehicleModelId` | String(Long) | 否 | 车型 ID | +| `items[].vehicleModelName` | String | 否 | 车型名,最长 128 字符 | +| `items[].driverId` | String(Long) | 否 | 司机 ID | +| `items[].driverName` | String | 否 | 司机名,最长 64 字符 | +| `items[].amount` | Decimal | 是 | 金额,0~10 位整数、2 位小数 | +| `items[].paymentMethod` | String | 是 | `CASH_PAID/SIGNED/COMPANY_PAID` | +| `items[].settlementConfirmStatus` | String | 是 | `UNCONFIRMED/CONFIRMED` | +| `items[].remark` | String | 否 | 最长 500 字符 | +| `items[].voucherUrls` | String[] | 否 | 最多 9 个 http/https URL,单个最长 1024 字符 | + +未知字段会被拒绝。 + +**GET/PUT 响应 `data`** + +| 字段 | 类型 | 说明 | +|---|---|---| +| `orderId` | String(Long) | 订单 ID | +| `version` | Long | 当前草稿版本 | +| `totalAmount` | Decimal | 当前全部明细金额合计 | +| `allConfirmed` | Boolean | 非空行是否全部已确认;合法空集为 `true` | +| `items` | VehicleItem[] | 当前全量明细 | +| `items[].id` | String(Long) | 行 ID | +| `items[].sourceType` | String | `FLEET/MANUAL` | +| `items[].sourceTypeName` | String | 车务/手工 | +| `items[].serviceDate` | Date | 服务日期 | +| `items[].vehicleId` | String(Long) | 车辆 ID,可空 | +| `items[].vehiclePlate` | String | 车牌,可空 | +| `items[].vehicleModelId` | String(Long) | 车型 ID,可空 | +| `items[].vehicleModelName` | String | 车型名,可空 | +| `items[].driverId` | String(Long) | 司机 ID,可空 | +| `items[].driverName` | String | 司机名,可空 | +| `items[].amount` | Decimal | 核单金额 | +| `items[].paymentMethod` | String | 付款方式编码 | +| `items[].paymentMethodName` | String | 付款方式名称 | +| `items[].settlementConfirmStatus` | String | 确认状态 | +| `items[].settlementConfirmStatusName` | String | 未确认/已确认 | +| `items[].remark` | String | 备注 | +| `items[].voucherUrls` | String[] | 凭证 URL | + +**业务边界** + +- `FLEET` 行的日期、车辆、司机、金额和付款方式不可修改或删除;只允许修改确认状态、备注、凭证。 +- 全量保存时必须带回全部 `FLEET` 行。手工行可新增、修改或从全量数组中删除。 +- 手工新行必须先保存为 `UNCONFIRMED`;已有 ID 后可保存为 `CONFIRMED`。 +- `version` 不匹配返回 584108,必须重新 GET 后再保存。 +- 旧响应字段 `frozen/requirementId/settlementReady/totalVehicleFee` 及 Fleet 对账明细字段不再对管理后台输出。 + +### 3.4 人员费用五个 Tab + +**接口路径** + +| 角色 | GET/PUT 路径 | `detail` 结构 | +|---|---|---| +| 领队 | `/v3/admin/order/:orderId/settlement/staff-fees/leaders` | `days + per_day` | +| 司机 | `/v3/admin/order/:orderId/settlement/staff-fees/drivers` | `days[] + extra_cost + extra_breakdown[]` | +| 导游 | `/v3/admin/order/:orderId/settlement/staff-fees/guides` | `persons[]` | +| 摄影师 | `/v3/admin/order/:orderId/settlement/staff-fees/photographers` | `persons[]` | +| 其他 | `/v3/admin/order/:orderId/settlement/staff-fees/others` | `items[]` | + +**PUT 请求体** + +| 字段 | 类型 | 必填 | 说明 | +|---|---|---|---| +| `items` | StaffItem[] | 是 | 当前角色全量数组;空数组清空该 Tab 可删除的行 | +| `items[].id` | String(Long) | 更新时是 | 已保存行 ID;新行为空 | +| `items[].sourceType` | String | 是 | `STAFF_ASSIGNMENT/MANUAL/SYSTEM` | +| `items[].staffId` | String(Long) | 否 | 人员安排 ID;聚合或手工行可空 | +| `items[].detail` | Object | 是 | 由路径角色固定,结构见下表 | +| `items[].reimburse` | Decimal | 否 | 小额报销,空按 0 | +| `items[].paymentMethod` | String | 否 | 空按 `COMPANY_PAID` | +| `items[].voucherUrls` | String[] | 否 | 最多 9 个 http/https URL | +| `items[].settlementConfirmStatus` | String | 是 | `UNCONFIRMED/CONFIRMED` | +| `items[].settleStatus` | String | 否 | 辅助人员 `PENDING/COMPLETED`,空按 `PENDING` | +| `items[].settledDate` | Date | 否 | 辅助人员结算日期 | +| `items[].transferRef` | String | 条件必填 | `settleStatus=COMPLETED` 时必填,最长 128 字符 | +| `items[].remark` | String | 否 | 最长 500 字符 | + +**角色 `detail` 字段** + +| 路径角色 | 字段 | 类型 | 必填 | 说明 | +|---|---|---|---|---| +| leaders | `days` | Integer | 是 | 天数,最小 0 | +| leaders | `per_day` | Decimal | 是 | 每天费用,最小 0 | +| drivers | `days` | DriverDay[] | 是 | 服务日明细 | +| drivers | `days[].service_date` | Date | 是 | 服务日期 | +| drivers | `days[].vehicle_brief` | String | 否 | 车辆摘要 | +| drivers | `days[].daily_fee` | Decimal | 是 | 日费,仅回显,不计入人员费用 | +| drivers | `days[].is_used` | Boolean | 否 | 是否使用 | +| drivers | `days[].note` | String | 否 | 备注 | +| drivers | `extra_cost` | Decimal | 否 | 额外费用,空按 0 | +| drivers | `extra_breakdown` | ExtraItem[] | 否 | 合计必须等于 `extra_cost` | +| drivers | `extra_breakdown[].name` | String | 是 | 费用名 | +| drivers | `extra_breakdown[].amount` | Decimal | 是 | 金额,最小 0 | +| drivers | `extra_breakdown[].note` | String | 否 | 备注 | +| guides/photographers | `persons` | Person[] | 是 | 人员计费明细 | +| guides/photographers | `persons[].name` | String | 是 | 姓名 | +| guides/photographers | `persons[].days` | Integer | 是 | 天数,最小 0 | +| guides/photographers | `persons[].per_day` | Decimal | 是 | 每天费用,最小 0 | +| guides/photographers | `persons[].note` | String | 否 | 备注 | +| others | `items` | OtherItem[] | 是 | 其他人员费用项 | +| others | `items[].name` | String | 是 | 费用名称 | +| others | `items[].amount` | Decimal | 是 | 金额,最小 0 | +| others | `items[].note` | String | 否 | 备注 | + +**GET 响应 `data`** + +| 字段 | 类型 | 说明 | +|---|---|---| +| `totalActualCost` | Decimal | 当前 Tab 实际费用合计 | +| `items` | StaffItem[] | 已保存行;未保存时可返回候选草稿 | +| `items[].id` | String(Long) | 行 ID;未保存候选为空 | +| `items[].sourceType` | String | 来源编码 | +| `items[].sourceTypeName` | String | 人员安排/手工/系统 | +| `items[].staffId` | String(Long) | 人员安排 ID,可空 | +| `items[].staffName` | String | 人员姓名或聚合摘要 | +| `items[].detail` | Object | 对应角色明细 | +| `items[].totalPlannedCost` | Decimal | 计划成本 | +| `items[].totalActualCost` | Decimal | 实际成本 | +| `items[].reimburse` | Decimal | 小额报销 | +| `items[].paymentMethod` | String | 付款方式 | +| `items[].voucherUrls` | String[] | 凭证 URL | +| `items[].settlementConfirmStatus` | String | 确认状态 | +| `items[].settlementConfirmStatusName` | String | 未确认/已确认 | +| `items[].settleStatus` | String | 辅助人员结算状态;主报账人为空 | +| `items[].settledDate` | Date | 结算日期 | +| `items[].transferRef` | String | 转账流水号 | +| `items[].isPrimaryReporter` | Boolean | 是否主报账人 | +| `items[].remark` | String | 备注 | + +PUT 成功返回统一成功包,`data=null`。 + +### 3.5 餐食费用 + +**接口** + +| 方法 | 路径 | 请求/响应 | +|---|---|---| +| GET | `/v3/admin/order/:orderId/settlement/meals` | `data` 为 MealItem[] | +| POST | `/v3/admin/order/:orderId/settlement/meals` | 请求 MealSave;响应 MealItem | +| PUT | `/v3/admin/order/:orderId/settlement/meals/:settlementId` | 请求 MealSave;响应 MealItem | + +**MealSave 请求** + +| 字段 | 类型 | 必填 | 说明 | +|---|---|---|---| +| `mealType` | String | 是 | `BREAKFAST/LUNCH/DINNER` | +| `mealDate` | Date | 否 | 发生日期 | +| `mealName` | String | 是 | 最长 200 字符 | +| `quantity` | Integer | 是 | 1~10000 | +| `unitPrice` | Decimal | 是 | 最多 8 位整数、2 位小数,最小 0 | +| `paymentMethod` | String | 是 | `CASH_PAID/COMPANY_PAID/SIGNED` | +| `sourceType` | String | 是 | `MEAL_ASSIGNMENT/MANUAL/SYSTEM`;新增接口请传 `MANUAL` | +| `settlementConfirmStatus` | String | 是 | `UNCONFIRMED/CONFIRMED` | +| `voucherUrls` | String[] | 否 | 最多 9 个 http/https URL | +| `remark` | String | 否 | 最长 512 字符 | + +`unitPrice × quantity` 不得超过 `99999999.99`。 + +**MealItem 响应** + +| 字段 | 类型 | 说明 | +|---|---|---| +| `id` | String(Long) | 餐食费用 ID | +| `mealType` | String | 餐型 | +| `mealDate` | Date | 发生日期 | +| `mealName` | String | 餐食名称 | +| `quantity` | Integer | 数量 | +| `unitPrice` | String(Decimal) | 单价 | +| `actualAmount` | String(Decimal) | 实际金额 | +| `paymentMethod` | String | 付款类型 | +| `sourceType` | String | 来源编码 | +| `sourceTypeName` | String | 餐饮安排/手工/系统 | +| `voucherUrls` | String[] | 凭证 URL | +| `settlementConfirmStatus` | String | 确认状态 | +| `settlementConfirmStatusName` | String | 未确认/已确认 | +| `remark` | String | 备注 | + +POST 创建的是手工行,必须先传 `UNCONFIRMED`;PUT 已存在行时可传 `CONFIRMED`,且不得改变原 `sourceType`。 + +### 3.6 其他支出 + +**接口** + +| 方法 | 路径 | 请求/响应 | +|---|---|---| +| GET | `/v3/admin/order/:orderId/settlement/other-expenses` | `data` 为 OtherExpenseItem[] | +| POST | `/v3/admin/order/:orderId/settlement/other-expenses` | 请求 OtherExpenseSave;响应 OtherExpenseItem | +| PUT | `/v3/admin/order/:orderId/settlement/other-expenses/:settlementId` | 请求 OtherExpenseSave;响应 OtherExpenseItem | + +**OtherExpenseSave 请求** + +| 字段 | 类型 | 必填 | 说明 | +|---|---|---|---| +| `expenseType` | String | 是 | `FUEL/TOLL/PARKING/RENTAL/MAINTENANCE/OTHER` | +| `projectName` | String | 是 | 最长 200 字符 | +| `expenseDate` | Date | 否 | 发生日期 | +| `actualAmount` | Decimal | 是 | 最多 8 位整数、2 位小数,最小 0 | +| `paymentMethod` | String | 是 | `CASH_PAID/COMPANY_PAID/SIGNED` | +| `settlementConfirmStatus` | String | 是 | `UNCONFIRMED/CONFIRMED` | +| `voucherUrls` | String[] | 否 | 最多 9 个 http/https URL | +| `remark` | String | 否 | 最长 512 字符 | + +**OtherExpenseItem 响应** + +| 字段 | 类型 | 说明 | +|---|---|---| +| `id` | String(Long) | 其他支出 ID | +| `expenseType` | String | 支出类型 | +| `projectName` | String | 项目名称 | +| `expenseDate` | Date | 发生日期 | +| `actualAmount` | String(Decimal) | 实际金额 | +| `paymentMethod` | String | 付款类型 | +| `sourceType` | String | `MANUAL/SYSTEM` | +| `sourceTypeName` | String | 手工/系统 | +| `voucherUrls` | String[] | 凭证 URL | +| `settlementConfirmStatus` | String | 确认状态 | +| `settlementConfirmStatusName` | String | 未确认/已确认 | +| `remark` | String | 备注 | + +POST 创建的是 `MANUAL` 行且必须先为 `UNCONFIRMED`;已有 ID 后通过 PUT 可改为 `CONFIRMED`。 + +### 3.7 其他收入 + +**接口** + +| 方法 | 路径 | 请求/响应 | +|---|---|---| +| GET | `/v3/admin/order/:orderId/settlement/other-incomes` | `data` 为列表聚合对象 | +| POST | `/v3/admin/order/:orderId/settlement/other-incomes` | 请求 Create;响应 OtherIncomeItem | +| PUT | `/v3/admin/order/:orderId/settlement/other-incomes/:incomeId` | 请求 Update;响应 OtherIncomeItem | + +**Create/Update 请求** + +| 字段 | 类型 | 必填 | 说明 | +|---|---|---|---| +| `requestId` | String | 仅 POST 是 | 同订单永久唯一,最长 64 字符 | +| `incomeDate` | Date | 是 | 收入日期 | +| `projectName` | String | 是 | 最长 100 字符 | +| `projectCategory` | String | 是 | 启用字典值,最长 64 字符 | +| `specification` | String | 否 | 票种/规格,最长 100 字符 | +| `quantity` | Decimal | 是 | 最多 8 位整数、4 位小数,最小 0 | +| `unitPrice` | Decimal | 是 | 最多 8 位整数、2 位小数,最小 0 | +| `settlementAmount` | Decimal | 是 | 最多 8 位整数、2 位小数,必须大于 0,且等于数量乘单价(四舍五入到 2 位) | +| `paymentMethod` | String | 是 | `CASH_PAID/COMPANY_PAID/SIGNED` | +| `settlementConfirmStatus` | String | 是 | `UNCONFIRMED/CONFIRMED` | +| `voucherUrls` | String[] | 否 | 最多 9 个 http/https URL | +| `remark` | String | 否 | 最长 500 字符 | + +**OtherIncomeItem 响应** + +| 字段 | 类型 | 说明 | +|---|---|---| +| `id` | String(Long) | 其他收入 ID | +| `requestId` | String | 手工新增幂等 ID;系统投影为空 | +| `incomeDate` | Date | 收入日期 | +| `projectName` | String | 项目名称 | +| `projectCategory` | String | 项目类别 | +| `projectCategoryName` | String | 项目类别名称 | +| `specification` | String | 票种/规格 | +| `quantity` | Decimal | 数量 | +| `unitPrice` | Decimal | 核算单价 | +| `settlementAmount` | Decimal | 核算金额 | +| `paymentMethod` | String | 付款类型 | +| `paymentMethodName` | String | 付款类型名称 | +| `voucherUrls` | String[] | 凭证 URL | +| `settlementConfirmStatus` | String | 确认状态 | +| `settlementConfirmStatusName` | String | 未确认/已确认 | +| `remark` | String | 备注 | +| `sourceType` | String | `MANUAL/SYSTEM` | +| `sourceTypeName` | String | 手工/系统 | +| `sourceId` | String(Long) | 关联来源 ID | + +**GET 聚合响应** + +| 字段 | 类型 | 说明 | +|---|---|---| +| `items` | OtherIncomeItem[] | 其他收入明细 | +| `deductions` | Deduction[] | 只读减费明细 | +| `deductions[].id` | String(Long) | 减费 ID | +| `deductions[].discountName` | String | 减费名称 | +| `deductions[].discountAmount` | Decimal | 减费金额 | +| `deductions[].sourceType` | String | 减费来源 | +| `deductions[].sourceId` | String(Long) | 来源业务 ID | +| `deductions[].createdAt` | DateTime | 创建时间 | +| `summary.surchargeAmount` | Decimal | 有效增费合计 | +| `summary.discountAmount` | Decimal | 有效减费合计 | +| `summary.netAdjustmentAmount` | Decimal | 增费减去减费 | + +手工新增行的公开来源为 `MANUAL`;系统自动投影行公开来源为 `SYSTEM`。原公开值 `ORDER_SURCHARGE` 不再返回。 + +### 3.8 完成核单 + +**接口**:`POST /v3/admin/order/:orderId/settlement/finalize` + +**请求体** + +| 字段 | 类型 | 必填 | 说明 | +|---|---|---|---| +| `remark` | String | 否 | 整体备注,最长 500 字符 | +| `reimbursementExpectedSourceFingerprint` | String | 是 | 主报账表 64 位小写 SHA-256 指纹 | +| `groupExpectedSourceFingerprint` | String | 是 | 单团核算表 64 位小写 SHA-256 指纹 | +| `reimbursementConfirmation` | Object | 是 | 主报账确认凭据 | +| `reimbursementConfirmation.transferDate` | Date | 条件必填 | 主报账净额非 0 时必填 | +| `reimbursementConfirmation.transferRef` | String | 条件必填 | 主报账净额非 0 时必填,最长 128 字符 | +| `reimbursementConfirmation.advanceSettledFlag` | Boolean | 是 | 预支是否已处理;`false` 是合法值 | +| `reimbursementConfirmation.signedVoucher` | Object | 是 | 签字凭证 | +| `reimbursementConfirmation.signedVoucher.files` | File[] | 是 | 1~9 项 | +| `reimbursementConfirmation.signedVoucher.files[].name` | String | 否 | 文件名,最长 255 字符 | +| `reimbursementConfirmation.signedVoucher.files[].url` | String | 是 | 文件 URL,最长 1024 字符 | +| `reimbursementConfirmation.signedVoucher.note` | String | 否 | 最长 500 字符 | + +**响应 `data`** + +| 字段 | 类型 | 说明 | +|---|---|---| +| `summaryId` | String(Long) | 核单汇总 ID | +| `finalSnapshotId` | String(Long) | 终态快照 ID | +| `finalSnapshotVersionNo` | Integer | 快照版本 | +| `finalSnapshotStatus` | String | 成功时为 `FINALIZED` | +| `orderId` | String(Long) | 订单 ID | +| `settledAt` | DateTime | 核单完成时间 | +| `totalAmount` | String(Decimal) | 订单总金额快照 | +| `paidAmount` | String(Decimal) | 已付金额快照 | +| `balanceAmount` | String(Decimal) | 尾款金额快照 | +| `roomCost` | String(Decimal) | 住宿实际成本 | +| `ticketCost` | String(Decimal) | 门票实际成本 | +| `staffCost` | String(Decimal) | 人员费用实际成本 | +| `subsidyCost` | String(Decimal) | 补助实际成本 | +| `mealCost` | String(Decimal) | 餐食实际成本 | +| `vehicleCost` | String(Decimal) | 车辆实际成本 | +| `otherExpenseCost` | String(Decimal) | 其他支出实际成本 | +| `insurancePremium` | String(Decimal) | 保险实际保费 | +| `totalActualCost` | String(Decimal) | 总实际成本 | +| `driverTransferAmount` | String(Decimal) | 给主报账人的转回金额 | +| `profitAmount` | String(Decimal) | 公司毛利 | +| `profitRate` | Decimal | 毛利率小数 | +| `orderStatusAfter` | String | 完成后的订单状态 | +| `mqTriggered` | Boolean | 当前固定为 `false` | +| `warnings` | String[] | 不阻塞完成核单的软预警 | + +**业务边界** + +- 住宿、门票/游玩、餐食、车辆、导游、摄影、其他收入、其他支出八类来源必须同步完成。 +- 任一非空分类存在 `UNCONFIRMED` 行时,finalize 返回 584310,不生成终态快照。 +- 系统来源事实变化会使对应行重新变为 `UNCONFIRMED`;应刷新、复核并保存后再 finalize。 + +## 4. 接口入参汇总 + +| 输入类型 | 适用接口 | 关键变化 | +|---|---|---| +| 路径参数 | 全部 26 个接口 | `orderId` 必填;行级修改另有 `settlementId/incomeId` | +| 全量明细 | 住宿、门票、车辆、五个人员 Tab | 必须提交完整 `items`;新增手工行先传 `UNCONFIRMED` | +| 单行保存 | 餐食、其他支出、其他收入 | POST 新增先未确认,PUT 已有行可确认 | +| 车辆版本 | 车辆 PUT | 必须原样回传最近 GET 的 `version` | +| 完成核单凭据 | finalize | 双报告指纹 + 主报账转账/签字凭据 | + +完整字段、必填性和校验已分别内联在 §3.1~§3.8。 + +## 5. 出参字段汇总 + +| 变化 | 适用响应 | +|---|---| +| 新增 `sourceType/sourceTypeName` | 人员、餐食、其他支出;其他收入的来源语义调整 | +| 新增 `settlementConfirmStatus/settlementConfirmStatusName` | 门票;餐食、其他支出、人员补齐名称 | +| Long ID 按字符串返回 | 住宿、门票、人员、车辆以及已有明确字符串序列化的资金明细 | +| 新车辆草稿结构 | `orderId/version/totalAmount/allConfirmed/items` | +| 删除车辆内部/兼容字段 | 不再输出 `frozen/requirementId/settlementReady/totalVehicleFee` 及 Fleet 对账字段 | + +## 6. 枚举 / 数据字典 + +### 6.1 住宿 `sourceType` + +| 值 | 中文 | 说明 | +|---|---|---| +| `HOUSE_ASSIGNMENT` | 配房结果 | 系统配房来源 | +| `MANUAL` | 手工 | 管理后台手工新增 | +| `SYSTEM` | 系统 | 其他系统来源;替代旧公开值 `TEMPLATE` | +| `TEMPLATE` | 历史兼容 | 仅请求兼容,响应不返回 | + +### 6.2 门票 `sourceType` + +| 值 | 中文 | 说明 | +|---|---|---| +| `SCENIC_ASSIGNMENT` | 景区 | 景区安排来源 | +| `ACTIVITY_ASSIGNMENT` | 游玩项目 | 活动安排来源 | +| `MANUAL` | 手工 | 手工新增;响应统一值 | +| `CUSTOM_ASSIGNMENT` | 历史兼容 | 仅请求兼容,响应归一为 `MANUAL` | + +### 6.3 餐食 `sourceType` + +| 值 | 中文 | 说明 | +|---|---|---| +| `MEAL_ASSIGNMENT` | 餐饮安排 | 系统餐饮安排来源 | +| `MANUAL` | 手工 | 管理后台新增 | +| `SYSTEM` | 系统 | 其他系统来源 | + +### 6.4 车辆 `sourceType` + +| 值 | 中文 | 说明 | +|---|---|---| +| `FLEET` | 车务 | 车务同步行,业务字段不可改删 | +| `MANUAL` | 手工 | 核单页手工补录 | + +### 6.5 人员 `sourceType` + +| 值 | 中文 | 说明 | +|---|---|---| +| `STAFF_ASSIGNMENT` | 人员安排 | 系统人员安排来源 | +| `MANUAL` | 手工 | 手工新增 | +| `SYSTEM` | 系统 | 其他系统来源 | + +### 6.6 其他收入/其他支出 `sourceType` + +| 值 | 中文 | 说明 | +|---|---|---| +| `MANUAL` | 手工 | 管理后台创建 | +| `SYSTEM` | 系统 | 自动投影或系统来源 | + +### 6.7 `settlementConfirmStatus` + +| 值 | 中文 | 说明 | +|---|---|---| +| `UNCONFIRMED` | 未确认 | 首次系统同步或手工新行的初始状态 | +| `CONFIRMED` | 已确认 | 已有行复核后保存的状态 | + +### 6.8 `paymentMethod` + +| 值 | 中文 | 说明 | +|---|---|---| +| `CASH_PAID` | 现付 | 现场/主报账人支付 | +| `COMPANY_PAID` | 公司付款 | 公司直接支付 | +| `SIGNED` | 签单 | 签单结算 | + +### 6.9 餐型 `mealType` + +| 值 | 中文 | +|---|---| +| `BREAKFAST` | 早餐 | +| `LUNCH` | 午餐 | +| `DINNER` | 晚餐 | + +### 6.10 支出类型 `expenseType` + +| 值 | 中文 | +|---|---| +| `FUEL` | 油费 | +| `TOLL` | 过路费 | +| `PARKING` | 停车费 | +| `RENTAL` | 租赁费 | +| `MAINTENANCE` | 维修保养 | +| `OTHER` | 其他 | + +### 6.11 人员辅助结算状态 `settleStatus` + +| 值 | 中文 | 说明 | +|---|---|---| +| `PENDING` | 待结算 | 默认值 | +| `COMPLETED` | 已结算 | 必须同时提交 `transferRef` | + +## 7. 错误码 + +| code | 含义 | 触发场景 | +|---|---|---| +| `400` | 参数校验失败 | 必填缺失、格式/长度/枚举错误、车辆或人员请求出现未知字段 | +| `584001/584010/584020/584050/584070` | 订单不存在 | 对应住宿、门票、人员、finalize 或通用查询找不到订单 | +| `584002/584011/584021` | 当前状态不可写 | 住宿、门票、人员费用不在允许的核单阶段 | +| `584006/584012/584022` | 枚举或角色非法 | 住宿付款、门票来源、人员角色非法 | +| `584067/584068/584069` | 住宿资源无效或暂不可用 | 手工酒店/房型无效或资源服务不可用 | +| `584071` | 无权访问该订单 | 公司隔离或现有订单权限不满足 | +| `584073` | 其他收入不存在 | `incomeId` 不属于当前订单 | +| `584074` | 当前状态不允许修改其他收入 | 核单状态不可写 | +| `584076` | 其他收入金额不一致 | `settlementAmount != quantity × unitPrice` | +| `584077` | 存在未确认的其他收入 | 生成报告或完成核单前仍有其他收入未确认 | +| `584086` | 无权修改核单资金数据 | 非主管、管理员或财务 | +| `584087` | `requestId` 冲突 | 同订单相同 `requestId` 被另一笔请求占用 | +| `584089` | 核单或结算已完成 | 再次修改资金明细 | +| `584090/584091` | 餐食/其他支出不存在 | 行 ID 不属于当前订单 | +| `584092` | 存在未确认的人员费用 | 生成报告或完成核单前仍有人员费用未确认 | +| `584094/584095` | 餐食/其他支出字段非法 | 字段越界或试图改变系统来源 | +| `584096/584097` | 付款方式/凭证非法 | 枚举错误或 URL 数量、格式错误 | +| `584098` | 餐食或其他支出存在未确认记录 | 生成报告或完成核单前仍有餐食/其他支出未确认 | +| `584100/584101/584102` | 车辆来源不可用/未就绪/为空 | 车务数据不可读、未完结/未确认、无可核单费用 | +| `584103/584104/584105` | 其他收入字典非法或不可用 | 项目类别、规格无效或字典不可用 | +| `584106` | 确认状态非法 | 非 `UNCONFIRMED/CONFIRMED` | +| `584107` | 手工新增行必须先未确认 | `id=null` 的手工新行直接传 `CONFIRMED` | +| `584108` | 车辆草稿版本冲突 | PUT 的 `version` 已过期 | +| `584109` | 车务来源字段不可改删 | 修改/漏传 `FLEET` 行的权威字段 | +| `584310` | 八类核单未全部确认或数据已变化 | finalize 前有非空未确认行、来源未就绪 | +| `584315` | 报告来源已变化 | finalize 双报告指纹过期 | +| `584317` | 当前报告状态不允许操作 | finalize 凭据结构或状态不满足 | +| `584325` | 完成核单必须提交当前指纹 | 双报告指纹缺失 | + +## 验证证据(8. 示例:典型 / 边界 / 异常) + +已完成以下测试环境路由与业务负向验证: + +- 部署任务 `80f1695a` 成功,order-v3 主、副实例滚动完成。 +- 两个核算中订单实调 + `GET /v3/admin/order/:orderId/settlement/step3/vehicles` + 均进入新代码并返回业务前置码 `584102`,证明新路由已生效。 +- 旧 `GET /v3/admin/order/:orderId/settlement/vehicle-fees` 返回 `404`,证明旧入口已下线。 + +本节下列 JSON 是按已合并 Controller/VO 契约给出的自包含调用示例。由于测试订单缺少可核单车辆费用,本次未取得车辆 DTO 正向数据,也未完成车辆链路 Full E2E;不得把上述 584102 负向结果描述为正向业务通过。 + +### 8.1 典型成功:确认车辆系统来源行 + +**请求** + +```http +PUT /v3/admin/order/900000000001/settlement/step3/vehicles +Authorization: Bearer +Content-Type: application/json +``` + +```json +{ + "version": 3, + "items": [ + { + "id": "930000000001", + "sourceType": "FLEET", + "serviceDate": "2026-07-30", + "vehicleId": "880000000001", + "vehiclePlate": "藏A12345", + "vehicleModelId": "870000000001", + "vehicleModelName": "七座商务车", + "driverId": "860000000001", + "driverName": "张师傅", + "amount": 1200.00, + "paymentMethod": "COMPANY_PAID", + "settlementConfirmStatus": "CONFIRMED", + "remark": "金额已核对", + "voucherUrls": [] + } + ] +} +``` + +**响应** + +```json +{ + "code": 200, + "message": "成功", + "data": { + "orderId": "900000000001", + "version": 4, + "totalAmount": 1200.00, + "allConfirmed": true, + "items": [ + { + "id": "930000000001", + "sourceType": "FLEET", + "sourceTypeName": "车务", + "serviceDate": "2026-07-30", + "vehicleId": "880000000001", + "vehiclePlate": "藏A12345", + "vehicleModelId": "870000000001", + "vehicleModelName": "七座商务车", + "driverId": "860000000001", + "driverName": "张师傅", + "amount": 1200.00, + "paymentMethod": "COMPANY_PAID", + "paymentMethodName": "公司付款", + "settlementConfirmStatus": "CONFIRMED", + "settlementConfirmStatusName": "已确认", + "remark": "金额已核对", + "voucherUrls": [] + } + ] + }, + "success": true +} +``` + +### 8.2 边界情况:合法空车辆草稿 + +无当前用车需求时,GET 可返回合法空集;`allConfirmed=true` 表示“空集中没有未确认行”,不表示存在车辆费用。 + +**请求** + +```http +GET /v3/admin/order/900000000001/settlement/step3/vehicles +Authorization: Bearer +``` + +无请求体。 + +**响应** + +```json +{ + "code": 200, + "message": "成功", + "data": { + "orderId": "900000000001", + "version": 1, + "totalAmount": 0.00, + "allConfirmed": true, + "items": [] + }, + "success": true +} +``` + +### 8.3 业务失败:手工新行直接确认 + +**请求** + +```http +POST /v3/admin/order/900000000001/settlement/meals +Authorization: Bearer +Content-Type: application/json +``` + +```json +{ + "mealType": "LUNCH", + "mealDate": "2026-07-30", + "mealName": "团队午餐", + "quantity": 10, + "unitPrice": 50.00, + "paymentMethod": "CASH_PAID", + "sourceType": "MANUAL", + "settlementConfirmStatus": "CONFIRMED", + "voucherUrls": [], + "remark": null +} +``` + +**响应** + +```json +{ + "code": 584107, + "message": "手工新增核单明细必须先保存为未确认", + "data": null, + "success": false +} +``` + +## 9. 业务边界 + +- ✅ **系统来源首次同步**:生成已有 ID 的 `UNCONFIRMED` 行;复核后可直接在对应 PUT 中保存为 `CONFIRMED`。 +- ✅ **手工新增**:第一次必须保存为 `UNCONFIRMED`;接口返回 ID 后,第二次更新才允许保存为 `CONFIRMED`。 +- ✅ **来源统一**:手工行统一公开为 `MANUAL`;系统行公开为各分类系统来源值,无法细分的系统行为 `SYSTEM`。 +- ❌ **不可混用确认和辅助结算状态**:`settlementConfirmStatus` 表示核单确认;人员 `settleStatus` 表示辅助人员款项是否结清。 +- ❌ **不可修改系统权威字段**:系统来源事实变化后应重新 GET;车辆 `FLEET` 行不得由前端改删。 +- ⚠️ **finalize 门禁**:八个核单分类来源必须就绪,且每个非空分类全部逐行 `CONFIRMED`。 +- ⚠️ **空分类**:合法空分类没有未确认行,但来源同步仍必须就绪;`allConfirmed=true` 不等于有费用。 + +## 10. 修改前后对比 + +### 10.1 字段级对比 + +| 范围 | 改前 | 改后 | +|---|---|---| +| 住宿系统来源 | `TEMPLATE` | `SYSTEM`;`TEMPLATE` 仅兼容旧入参 | +| 门票手工来源 | 可能返回 `CUSTOM_ASSIGNMENT` | 统一返回 `MANUAL` | +| 其他收入来源 | `ORDER_SURCHARGE` | `MANUAL` 或 `SYSTEM` | +| 门票行确认 | 无逐行确认字段 | 新增 `settlementConfirmStatus/Name` | +| 人员请求行 | 无 `id/sourceType/settlementConfirmStatus` | 三字段纳入全量保存契约 | +| 餐食请求/响应 | 无公开来源,确认名称不完整 | 增加 `sourceType/sourceTypeName/settlementConfirmStatusName` | +| 其他支出响应 | 无公开来源和确认名称 | 增加 `sourceType/sourceTypeName/settlementConfirmStatusName` | +| 住宿/门票/人员 ID | 部分按 JSON 数字输出 | 明细 `id`、来源 ID 按字符串输出 | +| 车辆顶层响应 | `frozen/requirementId/settlementReady/totalVehicleFee/items` | `orderId/version/totalAmount/allConfirmed/items` | +| 车辆行响应 | 暴露 Fleet 对账、冻结和自动车费字段 | 仅输出核单需要的来源、车辆、司机、金额、付款、确认、凭证字段 | + +### 10.2 行为级对比 + +| 行为 | 改前 | 改后 | +|---|---|---| +| 系统派生行初始状态 | 分类规则不一致,部分直接视为已确认 | 统一先 `UNCONFIRMED`,用户复核后保存为 `CONFIRMED` | +| 手工新增并确认 | 部分接口允许一次保存即确认 | 必须先未确认,取得 ID 后再确认 | +| 车辆入口 | 旧 `/settlement/vehicle-fees` 已下线且无新独立编辑入口 | 使用 `/settlement/step3/vehicles` GET/PUT | +| 车辆并发保存 | 无前端草稿版本 | 必须携带 `version`,冲突时刷新 | +| 完成核单 | 分类确认来源不完全统一 | 只接受八类来源就绪且非空行全部已确认的数据 | + +## 11. 影响评估 / 回滚 + +### 11.1 影响评估 + +- **是否破坏向后兼容**:是。车辆入口和响应结构为新契约;其他收入 `sourceType` 值发生变化;多个请求/响应新增确认与来源字段。 +- **前端是否必须同步上线**:是。需切换车辆接口、适配字符串 ID、新来源枚举和“先保存未确认、再确认”的交互。 + +### 11.2 回滚说明 + +若后端回滚,前端需同时回滚车辆 Step3 新入口及新增字段依赖;旧 +`/settlement/vehicle-fees` 两个接口在本次变更前已下线,不能作为回滚兜底。 + +## 12. 注意事项 + +- 删除对旧 `GET /settlement/vehicle-fees` 和 `POST /settlement/vehicle-fees/confirm` 的任何残留调用。 +- 车辆保存必须回传最近 GET 的 `version` 和全部 `FLEET` 行;584108 时刷新后让用户重新确认。 +- 不再把 `ORDER_SURCHARGE`、`TEMPLATE`、`CUSTOM_ASSIGNMENT` 当作新响应值。 +- 所有 Long 类型字符串 ID 按字符串比较、传递,不转为 JavaScript Number。 +- finalize 返回 584310 时,应刷新相关 Tab;来源变化可能已把已确认行重新置为未确认。 + +## 13. 关联 / 联系人 + +### 13.1 链接 + +- **Issue**: [#5356](https://git.1814.love:8443/wx/HL/issues/5356) +- **PR**: [#5362](https://git.1814.love:8443/wx/HL/pulls/5362) +- **Feature commit**: [6e396f6fc4](https://git.1814.love:8443/wx/HL/commit/6e396f6fc48fbf6581e87224bb85f2c811759727) +- **Merge commit**: [cfac945db2](https://git.1814.love:8443/wx/HL/commit/cfac945db268640b0b9e60b4d8c7ab55739a69e3) + +### 13.2 联系人 + +- **后端负责人**: @yaosutu