# 分销开票调整优化

> 09-24 调整需求：费用类型勾选分批开票、30/60/90 时间筛选、申请单维度记录与撤销、逾期赔付标识；含核心口径与页面-接口对应

- 类型：前端对接
- 分类：分销开票
- 端：分销小程序
- 后端服务：supplier
- 更新：2026-09-27
- 网页版：https://doc.xfun.fun/partner-invoice-adjust-api

对应 2026-09-24《繁花/优花速达开票调整优化》9 条需求的后端实现。全部为新增接口 + 既有接口加可选参数，旧版小程序不受影响。所有接口 POST + JSON，沿用花店登录态，前缀 /invoice。通用约定（响应结构、分页、时间与金额格式）与 上一版文档 完全一致，此处不重复。

## 接口一览

| 模块 | 路径 | 名称 | 状态 |
|----|----|----|----|
| 发票中心（新版） | [`/invoice/partner/type/list`](#invoice-partner-type-list) | 可开票类型汇总 | **新增** |
| 发票中心（新版） | [`/invoice/partner/flower/supplier/list`](#invoice-partner-flower-supplier-list) | 花材款供应商汇总 | **新增** |
| 发票中心（新版） | [`/invoice/partner/apply/preview`](#invoice-partner-apply-preview) | 一键开票预览 | **变更** +invoiceItemTypes、timeRange |
| 发票中心（新版） | [`/invoice/partner/apply/submit`](#invoice-partner-apply-submit) | 一键开票提交 | **变更** +invoiceItemTypes、timeRange |
| 开票记录（申请单维度） | [`/invoice/partner/application/list`](#invoice-partner-application-list) | 记录列表 | **新增** |
| 开票记录（申请单维度） | [`/invoice/partner/application/detail`](#invoice-partner-application-detail) | 申请单详情 | **新增** |
| 开票记录（申请单维度） | [`/invoice/partner/application/flower`](#invoice-partner-application-flower) | 花材款发票明细 | **新增** |
| 开票记录（申请单维度） | [`/invoice/partner/application/cancel`](#invoice-partner-application-cancel) | 撤销申请单 | **新增** |
| 既有接口 | `/invoice/partner/group/list` | 可开票分组列表 | **变更** +timeRange（可选） |
| 既有接口 | `/invoice/partner/group/order/list` | 组内订单列表 | **变更** +timeRange（可选） |

## 本版变化（相对 2026-09-07 版）

- **发票中心改「按费用类型」展示**：4 类各一行（供应链服务费 / 干线物流费 / 平台服务费 / 花材款），花材款聚合展示供应商家数与合计，不再按供应商平铺分组。数据源换成 [类型汇总接口](#invoice-partner-type-list)。
- **支持勾选费用类型分批开票**：预览 / 提交新增 `invoiceItemTypes`。金额按全量拆分固定，勾选只决定本次开哪些类型（见 [核心口径](#核心口径勾选与金额产品-2026-09-24-拍板)）。
- **新增时间筛选 `timeRange`**：30 / 60 / 90 天，按 **确认收货时间**。页面默认选中 30 天并 **显式传 30**；不传为「近 3 个月」旧口径（仅作旧版兼容，新版页面别用）。传其他值报错「时间筛选仅支持30/60/90天」。
- **开票记录改申请单维度**：一次申请一条记录，汇总状态新增「部分开票」。旧的按票据（request）平铺的记录列表不再是记录页数据源。
- **撤销改申请单级**：全部票据未开才可撤，任一已开（或已被百望受理）整单不可撤。不再提供单张票据撤销入口。
- **逾期补开标识**：逾期后商家补开的发票，已开票区仍带逾期标识与「已赔付百分之二」文案（赔付指花店已收到申请金额 2% 的寻梦币补偿）。
- 收据流程本次 **无变化**：收据不参与类型勾选，applyType=2 的「去开收据」流程照旧。

## 核心口径：勾选与金额（产品 2026-09-24 拍板）

> [!NOTE]
> **金额永远按全量拆分固定，勾选费用类型只决定本次开哪些类型的票，分批开总和不变。**

- 一笔订单的实付被拆成 4 项（花材款 / 供应链服务费 / 干线物流费 / 平台服务费），各项金额由后端全量算法一次算死，**相互独立、勾选不重算**。
- 例：可开票池共 100 元 = 花材款 70 + 物流 10 + 供应链 5 + 平台 15。只勾「花材款」提交 → 本次开 70 元；剩下 30 元留在池子里，下次勾其余类型再开，两次合计仍是 100。
- 所以前端 **底部合计 = 勾选行的 `amount` 直接相加**，不需要再调预览接口算勾选组合的金额（预览接口也支持传勾选，返回值一致）。
- 花材款只能 **整体勾选**，不能只选其中部分供应商。[供应商汇总页](#invoice-partner-flower-supplier-list)是纯查看的下钻页，与勾选状态无关。
- 未勾选任何类型时禁用提交按钮；全不勾传给后端会报「所选费用类型没有可开票金额」。

## 页面与接口对应（按原型）

| 原型页面 | 用到的接口 |
|----|----|
| 发票中心（类型勾选列表） | [类型汇总](#invoice-partner-type-list)渲染 4 行 + 勾选框；切时间筛选重新请求；底部合计前端累加勾选行 amount |
| 平台项行（供应链 / 物流 / 平台服务费）→ 订单列表 | 旧接口 `/partner/group/order/list`：传 `invoiceItemType` + `statusTab: 2` + 与列表页相同的 `timeRange`；**不传 supplierId**（更不能传字符串 "undefined"，会报消息解析错误）。返回订单数与金额和类型汇总行一致 |
| 花材款 → 供应商汇总页 | 点花材款行进入，[供应商汇总](#invoice-partner-flower-supplier-list)（带同样的 timeRange）；纯展示，返回不改变勾选 |
| 申请开票（勾选页直接提交，无确认页） | 选抬头 → [提交](#invoice-partner-apply-submit)（invoiceItemTypes + timeRange + invoiceTitleId）。[预览](#invoice-partner-apply-preview)在新版发票流程 **可不调**（已选金额=勾选行 amount 相加即准确值），想做提交前金额校验再调；收据流程（applyType=2）仍用预览 |
| 开票记录列表 | [记录列表](#invoice-partner-application-list)（分页）；行上展示汇总状态、逾期角标（hasOverdue） |
| 申请详情 | [申请单详情](#invoice-partner-application-detail)；平台三项行直接给 requestNo / invoiceUrl，花材款行进[花材款明细](#invoice-partner-application-flower) |
| 花材款发票明细 | [花材款明细](#invoice-partner-application-flower)三区（待开 / 已开 / 已逾期）；单张票所含订单用旧接口 `/partner/request/detail`（传 requestNo） |
| 撤销 | 列表 / 详情 `canCancel=true` 时展示按钮 → [撤销申请单](#invoice-partner-application-cancel) |

## 枚举

#### timeRange 时间筛选，按确认收货时间

| 值 | 含义 |
|----|----|
| 30 | 近 30 天（页面默认，显式传） |
| 60 | 近 60 天 |
| 90 | 近 90 天 |
| 不传 | 近 3 个月（仅旧版兼容） |
| 其他值 | 报错 |

#### invoiceItemType 费用类型，同旧版

0 花材款 · 1 供应链服务费 · 2 干线物流费 · 3 平台服务费

#### 申请单汇总状态 status 记录列表 / 详情头部

| 值  | 文案       | 含义                         |
|-----|------------|------------------------------|
| 1   | 待商家开票 | 所有票据都还没开             |
| 2   | 已开发票   | 所有票据都已开出             |
| 3   | 部分开票   | 已开了一部分，其余待开或逾期 |
| 4   | 已撤销     | 整单撤销                     |

#### 详情内类型行状态 typeItems\[\].status

| 值  | 文案     | 说明                               |
|-----|----------|------------------------------------|
| 1   | 未开发票 | —                                  |
| 2   | 已开发票 | 平台项此时 `invoiceUrl` 非空       |
| 3   | 部分开票 | 仅花材款可能出现（部分供应商已开） |
| 4   | 已逾期   | 该类型票据全部逾期未开             |

#### 逾期与赔付标识

- `hasOverdue`（列表 / 详情）：申请单里存在逾期票据，含「逾期后又补开」的。
- `overdueFlag`（花材款明细行）：已逾期区恒 true；**已开票区为 true 表示逾期补开**，行上要同时展示逾期标识与 `compensateDesc`。
- `compensateDesc`：逾期时固定「已赔付百分之二」，直接展示后端文案；未逾期为空串。

## 发票中心（新版） · 前缀 /invoice

### POST /invoice/partner/type/list · 可开票类型汇总

**新增**

新版发票中心的数据源。返回 4 类费用各一行（固定顺序：供应链服务费 → 干线物流费 → 平台服务费 → 花材款），金额为 0 的类型不返回。每行对应一个勾选框，勾选状态由前端维护。

#### 请求

| 字段        | 类型 | 必填 | 说明                                    |
|-------------|------|------|-----------------------------------------|
| `timeRange` | int  | 否   | 30 / 60 / 90；页面默认选 30 并显式传 30 |

#### 响应 data 为数组，可能为空（无可开票订单）

| 字段 | 类型 | 说明 |
|----|----|----|
| `invoiceItemType` | int | 0 花材款 · 1 供应链服务费 · 2 干线物流费 · 3 平台服务费 |
| `typeName` | string | 类型名，直接展示 |
| `amount` | decimal | 该类型可开票金额合计。勾选后底部合计 = 勾选行 amount 相加 |
| `orderCount` | int | 关联订单数（同类型跨供应商去重） |
| `supplierCount` | int | 可开票供应商家数；仅花材款 \> 0（如「共5家供应商」），平台项为 0 |

#### 示例

```text
// 请求
{ "timeRange": 30 }
// 响应
{
  "code": 0, "msg": "success",
  "data": [
    { "invoiceItemType": 1, "typeName": "供应链服务费", "amount": 48.00, "orderCount": 12, "supplierCount": 0 },
    { "invoiceItemType": 2, "typeName": "干线物流费", "amount": 96.50, "orderCount": 10, "supplierCount": 0 },
    { "invoiceItemType": 3, "typeName": "平台服务费", "amount": 63.50, "orderCount": 12, "supplierCount": 0 },
    { "invoiceItemType": 0, "typeName": "花材款", "amount": 1356.00, "orderCount": 12, "supplierCount": 5 }
  ]
}
```

### POST /invoice/partner/flower/supplier/list · 花材款供应商汇总

**新增**

点花材款行进入的下钻页：按供应商展示可开票金额与订单数。**纯查看**，返回列表页不改变勾选状态；花材款只能整体勾选，此页没有勾选框。传与列表页相同的 timeRange。

#### 请求

| 字段        | 类型 | 必填 | 说明                 |
|-------------|------|------|----------------------|
| `timeRange` | int  | 否   | 与类型汇总页保持一致 |

#### 响应 data 为数组

| 字段           | 类型    | 说明               |
|----------------|---------|--------------------|
| `supplierId`   | long    | 供应商 id          |
| `supplierName` | string  | 供应商名称         |
| `amount`       | decimal | 该供应商可开票金额 |
| `orderCount`   | int     | 关联订单数         |

#### 示例

```text
// 请求
{ "timeRange": 30 }
// 响应
{ "code": 0, "msg": "success",
  "data": [
    { "supplierId": 1021, "supplierName": "云南甲花卉", "amount": 656.00, "orderCount": 6 },
    { "supplierId": 1088, "supplierName": "斗南乙鲜花", "amount": 700.00, "orderCount": 7 }
  ] }
```

### POST /invoice/partner/apply/preview · 一键开票预览

**变更**：+invoiceItemTypes、timeRange

既有接口加两个可选参数，响应结构不变（见旧文档）。新版发票流程没有确认页，**可以不调**此接口（勾选合计前端累加即可）；保留给旧版一键流程、收据流程（applyType=2）、以及可选的提交前金额校验。

#### 请求 新增字段，其余不变

| 字段 | 类型 | 必填 | 说明 |
|----|----|----|----|
| `applyType` | int | 否 | 新版发票中心传 **1**（仅发票）；收据流程照旧传 2 |
| `invoiceItemTypes` | int\[\] | 否 | **新增** 勾选的费用类型，如 `[0, 2]`；不传 = 全部（旧版行为） |
| `timeRange` | int | 否 | **新增** 30 / 60 / 90，与列表页一致；不传 = 近 3 个月旧口径 |

#### 响应

- 结构同旧版：`totalAmount`（勾选类型合计）、`orderCount`（勾选类型去重订单数）、`supplierBreakdown`（只含勾选类型的行）。
- 勾选的类型都没有可开票金额时报错：「所选费用类型没有可开票金额」。

#### 示例

```text
// 请求：只勾了花材款和干线物流费，近30天
{ "applyType": 1, "invoiceItemTypes": [0, 2], "timeRange": 30 }
```

### POST /invoice/partner/apply/submit · 一键开票提交

**变更**：+invoiceItemTypes、timeRange

既有接口加同样两个可选参数。一次提交生成 **一个申请单**（applicationId），内部按勾选类型生成多张票据、各自独立开票。未勾的类型金额留在可开票池，之后可再次勾选提交（新申请单）。

#### 请求 新增字段，其余不变

| 字段 | 类型 | 必填 | 说明 |
|----|----|----|----|
| `invoiceTitleId` | long | 是 | 发票抬头 ID（同旧版） |
| `applyType` | int | 否 | 新版发票中心传 **1**；收据流程照旧传 2（收据不受勾选影响） |
| `invoiceItemTypes` | int\[\] | 否 | **新增** 与预览一致，务必传同一份勾选 |
| `timeRange` | int | 否 | **新增** 与预览一致 |

#### 响应 同旧版

```text
// 请求
{ "invoiceTitleId": 88, "applyType": 1, "invoiceItemTypes": [0, 2], "timeRange": 30 }
// 响应
{ "code": 0, "msg": "success", "data": { "applicationId": 3301, "receiptIds": [] } }
```

## 开票记录（申请单维度） · 前缀 /invoice

### POST /invoice/partner/application/list · 记录列表

**新增**

新版开票记录页数据源：一次申请一条记录，按申请时间倒序，分页。替代旧的按票据平铺的 `/partner/request/list`。

#### 请求

| 字段 | 类型 | 必填 | 说明 |
|----|----|----|----|
| `pageIndex` | int | 是 | 从 1 起 |
| `pageSize` | int | 是 | 建议 10 |
| `statusTab` | int | 否 | **新增** 三 Tab 方案：1 开票中（汇总状态为待商家开票或部分开票）2 已完成（已开发票）3 已逾期（存在逾期未开票据）；不传返回全部（含已撤销）。已逾期 Tab 下记录的 status 仍是 1 或 3，卡片上的逾期标识用 hasOverdue；逾期没开完的单同时出现在 Tab 1 和 3 |

#### 响应 data.records 每行

| 字段            | 类型     | 说明                                         |
|-----------------|----------|----------------------------------------------|
| `id`            | long     | 申请单 ID，后续详情 / 明细 / 撤销都传它      |
| `applicationNo` | string   | 申请编号                                     |
| `createTime`    | datetime | 申请时间                                     |
| `titleName`     | string   | 发票抬头，卡片直接展示                       |
| `taxNumber`     | string   | 税号，个人抬头可能为空                       |
| `totalAmount`   | decimal  | 汇总金额（撤销的申请单也保留原金额）         |
| `status`        | int      | 1 待商家开票 2 已开发票 3 部分开票 4 已撤销  |
| `statusDesc`    | string   | 状态文案，直接展示                           |
| `hasOverdue`    | boolean  | 存在逾期票据（含逾期补开），行上展示逾期角标 |
| `canCancel`     | boolean  | 可撤销：全部票据未开时 true                  |

#### 示例

```text
// 请求
{ "pageIndex": 1, "pageSize": 10, "statusTab": 1 }
// 响应
{ "code": 0, "msg": "success",
  "data": { "total": 2, "current": 1, "pages": 1, "records": [
    { "id": 3301, "applicationNo": "FP20260927001", "createTime": "2026-09-27 10:00:00",
      "totalAmount": 1452.50, "status": 3, "statusDesc": "部分开票", "hasOverdue": false, "canCancel": false },
    { "id": 3288, "applicationNo": "FP20260920007", "createTime": "2026-09-20 15:20:00",
      "totalAmount": 800.00, "status": 2, "statusDesc": "已开发票", "hasOverdue": true, "canCancel": false }
  ] } }
```

### POST /invoice/partner/application/detail · 申请单详情

**新增**

票据信息（抬头 / 税号 / 金额 / 时间）+ 按费用类型的发票明细行。平台三项每行就是一张票，直接给票据编号和发票 URL；花材款是聚合行（张数 = 供应商数），点击进[花材款明细页](#invoice-partner-application-flower)。

#### 请求

| 字段            | 类型 | 必填 | 说明          |
|-----------------|------|------|---------------|
| `applicationId` | long | 是   | 记录列表的 id |

#### 响应

| 字段 | 类型 | 说明 |
|----|----|----|
| `id / applicationNo / createTime / totalAmount / status / statusDesc / hasOverdue / canCancel` | — | 同记录列表行 |
| `titleName` | string | 发票抬头 |
| `taxNumber` | string | 税号 |
| `typeItems` | array | 发票明细，按费用类型一行 |
| `invoiceItemType` | int | 0 花材款 · 1 供应链服务费 · 2 干线物流费 · 3 平台服务费 |
| `typeName` | string | 类型名 |
| `amount` | decimal | 该类型申请金额 |
| `orderCount` | int | 关联订单数 |
| `status` | int | 1 未开发票 2 已开发票 3 部分开票 4 已逾期 |
| `statusDesc` | string | 状态文案 |
| `pendingCount` | int | 待开票张数。花材款 = 待开供应商数；平台项 0 或 1 |
| `invoicedCount` | int | 已开票张数，口径同上 |
| `requestNo` | string | 票据编号。**平台项**用它调旧接口 `/partner/request/detail` 看所含订单；**花材款为空**，走花材款明细页 |
| `invoiceUrl` | string | 发票文件 URL，平台项已开票时有值，预览 / 下载直接用 |

#### 示例

```text
// 请求
{ "applicationId": 3301 }
// 响应（截取）
{ "code": 0, "msg": "success",
  "data": {
    "id": 3301, "applicationNo": "FP20260927001", "titleName": "昆明某某花店", "taxNumber": "91530100XXXX",
    "createTime": "2026-09-27 10:00:00", "totalAmount": 1452.50,
    "status": 3, "statusDesc": "部分开票", "canCancel": false, "hasOverdue": false,
    "typeItems": [
      { "invoiceItemType": 2, "typeName": "干线物流费", "amount": 96.50, "orderCount": 10,
        "status": 2, "statusDesc": "已开发票", "pendingCount": 0, "invoicedCount": 1,
        "requestNo": "RQ20260927002", "invoiceUrl": "https://…/rq2.pdf" },
      { "invoiceItemType": 0, "typeName": "花材款", "amount": 1356.00, "orderCount": 12,
        "status": 3, "statusDesc": "部分开票", "pendingCount": 3, "invoicedCount": 2,
        "requestNo": null, "invoiceUrl": null }
    ] } }
```

### POST /invoice/partner/application/flower · 花材款发票明细

**新增**

申请详情里点花材款行进入。按供应商一张票，分三区：待开票 / 已开票（含逾期补开）/ 已逾期。单张票所含订单用旧接口 `/partner/request/detail` 传 requestNo。

#### 请求

| 字段            | 类型 | 必填 | 说明   |
|-----------------|------|------|--------|
| `applicationId` | long | 是   | 同详情 |

#### 响应

| 字段 | 类型 | 说明 |
|----|----|----|
| `totalAmount` | decimal | 花材款合计金额 |
| `supplierCount` | int | 供应商家数（= 票据张数） |
| `pendingList / invoicedList / overdueList` | array | 三个分区，行结构相同： |
| `requestNo` | string | 票据编号，下钻票据详情用 |
| `supplierId` / `supplierName` | — | 供应商 |
| `amount` | decimal | 该票金额 |
| `orderCount` | int | 关联订单数 |
| `invoiceUrl` | string | 发票文件 URL，已开票区有值 |
| `overdueFlag` | boolean | 已逾期区恒 true；**已开票区 true = 逾期补开**，行上带逾期标识 |
| `compensateDesc` | string | 逾期时「已赔付百分之二」，直接展示；未逾期为空串 |

#### 示例

```text
// 请求
{ "applicationId": 3301 }
// 响应（截取）
{ "code": 0, "msg": "success",
  "data": { "totalAmount": 1356.00, "supplierCount": 5,
    "pendingList": [
      { "requestNo": "RQ20260927005", "supplierId": 1090, "supplierName": "丙花卉", "amount": 300.00,
        "orderCount": 3, "invoiceUrl": null, "overdueFlag": false, "compensateDesc": "" } ],
    "invoicedList": [
      { "requestNo": "RQ20260927003", "supplierId": 1021, "supplierName": "云南甲花卉", "amount": 656.00,
        "orderCount": 6, "invoiceUrl": "https://…/rq3.pdf", "overdueFlag": false, "compensateDesc": "" },
      { "requestNo": "RQ20260910001", "supplierId": 1088, "supplierName": "斗南乙鲜花", "amount": 400.00,
        "orderCount": 4, "invoiceUrl": "https://…/rq1.pdf", "overdueFlag": true, "compensateDesc": "已赔付百分之二" } ],
    "overdueList": [
      { "requestNo": "RQ20260901009", "supplierId": 1105, "supplierName": "丁园艺", "amount": 0.00,
        "orderCount": 1, "invoiceUrl": null, "overdueFlag": true, "compensateDesc": "已赔付百分之二" } ] } }
```

### POST /invoice/partner/application/cancel · 撤销申请单

**新增**

申请单级撤销：整单所有票据都未开（且未被开票平台受理）才可撤，任一已开整单不可撤。前端只在 `canCancel=true` 时展示按钮，仍需处理并发导致的报错。撤销后金额回到可开票池，可重新勾选申请。

#### 请求

| 字段            | 类型 | 必填 | 说明 |
|-----------------|------|------|------|
| `applicationId` | long | 是   | —    |

#### 响应与错误文案

- 成功：`{ "code": 0, "data": true }`，刷新列表即可（该单变「已撤销」，金额保留展示）。
- 「申请单不存在」——id 错误或不属于当前花店。
- 「当前状态不可撤销」——已撤销 / 已完成的单。
- 「已有票据开出或逾期，无法撤销申请」「已有票据发起开票，无法撤销申请」——期间商家开了票或百望已受理，提示后刷新详情。
- 「票据状态已变化，请刷新后重试」——并发冲突，重试即可。

## 旧接口的去留

| 旧接口 | 新版怎么用 |
|----|----|
| `/partner/group/list`、`/partner/group/order/list` | 保留并支持 `timeRange`（可选）。新版里 `group/order/list` 就是平台项行的订单下钻：`invoiceItemType` + `statusTab: 2` + `timeRange`，不传 supplierId。**timeRange 必须与列表页一致**，不传会落到近 3 个月旧口径，订单数对不上 |
| `/partner/request/detail` | **继续使用**：单张票据的所含订单与每单金额，从申请详情（平台项 requestNo）或花材款明细（每行 requestNo）下钻 |
| `/partner/request/list` | 保留但新版记录页不再使用，改用 [/partner/application/list](#invoice-partner-application-list) |
| `/partner/request/cancel` | 保留但新版不再暴露单张撤销入口，撤销一律走 [/partner/application/cancel](#invoice-partner-application-cancel) |
| 收据三接口 `/receipt/application/*`、可开收据 `/partner/receipt/order/list` | 不变，见旧文档 |

> [!NOTE]
> 联调环境：测试服已部署（bulky-test/purchaser）。所有新接口未登录统一返回 `{ "code": 1, "msg": "当前未登录" }`，请带花店登录态调试；接口行为与文档不符时以 knife4j 在线文档为准并反馈后端。

对应后端分支 xiaofun/xm-bulky-mall-suppiler · 2026-09-24 需求，09-24 已上测（bulky-test/purchaser）。旧版对接文档见 [partner-invoice-api](/partner-invoice-api)。
