# 发票申请入参票种修正（最终契约）

> 变更类型：修改接口（破坏性）
> 端类型：管理后台
> 日期：2026-06-23 | Issue：#4290 | PR：#4292 / #4302 | 服务：hl-order-service-v3

> ⚠️ **破坏性变更**：`invoiceType` 删除 `ELECTRONIC`、`email` 改为始终必填 + 格式校验、新增 `taxNo`/银行/注册信息专票五项校验、删除 `mailAddress` 字段、专票限 `COMPANY` 抬头。前端申请表单需同步改动，上线时前后端须同步发布。

> **勘误（截至 PR #4311 收口）**：抬头字段统一 `titleName`、金额 `amount` 改 JSON number（单位元，非分/×100）、票种仅 2 值（`VAT_NORMAL`/`VAT_SPECIAL`）、专票限公司抬头、入参和出参均无 `mailAddress`。关联 Issue [#4290](https://git.1814.love:8443/wx/HL/issues/4290) [#4296](https://git.1814.love:8443/wx/HL/issues/4296) [#4310](https://git.1814.love:8443/wx/HL/issues/4310) / PR [#4292](https://git.1814.love:8443/wx/HL/pulls/4292) [#4302](https://git.1814.love:8443/wx/HL/pulls/4302) [#4311](https://git.1814.love:8443/wx/HL/pulls/4311)。

> **勘误（截至 PR #4311 收口）**：抬头字段统一 `titleName`、金额 `amount` 改 JSON number（单位元，非分/x100）、票种仅 2 值（`VAT_NORMAL`/`VAT_SPECIAL`）、专票限公司抬头、入参和出参均无 `mailAddress`。关联 Issue [#4290](https://git.1814.love:8443/wx/HL/issues/4290) [#4296](https://git.1814.love:8443/wx/HL/issues/4296) [#4310](https://git.1814.love:8443/wx/HL/issues/4310) / PR [#4292](https://git.1814.love:8443/wx/HL/pulls/4292) [#4302](https://git.1814.love:8443/wx/HL/pulls/4302) [#4311](https://git.1814.love:8443/wx/HL/pulls/4311)。

---

## 接口背景

发票模型经 PR #4292 + #4302 多轮修正后定稿：删除原设计中的「电子发票」票种（业务决策统一走 VAT_NORMAL 普票），对专票收严字段校验（专票限公司抬头 + 必填五项），email 改为始终必填（全系统电子交付），删除纸质邮寄字段 `mailAddress`。

本文档为最终契约，与本月早前 `23_4172_...` 系列文件中的历史设计存在差异，以本文档为准。

---

## 变更清单

| # | 变更类型 | 说明 |
|---|---------|------|
| 1 | ⚠️ 删枚举值 | `invoiceType` 删除 `ELECTRONIC`，只保留 `VAT_NORMAL` / `VAT_SPECIAL` |
| 2 | ⚠️ 删字段 | 申请入参 + 详情出参均删除 `mailAddress`（纸质邮寄地址） |
| 3 | ⚠️ 校验收严 | `email` 改为始终必填（原为条件必填），并加邮箱格式校验 |
| 4 | ⚠️ 新增校验 | `taxNo`：单位抬头或专票时必填（原仅"公司抬头"须填） |
| 5 | ⚠️ 新增校验 | `bankName` / `bankAccount` / `registAddress` / `registPhone`：专票必填（原无此四项限制） |
| 6 | ⚠️ 新增错误码 | `581523` 专票只能开给单位（`titleType` 须 `COMPANY`） |
| 7 | ✨ 行为明确 | `VAT_SPECIAL` 自动限 `COMPANY` 抬头，个人选专票被拒（581523） |

---

## 接口详情

### 管理后台申请接口

| 项 | 说明 |
|---|------|
| **方法 + 路径** | `POST /v3/admin/order/{orderId}/invoice/apply` |
| **接口名** | 管理后台代客申请发票 |
| **描述** | 财务或定制师代客提交开票申请，支持普票/专票，全电子交付 |
| **认证** | 管理后台 JWT（Bearer Token） |
| **幂等性** | 非幂等，重复提交触发 581511（一单一票） |
| **限流** | 无特殊限流 |

---

## 接口入参

### 路径参数

| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| `orderId` | string（Long 雪花 ID） | 是 | 订单 ID，字符串传输防精度丢失 |

### 请求体字段

