--- schema: "hl-changelog/v2" ticket: "5356" title: "核单八类来源确认状态与车辆 Step3 接口统一" consumer: "admin" change_type: "修改接口" backend_status: "deployed" gateway_status: "verified" frontend_status: "implemented" frontend_owner: "Pi" frontend_ref: "9b9865a50304b1acc55ab5e4f94bb7dfe52293c0" target_release: "" verified_at: "2026-07-30" status_note: "管理后台已统一八类核单来源与逐行确认状态,车辆改用 Order Step3 GET/PUT 并携带 version、保护 FLEET 权威字段;pnpm checkpoint 全量通过,业务提交 9b9865a50304b1acc55ab5e4f94bb7dfe52293c0 已推送至 origin/v2.1。后端车辆 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