diff --git a/changelogs-v2-mp/2026-06/21_4174_发票小程序端申请+可见性+签名下载-新增接口-小程序端.md b/changelogs-v2-mp/2026-06/21_4174_发票小程序端申请+可见性+签名下载-新增接口-小程序端.md new file mode 100644 index 0000000..95fd971 --- /dev/null +++ b/changelogs-v2-mp/2026-06/21_4174_发票小程序端申请+可见性+签名下载-新增接口-小程序端.md @@ -0,0 +1,241 @@ +# 发票补齐 - 小程序端申请 + 可见性过滤 + 签名下载(小程序端) + +> Issue: [#4174](https://git.1814.love:8443/wx/HL/issues/4174) / [#4182](https://git.1814.love:8443/wx/HL/issues/4182) +> PR: [#4181](https://git.1814.love:8443/wx/HL/pulls/4181)(mp 端申请+可见性)/ [#4184](https://git.1814.love:8443/wx/HL/pulls/4184)(签名下载) +> 日期: 2026-06-21 +> 服务: hl-order-service-v3(invoice 域) +> 端类型: 小程序端 + +--- + +## 1. 接口背景 + +小程序发票模块本次补齐三项改动: + +- **申请门槛收紧**(PR4 / Issue #4174):原 collab 域 POST /v3/internal/mp/order/{orderId}/invoice/apply 路径不变,但实现从 collab 迁入 invoice 域,门槛由宽松(可叠加多张)改为严格(一单一票 + 订单必须 COMPLETED + 金额不超订单总价)。 +- **出参可见性过滤**(PR4):GET /v3/internal/mp/order/{orderId}/invoice/list 和 GET /v3/internal/mp/order/invoice/{invoiceId}/detail 出参中,fileUrl 字段现在仅在 ISSUED / PUSHED 状态时返回;REQUESTED 状态返回 null(处理中,前端勿展示 PDF 链接)。 +- **新增签名下载端点**(#4184 / Issue #4182):GET /v3/internal/mp/order/invoice/{invoiceId}/download,返回 1 小时有效期 OSS 签名 URL,供前端调 wx.openDocument 预览或下载发票 PDF。 + +--- + +## 2. 变更清单 + +| # | 接口 | 变更类型 | 说明 | +|---|------|----------|------| +| 1 | POST /v3/internal/mp/order/{orderId}/invoice/apply | 修改接口(行为收紧) | 申请门槛由宽松改严格:一单一票 + 订单 COMPLETED + 金额不超总价;resp status 字段值从 APPLIED 改为 REQUESTED | +| 2 | GET /v3/internal/mp/order/{orderId}/invoice/list | 修改接口(字段行为变化) | fileUrl 字段:REQUESTED 状态由有值改为 null;ISSUED / PUSHED 保持有值 | +| 3 | GET /v3/internal/mp/order/invoice/{invoiceId}/detail | 修改接口(字段行为变化) | fileUrl 字段同上:REQUESTED 返 null,ISSUED / PUSHED 有值 | +| 4 | GET /v3/internal/mp/order/invoice/{invoiceId}/download | 新增接口 | 返回发票 PDF 的 OSS 签名下载 URL(1 小时有效期),含 IDOR 鉴权 | + +--- + +## 3. 接口详情 + +认证:所有接口走小程序 JWT 认证,Header 携带 Authorization: Bearer token(小程序 openId → userId)。 +幂等性:apply 非幂等(一单一票,重复提交报 581511);download 幂等(每次生成新签名 URL)。 +限流:网关全局限流。 + +--- + +## 4. 接口入参 + +### 4.1 路径参数 / Query 参数 + +| 接口 | 参数名 | 类型 | 必填 | 说明 | +|------|--------|------|------|------| +| POST /{orderId}/apply | orderId | Long | 是 | 路径参数,订单 ID | +| GET /{orderId}/invoice/list | orderId | Long | 是 | 路径参数,订单 ID | +| GET /invoice/{invoiceId}/detail | invoiceId | Long | 是 | 路径参数,发票 ID | +| GET /invoice/{invoiceId}/download | invoiceId | Long | 是 | 路径参数,发票 ID | + +### 4.2 请求体字段 + +**POST /v3/internal/mp/order/{orderId}/invoice/apply** + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| amount | String | 是 | 开票金额(字符串,单位元),不超过订单总价 | +| invoiceType | String | 是 | 发票类型,枚举见 6 节 | +| titleType | String | 是 | 抬头类型:PERSONAL / COMPANY | +| title | String | 是 | 发票抬头(姓名或公司名) | +| taxNo | String | 条件必填 | 税号(titleType=COMPANY 时必填) | +| bankName | String | 条件必填 | 开户行(invoiceType=VAT_SPECIAL 时必填) | +| bankAccount | String | 条件必填 | 银行账号(invoiceType=VAT_SPECIAL 时必填) | +| registAddress | String | 条件必填 | 注册地址(invoiceType=VAT_SPECIAL 时必填) | +| registPhone | String | 条件必填 | 注册电话(invoiceType=VAT_SPECIAL 时必填) | +| email | String | 条件必填 | 收票邮箱(invoiceType=ELECTRONIC 时必填) | +| remark | String | 否 | 备注 | + +GET 接口无请求体。 + +--- + +## 5. 出参字段 + +**POST /v3/internal/mp/order/{orderId}/invoice/apply** + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | String | 新建发票 ID(Long 序列化) | +| status | String | 固定为 REQUESTED | + +注意:status 值此前旧实现返回 APPLIED(collab 域),新实现返回 REQUESTED(invoice 域)。请前端对齐新值。 + +**GET /v3/internal/mp/order/{orderId}/invoice/list(单条字段,变化部分)** + +| 字段 | 原来行为 | 现在行为 | +|------|---------|---------| +| fileUrl | REQUESTED / ISSUED / PUSHED 均有值 | REQUESTED 返 null;ISSUED / PUSHED 有值 | + +其余字段不变(id / orderId / status / statusName / amount / invoiceType / title / taxNo / createTime 等)。 + +**GET /v3/internal/mp/order/invoice/{invoiceId}/detail(变化部分)** + +| 字段 | 原来行为 | 现在行为 | +|------|---------|---------| +| fileUrl | 同上,恒有值 | REQUESTED 返 null;ISSUED / PUSHED 有值 | + +**GET /v3/internal/mp/order/invoice/{invoiceId}/download(新增接口)** + +| 字段 | 类型 | 说明 | +|------|------|------| +| fileUrl | String | OSS 签名下载 URL(1 小时有效期,格式为 HTTPS 带签名参数的 URL) | +| pdfName | String | PDF 文件名(可选,用于 wx.openDocument 的 name 参数) | + +--- + +## 6. 枚举 / 数据字典 + +**InvoiceStatus - 发票状态(影响 fileUrl 可见性)** + +| 枚举值 | 中文名 | fileUrl 是否可见 | +|--------|--------|----------------| +| REQUESTED | 待开票 | null(处理中,前端勿展示 PDF 链接) | +| ISSUED | 已开票 | 有值 | +| PUSHED | 已推送 | 有值 | +| VOIDED | 已作废 | null | + +**InvoiceType - 发票类型** + +| 枚举值 | 中文名 | +|--------|--------| +| VAT_NORMAL | 增值税普通发票 | +| VAT_SPECIAL | 增值税专用发票 | +| ELECTRONIC | 电子发票 | + +**TitleType - 抬头类型** + +| 枚举值 | 中文名 | +|--------|--------| +| PERSONAL | 个人 | +| COMPANY | 公司 | + +--- + +## 7. 错误码 + +| 错误码 | 常量 | 触发场景 | +|--------|------|----------| +| 581510 | INVOICE_ORDER_NOT_COMPLETED | 订单状态非 COMPLETED,不可申请开票 | +| 581511 | INVOICE_ALREADY_EXISTS | 该订单已有有效发票,不可重复申请(一单一票) | +| 581512 | INVOICE_AMOUNT_EXCEED | 开票金额超过订单总价 | +| 581513 | INVOICE_TYPE_INVALID | 发票类型枚举值非法 | +| 581514 | INVOICE_TAX_NO_REQUIRED | 公司抬头 / 专票时税号为必填 | +| 581515 | INVOICE_VAT_SPECIAL_FIELDS_REQUIRED | 专票时开户行 / 银行账号 / 注册地址 / 注册电话为必填 | +| 581516 | INVOICE_EMAIL_REQUIRED | 电子发票时邮箱为必填 | +| 581517 | INVOICE_NOT_DOWNLOADABLE | 发票尚未开具(REQUESTED / VOIDED 状态),暂时无法下载 | +| 581518 | INVOICE_FORBIDDEN | 无权访问该发票(IDOR 防护,含发票不存在场景) | +| 581519 | INVOICE_SIGNED_URL_FAILED | 获取签名下载 URL 失败,请稍后重试(hl-user-service 暂时不可用) | + +--- + +## 8. 示例 + +### 8.1 典型成功:申请开票 + +POST /v3/internal/mp/order/1234567890123456789/invoice/apply + +请求体: + + +响应: + + +### 8.2 边界情况:列表中 REQUESTED 状态 fileUrl 为 null + +GET /v3/internal/mp/order/1234567890123456789/invoice/list 响应示例(某张待开票发票): + + +### 8.3 业务失败:REQUESTED 状态发票不可下载 + +GET /v3/internal/mp/order/invoice/111/download + +响应: + + +--- + +## 9. 业务边界 + +**适用:** +- 申请:订单状态必须为 COMPLETED;无有效发票(一单一票);金额不超订单总价 +- 下载:发票状态为 ISSUED 或 PUSHED;且该发票属于当前登录用户的订单 + +**不适用:** +- 订单 COMPLETED 之前不可申请 +- 已有有效发票,不可重复申请 +- REQUESTED / VOIDED 状态发票不可下载(报 581517) +- 访问他人订单的发票报 581518(IDOR 防护,不返回 404 以避免泄露存在性) + +**特殊边界:** +- 签名 URL 有效期 1 小时。前端如果缓存了 fileUrl,每次打开下载前须重新调 download 接口获取最新签名 URL,不可长期复用。 +- wx.openDocument 下载后端直接返回签名 URL,前端无需中转。 + +--- + +## 10. 修改前后对比 + +### POST apply 申请门槛变化 + +| 项目 | 变更前(旧 collab 实现) | 变更后(invoice 域) | +|------|----------------------|-------------------| +| 一单一票限制 | 无(可叠加多张) | 有(existsActiveByOrderId 拦截) | +| 金额校验 | 已开+本次 <= 总价 | 本次金额 <= 总价(一单一票前提下等价) | +| 响应 status 值 | APPLIED | REQUESTED | + +### GET list / detail fileUrl 可见性变化 + +| 发票状态 | 变更前 fileUrl | 变更后 fileUrl | +|---------|--------------|--------------| +| REQUESTED | 有值(OSS 公链) | null(前端显示处理中) | +| ISSUED | 有值 | 有值(不变) | +| PUSHED | 有值 | 有值(不变) | +| VOIDED | 有值 | null | + +--- + +## 11. 影响评估 / 回滚 + +- 破坏兼容性(重要):fileUrl 字段在 REQUESTED 状态下由有值变为 null。前端如果之前有展示 REQUESTED 状态下 PDF 链接的逻辑(点击打开 OSS 文件),现在会读到 null,需要做 null 判断,展示「处理中」状态文案。 +- apply status 值变化:从 APPLIED 改为 REQUESTED。前端如果有对 status 值做 hardcode 判断(if status == APPLIED),需要更新。 +- 前端同步上线建议:fileUrl null 判断 + apply 响应 status 对齐必须在本次版本同步更新。 +- 回滚方案:回滚旧版本,fileUrl 可见性恢复,download 端点返回 404。apply 的 status 回退为 APPLIED(如旧版本有此值)。 + +--- + +## 12. 注意事项 + +1. fileUrl null 判断:list / detail 接口读到 fileUrl 为 null 时,前端应显示「处理中」或「待开票」而不是空链接。 +2. 签名 URL 勿缓存超过 1 小时:download 返回的签名 URL 有效期 1 小时,前端每次用前须重新调接口,不可全局缓存复用。 +3. apply 响应 status 值变更:旧值 APPLIED 已废弃,现在返回 REQUESTED。如前端有 switch/if 判断该值,须对齐。 +4. download IDOR 防护:发票不存在或不属于当前用户均返回 581518(INVOICE_FORBIDDEN),不返回 404,避免存在性枚举攻击。 + +--- + +## 13. 关联 / 联系人 + +- Issue(小程序端 mp): https://git.1814.love:8443/wx/HL/issues/4174 +- Issue(签名下载): https://git.1814.love:8443/wx/HL/issues/4182 +- PR(mp 端 #4181): https://git.1814.love:8443/wx/HL/pulls/4181 +- PR(签名下载 #4184): https://git.1814.love:8443/wx/HL/pulls/4184 +- 后端负责人: yaosutu diff --git a/changelogs-v2/2026-06/21_4172_4173_发票财务开票闭环+定制师代申请-新增接口-管理后台.md b/changelogs-v2/2026-06/21_4172_4173_发票财务开票闭环+定制师代申请-新增接口-管理后台.md new file mode 100644 index 0000000..ce81f8c --- /dev/null +++ b/changelogs-v2/2026-06/21_4172_4173_发票财务开票闭环+定制师代申请-新增接口-管理后台.md @@ -0,0 +1,314 @@ +# 发票补齐 - 财务开票闭环 + 定制师代申请(管理后台) + +> Issue: [#4172](https://git.1814.love:8443/wx/HL/issues/4172) / [#4173](https://git.1814.love:8443/wx/HL/issues/4173) +> PR: [#4179](https://git.1814.love:8443/wx/HL/pulls/4179) / [#4180](https://git.1814.love:8443/wx/HL/pulls/4180) +> 日期: 2026-06-21 +> 服务: hl-order-service-v3(invoice 域) +> 端类型: 管理后台 + +--- + +## 1. 接口背景 + +order-v3 发票模块此前缺少财务侧开票操作和定制师代客申请入口。本次补齐: + +- 财务开票闭环(PR2 / Issue #4172):财务人员上传发票 PDF 至 OSS,完成开票(REQUESTED 变 ISSUED),支持重新上传覆盖;以及发票管理列表(分页 + Tab 统计 + 当月金额统计 + 关键词搜索)。 +- 定制师代客申请(PR3 / Issue #4173):定制师代用户提交开票申请,门槛与小程序端统一(订单 COMPLETED + 一单一票 + 申请金额不超订单总价)。 +- 已有接口字段补全:GET /v3/admin/order/{id}/invoices 出参补充 8 个原来恒为 null 的字段。 + +--- + +## 2. 变更清单 + +| # | 接口 | 变更类型 | 说明 | +|---|------|----------|------| +| 1 | POST /v3/admin/order/invoice/upload | 新增接口 | 财务上传发票文件至 OSS,返回 fileUrl(不绑定发票记录) | +| 2 | PUT /v3/admin/order/invoice/{id}/issue | 新增接口 | 完成开票,状态 REQUESTED 变 ISSUED | +| 3 | PUT /v3/admin/order/invoice/{id}/reupload | 新增接口 | 重新上传覆盖文件(ISSUED / PUSHED 均可) | +| 4 | GET /v3/admin/order/invoice/page | 新增接口 | 发票管理列表(分页 + Tab 计数 + 当月统计 + 搜索) | +| 5 | POST /v3/admin/order/{orderId}/invoice/apply | 新增接口 | 定制师代客申请开票 | +| 6 | GET /v3/admin/order/{id}/invoices(已有) | 修改接口(字段补全) | 出参新增:fileUrl / pdfName / pdfSize / invoiceNo / issuedAt / issuedBy / requestedAt / requestedBy | + +--- + +## 3. 接口详情 + +认证:所有接口走管理后台 JWT,Header 携带 Authorization: Bearer token。upload 使用 multipart/form-data,其余 application/json。 +幂等性:upload 每次生成新 OSS 文件,无幂等。issue / reupload 依赖发票状态守卫,状态不符报业务错误。 +限流:网关全局限流,无接口级特殊限流。 + +--- + +## 4. 接口入参 + +### 4.1 路径参数 / Query 参数 + +| 接口 | 参数名 | 类型 | 必填 | 说明 | +|------|--------|------|------|------| +| PUT /issue/{id} | id | Long | 是 | 发票记录 ID | +| PUT /reupload/{id} | id | Long | 是 | 发票记录 ID | +| GET /page | page | Integer | 否 | 页码,默认 1 | +| GET /page | pageSize | Integer | 否 | 每页条数,默认 20 | +| GET /page | tab | String | 否 | Tab 过滤,见 6 节,默认 ALL | +| GET /page | keyword | String | 否 | 关键词搜索(发票号 / 申请人) | +| GET /page | startDate | String | 否 | 申请开始日期 yyyy-MM-dd | +| GET /page | endDate | String | 否 | 申请结束日期 yyyy-MM-dd | +| POST /{orderId}/apply | orderId | Long | 是 | 路径参数,订单 ID | + +### 4.2 请求体字段 + +**POST /v3/admin/order/invoice/upload** + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| file | MultipartFile | 是 | 发票 PDF 文件(multipart/form-data) | + +**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 文件大小(字节) | + +**POST /v3/admin/order/{orderId}/invoice/apply** + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| amount | String | 是 | 开票金额(字符串,单位元),不超过订单总价 | +| invoiceType | String | 是 | 发票类型,枚举见 6 节 | +| titleType | String | 是 | 抬头类型:PERSONAL / COMPANY | +| title | String | 是 | 发票抬头(姓名或公司名) | +| taxNo | String | 条件必填 | 税号(titleType=COMPANY 时必填) | +| bankName | String | 条件必填 | 开户行(invoiceType=VAT_SPECIAL 时必填) | +| bankAccount | String | 条件必填 | 银行账号(invoiceType=VAT_SPECIAL 时必填) | +| registAddress | String | 条件必填 | 注册地址(invoiceType=VAT_SPECIAL 时必填) | +| registPhone | String | 条件必填 | 注册电话(invoiceType=VAT_SPECIAL 时必填) | +| email | String | 条件必填 | 收票邮箱(invoiceType=ELECTRONIC 时必填) | +| remark | String | 否 | 备注 | + +--- + +## 5. 出参字段 + +**POST /v3/admin/order/invoice/upload** + +| 字段 | 类型 | 说明 | +|------|------|------| +| data | String | 上传成功后的 OSS 文件 URL,用于后续 issue / reupload 接口 | + +**PUT issue 和 reupload:** 操作成功返回 HTTP 200,data 为 null。 + +**GET /v3/admin/order/invoice/page** + +| 字段 | 类型 | 说明 | +|------|------|------| +| records | Array | 发票列表(见下表) | +| total | Long | 总记录数 | +| page | Integer | 当前页码 | +| pageSize | Integer | 每页条数 | +| tabCounts | Object | 各 Tab 计数(requestedCount / issuedCount / pushedCount) | +| stats | Object | 当月统计(currentMonthIssuedCount / currentMonthIssuedAmount) | + +records 单条字段: + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | String | 发票 ID(Long 序列化) | +| orderId | String | 所属订单 ID(Long 序列化) | +| status | String | 发票状态枚举,见 6 节 | +| statusName | String | 状态中文名 | +| amount | String | 开票金额(字符串,单位元) | +| invoiceType | String | 发票类型枚举 | +| invoiceTypeName | String | 发票类型中文名 | +| titleType | String | 抬头类型 | +| title | String | 发票抬头 | +| taxNo | String | 税号 | +| fileUrl | String | 发票 PDF 地址(ISSUED / PUSHED 有值,其余 null) | +| pdfName | String | PDF 文件名 | +| pdfSize | Long | PDF 文件大小(字节) | +| invoiceNo | String | 发票号码 | +| issuedAt | String | 开票时间(ISO 8601) | +| issuedBy | String | 开票人姓名(财务真实姓名) | +| requestedAt | String | 申请时间(ISO 8601) | +| requestedBy | String | 申请人(定制师姓名;mp 端为 userId 字符串) | +| createTime | String | 记录创建时间 | + +**POST /v3/admin/order/{orderId}/invoice/apply** + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | String | 新建发票 ID(Long 序列化) | +| status | String | 固定为 REQUESTED(申请后待开票) | + +**GET /v3/admin/order/{id}/invoices(已有接口,字段补全)** + +以下字段由恒 null 变为有值:fileUrl / pdfName / pdfSize / invoiceNo / issuedAt / issuedBy / requestedAt / requestedBy。字段含义同 /page 出参表。 + +--- + +## 6. 枚举 / 数据字典 + +**InvoiceStatus - 发票状态** + +| 枚举值 | 中文名 | 说明 | +|--------|--------|------| +| REQUESTED | 待开票 | 用户 / 定制师已申请,等待财务处理 | +| ISSUED | 已开票 | 财务已完成开票并上传 PDF | +| PUSHED | 已推送 | 发票已推送给客户 | +| VOIDED | 已作废 | 发票已作废 | + +**Tab 过滤值(GET /page 入参 tab 字段)** + +| 值 | 说明 | +|----|------| +| ALL | 全部(默认) | +| REQUESTED | 仅待开票 | +| ISSUED | 仅已开票 | +| PUSHED | 仅已推送 | + +**InvoiceType - 发票类型** + +| 枚举值 | 中文名 | +|--------|--------| +| VAT_NORMAL | 增值税普通发票 | +| VAT_SPECIAL | 增值税专用发票 | +| ELECTRONIC | 电子发票 | + +**TitleType - 抬头类型** + +| 枚举值 | 中文名 | +|--------|--------| +| PERSONAL | 个人 | +| COMPANY | 公司 | + +--- + +## 7. 错误码 + +| 错误码 | 常量 | 触发场景 | +|--------|------|----------| +| 581502 | INVOICE_CANNOT_ISSUE | 发票状态非 REQUESTED,不允许开票 | +| 581503 | INVOICE_CANNOT_REUPLOAD | 发票状态非 ISSUED / PUSHED,不允许重新上传 | +| 581504 | INVOICE_FILE_UPLOAD_FAILED | 文件上传失败(OSS 或 hl-user-service 不可用) | +| 581510 | INVOICE_ORDER_NOT_COMPLETED | 订单状态非 COMPLETED,不可申请开票 | +| 581511 | INVOICE_ALREADY_EXISTS | 该订单已有有效发票,不可重复申请(一单一票) | +| 581512 | INVOICE_AMOUNT_EXCEED | 开票金额超过订单总价 | +| 581513 | INVOICE_TYPE_INVALID | 发票类型枚举值非法 | +| 581514 | INVOICE_TAX_NO_REQUIRED | 公司抬头 / 专票时税号为必填 | +| 581515 | INVOICE_VAT_SPECIAL_FIELDS_REQUIRED | 专票时开户行 / 银行账号 / 注册地址 / 注册电话为必填 | +| 581516 | INVOICE_EMAIL_REQUIRED | 电子发票时邮箱为必填 | + +--- + +## 8. 示例 + +### 8.1 典型成功:财务两步完成开票 + +Step 1 上传文件: + +POST /v3/admin/order/invoice/upload,Content-Type: multipart/form-data,file 字段传 PDF 文件。 + +响应: + + + +Step 2 完成开票: + +PUT /v3/admin/order/invoice/1234567890123456789/issue,请求体: + + + +响应: + + + +### 8.2 边界情况:发票列表 tab=PUSHED 无记录 + +GET /v3/admin/order/invoice/page?tab=PUSHED&page=1&pageSize=20 + +响应: + + + +### 8.3 业务失败:订单未完成,定制师代申请被拒 + +POST /v3/admin/order/9876543210987654321/invoice/apply,请求体: + + + +订单状态为 CUSTOMIZING(定制中)时,响应: + + + +--- + +## 9. 业务边界 + +**适用:** +- 财务开票:发票状态必须为 REQUESTED +- 重新上传:发票状态为 ISSUED 或 PUSHED +- 定制师代申请:订单状态必须为 COMPLETED(出行结束后) + +**不适用:** +- 订单处于 PAID、CUSTOMIZING、TRAVELLING 等非 COMPLETED 状态 +- 已存在有效发票(一单一票),不可重复申请 +- 开票金额超过订单 orderAmount(产品原售价) + +**特殊边界:** +- PUSHED 状态重新上传,状态自动降级为 ISSUED,需重新推送给客户 +- 发票列表 NONE Tab(未申请的 COMPLETED 订单)当前未实现,传 tab=NONE 返回空集合 + +--- + +## 10. 修改前后对比 + +**GET /v3/admin/order/{id}/invoices 出参字段补全:** + +| 字段名 | 变更前 | 变更后 | +|--------|--------|--------| +| fileUrl | 恒 null | ISSUED / PUSHED 时有值(OSS URL) | +| pdfName | 恒 null | 有值 | +| pdfSize | 恒 null | 有值(字节数) | +| invoiceNo | 恒 null | 有值(发票号码) | +| issuedAt | 恒 null | 有值(ISO 8601 时间戳) | +| issuedBy | 恒 null | 有值(开票人姓名) | +| requestedAt | 恒 null | 有值(申请时间) | +| requestedBy | 恒 null | 有值(申请人姓名) | + +--- + +## 11. 影响评估 / 回滚 + +- 破坏兼容性:GET /{id}/invoices 字段由 null 变为有值,前端对 null 的防护代码可安全保留。新增 5 个接口对旧版本无影响。 +- 前端同步上线:无强制要求,新接口按需接线,/invoices 字段补全向后兼容。 +- 回滚方案:回滚旧版本,新接口返回 404,/invoices 出参回退为 null,已写入 DB 的开票信息保留不丢失。 + +--- + +## 12. 注意事项 + +1. upload 接口仅上传,不绑定发票:返回的 fileUrl 须在调用 issue / reupload 时传入,否则 OSS 文件闲置。 +2. PUSHED 状态重新上传后降级为 ISSUED,前端应提示操作者需要重新推送给客户。 +3. 专票五项(taxNo / bankName / bankAccount / registAddress / registPhone)缺任一即报 581515。 +4. requestedBy 记录的是登录管理员真实姓名(来自网关注入的 X-Admin-RealName),不是用户姓名。 +5. amount、currentMonthIssuedAmount 均为 String 类型,前端须按字符串接收,避免 JS 大数精度丢失。 + +--- + +## 13. 关联 / 联系人 + +- Issue(财务开票): https://git.1814.love:8443/wx/HL/issues/4172 +- Issue(定制师代申请): https://git.1814.love:8443/wx/HL/issues/4173 +- PR(财务开票 #4179): https://git.1814.love:8443/wx/HL/pulls/4179 +- PR(定制师代申请 #4180): https://git.1814.love:8443/wx/HL/pulls/4180 +- 后端负责人: yaosutu