# 发票申请删 amount · 门槛放宽 · 开票/重传核单门槛（管理后台）

- **接口**：POST /v3/admin/order/{orderId}/invoice/apply + PUT /v3/admin/order/invoice/{id}/issue + PUT /v3/admin/order/invoice/{id}/reupload
- **变更类型**：修改接口，破坏性变更（申请入参删 amount；新增错误码 581524/581525；门槛扩展）
- **端类型**：管理后台
- **日期**：2026-06-25
- **Issue**：[#4353](https://git.1814.love:8443/wx/HL/issues/4353)
- **PR**：[#4360](https://git.1814.love:8443/wx/HL/pulls/4360)

本次三项改动：1. 申请入参删 amount，开票时后端自动算；2. 申请门槛放宽至定制中及以后；3. 开票/重传新增核单门槛。

---

## 1 接口背景

发票模块本次 PR #4360 三项改动：

1. **申请入参删 amount**：开票金额不再前端传入，开票时后端按规则自动计算（正常单=应收总额；取消单=净实收）。
2. **申请门槛放宽**：原仅允许 COMPLETED 订单申请；放宽为定制中及以后均可申请，同时支持已取消但净实收 > 0 的订单。
3. **开票/重传新增核单门槛**：完成开票（issue）和重新上传（reupload）操作要求订单核单完成（reviewStatus=COMPLETED），防止未结算的订单开出金额有误的发票。

---

## 2 变更清单

| # | 变更项 | 变更前 | 变更后 |
|---|--------|--------|--------|
| 1 | POST apply 入参 amount | 必填字段，前端传开票金额 | 已删除，后端开票时自动算 |
| 2 | POST apply 申请门槛（正常订单） | 订单状态必须 COMPLETED | 状态 ∈ {CUSTOMIZING / PENDING_DEPARTURE / TRAVELLING / COMPLETED} 均可申请 |
| 3 | POST apply 申请门槛（取消订单） | 不允许 | CANCELLED 且净实收（已付 - 已退）> 0 时可申请 |
| 4 | PUT issue 操作门槛 | 仅检查发票状态为 REQUESTED | 增加：订单核单完成（reviewStatus=COMPLETED）才可开票，否则 581525 |
| 5 | PUT reupload 操作门槛 | 仅检查发票状态为 ISSUED/PUSHED | 增加：订单核单完成才可重传，否则 581525 |
| 6 | 新错误码 581524 | 无 | INVOICE_ORDER_NOT_APPLICABLE：订单不满足申请条件 |
| 7 | 新错误码 581525 | 无 | INVOICE_ORDER_NOT_REVIEWED：订单核单未完成，不可开票/重传 |

---

## 3 接口详情

### 3.1 代客申请发票

| 属性 | 值 |
|------|----|
| 方法 | POST |
| 路径 | /v3/admin/order/{orderId}/invoice/apply |
| 描述 | 财务或定制师代客提交开票申请（申请阶段不填金额，开票时后端自动算） |
| 认证 | Bearer JWT（管理员） |
| 幂等性 | 非幂等，重复提交触发 581511（一单一票） |
| 限流 | 无特殊限制 |

### 3.2 完成开票

| 属性 | 值 |
|------|----|
| 方法 | PUT |
| 路径 | /v3/admin/order/invoice/{id}/issue |
| 描述 | 财务上传发票 PDF 并完成开票，状态 REQUESTED -> ISSUED；需订单核单完成 |
| 认证 | Bearer JWT（管理员） |
| 幂等性 | 状态守卫，非幂等 |
| 限流 | 无特殊限制 |

### 3.3 重新上传发票

| 属性 | 值 |
|------|----|
| 方法 | PUT |
| 路径 | /v3/admin/order/invoice/{id}/reupload |
| 描述 | 财务重新上传发票文件（ISSUED/PUSHED 均可）；需订单核单完成 |
| 认证 | Bearer JWT（管理员） |
| 幂等性 | 覆盖写，非幂等 |
| 限流 | 无特殊限制 |

---

## 4 接口入参

### 4.1 路径参数

| 接口 | 参数名 | 类型 | 必填 | 说明 |
|------|--------|------|------|------|
| POST apply | orderId | string（Long 雪花） | 是 | 订单 ID |
| PUT issue | id | string（Long 雪花） | 是 | 发票记录 ID |
| PUT reupload | id | string（Long 雪花） | 是 | 发票记录 ID |

### 4.2 请求体字段

**POST /v3/admin/order/{orderId}/invoice/apply（AdminInvoiceApplyReqVO，⚠️ amount 已删）**

当前完整字段列表（无 amount）：

| 字段 | 类型 | 必填条件 | 说明 |
|------|------|---------|------|
| invoiceType | string | 始终必填 | 发票类型，只有 VAT_NORMAL / VAT_SPECIAL 两值 |
| titleType | string | 始终必填 | 抬头类型：COMPANY（单位）/ PERSONAL（个人）；VAT_SPECIAL 只能 COMPANY |
| titleName | string | 始终必填 | 发票抬头（企业全称或个人姓名） |
| taxNo | string | titleType=COMPANY 或 invoiceType=VAT_SPECIAL 时必填 | 纳税人识别号 |
| bankName | string | invoiceType=VAT_SPECIAL 时必填 | 开户银行名称 |
| bankAccount | string | invoiceType=VAT_SPECIAL 时必填 | 银行账号 |
| registAddress | string | invoiceType=VAT_SPECIAL 时必填 | 注册地址 |
| registPhone | string | invoiceType=VAT_SPECIAL 时必填 | 注册电话 |
| email | string | 始终必填 | 收件邮箱，需通过邮箱格式校验 |
| remark | string | 选填 | 申请备注 |

**PUT /v3/admin/order/invoice/{id}/issue**

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| fileUrl | string | 是 | upload 接口返回的 OSS 地址 |
| pdfName | string | 否 | PDF 文件名（展示用） |
| pdfSize | Long | 否 | PDF 文件大小（字节） |
| invoiceNo | string | 否 | 发票号码 |

**PUT /v3/admin/order/invoice/{id}/reupload**

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| fileUrl | string | 是 | 新发票 PDF 的 OSS 地址 |
| pdfName | string | 否 | 新 PDF 文件名 |
| pdfSize | Long | 否 | 新 PDF 文件大小（字节） |

---

## 5 出参字段

**POST apply 响应**：成功返回新建发票 ID（字符串，雪花 ID）。



**PUT issue / reupload 响应**：操作成功返回 HTTP 200，data 为 null。

---

## 6 枚举 / 数据字典

### 6.1 发票类型（invoiceType）

| code | 说明 |
|------|------|
| VAT_NORMAL | 增值税普通发票 |
| VAT_SPECIAL | 增值税专用发票 |

### 6.2 抬头类型（titleType）

| code | 说明 | 限制 |
|------|------|------|
| COMPANY | 单位 | 普票/专票均可 |
| PERSONAL | 个人 | 只能选 VAT_NORMAL；选 VAT_SPECIAL 返回 581523 |

### 6.3 订单状态与可申请关系

| 订单状态（code） | 中文 | 可申请？ | 说明 |
|----------------|------|---------|------|
| CUSTOMIZING | 定制中 | 是 | 放宽新增 |
| PENDING_DEPARTURE | 待出行 | 是 | 放宽新增 |
| TRAVELLING | 出行中 | 是 | 放宽新增 |
| COMPLETED | 已完成 | 是 | 原有 |
| CANCELLED | 已取消 | 条件是 | 净实收（已付 - 已退）> 0 时可申请 |
| 其他状态 | - | 否 | 报 581524 |

### 6.4 开票时金额自动计算规则

| 订单类型 | 金额计算公式 | 说明 |
|----------|-------------|------|
| 正常单（非 CANCELLED） | 应收总额 = 订单总价 + 增项 - 优惠 | 申请时不确定，开票时算 |
| 取消单（CANCELLED） | 净实收 = 已付金额 - 已退金额 | 净实收 = 0 则拒绝申请 |

---

## 7 错误码

| 错误码 | 常量 | 触发场景 |
|--------|------|----------|
| 581502 | INVOICE_CANNOT_ISSUE | 发票状态非 REQUESTED，不允许开票 |
| 581503 | INVOICE_CANNOT_REUPLOAD | 发票状态非 ISSUED/PUSHED，不允许重新上传 |
| 581511 | INVOICE_ALREADY_EXISTS | 该订单已有有效发票，不可重复申请（一单一票） |
| 581513 | INVOICE_TYPE_INVALID | 发票类型枚举值非法 |
| 581514 | INVOICE_TAX_NO_REQUIRED | 公司抬头/专票时税号为必填 |
| 581515 | INVOICE_VAT_SPECIAL_FIELDS_REQUIRED | 专票时开户行/银行账号/注册地址/注册电话为必填 |
| 581516 | INVOICE_EMAIL_REQUIRED | 收件邮箱为必填或格式错误（HTTP 400） |
| 581523 | INVOICE_VAT_SPECIAL_PERSONAL_FORBIDDEN | 专票只能开给单位 |
| 581524 | INVOICE_ORDER_NOT_APPLICABLE | ✨ 新增：订单状态不在可申请集合内，或取消单净实收为 0 |
| 581525 | INVOICE_ORDER_NOT_REVIEWED | ✨ 新增：开票/重传时订单核单未完成（reviewStatus != COMPLETED） |
---

## 8 示例

### 8.1 典型成功——普票申请（无 amount 字段）

请求
```
POST /v3/admin/order/1920000000000000001/invoice/apply
Authorization: Bearer <token>
Content-Type: application/json
```
```json
{
  "invoiceType": "VAT_NORMAL",
  "titleType": "PERSONAL",
  "titleName": "李四",
  "email": "lisi@example.com",
  "remark": "报销使用"
}
```

响应
```json
{"code": 200, "msg": "success", "data": "1934567890123456789"}
```

### 8.2 边界情况——取消订单且净实收 > 0 可申请

说明：订单已取消，实付 3000 元、退款 1000 元，净实收 2000 元 > 0，申请通过。开票时金额自动计算为净实收 2000 元。

请求（结构同 8.1，orderId 换为该取消订单 ID）

响应
```json
{"code": 200, "msg": "success", "data": "1934567890123456700"}
```

### 8.3 业务失败——订单核单未完成，完成开票被拒

请求
```
PUT /v3/admin/order/invoice/1934567890123456789/issue
Authorization: Bearer <token>
Content-Type: application/json
```
```json
{
  "fileUrl": "https://oss.example.com/invoice/2026/06/xxx.pdf",
  "pdfName": "发票_北京科技.pdf",
  "invoiceNo": "12345678"
}
```

响应（订单 reviewStatus 非 COMPLETED）
```json
{"code": 581525, "msg": "订单核单未完成，不可开具发票", "data": null}
```

---

## 9 业务边界

适用：
- POST apply：订单状态 ∈ {CUSTOMIZING / PENDING_DEPARTURE / TRAVELLING / COMPLETED}，或 CANCELLED 且净实收 > 0
- PUT issue：发票状态为 REQUESTED，且订单 reviewStatus = COMPLETED
- PUT reupload：发票状态为 ISSUED/PUSHED，且订单 reviewStatus = COMPLETED

不适用：
- POST apply：订单为 PAID（已支付但未进入定制阶段）、或 CANCELLED 且净实收 = 0，报 581524
- PUT issue/reupload：订单核单未完成（reviewStatus != COMPLETED），报 581525

特殊边界：
- 取消单开票金额 = 净实收（已付 - 已退），不是订单总价；正常单 = 应收总额
- 一单一票：同一订单只允许一张有效发票（REQUESTED/ISSUED/PUSHED 状态），若已有则报 581511

---

## 10 修改前后对比

### apply 入参字段对比

| 字段 | 变更前 | 变更后 |
|------|--------|--------|
| amount | 始终必填，JSON number，单位元 | ⚠️ 已删除，后端开票时自动算 |

### apply 申请门槛对比

| 场景 | 变更前 | 变更后 |
|------|--------|--------|
| 正常订单 | 仅 COMPLETED 可申请，其余报 581510 | CUSTOMIZING / PENDING_DEPARTURE / TRAVELLING / COMPLETED 均可申请 |
| 取消订单 | 不允许 | 净实收 > 0 时可申请，= 0 报 581524 |
| 不满足时错误码 | 581510 INVOICE_ORDER_NOT_COMPLETED（已废弃） | 581524 INVOICE_ORDER_NOT_APPLICABLE（新增） |

### issue / reupload 门槛对比

| 操作 | 变更前 | 变更后 |
|------|--------|--------|
| PUT issue | 仅检查发票状态为 REQUESTED | 增加：订单核单完成（reviewStatus=COMPLETED），否则 581525 |
| PUT reupload | 仅检查发票状态为 ISSUED/PUSHED | 增加：订单核单完成，否则 581525 |

---

## 11 影响评估 / 回滚

破坏兼容性：是

- 前端申请表单必须删除开票金额输入框，不再传 amount 字段
- 前端申请成功弹窗/提示中不展示金额（金额在开票时才确定）
- 财务点击「完成开票」，若订单未核单则收到 581525，前端需展示「订单核单未完成，不可开票」提示

前端同步上线：申请表单删 amount 必须在本次后端部署后同步更新；财务开票侧 581525 错误提示须同步适配。

回滚方案：回滚后端至 #4360 前版本，amount 字段恢复必填，申请门槛收回到仅 COMPLETED，581524/581525 不再存在。

---

## 12 注意事项

1. **amount 字段已删**：前端申请表单必须移除金额输入框，不传 amount。传入后端会忽略，导致用户困惑（以为填了金额实际无效）。
2. **开票时金额自动写入**：发票金额由后端在 issue 操作时根据订单类型自动计算，财务无需手填金额。
3. **申请门槛放宽的注意点**：定制中/待出行/出行中提前申请的发票，开票时（issue）后端按当时应收总额计算金额。财务须在核单完成后再开票（581525 已强制保证）。
4. **核单门槛（581525）**：完成开票和重传均要求核单完成。财务若收到此错误，需先在核单管理页完成核单，再回来开票。
5. **取消单申请**：CANCELLED 订单申请发票，开票金额为净实收（已付 - 已退）。若净实收为 0，申请被 581524 拒绝。

---

## 13 关联 / 联系人

- **Issue**：[#4353 发票模块门槛放宽与开票规则优化](https://git.1814.love:8443/wx/HL/issues/4353)
- **PR**：[#4360](https://git.1814.love:8443/wx/HL/pulls/4360)
- **后端负责人**：腰苏图（yaosutu）