Date: 2026-07-24
后端 REST 接口契约。挂在 InfoInvoiceController(@RequestMapping("/system/invoice")),风格镜像 InfoAddressController(@Anonymous @Auth + @RequestHeader String token + JwtUtil.getusid(token) 取 userId)。实体 InfoInvoice 即请求体 / 响应体(无专门 DTO/VO,与 InfoAddress 一致)。
字段格式 / 必填由 jakarta 校验注解(
@NotBlank/@Pattern/InfoInvoice实体上,springfox 自动识别为 Swagger 的 required/pattern(前端在 Knife4j/doc.html可见字段约束);接口与字段完整含义、示例、按类型的必填条件见本文件。Controller 另用@Api/@ApiOperation给出接口描述。 说明:实体在ruoyi-system模块(无 swagger 依赖),故字段级描述走校验注解 + 本文档,与 010ApplyInvoiceDto同一约定。
category 是类型区分依据(新增/修改的入参分支、列表渲染分支都以它为准):
| category | 含义 | 必传字段 | 不传字段 |
|---|---|---|---|
| B2C | 个人发票 | titleName、buyerName、carrierType、carrierNum(载具=2 时再加 buyerEmail) | buyerUbn |
| B2B | 公司发票 | titleName、buyerName、buyerUbn、buyerEmail | carrierType、carrierNum |
/system/invoice/getinvoice列出当前用户全部抬头(按 id 倒序)。
入参:@RequestHeader String token。
响应 AjaxResult data:List<InfoInvoice>。前端按每行的 category 区分类型并渲染对应字段:
| 字段 | B2C 行 | B2B 行 |
|---|---|---|
category |
"B2C" |
"B2B" ← 类型判别字段 |
titleName |
有 | 有 |
buyerName |
个人姓名 | 公司名 |
buyerUbn |
null(不展示) |
统编(展示) |
buyerEmail |
载具=2 时有值,否则 null |
邮箱(展示) |
carrierType |
0/1/2(展示载具) |
null(不展示) |
carrierNum |
载具号码(展示) | null(不展示) |
渲染建议:列表项以
titleName为主标题,副标题按 category 走——B2B 显示「公司名 · 统编」,B2C 显示「姓名 · 载具类型描述」。
/system/invoice/invoice新增或更新(saveOrUpdate:有 id 走更新、无 id 走新增)。
入参:@RequestHeader String token + @RequestBody InfoInvoice(字段格式由 @Valid 先校验、条件必填与类型互斥由 service 强校验)。
完整字段表(前端据此传参)
| 字段 | 类型 | 含义 | 格式 / 示例 | B2C | B2B |
|---|---|---|---|---|---|
| id | Long | 主键 | 新增不传;修改必传(101) |
同左 | 同左 |
| titleName | String | 抬头备注名 | 任意,公司-美食達 |
✅必填 | ✅必填 |
| category | String | 发票类型 | B2C / B2B |
✅必填=B2C |
✅必填=B2B |
| buyerName | String | 买方名称 | B2C 个人姓名 / B2B 公司名 | ✅必填 | ✅必填 |
| buyerUbn | String | 统一编号 | 8 位数字 12345678 |
❌不传 | ✅必填 |
| buyerEmail | String | 邮箱 | ms@example.com |
⚪载具=2 时必填,其余可空 | ✅必填 |
| carrierType | String | 载具类型 | 0手机条码/1自然人凭证/2ezPay会员 |
✅必填 | ❌不传 |
| carrierNum | String | 载具号码 | 随 carrierType(见下) | ✅必填 | ❌不传 |
| userId | Long | 用户id | 后端按 token 自动填,前端不传 | — | — |
载具号码格式(carrierNum,随 carrierType)
0 手机条码:以 / 开头,如 /ABC12341 自然人凭证:2 位大写字母 + 14 位数字,如 AB123456789012342 ezPay 会员载具:会员账号(非空),且须同时带 buyerEmail处理:① getusid(token) → setUserId 强制覆盖(防越权);② @Valid 字段格式校验;③ validateInvoiceProfile 条件必填 + 类型互斥强校验;④ 更新时校验该 id 属于当前 user;⑤ saveOrUpdate。
响应:成功 {code:200, data:id};校验失败 {code:500, msg:"<具体原因>"}。
请求示例
B2C / 手机条码载具:
{ "titleName": "个人-手机条码", "category": "B2C", "buyerName": "王小明", "carrierType": "0", "carrierNum": "/ABC1234" }
B2C / ezPay 会员载具(须带邮箱):
{ "titleName": "个人-会员载具", "category": "B2C", "buyerName": "王小明", "carrierType": "2", "carrierNum": "C1597485444", "buyerEmail": "xm@example.com" }
B2B 公司:
{ "titleName": "公司-美食達", "category": "B2B", "buyerName": "美食達有限公司", "buyerUbn": "12345678", "buyerEmail": "ms@example.com" }
修改(带 id,覆盖该条):
{ "id": 101, "titleName": "公司-美食達", "category": "B2B", "buyerName": "美食達有限公司", "buyerUbn": "12345678", "buyerEmail": "ms@example.com" }
/system/invoice/getinvoicexq?id=详情;越权(id 不属于当前 user)返回 data: null。
响应 AjaxResult data:单条 InfoInvoice(字段同列表单行)。
/system/invoice/deleinvoice?id=删除(硬删除);越权 / 不存在返回失败 {code:500, msg:"抬头不存在或无权操作"}。
validateInvoiceProfile)| 场景 | category | buyerName | buyerUbn | buyerEmail | carrierType + carrierNum |
|---|---|---|---|---|---|
| B2C / 手机条码(0) | B2C | ✅必填 | ❌空 | ⚪可选 | ✅必填(0 + 号以 / 开头) |
| B2C / 自然人凭证(1) | B2C | ✅必填 | ❌空 | ⚪可选 | ✅必填(1 + ^[A-Z]{2}\d{14}$) |
| B2C / ezPay 会员(2) | B2C | ✅必填 | ❌空 | ✅必填 | ✅必填(2 + 号非空) |
| B2B 公司 | B2B | ✅必填(公司名) | ✅必填(^\d{8}$) |
✅必填(邮箱格式) | ❌空 |
通用:titleName 非空;category ∈ {B2C, B2B};buyerEmail 若填则校验邮箱格式。
客户端开票时:① GET /system/invoice/getinvoice 拉抬头;② 用户选一条 → 客户端按字段映射填入 ApplyInvoiceDto(category / buyerName / buyerUbn / buyerEmail / carrierType / carrierNum)→ ③ POST /system/userOrder/applyInvoice(010,不改)。抬头仅作输入快捷方式,010 仍做最终校验兜底。