- 新增 GET /v3/admin/order/invoice/{id} 发票详情接口(全量字段含专票四项/开票痕迹/推送痕迹)
- GET /v3/admin/order/invoice/page 列表行新增 9 个订单冗余字段
- tab 入参新增 NONE(未申请,代客申请候选)
- tabCounts 和 stats 从扁平对象改为数组(破坏性)
15 KiB
15 KiB
发票列表 page 结构调整(破坏性变更)
变更类型:修改接口(破坏性) 端类型:管理后台 日期:2026-06-23 | Issue:#4265 / #4271 | PR:#4267 / #4271 / #4272 | 服务:hl-order-service-v3
破坏性变更:
tabCounts和stats两个字段的数据结构均已从扁平对象改为数组,前端 tab 角标读取、统计卡渲染、列表行新增字段全部需要改动,上线时前后端需同步发布。
接口背景
财务发票管理列表本轮进行了三项结构升级:
- 列表行新增订单冗余字段(产品、出行人数、定制师等),让财务在列表页即可看到关键订单信息
- 新增 NONE tab(未申请),支持代客申请场景——展示已完成但尚未申请发票的订单
tabCounts和stats从扁平对象改为数组,便于前端遍历渲染、后端动态扩展 tab 和统计维度
变更清单
| # | 变更类型 | 具体内容 |
|---|---|---|
| 1 | 出参新增字段 | records[] 行新增 orderNo(补回填)、productName、tierName、contactName、customizerName、departureDate、adultCount、childCount、youngChildCount 共 9 个字段 |
| 2 | 入参新增枚举值 | tab 参数新增 NONE(未申请),返回「已完成且无有效发票」的订单行 |
| 3 | 出参结构破坏性变更 | tabCounts 从扁平对象改为数组 TabCountItem[] |
| 4 | 出参结构破坏性变更 | stats 从扁平对象改为数组 StatItem[] |
接口详情
| 项 | 说明 |
|---|---|
| 方法 + 路径 | GET /v3/admin/order/invoice/page |
| 接口名 | 财务发票管理列表(分页) |
| 描述 | 分页查询发票列表,支持多 tab 过滤、统计卡汇总、tab 计数角标 |
| 认证 | 管理后台 JWT(Bearer Token) |
| 幂等性 | 只读,天然幂等 |
| 限流 | 无特殊限流 |
接口入参
Query 参数
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
tab |
string | 否 | Tab 过滤,见枚举节;默认 ALL;本次新增 NONE |
pageNo |
integer | 否 | 页码,默认 1 |
pageSize |
integer | 否 | 每页条数,默认 20,最大 100 |
keyword |
string | 否 | 关键词搜索(订单号、客户名、定制师名) |
invoiceType |
string | 否 | 发票类型过滤,见枚举 |
startDate |
string | 否 | 申请日期起,格式 YYYY-MM-DD |
endDate |
string | 否 | 申请日期止,格式 YYYY-MM-DD |
请求体
无。
出参字段
返回结构:Result<PageResult<AdminInvoicePageRespVO>>,分页对象顶层附带 tabCounts(数组)和 stats(数组)。
分页包装层
| 字段名 | 类型 | 说明 |
|---|---|---|
total |
integer | 总条数 |
pageNo |
integer | 当前页码 |
pageSize |
integer | 每页大小 |
records |
array | 当前页数据行,见下表 |
tabCounts |
array | 各 tab 计数数组(已改为数组),见 TabCountItem |
stats |
array | 统计卡汇总数组(已改为数组),见 StatItem |
records[] 行字段
| 字段名 | 类型 | 说明 |
|---|---|---|
id |
string / null | 发票 ID;NONE tab 行为 null |
orderId |
string | 订单 ID |
orderNo |
string | 订单编号(本次补充回填,原定义但无值) |
invoiceType |
string / null | 发票类型;NONE tab 行为 null |
invoiceTypeText |
string / null | 发票类型中文名;NONE tab 行为 null |
titleName |
string / null | 发票抬头;NONE tab 行为 null |
amount |
string / null | 开票金额(分),字符串;NONE tab 行为 null |
status |
string | 发票状态;NONE tab 行固定为 "NONE" |
statusText |
string | 发票状态中文名;NONE tab 行固定为 "未申请" |
requestedAt |
string / null | 申请时间;NONE tab 行为 null |
productName |
string | 产品名称(新增) |
tierName |
string / null | 产品档次名称(新增) |
contactName |
string | 客户联系人姓名(新增) |
customizerName |
string / null | 定制师姓名(新增) |
departureDate |
string | 出发日期,格式 YYYY-MM-DD(新增) |
adultCount |
integer | 成人数(新增) |
childCount |
integer | 儿童数(新增) |
youngChildCount |
integer | 婴儿数(新增) |
出行人数展示:将
adultCount/childCount/youngChildCount三值组合为「2成人1儿童」等文案,数量为 0 的维度可省略。
TabCountItem 字段
| 字段名 | 类型 | 说明 |
|---|---|---|
code |
string | Tab 枚举值 |
name |
string | Tab 中文名 |
count |
integer | 该 tab 下的条数 |
StatItem 字段
| 字段名 | 类型 | 说明 |
|---|---|---|
code |
string | 统计维度枚举值 |
name |
string | 统计维度中文名(直接渲染,无需前端自行拼接) |
count |
integer | 该维度数量 |
amount |
string | 该维度金额,单位分,字符串,保留两位小数,如 "98600.00" |
枚举 / 数据字典
tab 入参枚举(含新增 NONE)
| 枚举值 | 说明 | records 行特征 |
|---|---|---|
REQUESTED |
待开票 | 正常发票行,发票字段有值 |
ISSUED |
待推送(已开票) | 正常发票行,fileUrl 有值 |
PUSHED |
已推送 | 正常发票行,pushedAt 有值 |
NONE |
未申请(新增) | 订单行,id / invoiceType 等发票字段均为 null,status="NONE" |
ALL |
全部 | 混合行,包含 NONE 行和发票行 |
tabCounts 数组 code 枚举
| code | name |
|---|---|
REQUESTED |
待开票 |
ISSUED |
待推送 |
PUSHED |
已推送 |
NONE |
未申请 |
ALL |
全部 |
stats 数组 code 枚举
| code | name | 备注 |
|---|---|---|
REQUESTED |
待开票 | 待处理发票的金额汇总 |
ISSUED |
已开票·待推送 | 含义与 tabCounts 中 ISSUED 相同,语境不同 |
PUSHED |
已推送 | 已推送给客户的金额汇总 |
CURRENT_MONTH_ISSUED |
本月已开票 | 当月开票汇总(跨 ISSUED + PUSHED) |
ISSUED在 tabCounts 中文名是「待推送」,在 stats 中文名是「已开票·待推送」,含义相同但语境不同。后端按语境在name字段直接返回正确文案,前端直接渲染name,不要按 code 自行拼文案。
发票类型(invoiceType)
| 枚举值 | 中文名 |
|---|---|
VAT_NORMAL |
增值税普通发票 |
VAT_SPECIAL |
增值税专用发票 |
ELECTRONIC |
电子发票 |
错误码
| 错误码 | 含义 | 触发场景 |
|---|---|---|
401 |
未授权 | 未携带有效 JWT |
403 |
无权限 | 当前账号无发票管理权限 |
400 |
参数错误 | tab 传入不存在的枚举值 |
示例
典型成功(ALL tab,返回数组结构)
请求:
GET /v3/admin/order/invoice/page?tab=ALL&pageNo=1&pageSize=20
Authorization: Bearer <token>
响应:
{
"code": 200,
"msg": "success",
"data": {
"total": 12,
"pageNo": 1,
"pageSize": 20,
"records": [
{
"id": "1934567890123456789",
"orderId": "1920000000000000001",
"orderNo": "ORD2026062300001",
"invoiceType": "VAT_NORMAL",
"invoiceTypeText": "增值税普通发票",
"titleName": "呼籁科技有限公司",
"amount": "98600",
"status": "REQUESTED",
"statusText": "待开票",
"requestedAt": "2026-06-20T14:30:00",
"productName": "云南香格里拉深度游5日",
"tierName": "标准档",
"contactName": "李四",
"customizerName": "王五",
"departureDate": "2026-07-10",
"adultCount": 2,
"childCount": 1,
"youngChildCount": 0
}
],
"tabCounts": [
{ "code": "REQUESTED", "name": "待开票", "count": 3 },
{ "code": "ISSUED", "name": "待推送", "count": 2 },
{ "code": "PUSHED", "name": "已推送", "count": 2 },
{ "code": "NONE", "name": "未申请", "count": 5 },
{ "code": "ALL", "name": "全部", "count": 12 }
],
"stats": [
{ "code": "REQUESTED", "name": "待开票", "count": 3, "amount": "1000.00" },
{ "code": "ISSUED", "name": "已开票·待推送", "count": 2, "amount": "98600.00" },
{ "code": "PUSHED", "name": "已推送", "count": 2, "amount": "50000.00" },
{ "code": "CURRENT_MONTH_ISSUED", "name": "本月已开票", "count": 4, "amount": "148600.00" }
]
}
}
边界情况(NONE tab,发票字段为 null 的订单行)
请求:
GET /v3/admin/order/invoice/page?tab=NONE&pageNo=1&pageSize=20
Authorization: Bearer <token>
响应:
{
"code": 200,
"msg": "success",
"data": {
"total": 5,
"pageNo": 1,
"pageSize": 20,
"records": [
{
"id": null,
"orderId": "1920000000000000099",
"orderNo": "ORD2026062200099",
"invoiceType": null,
"invoiceTypeText": null,
"titleName": null,
"amount": null,
"status": "NONE",
"statusText": "未申请",
"requestedAt": null,
"productName": "西藏拉萨朝圣7日",
"tierName": null,
"contactName": "钱八",
"customizerName": "孙九",
"departureDate": "2026-06-15",
"adultCount": 4,
"childCount": 0,
"youngChildCount": 1
}
],
"tabCounts": [
{ "code": "REQUESTED", "name": "待开票", "count": 3 },
{ "code": "ISSUED", "name": "待推送", "count": 2 },
{ "code": "PUSHED", "name": "已推送", "count": 2 },
{ "code": "NONE", "name": "未申请", "count": 5 },
{ "code": "ALL", "name": "全部", "count": 12 }
],
"stats": [
{ "code": "REQUESTED", "name": "待开票", "count": 3, "amount": "1000.00" },
{ "code": "ISSUED", "name": "已开票·待推送", "count": 2, "amount": "98600.00" },
{ "code": "PUSHED", "name": "已推送", "count": 2, "amount": "50000.00" },
{ "code": "CURRENT_MONTH_ISSUED", "name": "本月已开票", "count": 4, "amount": "148600.00" }
]
}
}
业务失败(tab 枚举值不存在)
请求:
GET /v3/admin/order/invoice/page?tab=INVALID
Authorization: Bearer <token>
响应:
{
"code": 400,
"msg": "参数错误:tab 枚举值不合法",
"data": null
}
业务边界
适用场景
- 财务管理后台「发票管理」列表页
- 代客申请场景:切换到 NONE tab 查看「已完成但未申请发票」的订单,点击进入代客申请流程
不适用场景
- 小程序端查看发票状态(走小程序端专属接口)
- 查询单张发票全量明细(走
GET /v3/admin/order/invoice/{id}详情接口)
特殊边界
- NONE tab 行是订单行,不是发票行:
id为 null,发票字段均为 null,status固定为字符串"NONE",前端需按 status 判断是否展示发票操作按钮 - ALL tab 混合 NONE 行和发票行:遍历 records 时需按
status === "NONE"区分行类型 amount单位为分,字符串格式,前端展示时除以 100 转为元
修改前后对比
tabCounts 字段
旧(扁平对象):
{
"tabCounts": {
"requestedCount": 3,
"issuedCount": 2,
"pushedCount": 2,
"allCount": 12,
"noneCount": 5
}
}
新(数组,现行):
{
"tabCounts": [
{ "code": "REQUESTED", "name": "待开票", "count": 3 },
{ "code": "ISSUED", "name": "待推送", "count": 2 },
{ "code": "PUSHED", "name": "已推送", "count": 2 },
{ "code": "NONE", "name": "未申请", "count": 5 },
{ "code": "ALL", "name": "全部", "count": 12 }
]
}
stats 字段
旧(扁平对象):
{
"stats": {
"requestedCount": 3,
"requestedAmount": 1000,
"issuedCount": 2,
"issuedAmount": 98600,
"pushedCount": 2,
"pushedAmount": 50000,
"currentMonthIssuedCount": 4,
"currentMonthIssuedAmount": 148600
}
}
新(数组,现行):
{
"stats": [
{ "code": "REQUESTED", "name": "待开票", "count": 3, "amount": "1000.00" },
{ "code": "ISSUED", "name": "已开票·待推送", "count": 2, "amount": "98600.00" },
{ "code": "PUSHED", "name": "已推送", "count": 2, "amount": "50000.00" },
{ "code": "CURRENT_MONTH_ISSUED", "name": "本月已开票", "count": 4, "amount": "148600.00" }
]
}
tab 入参新增值
| 旧可选值 | 新增值 |
|---|---|
| REQUESTED / ISSUED / PUSHED / ALL | 新增 NONE(未申请) |
records[] 行新增字段
| 字段 | 变化 |
|---|---|
orderNo |
原已定义,本次补充回填(实际有值) |
productName |
新增 |
tierName |
新增 |
contactName |
新增 |
customizerName |
新增 |
departureDate |
新增 |
adultCount |
新增 |
childCount |
新增 |
youngChildCount |
新增 |
影响评估 / 回滚
破坏兼容性评估
| 模块 | 是否影响 | 必须改动 |
|---|---|---|
| Tab 角标数字渲染 | 是 | 旧代码读 tabCounts.requestedCount 等扁平字段会 undefined,需改为遍历数组按 code 取值 |
| 统计卡渲染 | 是 | 旧代码读 stats.requestedCount / stats.requestedAmount 等扁平字段会 undefined,需改为遍历数组按 code 取 count / amount |
| 列表行渲染 | 需新增 | 新增 9 个字段需接线渲染(产品名、出行人数组合、定制师、出发日期等) |
| NONE tab 处理 | 需新增 | NONE tab 下行无发票 id,代客申请按钮需按 status==="NONE" 判断 |
| ALL tab 混合行 | 需新增 | ALL tab 下需区分 NONE 行和正常发票行 |
前端同步上线要求
本次变更前后端需同步发布。旧前端读新后端的数组结构,tab 角标和统计卡均会渲染为空。
回滚方案
如需回滚后端,通知前端同步回滚对应代码;两端数据结构不匹配会导致 tab 角标消失和统计卡白屏。
注意事项
tabCounts和stats均已变为数组,旧的扁平对象键名已全部废弃,前端读取方式必须更新- NONE tab 行中
status是字符串"NONE",而不是 null,前端用status === "NONE"判断行类型 stats中的amount单位为分,字符串格式,保留两位小数,前端展示时除以 100 转为元ISSUED在 tabCounts 和 stats 中name不同,直接渲染name字段即可,不要自行拼接adultCount/childCount/youngChildCount为整数,0 的维度建议展示时省略
关联 / 联系人
| 项 | 内容 |
|---|---|
| Issue | #4265 发票列表 NONE tab / #4271 列表订单冗余字段 |
| PR | #4267 / #4271 / #4272 stats 拍平 |
| 后端负责人 | yst |