---
schema: "hl-changelog/v2"
ticket: "5477"
title: "核单其他收入移除项目类别与票种规格字段"
consumer: "admin"
change_type: "修改接口"
author: "yaosutu(GIT)"
backend_status: "deployed"
gateway_status: "verified"
frontend_status: "implemented"
frontend_owner: "pi-main-session"
frontend_ref: "hl-admin@9f46fa040a77cd899bbc9c21b9b8304a518499e2"
target_release: ""
verified_at: "2026-08-04"
status_note: "PR #5488 已合并 dev-v3；测试服真实网关已验证 POST 无旧字段成功、PUT 多传旧字段被忽略、GET/POST/PUT 响应均不含 projectCategory/projectCategoryName/specification。前端需删除项目类别与票种规格控件及相关字段读写。"
updated_at: "2026-08-04"
base: "dev-v3"
---

# 【⚠️ 修改接口·管理后台】核单其他收入移除项目类别与票种规格字段（#5477）

> **PR**: [#5488](https://git.1814.love:8443/wx/HL/pulls/5488) | **服务**: hl-order-service-v3 | **更新时间**: 2026-08-04 16:30

## 1. 接口背景

“其他收入”页签本身已表达项目类型，继续要求填写“项目类别”属于重复信息；“票种/规格”也不适用于其他收入。此次统一收口新增、修改和查询契约，前端应删除这两个控件及相关字段读写。

## 2. 变更清单

| # | 接口 | 方法 | 路径 | 变更类型 | 前端动作 |
|---|---|---|---|---|---|
| 1 | 查询其他收入核单明细及增减费汇总 | GET | `/v3/admin/order/{orderId}/settlement/other-incomes` | 删除出参字段 | 停止读取 `projectCategory`、`projectCategoryName`、`specification` |
| 2 | 新增其他收入并原子创建订单增费 | POST | `/v3/admin/order/{orderId}/settlement/other-incomes` | 删除入参、出参字段 | 删除项目类别与票种规格控件；停止传两个旧入参 |
| 3 | 修改其他收入 | PUT | `/v3/admin/order/{orderId}/settlement/other-incomes/{incomeId}` | 删除入参、出参字段 | 删除项目类别与票种规格控件；停止传两个旧入参 |

> DELETE 接口签名与返回结构未变化，不属于本次契约变更。

## 3. 接口详情

### 3.1 查询其他收入核单明细及增减费汇总

- **方法/路径**：`GET /v3/admin/order/{orderId}/settlement/other-incomes`
- **使用场景**：进入其他收入页签，以及新增、修改、删除后刷新明细和汇总。
- **认证**：需要管理后台登录态；配房角色不可访问。
- **幂等性**：只读接口，可安全重复调用。
- **限流**：未声明接口专属限流。

**路径参数**：

| 字段 | 类型 | 必填 | 说明 | 校验 |
|---|---|---:|---|---|
| `orderId` | String(Long) | 是 | 订单 ID | 必须大于 0 |

**请求体**：无。

**成功响应 `data`**：

| 字段 | 类型 | 可空 | 说明 |
|---|---|---:|---|
| `items` | `OtherIncomeItem[]` | 否 | 其他收入明细；无数据返回 `[]` |
| `deductions` | `Deduction[]` | 否 | 只读减费明细；无数据返回 `[]` |
| `summary` | `Summary` | 否 | 当前有效增减费汇总 |

`OtherIncomeItem` 完整字段：

| 字段 | 类型 | 可空 | 说明 |
|---|---|---:|---|
| `id` | String(Long) | 否 | 其他收入 ID |
| `requestId` | String | 是 | 手工新增幂等请求 ID；自动投影时为空 |
| `incomeDate` | LocalDate | 否 | 收入日期，`YYYY-MM-DD` |
| `projectName` | String | 否 | 项目名称 |
| `quantity` | Decimal | 否 | 数量 |
| `unitPrice` | Decimal | 否 | 核算单价 |
| `settlementAmount` | Decimal | 否 | 核算金额 |
| `paymentMethod` | String | 否 | 付款类型，见 §6.1 |
| `paymentMethodName` | String | 否 | 付款类型中文名 |
| `voucherUrls` | String[] | 是 | 凭证 URL 列表 |
| `settlementConfirmStatus` | String | 否 | 确认状态，见 §6.2 |
| `settlementConfirmStatusName` | String | 否 | 确认状态中文名 |
| `remark` | String | 是 | 备注 |
| `sourceType` | String | 否 | 来源类型，见 §6.3 |
| `sourceTypeName` | String | 否 | 来源类型中文名 |
| `sourceId` | String(Long) | 是 | 来源附加费 ID |

`Deduction` 完整字段：

| 字段 | 类型 | 可空 | 说明 |
|---|---|---:|---|
| `id` | String(Long) | 否 | 减费 ID |
| `discountName` | String | 否 | 减费名称 |
| `discountAmount` | Decimal | 否 | 减费金额 |
| `sourceType` | String | 是 | 来源类型 |
| `sourceId` | String(Long) | 是 | 来源业务 ID |
| `createdAt` | LocalDateTime | 是 | 创建时间 |

`Summary` 完整字段：

| 字段 | 类型 | 说明 |
|---|---|---|
| `surchargeAmount` | Decimal | 有效增费合计 |
| `discountAmount` | Decimal | 有效减费合计 |
| `netAdjustmentAmount` | Decimal | 净调整额，即增费减去减费 |

**本接口错误码与业务边界**：见 §7、§9；查询不允许再依赖三个已删除字段。

**典型成功示例**：

```http
GET /v3/admin/order/2084000000000002978/settlement/other-incomes
Authorization: Bearer <token>
```

```json
{
  "code": 200,
  "message": "success",
  "data": {
    "items": [{
      "id": "2084000000000004978",
      "requestId": "oi-5477-0001",
      "incomeDate": "2026-08-04",
      "projectName": "酒店升级补差",
      "quantity": 2,
      "unitPrice": 12.34,
      "settlementAmount": 24.68,
      "paymentMethod": "COMPANY_PAID",
      "paymentMethodName": "公司付款",
      "voucherUrls": [],
      "settlementConfirmStatus": "UNCONFIRMED",
      "settlementConfirmStatusName": "未确认",
      "remark": null,
      "sourceType": "MANUAL",
      "sourceTypeName": "手工",
      "sourceId": "2084000000000004979"
    }],
    "deductions": [],
    "summary": {
      "surchargeAmount": 24.68,
      "discountAmount": 0,
      "netAdjustmentAmount": 24.68
    }
  },
  "success": true
}
```

**边界示例（空列表）**：

```http
GET /v3/admin/order/2084000000000002978/settlement/other-incomes
Authorization: Bearer <token>
```

```json
{
  "code": 200,
  "message": "success",
  "data": {
    "items": [],
    "deductions": [],
    "summary": {"surchargeAmount": 0, "discountAmount": 0, "netAdjustmentAmount": 0}
  },
  "success": true
}
```

**异常示例（非法订单 ID）**：

```http
GET /v3/admin/order/0/settlement/other-incomes
Authorization: Bearer <token>
```

```json
{"code": 400, "message": "订单 ID 必须大于 0", "data": null, "success": false}
```

### 3.2 新增其他收入并原子创建订单增费

- **方法/路径**：`POST /v3/admin/order/{orderId}/settlement/other-incomes`
- **使用场景**：核单人员新增一条手工其他收入。
- **认证**：需要管理后台登录态；仅超级管理员、管理员或财务可写。
- **幂等性**：`requestId` 是同一订单内永久唯一的稳定幂等键；同一 `requestId` 与相同载荷重试返回同一结果，不同载荷冲突返回 `584087`。
- **限流**：未声明接口专属限流。

**路径参数**：`orderId`，String(Long)，必填且必须大于 0。

**请求体完整字段**：

| 字段 | 类型 | 必填 | 校验与说明 |
|---|---|---:|---|
| `requestId` | String | 是 | 最长 64；同订单内永久唯一 |
| `incomeDate` | LocalDate | 是 | `YYYY-MM-DD` |
| `projectName` | String | 是 | 非空，最长 100 |
| `quantity` | Decimal | 是 | ≥0，最多 8 位整数、4 位小数 |
| `unitPrice` | Decimal | 是 | ≥0，最多 8 位整数、2 位小数 |
| `settlementAmount` | Decimal | 是 | ≥0.01，最多 8 位整数、2 位小数；必须等于 `quantity × unitPrice` 四舍五入到 2 位 |
| `paymentMethod` | String | 是 | 见 §6.1 |
| `settlementConfirmStatus` | String | 是 | 新增只能为 `UNCONFIRMED` |
| `voucherUrls` | String[] | 否 | 最多 9 项；每项最长 1024，必须为 `http/https` URL |
| `remark` | String | 否 | 最长 500 |

**成功响应**：`data` 为 §3.1 的完整 `OtherIncomeItem`；不含 `projectCategory`、`projectCategoryName`、`specification`。

**典型成功请求与响应**：

```http
POST /v3/admin/order/2084000000000002978/settlement/other-incomes
Authorization: Bearer <token>
Content-Type: application/json
```

```json
{
  "requestId": "oi-5477-0001",
  "incomeDate": "2026-08-04",
  "projectName": "酒店升级补差",
  "quantity": 2,
  "unitPrice": 12.34,
  "settlementAmount": 24.68,
  "paymentMethod": "COMPANY_PAID",
  "settlementConfirmStatus": "UNCONFIRMED",
  "voucherUrls": [],
  "remark": null
}
```

```json
{
  "code": 200,
  "message": "success",
  "data": {
    "id": "2084000000000004978",
    "requestId": "oi-5477-0001",
    "incomeDate": "2026-08-04",
    "projectName": "酒店升级补差",
    "quantity": 2,
    "unitPrice": 12.34,
    "settlementAmount": 24.68,
    "paymentMethod": "COMPANY_PAID",
    "paymentMethodName": "公司付款",
    "voucherUrls": [],
    "settlementConfirmStatus": "UNCONFIRMED",
    "settlementConfirmStatusName": "未确认",
    "remark": null,
    "sourceType": "MANUAL",
    "sourceTypeName": "手工",
    "sourceId": "2084000000000004979"
  },
  "success": true
}
```

**边界请求与响应（旧字段仍被旧客户端多传）**：

```json
{
  "requestId": "oi-5477-legacy-0001",
  "incomeDate": "2026-08-04",
  "projectName": "历史客户端补差",
  "projectCategory": "HOTEL",
  "specification": "VIP",
  "quantity": 1,
  "unitPrice": 0.01,
  "settlementAmount": 0.01,
  "paymentMethod": "CASH_PAID",
  "settlementConfirmStatus": "UNCONFIRMED",
  "voucherUrls": []
}
```

```json
{
  "code": 200,
  "message": "success",
  "data": {
    "id": "2084000000000004980",
    "requestId": "oi-5477-legacy-0001",
    "incomeDate": "2026-08-04",
    "projectName": "历史客户端补差",
    "quantity": 1,
    "unitPrice": 0.01,
    "settlementAmount": 0.01,
    "paymentMethod": "CASH_PAID",
    "paymentMethodName": "现付",
    "voucherUrls": [],
    "settlementConfirmStatus": "UNCONFIRMED",
    "settlementConfirmStatusName": "未确认",
    "remark": null,
    "sourceType": "MANUAL",
    "sourceTypeName": "手工",
    "sourceId": "2084000000000004981"
  },
  "success": true
}
```

旧字段会被忽略，响应不会回显。该兼容仅用于过渡；前端仍必须停止发送。

**异常请求与响应（缺少确认状态）**：

```json
{
  "requestId": "oi-5477-invalid-0001",
  "incomeDate": "2026-08-04",
  "projectName": "无确认状态",
  "quantity": 1,
  "unitPrice": 10,
  "settlementAmount": 10,
  "paymentMethod": "CASH_PAID"
}
```

```json
{"code": 400, "message": "确认状态不能为空", "data": null, "success": false}
```

### 3.3 修改其他收入

- **方法/路径**：`PUT /v3/admin/order/{orderId}/settlement/other-incomes/{incomeId}`
- **使用场景**：修改已有其他收入的公开业务字段。
- **认证**：需要管理后台登录态；仅超级管理员、管理员或财务可写。
- **幂等性**：无单独幂等键；重复提交相同最终载荷不会改变公开结果。调用方不得依赖并发请求顺序。
- **限流**：未声明接口专属限流。

**路径参数**：

| 字段 | 类型 | 必填 | 说明 | 校验 |
|---|---|---:|---|---|
| `orderId` | String(Long) | 是 | 订单 ID | 必须大于 0 |
| `incomeId` | String(Long) | 是 | 其他收入 ID | 必须大于 0，且必须属于该订单 |

**请求体完整字段**：与 POST 相同，但没有 `requestId`；`settlementConfirmStatus` 可为 `UNCONFIRMED` 或 `CONFIRMED`。字段长度、金额一致性、凭证约束均与 §3.2 相同。

**成功响应**：`data` 为 §3.1 的完整 `OtherIncomeItem`；不含 `projectCategory`、`projectCategoryName`、`specification`。

**典型成功请求与响应**：

```http
PUT /v3/admin/order/2084000000000002978/settlement/other-incomes/2084000000000004978
Authorization: Bearer <token>
Content-Type: application/json
```

```json
{
  "incomeDate": "2026-08-04",
  "projectName": "酒店升级补差-已更新",
  "quantity": 3,
  "unitPrice": 15.00,
  "settlementAmount": 45.00,
  "paymentMethod": "CASH_PAID",
  "settlementConfirmStatus": "UNCONFIRMED",
  "voucherUrls": [],
  "remark": "金额已核对"
}
```

```json
{
  "code": 200,
  "message": "success",
  "data": {
    "id": "2084000000000004978",
    "requestId": "oi-5477-0001",
    "incomeDate": "2026-08-04",
    "projectName": "酒店升级补差-已更新",
    "quantity": 3,
    "unitPrice": 15,
    "settlementAmount": 45,
    "paymentMethod": "CASH_PAID",
    "paymentMethodName": "现付",
    "voucherUrls": [],
    "settlementConfirmStatus": "UNCONFIRMED",
    "settlementConfirmStatusName": "未确认",
    "remark": "金额已核对",
    "sourceType": "MANUAL",
    "sourceTypeName": "手工",
    "sourceId": "2084000000000004982"
  },
  "success": true
}
```

**边界请求与响应（夹带旧字段）**：请求可额外包含 `projectCategory`、`specification`，服务端会忽略，仍按上述公开字段更新，响应不会出现三个旧字段。

```json
{
  "incomeDate": "2026-08-04",
  "projectName": "酒店升级补差-兼容请求",
  "projectCategory": "OLD_JSON_CATEGORY_SHOULD_BE_IGNORED",
  "specification": "OLD_JSON_SPEC_SHOULD_BE_IGNORED",
  "quantity": 3,
  "unitPrice": 15,
  "settlementAmount": 45,
  "paymentMethod": "CASH_PAID",
  "settlementConfirmStatus": "UNCONFIRMED",
  "voucherUrls": []
}
```

```json
{
  "code": 200,
  "message": "success",
  "data": {
    "id": "2084000000000004978",
    "requestId": "oi-5477-0001",
    "incomeDate": "2026-08-04",
    "projectName": "酒店升级补差-兼容请求",
    "quantity": 3,
    "unitPrice": 15,
    "settlementAmount": 45,
    "paymentMethod": "CASH_PAID",
    "paymentMethodName": "现付",
    "voucherUrls": [],
    "settlementConfirmStatus": "UNCONFIRMED",
    "settlementConfirmStatusName": "未确认",
    "remark": null,
    "sourceType": "MANUAL",
    "sourceTypeName": "手工",
    "sourceId": "2084000000000004982"
  },
  "success": true
}
```

**异常请求与响应（金额不一致）**：

```json
{
  "incomeDate": "2026-08-04",
  "projectName": "金额错误",
  "quantity": 3,
  "unitPrice": 15,
  "settlementAmount": 44,
  "paymentMethod": "CASH_PAID",
  "settlementConfirmStatus": "UNCONFIRMED"
}
```

```json
{"code": 584076, "message": "其他收入核算金额必须等于数量乘以核算单价", "data": null, "success": false}
```

## 4. 接口入参汇总

| 接口 | 路径参数 | 请求体差异 | 已删除字段 |
|---|---|---|---|
| GET | `orderId` | 无 | 无入参；响应删除三个字段 |
| POST | `orderId` | 比 PUT 多必填 `requestId`；新增必须从 `UNCONFIRMED` 开始 | `projectCategory`、`specification` |
| PUT | `orderId`、`incomeId` | 无 `requestId`；确认状态可为两种合法值 | `projectCategory`、`specification` |

完整字段、类型和校验均已在 §3 对应接口内列出。

## 5. 出参字段汇总

POST、PUT 返回单个 `OtherIncomeItem`；GET 返回 `items[] + deductions[] + summary`。`OtherIncomeItem` 的完整当前字段见 §3.1，三种接口均不再返回：

| 已删除字段 | 原类型 | 当前替代 |
|---|---|---|
| `projectCategory` | String | 无；前端删除对应状态与控件 |
| `projectCategoryName` | String | 无；前端删除对应展示读取 |
| `specification` | String | 无；前端删除对应状态与控件 |

## 6. 枚举 / 数据字典

### 6.1 `paymentMethod`（付款类型）

**所属字段**：POST/PUT 入参、`OtherIncomeItem.paymentMethod` | **类型**：String | **必填**：是

| 值 | 中文 | 说明 |
|---|---|---|
| `CASH_PAID` | 现付 | 已现场支付 |
| `COMPANY_PAID` | 公司付款 | 由公司付款 |
| `SIGNED` | 签单 | 签单结算 |

### 6.2 `settlementConfirmStatus`（确认状态）

**所属字段**：POST/PUT 入参、`OtherIncomeItem.settlementConfirmStatus` | **类型**：String | **必填**：是

| 值 | 中文 | 说明 |
|---|---|---|
| `UNCONFIRMED` | 未确认 | POST 新增时唯一允许的初始值 |
| `CONFIRMED` | 已确认 | 仅对已有明细通过 PUT 更新使用 |

### 6.3 `sourceType`（公开来源类型）

**所属字段**：`OtherIncomeItem.sourceType` | **类型**：String | **必填**：响应必有

| 值 | 中文 | 说明 |
|---|---|---|
| `MANUAL` | 手工 | 管理后台手工新增 |
| `SYSTEM` | 系统 | 系统投影来源 |

`projectCategory` 对应的数据字典不再属于其他收入接口契约；前端应删除该字典请求和映射逻辑。

## 7. 错误码

| code | 含义 | 触发场景 |
|---:|---|---|
| `400` | 参数校验失败 | 缺必填字段、长度/格式超限、非法枚举、订单 ID 非正数等 |
| `401` | 未登录或登录态失效 | 缺少有效管理后台凭证 |
| `584073` | 其他收入不存在或不属于当前订单 | PUT 的 `incomeId` 不存在或订单归属不符 |
| `584074` | 当前核单状态不允许修改其他收入 | 订单核单状态不是待核单或核单中 |
| `584075` | 其他收入关联的附加费来源无效 | 关联来源无法建立或已失效 |
| `584076` | 核算金额不等于数量乘以单价 | `quantity × unitPrice` 四舍五入到 2 位后与金额不一致 |
| `584086` | 无权修改核单资金数据 | 写接口调用角色不是超级管理员、管理员或财务 |
| `584087` | `requestId` 已用于另一笔其他收入 | POST 重用幂等键但载荷不同 |
| `584088` | 核单凭证数据损坏 | 历史凭证数据无法读取 |
| `584089` | 核单或结算已完成，资金数据不可修改 | 对完成后的资金事实调用 POST/PUT |
| `584106` | 确认状态非法 | 非 `UNCONFIRMED/CONFIRMED` |
| `584107` | 手工新增必须先保存为未确认 | POST 直接传 `CONFIRMED` |

旧错误码 `584103`（项目类别非法）、`584104`（规格非法）、`584105`（相关字典不可用）不再由这三个接口触发。

## 8. 示例索引

三类示例均已与接口放在一起，避免跨节拼接：

| 接口 | 典型成功 | 边界 | 业务失败 |
|---|---|---|---|
| GET | §3.1 有明细 | §3.1 空列表 | §3.1 非法 `orderId` |
| POST | §3.2 不传旧字段创建 | §3.2 旧请求多传字段被忽略 | §3.2 缺确认状态 |
| PUT | §3.3 正常更新 | §3.3 夹带旧字段被忽略 | §3.3 金额不一致 |

## 9. 业务边界

- ✅ GET 用于读取当前其他收入、减费与汇总；空数据稳定返回空数组。
- ✅ POST/PUT 仅适用于核单资金仍可修改的订单，且调用角色必须具备资金写权限。
- ✅ POST 必须携带稳定 `requestId`，并以 `UNCONFIRMED` 创建；后续可通过 PUT 改为 `CONFIRMED`。
- ✅ `settlementAmount` 必须等于 `quantity × unitPrice` 四舍五入到 2 位。
- ⚠️ 旧客户端继续多传 `projectCategory/specification` 时，新接口会忽略；这不是继续保留控件的理由。
- ❌ 核单或结算完成后禁止 POST/PUT；不存在或跨订单的 `incomeId` 禁止更新。
- ❌ 前端不得从其他字段猜测、拼装或恢复已删除的项目类别与票种规格。

## 10. 修改前后对比

### 10.1 字段级对比

| 接口字段 | 原来 | 现在 |
|---|---|---|
| POST/PUT `projectCategory` | 必填 String，受项目类别字典校验 | 已从契约删除；旧 JSON 多传会被忽略 |
| POST/PUT `specification` | 可选 String，受规格字典校验 | 已从契约删除；旧 JSON 多传会被忽略 |
| GET/POST/PUT `projectCategory` | 响应返回 | 不再返回 |
| GET/POST/PUT `projectCategoryName` | 响应返回中文名 | 不再返回 |
| GET/POST/PUT `specification` | 响应返回 | 不再返回 |

### 10.2 行为级对比

| 行为 | 原来 | 现在 |
|---|---|---|
| 新增/修改表单 | 必须维护项目类别，可选维护票种规格 | 两个控件都删除，只提交当前公开字段 |
| 旧客户端多传旧字段 | 参与校验和保存 | 被忽略，且响应不回显 |
| 查询展示 | 可读取类别、类别名称和规格 | 三个字段不存在，禁止继续读取或设置默认值 |
| 相关字典异常 | 可能阻断写入 | 不再属于其他收入接口错误面 |

## 11. 影响评估 / 回滚

### 11.1 影响评估

- **是否破坏向后兼容**：响应字段删除属于破坏性变化；但旧前端继续多传两个旧请求字段时，新接口会忽略，因此后端先上线兼容旧请求。
- **前端是否必须同步上线**：必须。删除项目类别与票种规格控件、请求字段、响应读取和相关字典依赖。
- **上线顺序边界**：允许“新后端 → 旧前端”短暂过渡；不允许“新前端 → 旧后端”，因为旧后端仍要求 `projectCategory`。

### 11.2 回滚方案

- 若接口契约回滚到旧版本，必须同步恢复前端 `projectCategory` 必填提交，否则旧接口会拒绝新增/修改。
- 仅回滚前端到旧版本不会阻断新接口写入，但旧页面读取不到三个已删除响应字段，类别/规格区域会为空，因此不建议长期维持。

## 12. 注意事项

- 删除项目类别控件、票种规格控件及其表单校验。
- 停止在 POST/PUT 请求中传 `projectCategory`、`specification`。
- 停止读取 GET/POST/PUT 响应中的 `projectCategory`、`projectCategoryName`、`specification`。
- 删除其他收入页面对项目类别字典、票种规格字典的加载与映射。
- Long ID 继续按 String 消费；空明细继续按 `[]` 处理。

## 13. 关联 / 联系人

### 13.1 链接

- **Issue**: [#5477](https://git.1814.love:8443/wx/HL/issues/5477)
- **PR**: [#5488](https://git.1814.love:8443/wx/HL/pulls/5488)
- **Merge commit**: [da1ee4ebc106](https://git.1814.love:8443/wx/HL/commit/da1ee4ebc1068e5ca20b683769238a3cf165b4b5)

### 13.2 联系人

- **后端负责人**: @yaosutu
- **QA 验证**: Issue #5477 接口验收已完成（管理后台真实网关，TARGETED_FALLBACK）