| 字段名 | 类型 | 必填条件 | 说明 |
|--------|------|---------|------|
| `invoiceType` | string | 始终必填 | 发票类型，只有 `VAT_NORMAL` / `VAT_SPECIAL` 两值，**不含 ELECTRONIC** |
| `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` 时必填 | 注册电话 |
| `amount` | number | 始终必填 | 开票金额，JSON number，单位**元**（如 `986.00`），须 > 0 且 ≤ 订单总金额 |
| `email` | string | 始终必填 | 收件邮箱，全电子交付，须通过邮箱格式校验 |
| `remark` | string | 选填 | 申请备注 |
| `applyReason` | string | 选填 | 申请原因（与 remark 二选一或并用） |

> ⚠️ `mailAddress` 字段**已删除**，后端不接收，填了也忽略。

---

## 出参字段

返回结构：`Result<Long>` — 返回新建发票 ID（字符串，雪花 ID）。

| 字段名 | 类型 | 说明 |
|--------|------|------|
| `data` | string | 新建发票 ID，如 `"1934567890123456789"` |

---

## 枚举 / 数据字典

### 发票类型（invoiceType）

| 枚举值 | 中文名 | 校验规则 |
|--------|--------|---------|
| `VAT_NORMAL` | 增值税普通发票 | bankName / bankAccount / registAddress / registPhone 均不需要 |
| `VAT_SPECIAL` | 增值税专用发票 | taxNo + 银行四项均必填，且 titleType 必须 COMPANY |

> ~~`ELECTRONIC`~~ 已删除，禁止传入（传入返回 581513）。

### 抬头类型（titleType）

| 枚举值 | 中文名 | 限制 |
|--------|--------|------|
| `COMPANY` | 单位 | 普票/专票均可 |
| `PERSONAL` | 个人 | 只能选 `VAT_NORMAL`，选 `VAT_SPECIAL` 返回 581523 |

---

## 错误码

| 错误码 | 含义 | 触发场景 |
|--------|------|---------|
| `581510` | 订单未完成，不可开票 | 订单状态不是 `COMPLETED` |
| `581511` | 一单一票，已有有效发票 | 该订单已存在 REQUESTED / ISSUED / PUSHED 状态发票 |
| `581512` | 开票金额超订单总额 | `amount` > 订单 `orderAmount` |
| `581513` | 发票类型非法 | `invoiceType` 不是 `VAT_NORMAL` 或 `VAT_SPECIAL` |
| `581514` | 公司抬头或专票须填税号 | `titleType=COMPANY` 或 `invoiceType=VAT_SPECIAL` 时 `taxNo` 为空 |
| `581515` | 专票须填银行及注册信息 | `invoiceType=VAT_SPECIAL` 时 taxNo / bankName / bankAccount / registAddress / registPhone 任一为空 |
| `581516` | 收件邮箱不能为空 | 入参层 `@NotBlank`，返回 HTTP 400「收件邮箱不能为空」；格式错误返回 400「邮箱格式不正确」 |
| `581523` | 专票只能开给单位 | `invoiceType=VAT_SPECIAL` 且 `titleType=PERSONAL` |
| `401` | 未授权 | 未携带有效 JWT |
| `403` | 无权限 | 当前账号无发票申请权限 |

> 注意：581516 在入参校验层（`@NotBlank` / `@Email`）返回 HTTP 400，不走业务错误码；581513~581523 走 `BusinessException` 返回 200 包体。

---

## 示例

### 典型成功（增值税普通发票，个人抬头）

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

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

### 典型成功（增值税专用发票，公司抬头，全字段）

请求：
```
POST /v3/admin/order/1920000000000000002/invoice/apply
Authorization: Bearer <token>
Content-Type: application/json
```
```json
{
  "invoiceType": "VAT_SPECIAL",
  "titleType": "COMPANY",
  "titleName": "某某贸易有限公司",
  "taxNo": "91310000YYYYYYYYYY",
  "bankName": "工商银行上海支行",
  "bankAccount": "6222000000000001",
  "registAddress": "上海市浦东新区XX路XX号",
  "registPhone": "021-12345678",
  "amount": 1500.00,
  "email": "finance@company.com",
  "remark": null
}
```

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

### 业务失败（专票个人抬头被拒，错误码 581523）

请求：
```
POST /v3/admin/order/1920000000000000003/invoice/apply
Authorization: Bearer <token>
Content-Type: application/json
```
```json
{
  "invoiceType": "VAT_SPECIAL",
  "titleType": "PERSONAL",
  "titleName": "张三",
  "taxNo": null,
  "amount": 500.00,
  "email": "zhangsan@example.com"
}
```

