From dbef2f33977c087984e56eaa6ec7dfd73a79b4ac Mon Sep 17 00:00:00 2001 From: yaosutu <770858045@qq.com> Date: Wed, 29 Jul 2026 09:25:17 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=A0=B8=E5=8D=95=E5=85=B6?= =?UTF-8?q?=E4=BB=96=E6=94=B6=E6=94=AF=E7=A1=AE=E8=AE=A4=E7=8A=B6=E6=80=81?= =?UTF-8?q?=E4=B8=8E=E9=A1=B9=E7=9B=AE=E7=B1=BB=E5=88=AB=E5=8F=98=E6=9B=B4?= =?UTF-8?q?=E8=AF=B4=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...他收支确认状态与项目类别-修改接口-管理后台.md | 1053 +++++++++++++++++ .../29_5320_核单其他收入项目类别字典.md | 322 +++++ 2 files changed, 1375 insertions(+) create mode 100644 changelogs-v2/2026-07/29_5320_核单其他收支确认状态与项目类别-修改接口-管理后台.md create mode 100644 changelogs/2026-07/29_5320_核单其他收入项目类别字典.md diff --git a/changelogs-v2/2026-07/29_5320_核单其他收支确认状态与项目类别-修改接口-管理后台.md b/changelogs-v2/2026-07/29_5320_核单其他收支确认状态与项目类别-修改接口-管理后台.md new file mode 100644 index 0000000..641294c --- /dev/null +++ b/changelogs-v2/2026-07/29_5320_核单其他收支确认状态与项目类别-修改接口-管理后台.md @@ -0,0 +1,1053 @@ +--- +schema: "hl-changelog/v2" +ticket: "5320" +title: "核单其他收支确认状态与项目类别" +consumer: "admin" +change_type: "修改接口" +backend_status: "deployed" +gateway_status: "not_required" +frontend_status: "pending" +frontend_owner: "" +frontend_ref: "" +target_release: "" +verified_at: "" +status_note: "其他收入、其他支出保存时必须提交确认状态并原样保存;其他收入项目类别和非空票种规格改为启用字典值校验。" +updated_at: "2026-07-29" +base: "dev-v3" +generated: "2026-07-29T09:19:00+08:00" +--- + +# 【⚠️ 修改接口·管理后台】核单其他收支确认状态与项目类别(#5320) + +> **PR**: #5330 | **服务**: hl-order-service-v3 | **更新时间**: 2026-07-29 09:19 + +## 1. 接口背景 + +核单「其他收入」「其他支出」需要在保存时明确记录当前确认状态,不能再由后端统一改成已确认。四个保存接口新增必填字段 `settlementConfirmStatus`,调用方传 `UNCONFIRMED` 或 `CONFIRMED`,响应返回实际保存值。 + +其他收入同时收紧项目类别和票种/规格: + +- `projectCategory` 必须提交 `settlement_other_income_project_category` 字典的启用 `dictValue`; +- `specification` 可空,非空时必须提交 `settlement_ticket_spec` 字典的启用 `dictValue`; +- 响应 `projectCategoryName` 返回项目类别字典的中文 `dictLabel`。 + +> **关键变化**:#5310 的“保存即确认”规则已被本次变更替代。保存接口不再强制写 `CONFIRMED`,调用方必须明确提交确认状态。此前删除的两个独立确认接口不恢复。 + +路径中的 `:orderId`、`:incomeId`、`:settlementId` 表示对应的路径参数。 + +## 变更接口 + +| # | 接口 | 方法 | 路径 | 变更类型 | 说明 | +|---|------|------|------|----------|------| +| 1 | 新增其他收入并原子创建订单增费 | POST | `/v3/admin/order/:orderId/settlement/other-incomes` | 修改 | `settlementConfirmStatus` 改为必填;项目类别和非空规格按启用字典校验 | +| 2 | 修改其他收入 | PUT | `/v3/admin/order/:orderId/settlement/other-incomes/:incomeId` | 修改 | `settlementConfirmStatus` 改为必填并保存传入值;项目类别和非空规格按启用字典校验 | +| 3 | 新增其他支出 | POST | `/v3/admin/order/:orderId/settlement/other-expenses` | 修改 | `settlementConfirmStatus` 改为必填并保存传入值 | +| 4 | 修改其他支出 | PUT | `/v3/admin/order/:orderId/settlement/other-expenses/:settlementId` | 修改 | `settlementConfirmStatus` 改为必填并保存传入值 | + +## 3. 接口详情 + +### 3.1 新增其他收入 + +- **方法 + 路径**:`POST /v3/admin/order/:orderId/settlement/other-incomes` +- **接口名**:新增其他收入并原子创建订单增费 +- **使用场景**:在核单其他收入页新增一条记录,并明确该记录当前是未确认还是已确认。 +- **认证**:需要管理后台登录态;需要资金写入权限。 +- **幂等性**:是;同一订单内 `requestId` 永久唯一。相同 `requestId` 且完整请求载荷一致时返回同一条记录;确认状态也是幂等载荷的一部分。 +- **限流**:无接口专属限流。 + +#### 路径参数 + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `orderId` | Long | 是 | 订单 ID,必须大于 0 | + +#### 请求体 + +| 字段 | 类型 | 必填 | 说明 | 校验规则 | +|------|------|------|------|----------| +| `requestId` | String | 是 | 客户端生成的稳定幂等请求 ID,同订单内永久唯一 | 非空,最长 64 字符 | +| `incomeDate` | String(date) | 是 | 收入日期,格式 `YYYY-MM-DD` | 非空 | +| `projectName` | String | 是 | 项目名称 | 非空,最长 100 字符 | +| `projectCategory` | String | 是 | 项目类别编码 | 必须是 `settlement_other_income_project_category` 的启用 `dictValue` | +| `specification` | String | 否 | 票种/规格 | 最长 100 字符;非空时必须是 `settlement_ticket_spec` 的启用 `dictValue` | +| `quantity` | Decimal | 是 | 数量 | >= 0,最多 8 位整数、4 位小数 | +| `unitPrice` | Decimal | 是 | 核算单价 | >= 0,最多 8 位整数、2 位小数 | +| `settlementAmount` | Decimal | 是 | 核算金额 | > 0,最多 8 位整数、2 位小数;必须等于 `quantity * unitPrice` 四舍五入到 2 位 | +| `paymentMethod` | String | 是 | 付款类型 | `CASH_PAID` / `COMPANY_PAID` / `SIGNED` | +| `settlementConfirmStatus` | String | 是 | 本次保存的确认状态 | `UNCONFIRMED` / `CONFIRMED` | +| `voucherUrls` | String[] | 否 | 凭证 URL 列表 | 最多 9 项;每项最长 1024 字符;必须是 http/https | +| `remark` | String | 否 | 备注 | 最长 500 字符 | + +#### 响应 `Result` + +| 字段 | 类型 | 说明 | +|------|------|------| +| `code` | Integer | 业务状态码,成功为 `200` | +| `message` | String | 响应消息,成功为“成功” | +| `data.id` | String | 其他收入 ID | +| `data.requestId` | String / null | 手工新增幂等请求 ID;自动投影记录为空 | +| `data.incomeDate` | String(date) | 收入日期 | +| `data.projectName` | String | 项目名称 | +| `data.projectCategory` | String | 项目类别 `dictValue` | +| `data.projectCategoryName` | String | 项目类别 `dictLabel`;标签缺失时回退为 `projectCategory` | +| `data.specification` | String / null | 票种/规格 `dictValue` | +| `data.quantity` | Decimal | 数量 | +| `data.unitPrice` | Decimal | 核算单价 | +| `data.settlementAmount` | Decimal | 核算金额 | +| `data.paymentMethod` | String | 付款类型 | +| `data.paymentMethodName` | String | 付款类型名称 | +| `data.voucherUrls` | String[] | 凭证 URL 列表 | +| `data.settlementConfirmStatus` | String | 实际保存的确认状态,与请求一致 | +| `data.settlementConfirmStatusName` | String | `未确认` / `已确认` | +| `data.remark` | String / null | 备注 | +| `data.sourceType` | String | 固定为 `ORDER_SURCHARGE` | +| `data.sourceTypeName` | String | 固定为“订单增费” | +| `data.sourceId` | String | 来源附加费 ID | +| `traceId` | String / null | 链路追踪 ID | +| `success` | Boolean | `code=200` 时为 `true` | + +#### 错误码 + +| code | 含义 | 触发场景 | +|------|------|----------| +| `400` | 参数校验失败 | 必填缺失、字段长度超限、确认状态/付款类型格式非法、凭证 URL 非法、金额不等于数量乘单价 | +| `584074` | 当前核单状态不允许修改其他收入 | 订单核单状态不允许写入 | +| `584075` | 其他收入关联的附加费来源无效 | 无法得到有效来源记录 | +| `584076` | 其他收入核算金额必须等于数量乘以核算单价 | 金额关系不一致 | +| `584086` | 无权修改核单资金数据 | 当前账号不是允许写入资金数据的角色 | +| `584087` | requestId 已用于另一笔其他收入 | 同订单重复使用 `requestId`,但请求载荷不同 | +| `584089` | 核单或结算已完成,资金数据不可再修改 | 订单资金数据已锁定 | +| `584103` | 其他收入项目类别不在启用字典范围内 | `projectCategory` 不是启用 `dictValue` | +| `584104` | 其他收入票种/规格不在启用字典范围内 | 非空 `specification` 不是启用 `dictValue` | +| `584105` | 结算字典暂时不可用,请稍后重试 | 字典服务失败、空响应或无启用项 | +| `584106` | 确认状态非法 | 服务层收到非 `UNCONFIRMED` / `CONFIRMED`;公开接口通常先由参数校验返回 `400` | + +#### 业务边界 + +- `settlementConfirmStatus` 必须显式提交;不传不会默认成 `CONFIRMED`。 +- 传 `UNCONFIRMED` 时记录保存成功并返回未确认;提交核单前仍需改为 `CONFIRMED`。 +- `projectCategory` 提交英文 `dictValue`,不能提交中文 `dictLabel`。 +- `specification` 可省略或传 `null`;非空时必须是票种规格启用值。 +- `requestId` 的重复请求必须连确认状态在内保持完整载荷一致,否则返回 `584087`。 + +#### 示例 + +##### 典型成功:保存为已确认 + +**请求**: + +```http +POST /v3/admin/order/60001/settlement/other-incomes +Authorization: Bearer +Content-Type: application/json +``` + +```json +{ + "requestId": "oi-20260729-0001", + "incomeDate": "2026-07-29", + "projectName": "临时加收门票", + "projectCategory": "TICKET", + "specification": "成人票", + "quantity": 2, + "unitPrice": 120.00, + "settlementAmount": 240.00, + "paymentMethod": "CASH_PAID", + "settlementConfirmStatus": "CONFIRMED", + "voucherUrls": ["https://cdn.example.com/vouchers/income-1.jpg"], + "remark": "现场补收" +} +``` + +**响应**: + +```json +{ + "code": 200, + "message": "成功", + "data": { + "id": "99001", + "requestId": "oi-20260729-0001", + "incomeDate": "2026-07-29", + "projectName": "临时加收门票", + "projectCategory": "TICKET", + "projectCategoryName": "门票/游玩项目", + "specification": "成人票", + "quantity": 2, + "unitPrice": 120.00, + "settlementAmount": 240.00, + "paymentMethod": "CASH_PAID", + "paymentMethodName": "现付", + "voucherUrls": ["https://cdn.example.com/vouchers/income-1.jpg"], + "settlementConfirmStatus": "CONFIRMED", + "settlementConfirmStatusName": "已确认", + "remark": "现场补收", + "sourceType": "ORDER_SURCHARGE", + "sourceTypeName": "订单增费", + "sourceId": "88001" + }, + "traceId": "a1b2c3d4-e5f6-7890", + "success": true +} +``` + +##### 边界成功:无规格、保存为未确认 + +**请求**: + +```http +POST /v3/admin/order/60001/settlement/other-incomes +Authorization: Bearer +Content-Type: application/json +``` + +```json +{ + "requestId": "oi-20260729-0002", + "incomeDate": "2026-07-29", + "projectName": "其他收入", + "projectCategory": "OTHER", + "specification": null, + "quantity": 0.0001, + "unitPrice": 100.00, + "settlementAmount": 0.01, + "paymentMethod": "SIGNED", + "settlementConfirmStatus": "UNCONFIRMED", + "voucherUrls": [], + "remark": null +} +``` + +**响应**: + +```json +{ + "code": 200, + "message": "成功", + "data": { + "id": "99002", + "requestId": "oi-20260729-0002", + "incomeDate": "2026-07-29", + "projectName": "其他收入", + "projectCategory": "OTHER", + "projectCategoryName": "其他", + "specification": null, + "quantity": 0.0001, + "unitPrice": 100.00, + "settlementAmount": 0.01, + "paymentMethod": "SIGNED", + "paymentMethodName": "签单", + "voucherUrls": [], + "settlementConfirmStatus": "UNCONFIRMED", + "settlementConfirmStatusName": "未确认", + "remark": null, + "sourceType": "ORDER_SURCHARGE", + "sourceTypeName": "订单增费", + "sourceId": "88002" + }, + "traceId": "b2c3d4e5-f6a7-8901", + "success": true +} +``` + +##### 业务失败:项目类别不是启用字典值 + +**请求**: + +```http +POST /v3/admin/order/60001/settlement/other-incomes +Authorization: Bearer +Content-Type: application/json +``` + +```json +{ + "requestId": "oi-20260729-0003", + "incomeDate": "2026-07-29", + "projectName": "自由文本类别", + "projectCategory": "房差", + "quantity": 1, + "unitPrice": 100.00, + "settlementAmount": 100.00, + "paymentMethod": "CASH_PAID", + "settlementConfirmStatus": "CONFIRMED" +} +``` + +**响应**: + +```json +{ + "code": 584103, + "message": "其他收入项目类别不在启用字典范围内", + "data": null, + "traceId": "c3d4e5f6-a7b8-9012", + "success": false +} +``` + +### 3.2 修改其他收入 + +- **方法 + 路径**:`PUT /v3/admin/order/:orderId/settlement/other-incomes/:incomeId` +- **接口名**:修改其他收入;金额或项目名变化时原子冲销并重建订单增费 +- **使用场景**:修改已有其他收入,或把记录的确认状态在 `UNCONFIRMED` 与 `CONFIRMED` 之间明确切换。 +- **认证**:需要管理后台登录态;需要资金写入权限。 +- **幂等性**:否。 +- **限流**:无接口专属限流。 + +#### 路径参数 + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `orderId` | Long | 是 | 订单 ID,必须大于 0 | +| `incomeId` | Long | 是 | 其他收入 ID,必须大于 0 | + +#### 请求体 + +| 字段 | 类型 | 必填 | 说明 | 校验规则 | +|------|------|------|------|----------| +| `incomeDate` | String(date) | 是 | 收入日期,格式 `YYYY-MM-DD` | 非空 | +| `projectName` | String | 是 | 项目名称 | 非空,最长 100 字符 | +| `projectCategory` | String | 是 | 项目类别编码 | 必须是 `settlement_other_income_project_category` 的启用 `dictValue` | +| `specification` | String | 否 | 票种/规格 | 最长 100 字符;非空时必须是 `settlement_ticket_spec` 的启用 `dictValue` | +| `quantity` | Decimal | 是 | 数量 | >= 0,最多 8 位整数、4 位小数 | +| `unitPrice` | Decimal | 是 | 核算单价 | >= 0,最多 8 位整数、2 位小数 | +| `settlementAmount` | Decimal | 是 | 核算金额 | > 0,最多 8 位整数、2 位小数;必须等于 `quantity * unitPrice` 四舍五入到 2 位 | +| `paymentMethod` | String | 是 | 付款类型 | `CASH_PAID` / `COMPANY_PAID` / `SIGNED` | +| `settlementConfirmStatus` | String | 是 | 本次保存的确认状态 | `UNCONFIRMED` / `CONFIRMED` | +| `voucherUrls` | String[] | 否 | 凭证 URL 列表 | 最多 9 项;每项最长 1024 字符;必须是 http/https | +| `remark` | String | 否 | 备注 | 最长 500 字符 | + +#### 响应 `Result` + +| 字段 | 类型 | 说明 | +|------|------|------| +| `code` | Integer | 业务状态码,成功为 `200` | +| `message` | String | 响应消息,成功为“成功” | +| `data.id` | String | 其他收入 ID | +| `data.requestId` | String / null | 原记录的幂等请求 ID | +| `data.incomeDate` | String(date) | 收入日期 | +| `data.projectName` | String | 项目名称 | +| `data.projectCategory` | String | 项目类别 `dictValue` | +| `data.projectCategoryName` | String | 项目类别 `dictLabel`;标签缺失时回退为 `projectCategory` | +| `data.specification` | String / null | 票种/规格 `dictValue` | +| `data.quantity` | Decimal | 数量 | +| `data.unitPrice` | Decimal | 核算单价 | +| `data.settlementAmount` | Decimal | 核算金额 | +| `data.paymentMethod` | String | 付款类型 | +| `data.paymentMethodName` | String | 付款类型名称 | +| `data.voucherUrls` | String[] | 凭证 URL 列表 | +| `data.settlementConfirmStatus` | String | 实际保存的确认状态,与请求一致 | +| `data.settlementConfirmStatusName` | String | `未确认` / `已确认` | +| `data.remark` | String / null | 备注 | +| `data.sourceType` | String | 固定为 `ORDER_SURCHARGE` | +| `data.sourceTypeName` | String | 固定为“订单增费” | +| `data.sourceId` | String | 来源附加费 ID | +| `traceId` | String / null | 链路追踪 ID | +| `success` | Boolean | `code=200` 时为 `true` | + +#### 错误码 + +| code | 含义 | 触发场景 | +|------|------|----------| +| `400` | 参数校验失败 | 必填缺失、字段长度超限、确认状态/付款类型格式非法、凭证 URL 非法、金额不一致 | +| `584073` | 其他收入不存在或不属于当前订单 | `incomeId` 不存在或不属于 `orderId` | +| `584074` | 当前核单状态不允许修改其他收入 | 订单核单状态不允许写入 | +| `584076` | 其他收入核算金额必须等于数量乘以核算单价 | 金额关系不一致 | +| `584086` | 无权修改核单资金数据 | 当前账号不是允许写入资金数据的角色 | +| `584089` | 核单或结算已完成,资金数据不可再修改 | 订单资金数据已锁定 | +| `584103` | 其他收入项目类别不在启用字典范围内 | `projectCategory` 不是启用 `dictValue` | +| `584104` | 其他收入票种/规格不在启用字典范围内 | 非空 `specification` 不是启用 `dictValue` | +| `584105` | 结算字典暂时不可用,请稍后重试 | 字典服务失败、空响应或无启用项 | +| `584106` | 确认状态非法 | 服务层收到非 `UNCONFIRMED` / `CONFIRMED`;公开接口通常先由参数校验返回 `400` | + +#### 业务边界 + +- PUT 是完整字段保存,不能只传 `settlementConfirmStatus`;请求体中的其他必填字段必须一起提交。 +- 传 `UNCONFIRMED` 会把该记录保存为未确认,不会被后端改回 `CONFIRMED`。 +- 编辑旧自由文本项目类别时,必须改为 7 个启用 `dictValue` 之一。 +- 修改项目名称或金额会同步更新关联订单增费,但不改变确认状态的“按请求保存”规则。 + +#### 示例 + +##### 典型成功:从未确认改为已确认 + +**请求**: + +```http +PUT /v3/admin/order/60001/settlement/other-incomes/99002 +Authorization: Bearer +Content-Type: application/json +``` + +```json +{ + "incomeDate": "2026-07-29", + "projectName": "其他收入", + "projectCategory": "OTHER", + "specification": null, + "quantity": 1, + "unitPrice": 88.00, + "settlementAmount": 88.00, + "paymentMethod": "COMPANY_PAID", + "settlementConfirmStatus": "CONFIRMED", + "voucherUrls": [], + "remark": "已复核" +} +``` + +**响应**: + +```json +{ + "code": 200, + "message": "成功", + "data": { + "id": "99002", + "requestId": "oi-20260729-0002", + "incomeDate": "2026-07-29", + "projectName": "其他收入", + "projectCategory": "OTHER", + "projectCategoryName": "其他", + "specification": null, + "quantity": 1, + "unitPrice": 88.00, + "settlementAmount": 88.00, + "paymentMethod": "COMPANY_PAID", + "paymentMethodName": "公司付款", + "voucherUrls": [], + "settlementConfirmStatus": "CONFIRMED", + "settlementConfirmStatusName": "已确认", + "remark": "已复核", + "sourceType": "ORDER_SURCHARGE", + "sourceTypeName": "订单增费", + "sourceId": "88002" + }, + "traceId": "d4e5f6a7-b8c9-0123", + "success": true +} +``` + +##### 边界成功:主动改回未确认 + +**请求**: + +```http +PUT /v3/admin/order/60001/settlement/other-incomes/99001 +Authorization: Bearer +Content-Type: application/json +``` + +```json +{ + "incomeDate": "2026-07-29", + "projectName": "临时加收门票", + "projectCategory": "TICKET", + "specification": "成人票", + "quantity": 2, + "unitPrice": 120.00, + "settlementAmount": 240.00, + "paymentMethod": "CASH_PAID", + "settlementConfirmStatus": "UNCONFIRMED", + "voucherUrls": [], + "remark": "金额待复核" +} +``` + +**响应**: + +```json +{ + "code": 200, + "message": "成功", + "data": { + "id": "99001", + "requestId": "oi-20260729-0001", + "incomeDate": "2026-07-29", + "projectName": "临时加收门票", + "projectCategory": "TICKET", + "projectCategoryName": "门票/游玩项目", + "specification": "成人票", + "quantity": 2, + "unitPrice": 120.00, + "settlementAmount": 240.00, + "paymentMethod": "CASH_PAID", + "paymentMethodName": "现付", + "voucherUrls": [], + "settlementConfirmStatus": "UNCONFIRMED", + "settlementConfirmStatusName": "未确认", + "remark": "金额待复核", + "sourceType": "ORDER_SURCHARGE", + "sourceTypeName": "订单增费", + "sourceId": "88001" + }, + "traceId": "e5f6a7b8-c9d0-1234", + "success": true +} +``` + +##### 业务失败:规格不是启用字典值 + +**请求**: + +```http +PUT /v3/admin/order/60001/settlement/other-incomes/99001 +Authorization: Bearer +Content-Type: application/json +``` + +```json +{ + "incomeDate": "2026-07-29", + "projectName": "临时加收门票", + "projectCategory": "TICKET", + "specification": "儿童票", + "quantity": 1, + "unitPrice": 80.00, + "settlementAmount": 80.00, + "paymentMethod": "CASH_PAID", + "settlementConfirmStatus": "CONFIRMED" +} +``` + +**响应**: + +```json +{ + "code": 584104, + "message": "其他收入票种/规格不在启用字典范围内", + "data": null, + "traceId": "f6a7b8c9-d0e1-2345", + "success": false +} +``` + +### 3.3 新增其他支出 + +- **方法 + 路径**:`POST /v3/admin/order/:orderId/settlement/other-expenses` +- **接口名**:新增其他支出 +- **使用场景**:在核单其他支出页新增一条记录,并明确保存为未确认或已确认。 +- **认证**:需要管理后台登录态;需要资金写入权限。 +- **幂等性**:否。 +- **限流**:无接口专属限流。 + +#### 路径参数 + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `orderId` | Long | 是 | 订单 ID,必须大于 0 | + +#### 请求体 + +| 字段 | 类型 | 必填 | 说明 | 校验规则 | +|------|------|------|------|----------| +| `expenseType` | String | 是 | 支出类型 | `FUEL` / `TOLL` / `PARKING` / `RENTAL` / `MAINTENANCE` / `OTHER` | +| `projectName` | String | 是 | 项目名称 | 非空,最长 200 字符 | +| `expenseDate` | String(date) | 否 | 发生日期,格式 `YYYY-MM-DD` | 可空 | +| `actualAmount` | Decimal | 是 | 实际金额 | >= 0,最多 8 位整数、2 位小数 | +| `paymentMethod` | String | 是 | 付款类型 | `CASH_PAID` / `COMPANY_PAID` / `SIGNED` | +| `settlementConfirmStatus` | String | 是 | 本次保存的确认状态 | `UNCONFIRMED` / `CONFIRMED` | +| `voucherUrls` | String[] | 否 | 凭证 URL 列表 | 最多 9 项;每项非空、最长 1024 字符;必须是 http/https | +| `remark` | String | 否 | 备注 | 最长 512 字符 | + +#### 响应 `Result` + +| 字段 | 类型 | 说明 | +|------|------|------| +| `code` | Integer | 业务状态码,成功为 `200` | +| `message` | String | 响应消息,成功为“成功” | +| `data.id` | String | 其他支出 ID | +| `data.expenseType` | String | 支出类型 | +| `data.projectName` | String | 项目名称 | +| `data.expenseDate` | String(date) / null | 发生日期 | +| `data.actualAmount` | String | 实际金额,按字符串返回 | +| `data.paymentMethod` | String | 付款类型 | +| `data.voucherUrls` | String[] | 凭证 URL 列表 | +| `data.settlementConfirmStatus` | String | 实际保存的确认状态,与请求一致 | +| `data.remark` | String / null | 备注 | +| `traceId` | String / null | 链路追踪 ID | +| `success` | Boolean | `code=200` 时为 `true` | + +#### 错误码 + +| code | 含义 | 触发场景 | +|------|------|----------| +| `400` | 参数校验失败 | 必填缺失、字段长度超限、确认状态/支出类型/付款类型格式非法、金额或凭证格式非法 | +| `584086` | 无权修改核单资金数据 | 当前账号不是允许写入资金数据的角色 | +| `584089` | 核单或结算已完成,资金数据不可再修改 | 订单资金数据已锁定 | +| `584095` | 其他支出字段超出允许范围 | 支出类型、项目名、金额或备注超出业务允许范围 | +| `584096` | 付款类型不合法 | `paymentMethod` 不是允许值 | +| `584097` | 凭证 URL 格式或数量不合法 | 凭证数量、协议或单项长度不合法 | +| `584106` | 确认状态非法 | 服务层收到非 `UNCONFIRMED` / `CONFIRMED`;公开接口通常先由参数校验返回 `400` | +| `584307` | 当前核单状态不允许写入餐食或其他支出 | 订单核单状态不允许写入 | + +#### 业务边界 + +- `settlementConfirmStatus` 必须显式提交;不传不会默认成 `CONFIRMED`。 +- `actualAmount=0.00` 允许保存。 +- `expenseDate` 可为 `null`。 +- 传 `UNCONFIRMED` 时记录保存成功,但提交核单前仍需通过 PUT 改为 `CONFIRMED`。 + +#### 示例 + +##### 典型成功:保存为未确认 + +**请求**: + +```http +POST /v3/admin/order/60001/settlement/other-expenses +Authorization: Bearer +Content-Type: application/json +``` + +```json +{ + "expenseType": "TOLL", + "projectName": "过路费", + "expenseDate": "2026-07-29", + "actualAmount": 50.00, + "paymentMethod": "CASH_PAID", + "settlementConfirmStatus": "UNCONFIRMED", + "voucherUrls": ["https://cdn.example.com/vouchers/toll.jpg"], + "remark": "金额待复核" +} +``` + +**响应**: + +```json +{ + "code": 200, + "message": "成功", + "data": { + "id": "801", + "expenseType": "TOLL", + "projectName": "过路费", + "expenseDate": "2026-07-29", + "actualAmount": "50.00", + "paymentMethod": "CASH_PAID", + "voucherUrls": ["https://cdn.example.com/vouchers/toll.jpg"], + "settlementConfirmStatus": "UNCONFIRMED", + "remark": "金额待复核" + }, + "traceId": "07b8c9d0-e1f2-3456", + "success": true +} +``` + +##### 边界成功:0 元、无日期、保存为已确认 + +**请求**: + +```http +POST /v3/admin/order/60001/settlement/other-expenses +Authorization: Bearer +Content-Type: application/json +``` + +```json +{ + "expenseType": "OTHER", + "projectName": "0元备注支出", + "expenseDate": null, + "actualAmount": 0.00, + "paymentMethod": "SIGNED", + "settlementConfirmStatus": "CONFIRMED", + "voucherUrls": [], + "remark": null +} +``` + +**响应**: + +```json +{ + "code": 200, + "message": "成功", + "data": { + "id": "802", + "expenseType": "OTHER", + "projectName": "0元备注支出", + "expenseDate": null, + "actualAmount": "0.00", + "paymentMethod": "SIGNED", + "voucherUrls": [], + "settlementConfirmStatus": "CONFIRMED", + "remark": null + }, + "traceId": "18c9d0e1-f2a3-4567", + "success": true +} +``` + +##### 参数失败:未提交确认状态 + +**请求**: + +```http +POST /v3/admin/order/60001/settlement/other-expenses +Authorization: Bearer +Content-Type: application/json +``` + +```json +{ + "expenseType": "PARKING", + "projectName": "停车费", + "actualAmount": 30.00, + "paymentMethod": "CASH_PAID" +} +``` + +**响应**: + +```json +{ + "code": 400, + "message": "确认状态不能为空", + "data": null, + "traceId": "29d0e1f2-a3b4-5678", + "success": false +} +``` + +### 3.4 修改其他支出 + +- **方法 + 路径**:`PUT /v3/admin/order/:orderId/settlement/other-expenses/:settlementId` +- **接口名**:修改其他支出 +- **使用场景**:修改已有其他支出,或明确切换该记录的确认状态。 +- **认证**:需要管理后台登录态;需要资金写入权限。 +- **幂等性**:否。 +- **限流**:无接口专属限流。 + +#### 路径参数 + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `orderId` | Long | 是 | 订单 ID,必须大于 0 | +| `settlementId` | Long | 是 | 其他支出 ID,必须大于 0 | + +#### 请求体 + +| 字段 | 类型 | 必填 | 说明 | 校验规则 | +|------|------|------|------|----------| +| `expenseType` | String | 是 | 支出类型 | `FUEL` / `TOLL` / `PARKING` / `RENTAL` / `MAINTENANCE` / `OTHER` | +| `projectName` | String | 是 | 项目名称 | 非空,最长 200 字符 | +| `expenseDate` | String(date) | 否 | 发生日期,格式 `YYYY-MM-DD` | 可空 | +| `actualAmount` | Decimal | 是 | 实际金额 | >= 0,最多 8 位整数、2 位小数 | +| `paymentMethod` | String | 是 | 付款类型 | `CASH_PAID` / `COMPANY_PAID` / `SIGNED` | +| `settlementConfirmStatus` | String | 是 | 本次保存的确认状态 | `UNCONFIRMED` / `CONFIRMED` | +| `voucherUrls` | String[] | 否 | 凭证 URL 列表 | 最多 9 项;每项非空、最长 1024 字符;必须是 http/https | +| `remark` | String | 否 | 备注 | 最长 512 字符 | + +#### 响应 `Result` + +| 字段 | 类型 | 说明 | +|------|------|------| +| `code` | Integer | 业务状态码,成功为 `200` | +| `message` | String | 响应消息,成功为“成功” | +| `data.id` | String | 其他支出 ID | +| `data.expenseType` | String | 支出类型 | +| `data.projectName` | String | 项目名称 | +| `data.expenseDate` | String(date) / null | 发生日期 | +| `data.actualAmount` | String | 实际金额,按字符串返回 | +| `data.paymentMethod` | String | 付款类型 | +| `data.voucherUrls` | String[] | 凭证 URL 列表 | +| `data.settlementConfirmStatus` | String | 实际保存的确认状态,与请求一致 | +| `data.remark` | String / null | 备注 | +| `traceId` | String / null | 链路追踪 ID | +| `success` | Boolean | `code=200` 时为 `true` | + +#### 错误码 + +| code | 含义 | 触发场景 | +|------|------|----------| +| `400` | 参数校验失败 | 必填缺失、字段长度超限、确认状态/支出类型/付款类型格式非法、金额或凭证格式非法 | +| `584086` | 无权修改核单资金数据 | 当前账号不是允许写入资金数据的角色 | +| `584089` | 核单或结算已完成,资金数据不可再修改 | 订单资金数据已锁定 | +| `584091` | 其他支出不存在或不属于当前订单 | `settlementId` 不存在或不属于 `orderId` | +| `584095` | 其他支出字段超出允许范围 | 支出类型、项目名、金额或备注超出业务允许范围 | +| `584096` | 付款类型不合法 | `paymentMethod` 不是允许值 | +| `584097` | 凭证 URL 格式或数量不合法 | 凭证数量、协议或单项长度不合法 | +| `584106` | 确认状态非法 | 服务层收到非 `UNCONFIRMED` / `CONFIRMED`;公开接口通常先由参数校验返回 `400` | +| `584307` | 当前核单状态不允许写入餐食或其他支出 | 订单核单状态不允许写入 | + +#### 业务边界 + +- PUT 是完整字段保存,不能只传 `settlementConfirmStatus`。 +- 请求传 `UNCONFIRMED` 时,原先已确认的记录也会按本次请求保存为未确认。 +- `actualAmount` 在响应中是字符串,不是 JSON 数字。 +- 不存在或不属于当前订单的 `settlementId` 返回 `584091`。 + +#### 示例 + +##### 典型成功:修改并确认 + +**请求**: + +```http +PUT /v3/admin/order/60001/settlement/other-expenses/801 +Authorization: Bearer +Content-Type: application/json +``` + +```json +{ + "expenseType": "PARKING", + "projectName": "停车费", + "expenseDate": "2026-07-29", + "actualAmount": 35.00, + "paymentMethod": "COMPANY_PAID", + "settlementConfirmStatus": "CONFIRMED", + "voucherUrls": ["https://cdn.example.com/vouchers/parking.jpg"], + "remark": "已复核" +} +``` + +**响应**: + +```json +{ + "code": 200, + "message": "成功", + "data": { + "id": "801", + "expenseType": "PARKING", + "projectName": "停车费", + "expenseDate": "2026-07-29", + "actualAmount": "35.00", + "paymentMethod": "COMPANY_PAID", + "voucherUrls": ["https://cdn.example.com/vouchers/parking.jpg"], + "settlementConfirmStatus": "CONFIRMED", + "remark": "已复核" + }, + "traceId": "3ae1f2a3-b4c5-6789", + "success": true +} +``` + +##### 边界成功:改回未确认 + +**请求**: + +```http +PUT /v3/admin/order/60001/settlement/other-expenses/801 +Authorization: Bearer +Content-Type: application/json +``` + +```json +{ + "expenseType": "PARKING", + "projectName": "停车费", + "expenseDate": "2026-07-29", + "actualAmount": 35.00, + "paymentMethod": "COMPANY_PAID", + "settlementConfirmStatus": "UNCONFIRMED", + "voucherUrls": [], + "remark": "凭证待补" +} +``` + +**响应**: + +```json +{ + "code": 200, + "message": "成功", + "data": { + "id": "801", + "expenseType": "PARKING", + "projectName": "停车费", + "expenseDate": "2026-07-29", + "actualAmount": "35.00", + "paymentMethod": "COMPANY_PAID", + "voucherUrls": [], + "settlementConfirmStatus": "UNCONFIRMED", + "remark": "凭证待补" + }, + "traceId": "4bf2a3b4-c5d6-7890", + "success": true +} +``` + +##### 业务失败:其他支出不存在 + +**请求**: + +```http +PUT /v3/admin/order/60001/settlement/other-expenses/99999 +Authorization: Bearer +Content-Type: application/json +``` + +```json +{ + "expenseType": "OTHER", + "projectName": "不存在记录", + "actualAmount": 10.00, + "paymentMethod": "CASH_PAID", + "settlementConfirmStatus": "CONFIRMED" +} +``` + +**响应**: + +```json +{ + "code": 584091, + "message": "其他支出不存在或不属于当前订单", + "data": null, + "traceId": "5ca3b4c5-d6e7-8901", + "success": false +} +``` + +## 6. 枚举 / 数据字典 + +### 6.1 `settlementConfirmStatus` + +**所属字段**:四个接口请求和响应的 `settlementConfirmStatus` | **类型**:String | **必填**:是 + +| 值 | 中文 | 说明 | +|----|------|------| +| `UNCONFIRMED` | 未确认 | 保存成功但仍阻塞核单提交,需要后续 PUT 改为已确认 | +| `CONFIRMED` | 已确认 | 保存成功且该记录确认状态为已确认 | + +### 6.2 `settlement_other_income_project_category` + +**所属字段**:其他收入请求 `projectCategory`、响应 `projectCategory/projectCategoryName` + +| `dictValue` | `dictLabel` | 说明 | +|-------------|-------------|------| +| `HOTEL` | 住宿 | 住宿类其他收入 | +| `TICKET` | 门票/游玩项目 | 门票或游玩项目类其他收入 | +| `MEAL` | 餐食 | 餐食类其他收入 | +| `VEHICLE` | 车辆 | 车辆类其他收入 | +| `GUIDE` | 导游 | 导游类其他收入 | +| `PHOTOGRAPHER` | 摄影 | 摄影类其他收入 | +| `OTHER` | 其他 | 以上类别以外的其他收入 | + +加载接口: + +`GET /admin/dict/data/settlement_other_income_project_category` + +展示 `dictLabel`,保存提交 `dictValue`。 + +### 6.3 `settlement_ticket_spec` + +**所属字段**:其他收入请求/响应 `specification` | **类型**:String | **必填**:否 + +| `dictValue` | `dictLabel` | 说明 | +|-------------|-------------|------| +| `成人票` | 成人票 | 当前启用票种/规格 | + +加载接口: + +`GET /admin/dict/data/settlement_ticket_spec` + +后续增加启用项时,接口会返回新项;保存时提交选中项的 `dictValue`。 + +### 6.4 `paymentMethod` + +**所属字段**:四个接口请求/响应 `paymentMethod` + +| 值 | 中文 | +|----|------| +| `CASH_PAID` | 现付 | +| `COMPANY_PAID` | 公司付款 | +| `SIGNED` | 签单 | + +### 6.5 `expenseType` + +**所属字段**:其他支出请求/响应 `expenseType` + +| 值 | 中文 | +|----|------| +| `FUEL` | 油费 | +| `TOLL` | 过路费 | +| `PARKING` | 停车费 | +| `RENTAL` | 租赁费 | +| `MAINTENANCE` | 维修保养费 | +| `OTHER` | 其他 | + +### 6.6 `sourceType` + +**所属字段**:其他收入响应 `sourceType` + +| 值 | 中文 | +|----|------| +| `ORDER_SURCHARGE` | 订单增费 | + +## 验证证据 + +- merge commit `7477b03963` 中的 `SettlementControllerTest` 覆盖四个保存接口显式接收 + `UNCONFIRMED` / `CONFIRMED`,以及缺失或非法确认状态返回参数错误。 +- `SettlementDictValueValidatorTest` 覆盖项目类别、可选票种规格、确认状态、禁用字典项、 + 空字典响应及 Feign 异常。 +- hl-user-service 的迁移结构测试与 MySQL 迁移测试覆盖 7 个项目类别值、固定 ID、 + 重复执行和冲突失败。 +- changelog consumer gate、文件名校验、frontmatter 校验及仓库 46 项自动化测试均已通过。 +- 本次没有新增或修改 Gateway 路由,`gateway_status=not_required`。 + +## 9. 业务边界 + +- 四个接口都必须显式提交 `settlementConfirmStatus`,且后端按请求值保存。 +- `UNCONFIRMED` 记录可以保存,但提交核单时仍会被未确认门禁拦截。 +- #5310 删除的 + `POST /v3/admin/order/:orderId/settlement/other-incomes/confirm` + 和 + `POST /v3/admin/order/:orderId/settlement/other-expenses/confirm` + 不恢复;确认状态通过对应 PUT 完整保存。 +- 其他收入 `projectCategory` 只接受 7 个启用项目类别值。 +- 其他收入 `specification` 可空;非空时只接受 `settlement_ticket_spec` 启用值。 +- 字典不可用时其他收入 POST/PUT 失败,不会绕过校验写入。 + +## 10. 修改前后对比 + +### 10.1 字段级对比 + +| 字段 | 改前 | 改后 | +|------|------|------| +| `settlementConfirmStatus`(四个请求) | 无该请求字段;保存后强制 `CONFIRMED` | 必填,`UNCONFIRMED` / `CONFIRMED`,按请求保存 | +| `projectCategory`(其他收入) | 非空自由文本,最长 64 字符 | 必须是 `settlement_other_income_project_category` 启用 `dictValue` | +| `projectCategoryName`(其他收入响应) | 通常与 `projectCategory` 原值相同 | 返回项目类别 `dictLabel`,缺失时回退原值 | +| `specification`(其他收入) | 可选自由文本,最长 100 字符 | 可空;非空时必须是 `settlement_ticket_spec` 启用 `dictValue` | + +### 10.2 行为级对比 + +| 行为 | 改前 | 改后 | +|------|------|------| +| 新增/修改其他收入 | 保存即确认 | 调用方决定保存为未确认或已确认 | +| 新增/修改其他支出 | 保存即确认 | 调用方决定保存为未确认或已确认 | +| 切换确认状态 | 保存接口总是写 `CONFIRMED` | PUT 完整保存时传目标状态 | +| 其他收入项目类别 | 可提交自由文本 | 按启用字典强校验 | +| 其他收入规格 | 可提交自由文本 | 非空时按票种规格启用字典强校验 | +| 独立确认接口 | #5310 已删除 | 仍保持删除,不恢复 | + +## 11. 影响评估 + +- **是否破坏向后兼容**:是。四个请求新增必填字段;其他收入自由文本项目类别及非字典规格不再接受。 +- **前端是否必须同步上线**:是。调用四个接口时必须提交 `settlementConfirmStatus`;其他收入必须改用项目类别和票种规格字典值。 +- **前端读取兼容**:响应结构不新增字段,但 `projectCategoryName` 从原始值改为字典中文标签。 + +## 12. 注意事项 + +- 不能沿用“保存成功就一定已确认”的前端判断;以响应 `settlementConfirmStatus` 为准。 +- PUT 是完整保存,不是确认状态局部更新;切换状态时必须提交该接口全部必填字段。 +- 其他收入项目类别展示中文 `dictLabel`,请求提交英文 `dictValue`。 +- 票种/规格继续使用 `settlement_ticket_spec`,不要使用项目类别字典填充。 +- 旧记录如果保存了自由文本 `projectCategory` 或非字典 `specification`,再次编辑时必须先转换为当前启用字典值。 + +## 13. 关联 / 联系人 + +### 13.1 链接 + +- **Issue**: [#5320](https://git.1814.love:8443/wx/HL/issues/5320) +- **PR**: [#5330](https://git.1814.love:8443/wx/HL/pulls/5330) +- **Merge commit**: [7477b03963](https://git.1814.love:8443/wx/HL/commit/7477b0396352e7591e78a5d5ab9b080a640187d7) +- **前序变更**: [#5310](https://git.1814.love:8443/wx/HL/issues/5310) / [#5313](https://git.1814.love:8443/wx/HL/pulls/5313) + +### 13.2 联系人 + +- **后端负责人**: @yst diff --git a/changelogs/2026-07/29_5320_核单其他收入项目类别字典.md b/changelogs/2026-07/29_5320_核单其他收入项目类别字典.md new file mode 100644 index 0000000..c30dd72 --- /dev/null +++ b/changelogs/2026-07/29_5320_核单其他收入项目类别字典.md @@ -0,0 +1,322 @@ +# 【✨ 新增字典·管理后台】核单其他收入项目类别字典(#5320) + +> **PR**: #5330 | **服务**: hl-user-service | **更新时间**: 2026-07-29 09:19 + +## 1. 接口背景 + +核单「其他收入」的项目类别改为统一字典值。管理后台通过现有字典查询接口加载 7 个启用项,展示 `dictLabel`,保存其他收入时提交对应的 `dictValue`。 + +票种/规格不使用本字典,仍通过 `settlement_ticket_spec` 字典加载并提交。 + +## 2. 变更清单 + +| # | 接口 | 方法 | 路径 | 变更类型 | 说明 | +|---|------|------|------|----------|------| +| 1 | 按类型查询字典数据 | GET | `/admin/dict/data/settlement_other_income_project_category` | 修改接口 | 新增 7 个核单其他收入项目类别启用项 | + +## 3. 接口详情 + +### 3.1 按类型查询核单其他收入项目类别 + +- **方法 + 路径**:`GET /admin/dict/data/settlement_other_income_project_category` +- **接口名**:按类型查询字典数据 +- **使用场景**:加载核单其他收入的项目类别选项。 +- **认证**:需要管理后台 JWT。 +- **幂等性**:是,只读查询。 +- **限流**:无接口专属限流约定。 +- **排序**:按 `sortOrder` 升序,同一排序号再按 `dictDataId` 升序。 +- **过滤**:只返回 `status=ACTIVE` 的字典项。 + +## 4. 接口入参 + +### 4.1 路径参数 + +| 字段 | 类型 | 必填 | 固定值 | 说明 | +|------|------|------|--------|------| +| `dictType` | String | 是 | `settlement_other_income_project_category` | 核单其他收入项目类别字典类型编码 | + +### 4.2 Query 参数与请求体 + +无 Query 参数,无请求体。 + +## 5. 出参(响应) + +### 5.1 统一响应字段 + +| 字段 | 类型 | 说明 | +|------|------|------| +| `code` | Integer | 业务状态码,成功为 `200` | +| `message` | String | 响应消息,成功为“成功” | +| `data` | Array | 启用的字典项列表;无匹配项时为 `[]` | +| `traceId` | String / null | 链路追踪 ID | +| `success` | Boolean | `code=200` 时为 `true` | + +### 5.2 `data[]` 字典项字段 + +| 字段 | 类型 | 可为空 | 说明 | +|------|------|--------|------| +| `dictDataId` | Long | 否 | 字典数据 ID | +| `dictType` | String | 否 | 固定为 `settlement_other_income_project_category` | +| `dictLabel` | String | 否 | 展示文案 | +| `dictValue` | String | 否 | 保存其他收入时提交到 `projectCategory` 的值 | +| `icon` | String | 是 | 图标,本字典当前为 `null` | +| `color` | String | 是 | 展示色值,本字典当前为 `null` | +| `sortOrder` | Integer | 否 | 排序号,越小越靠前 | +| `status` | String | 否 | 字典项状态,本接口返回项为 `ACTIVE` | +| `remark` | String | 是 | 字典项说明 | +| `createdAt` | String | 是 | 创建时间,格式 `yyyy-MM-dd HH:mm:ss` | +| `updatedAt` | String | 是 | 更新时间,格式 `yyyy-MM-dd HH:mm:ss` | + +## 6. 枚举 / 数据字典 + +### 6.1 `settlement_other_income_project_category` + +**展示字段**:`dictLabel` | **提交字段**:`dictValue` | **提交目标**:其他收入保存接口的 `projectCategory` + +| `dictValue` | `dictLabel` | `sortOrder` | `status` | 说明 | +|-------------|-------------|-------------|----------|------| +| `HOTEL` | 住宿 | `10` | `ACTIVE` | 住宿类其他收入 | +| `TICKET` | 门票/游玩项目 | `20` | `ACTIVE` | 门票或游玩项目类其他收入 | +| `MEAL` | 餐食 | `30` | `ACTIVE` | 餐食类其他收入 | +| `VEHICLE` | 车辆 | `40` | `ACTIVE` | 车辆类其他收入 | +| `GUIDE` | 导游 | `50` | `ACTIVE` | 导游类其他收入 | +| `PHOTOGRAPHER` | 摄影 | `60` | `ACTIVE` | 摄影类其他收入 | +| `OTHER` | 其他 | `70` | `ACTIVE` | 以上类别以外的其他收入 | + +### 6.2 `status` + +| 值 | 中文 | 是否由本接口返回 | +|----|------|------------------| +| `ACTIVE` | 启用 | 是 | +| `INACTIVE` | 禁用 | 否;查询接口过滤禁用项 | + +### 6.3 票种/规格字典(独立) + +其他收入保存接口的 `specification` 不使用项目类别字典;其选项来自: + +`GET /admin/dict/data/settlement_ticket_spec` + +当前启用项为: + +| `dictValue` | `dictLabel` | 提交目标 | +|-------------|-------------|----------| +| `成人票` | 成人票 | 其他收入保存接口的 `specification` | + +## 7. 错误码 + +| code | 含义 | 触发场景 | +|------|------|----------| +| `200` | 成功 | 查询成功;没有匹配项时 `data=[]` | +| `401` | 未认证或认证失效 | 未携带有效管理后台 JWT | +| `500` | 系统异常 | 查询过程发生未预期异常 | + +## 8. 示例 + +### 8.1 典型成功 + +**请求**: + +```http +GET /admin/dict/data/settlement_other_income_project_category +Authorization: Bearer +``` + +无请求体。 + +**响应**: + +```json +{ + "code": 200, + "message": "成功", + "data": [ + { + "dictDataId": 101421, + "dictType": "settlement_other_income_project_category", + "dictLabel": "住宿", + "dictValue": "HOTEL", + "icon": null, + "color": null, + "sortOrder": 10, + "status": "ACTIVE", + "remark": "核单其他收入项目类别", + "createdAt": "2026-07-29 09:00:00", + "updatedAt": "2026-07-29 09:00:00" + }, + { + "dictDataId": 101422, + "dictType": "settlement_other_income_project_category", + "dictLabel": "门票/游玩项目", + "dictValue": "TICKET", + "icon": null, + "color": null, + "sortOrder": 20, + "status": "ACTIVE", + "remark": "核单其他收入项目类别", + "createdAt": "2026-07-29 09:00:00", + "updatedAt": "2026-07-29 09:00:00" + }, + { + "dictDataId": 101423, + "dictType": "settlement_other_income_project_category", + "dictLabel": "餐食", + "dictValue": "MEAL", + "icon": null, + "color": null, + "sortOrder": 30, + "status": "ACTIVE", + "remark": "核单其他收入项目类别", + "createdAt": "2026-07-29 09:00:00", + "updatedAt": "2026-07-29 09:00:00" + }, + { + "dictDataId": 101424, + "dictType": "settlement_other_income_project_category", + "dictLabel": "车辆", + "dictValue": "VEHICLE", + "icon": null, + "color": null, + "sortOrder": 40, + "status": "ACTIVE", + "remark": "核单其他收入项目类别", + "createdAt": "2026-07-29 09:00:00", + "updatedAt": "2026-07-29 09:00:00" + }, + { + "dictDataId": 101425, + "dictType": "settlement_other_income_project_category", + "dictLabel": "导游", + "dictValue": "GUIDE", + "icon": null, + "color": null, + "sortOrder": 50, + "status": "ACTIVE", + "remark": "核单其他收入项目类别", + "createdAt": "2026-07-29 09:00:00", + "updatedAt": "2026-07-29 09:00:00" + }, + { + "dictDataId": 101426, + "dictType": "settlement_other_income_project_category", + "dictLabel": "摄影", + "dictValue": "PHOTOGRAPHER", + "icon": null, + "color": null, + "sortOrder": 60, + "status": "ACTIVE", + "remark": "核单其他收入项目类别", + "createdAt": "2026-07-29 09:00:00", + "updatedAt": "2026-07-29 09:00:00" + }, + { + "dictDataId": 101427, + "dictType": "settlement_other_income_project_category", + "dictLabel": "其他", + "dictValue": "OTHER", + "icon": null, + "color": null, + "sortOrder": 70, + "status": "ACTIVE", + "remark": "核单其他收入项目类别", + "createdAt": "2026-07-29 09:00:00", + "updatedAt": "2026-07-29 09:00:00" + } + ], + "traceId": "a1b2c3d4-e5f6-7890", + "success": true +} +``` + +### 8.2 边界情况:不存在的字典类型 + +**请求**: + +```http +GET /admin/dict/data/not_exists +Authorization: Bearer +``` + +无请求体。 + +**响应**: + +```json +{ + "code": 200, + "message": "成功", + "data": [], + "traceId": "b2c3d4e5-f6a7-8901", + "success": true +} +``` + +### 8.3 业务失败:未认证 + +**请求**: + +```http +GET /admin/dict/data/settlement_other_income_project_category +``` + +无请求体,且未携带 `Authorization`。 + +**响应**: + +```json +{ + "code": 401, + "message": "未认证或登录已失效", + "data": null, + "traceId": "c3d4e5f6-a7b8-9012", + "success": false +} +``` + +## 9. 业务边界 + +- 查询接口只返回启用项,禁用项不进入 `data`。 +- 前端展示 `dictLabel`,保存时提交 `dictValue`;不要提交 `dictDataId` 或中文标签。 +- `projectCategory` 只接受上述 7 个启用值。 +- `specification` 是独立字段,非空时必须使用 `settlement_ticket_spec` 的启用 `dictValue`。 +- 字典为单层平铺列表,不包含父子层级。 + +## 10. 修改前后对比 + +### 10.1 字段级对比 + +| 项目 | 改前 | 改后 | +|------|------|------| +| 字典接口响应结构 | `Result>` | 不变 | +| 其他收入项目类别字典 | 无专用字典 | 新增 `settlement_other_income_project_category` | +| 可提交值 | 前端可能提交自由文本 | 固定为 7 个 `dictValue` | + +### 10.2 行为级对比 + +| 行为 | 改前 | 改后 | +|------|------|------| +| 加载项目类别 | 前端自行维护 | 调用本接口动态加载 | +| 保存项目类别 | 提交自由文本 | 提交选中项的 `dictValue` | +| 票种/规格 | 与项目类别未明确区分 | 继续独立读取 `settlement_ticket_spec` | + +## 11. 影响评估 + +- **是否破坏向后兼容**:字典查询接口本身兼容;其他收入保存接口开始校验项目类别启用值。 +- **前端是否必须同步上线**:是。保存其他收入前必须把 `projectCategory` 切换为本字典的 `dictValue`。 + +## 12. 注意事项 + +- 不要把 7 个项目类别写死为中文值;展示中文,提交英文 `dictValue`。 +- `projectCategory=TICKET` 时,`specification` 仍从 `settlement_ticket_spec` 选择。 +- 旧记录若存的是非字典值,编辑保存时需要改成上述 7 个值之一。 + +## 13. 关联 / 联系人 + +### 13.1 链接 + +- **Issue**: [#5320](https://git.1814.love:8443/wx/HL/issues/5320) +- **PR**: [#5330](https://git.1814.love:8443/wx/HL/pulls/5330) +- **Merge commit**: [7477b03963](https://git.1814.love:8443/wx/HL/commit/7477b0396352e7591e78a5d5ab9b080a640187d7) + +### 13.2 联系人 + +- **后端负责人**: @yst