响应：
```json
{
  "code": 581523,
  "msg": "增值税专用发票只能开给单位，个人抬头不可选专票",
  "data": null
}
```

---

## 业务边界

**适用场景**
- 财务在「发票管理 - 未申请 tab」找到已完成订单，代客提交开票申请
- 定制师在订单详情页为客户发起申请

**不适用场景**
- 重新开票（走 `POST /v3/admin/order/invoice/{id}/reissue` 重开接口，入参相同）
- 小程序客户自主申请（走小程序端专属接口 `POST /v3/internal/mp/order/{orderId}/invoice/apply`）

**特殊边界**
- 一单一票：同一订单只允许一张有效发票（REQUESTED / ISSUED / PUSHED 状态），若已有则返回 581511，须先作废旧发票再申请
- 订单状态：仅 `COMPLETED`（已完成）状态订单可申请，其他状态返回 581510
- 金额单位：`amount` 为 JSON number，单位**元**（如 `986.00`），>0，≤ 订单总金额；前端输入框以元为单位直接提交，无需 ×100

---

## 修改前后对比

### 发票类型（invoiceType 枚举）

| 旧值（历史设计） | 新值（最终实现） |
|--------|--------|
| `VAT_NORMAL` 增值税普通发票 | `VAT_NORMAL` 增值税普通发票（保留） |
| `VAT_SPECIAL` 增值税专用发票 | `VAT_SPECIAL` 增值税专用发票（保留） |
| `ELECTRONIC` 电子发票 | **已删除，禁止传入** |

### email 字段

| 旧行为 | 新行为 |
|--------|--------|
| 条件必填（电子发票时必填，普票/专票选填） | **始终必填** + 邮箱格式校验（全电子交付） |

### mailAddress 字段

| 旧行为 | 新行为 |
|--------|--------|
| 纸质发票邮寄地址（选填） | **已删除**（无纸质邮寄，全电子交付） |

### 专票校验

| 旧行为 | 新行为 |
|--------|--------|
| 无 titleType 限制 | **专票只能 COMPANY**（个人返回 581523） |
| bankName 等四项无强制 | **专票 bankName / bankAccount / registAddress / registPhone 全部必填**（返回 581515） |

---

## 影响评估 / 回滚

### 破坏兼容性评估（前端申请表单必须改动）

| 模块 | 必须改动 |
|------|---------|
| 发票类型下拉 | 去掉「电子发票」选项，只保留「增值税普通发票」/「增值税专用发票」 |
| 专票表单区块 | 新增开户行、银行账号、注册地址、注册电话 4 个必填输入框，仅在选专票时显示 |
| 抬头类型联动 | 选专票时，抬头类型只能选「单位」，隐藏或禁用「个人」选项 |
| email 字段 | 改为始终必填，加邮箱格式校验（正则或 HTML input type=email） |
| mailAddress 字段 | 移除，不再向后端传此字段 |

### 回滚方案

后端回滚至 #4292 前版本后，`ELECTRONIC` 枚举值重新有效，`email` 恢复条件必填，`mailAddress` 重新可用。前端需同步回滚或做前向兼容处理。

---

## 注意事项

1. `ELECTRONIC` 枚举值已删除，前端下拉不可出现此选项；老订单历史数据如有该枚举值，出参展示时可显示「未知类型」或联系后端处理
2. `email` 无论票种均必填，前端不可根据票种做条件隐藏
3. 专票表单区块（税号 + 开户行 + 账号 + 注册地址 + 注册电话）共 5 个字段，在选专票时全部必填，不可缺任何一项
4. `amount` 为 JSON number，单位**元**（如 `986.00`），前端输入框直接传元值，无需 ×100；后端已按元存储和校验

---

## 关联 / 联系人

| 项 | 内容 |
|----|------|
| **Issue（功能）** | [#4290 发票申请票种修正](https://git.1814.love:8443/wx/HL/issues/4290) |
| **Issue（小程序）** | [#4296 小程序发票申请同步修正](https://git.1814.love:8443/wx/HL/issues/4296) |
| **PR（票种删 ELECTRONIC）** | [#4292](https://git.1814.love:8443/wx/HL/pulls/4292) |
| **PR（专票限公司/email 必填/删 mailAddress）** | [#4302](https://git.1814.love:8443/wx/HL/pulls/4302) |
| **后端负责人** | yst |
