# 供应商申诉优化

> 首页本月扣款、我的处罚统计/明细/详情（处罚+退款支出+缺货赔付）、质检问题汇总；缺货赔付申诉 complainType=6；运营后台商品申诉 Tab 多类型列表与审核

- 类型：前端对接
- 分类：供应商
- 端：供应商小程序 / 运营后台
- 后端服务：supplier / admin
- 状态：已上测
- 测试分支：supplier/test · bulky-test/admin
- 需求单：XYLZ-3562
- 更新：2026-10-29
- 网页版：https://doc.xfun.fun/supplier-punish-center-api

> [!NOTE]
> XYLZ-3640「供应商申诉调整」（首页质检问题、换货扎数、场次卡口径与跳转、二级类目编码、状态标签、商品全称、客服 ID、凭证选填）的集中说明见 [供应商申诉调整 · 前端对接](/supplier-appeal-adjust-api)。

## 总览

| 端 | 接口 | 用途 |
|----|----|----|
| 供应商端 xm-bulky-supplier | `POST /home/supplierMoney` | 首页「我的处罚 —— 本月扣款」：字段 `monthPunishAmount` |
| 供应商端 xm-bulky-supplier | `POST /home/supplierPunishCount` | 首页「本月履约情况 —— 质检问题汇总」：字段 `qualityIssueNum` |
| 供应商端 xm-bulky-supplier | `POST /punish/center/summary` | 我的处罚顶部统计、三大类卡、二级类目卡、「全部类型」下拉 |
| 供应商端 xm-bulky-supplier | `POST /punish/center/list` | 我的处罚 - 处罚明细（筛选 + 分页） |
| 供应商端 xm-bulky-supplier | `POST /punish/center/detail` | 处罚详情页 |
| 供应商端 xm-bulky-supplier | `POST /home/qualityIssue/summary` | 质检问题汇总页（按月 / 按场次汇总 + 场次卡） |
| 供应商端 xm-bulky-supplier | `POST /comment/complain` | 提交申诉：支持处罚（3）、退款支出（2）、缺货赔付（6），凭证选填、申诉理由必填 |
| 供应商端 xm-bulky-supplier | `POST /comment/complain/cancel` | 撤回申诉（仅处罚申诉） |
| 运营后台 xm-bulky-admin | `POST /order/complain/list` | 「商品申诉」Tab 传 `complainTypeList: [1,2,6]`；返回来源标签、缺货赔付信息、可退金额 |
| 运营后台 xm-bulky-admin | `POST /order/complain/check` | 支持审核缺货赔付申诉（6）；2/6 同意金额上限、驳回必填原因 |

- `/home/supplierPunishCount` 除 `qualityIssueNum`/ `qualityLackNum`/ `qualityReturnNum`/ `qualityExchangeNum` 外的字段，以及 `/home/supplierMoney/detail`、`/home/supplierMoney/punish/detail` 供旧版小程序使用；「我的处罚」相关页面使用上表接口。
- 质检问题汇总页 **不提供处罚/申诉操作**，统一在「我的处罚」处理。

## 通用约定

- 全部 `POST` + JSON，返回 `{ "code": 0, "msg": "success", "data": … }`；`code != 0` 时 `msg` 直接 toast。
- 时间格式 `yyyy-MM-dd HH:mm:ss`；金额均为保留 2 位小数的数字。
- **统计方式**：两个统计页顶部都有「按月 / 按场次」切换，对应入参 `statType`（1 按月，默认；2 按场次），汇总数字按所选条件统计。
- **时间范围**：`month`（`2026-10`）与 `roundNo`（场次 `2026-10-14`），**都不传 = 当月**；传了 `roundNo` 时月份取该场次所在月。`statType=2` 且不传 `roundNo` 时，后端取所选月份最近一个场次，并在出参 `roundNo` 返回。按 **场次日期**归属月份，不是按处罚时间。
- **倒计时**：列表/详情/统计都返回 `serverTime`，剩余时间 = `appealDeadline - serverTime`，本地时钟只用于每秒递减，避免手机时间不准。
- **处罚项**：「我的处罚」把三种来源统一成一条处罚项，用 `sourceType + sourceId` 唯一标识（列表 key 用 `itemKey`）。

| sourceType | 来源 | sourceId 是 | 提交申诉用的 complainType | 申诉规则 |
|----|----|----|----|----|
| 1 | 处罚（质检、迟到、配货错误等） | 处罚 ID | 3 | 处理后 48h 内首诉；首诉驳回后 24h 内可再诉一次；申诉中可撤回 |
| 2 | 退款支出（售后单供应商承担） | 售后单 ID | 2 | 售后通过后 48h 内，只能申诉 1 次，不可撤回 |
| 3 | 缺货赔付（供应商承担） | 缺货赔付 ID | 6 | 扣款后 48h 内，只能申诉 1 次，不可撤回 |

是否可申诉、截止时间都由后端算好（`canAppeal` / `appealDeadline`），前端不用自己判断规则。

## 页面与接口对应

| 原型 | 页面元素 | 接口 / 字段 |
|----|----|----|
| 02 首页 | 「我的处罚 —— 本月扣款 ¥320.00 \>」 | 首页已调用的 `/home/supplierMoney` 字段 `monthPunishAmount`（不用另调接口）；点击进入我的处罚 |
| 02 首页 | 「本月履约情况 —— 质检问题汇总 16 扎 \>」 | 首页已调用的 `/home/supplierPunishCount` 字段 `qualityIssueNum`（不用另调接口），与质检问题汇总页按月当月「缺货 + 退货 + 换货」扎数一致；点击进入质检问题汇总 |
| 01 质检问题汇总 | 「按月 / 按场次」切换 | ④ 入参 `statType`：1 按月 / 2 按场次 |
| 01 质检问题汇总 | 顶部下拉：按月为月份（默认当月），按场次为场次 | 按月传 `month`；按场次传 `roundNo`（选项用 `roundOptions`，首次进入不传，后端取当月最近场次） |
| 01 质检问题汇总 | 汇总卡：售卖 N 扎 / 缺货 / 退货 / 换货 / 共 N 个场次 | `saleNum / lackNum / returnNum / exchangeNum / roundCount`，按所选月份或场次统计 |
| 01 质检问题汇总 | 「场次质检汇总」场次下拉（按月时） | `roundOptions`；选中后再调 ④ 带 `statType=1, roundNo`，只返回该场次卡片（汇总仍为整月） |
| 01 质检问题汇总 | 场次卡（商品行默认 3 行，「展开更多(n)」） | `rounds[]`，n = `items.length - 3` |
| 01 质检问题汇总 | 「查看处罚明细 —— 共 5 条处罚 已确认 · ¥120.00 \>」 | `punishCount / punishAmount`；跳我的处罚质检 Tab，summary 带 `statType:2, roundNo`，list 带 `statType:2, roundNo, category:1` |
| 03 我的处罚 | 「按月 / 按场次」切换 + 月份 / 场次下拉 | ① 入参 `statType / month / roundNo`；场次下拉用 ① 出参 `roundOptions`；② 明细传同样三个值 |
| 03 我的处罚 | 实际处罚、共 N 笔、总处罚、申诉成功 | ① `actualAmount / totalCount / totalAmount / appealSuccessAmount`，按所选月份或场次统计 |
| 03 我的处罚 | 三大类卡（质检/迟到/其他） | ① `categories[].actualAmount / totalCount` |
| 03 我的处罚 | Tab 下二级类目卡（类型统计·实际扣款） | ① `categories[].subCategories[]` |
| 03 我的处罚 | 处罚明细 + 三个筛选（全部类型 / 处罚状态 / 申诉状态） | ② `/punish/center/list` |
| 04 处罚详情 | 全部字段 + 申诉记录 + 去申诉 | ③ `/punish/center/detail` |
| 申诉页 | 提交申诉 / 撤回 | ⑤ `/comment/complain`、`/comment/complain/cancel` |

## 枚举

#### 类目（category → subCategory → 三级类型）

缺货/退货扎数按类目类型集合统计，只统计质检环节处罚（punish_link=1），人工下发、代卖等其他环节的处罚不计入；条件为 pid=0、data_status=0、handle_status IN (0,1)。换货扎数独立取 exchange_log，19、22 不计扎数。

| category | subCategory | 三级类型（sourceType / punishType：名称） | 卡片标题 title |
|----|----|----|----|
| 1 质检处罚 | 11 缺货 | 1/1 缺货处罚、1/6 缺货补货、1/9 缺货不补 | 缺货处罚 |
| 1 质检处罚 | 12 退货 | 1/7 退货不补、1/8 退货补货、1/11 退货不补转代卖、1/12 退货补货转代卖 | 退货处罚 |
| 1 质检处罚 | 14 降级 | 1/13 降1级、1/15 降2级 | 降级处罚 |
| 1 质检处罚 | 15 换货配货 | 1/25 换货未换、1/18 换货、1/19 配货错误处罚、1/22 先扫码后配货处罚 | 换货配货处罚 |
| 2 迟到处罚 | 21 配货迟到 | 1/14 迟到配货处罚、1/23 代配送货迟到处罚 | 配货迟到处罚 |
| 2 迟到处罚 | 22 换货迟到 | 1/20 换货迟到 | 换货迟到处罚 |
| 2 迟到处罚 | 23 迟到处罚 | 1/3 迟到处罚 | 迟到处罚 |
| 3 其他处罚 | 32 售后处罚 | 2/null 退款支出、3/null 缺货赔付 | 售后处罚 |
| 3 其他处罚 | 34 其他处罚 | 1/2 未打勾处罚、1/4 其他处罚、1/16 退款赠花、1/17 超时转代卖及其他未归类类型；以及非质检环节的 1/1、6、7、8、9、11、12、13、15 | 其他处罚 |

**质检类型按处罚环节归类**：处罚类型 1、6、7、8、9、11、12、13、15 在质检环节（`punish_link=1`）和其他环节（人工下发、代卖等）都会出现。只有质检环节的按上表归入「质检处罚」下的缺货 / 退货 / 降级；其他环节的同类型处罚统一归「其他处罚 · 其他处罚（34）」，明细、统计、二级类目卡、`typeOptions` 同一口径，质检问题汇总的缺货 / 退货扎数也只算质检环节。前端不用判断，按接口返回的 `category` / `subCategory` 展示即可。

二级类目 `subCategory` 有效编码仅以上 9 个，按表中顺序下发。以上树由 ① 的 `typeOptions` 下发，前端不要写死；「售后处罚」被后台开关关闭时，`typeOptions` 和 `subCategories` 里都不会出现 32。

#### 状态

| 字段 | 取值 | 说明 |
|----|----|----|
| `deductStatus` 扣款状态 | 1 待扣款 / 2 已扣款 / 3 已退回 | 待扣款 = 质检处罚待运营审核，**不计入**统计金额、不可申诉；已退回 = 申诉成功有退款（含部分退回） |
| `appealStatus` 申诉状态 | 0 未申诉 / 1 申诉中 / 2 申诉通过 / 3 申诉驳回 | 取最新一条未撤回的申诉 |

## 供应商端接口

### POST /home/supplierMoney

首页金额接口，其中 `monthPunishAmount` 用于「本月履约情况 —— 我的处罚 本月扣款 ¥320.00」。

| 字段 | 类型 | 说明 |
|----|----|----|
| `monthPunishAmount` | number | 本月扣款 = 我的处罚当月实际处罚，与 `/punish/center/summary` 按月不传月份时的 `actualAmount` 一致（不含待扣款）。**计算失败时为 null**，前端显示「--」或隐藏金额，不影响首页其他数据 |

```text
// 响应 data（仅列出本页相关字段）
{ "dayMoney": 126.90, "...": "...", "monthPunishAmount": 320.00 }
```

### POST /home/supplierPunishCount

首页「本月履约情况 —— 质检问题汇总 N 扎」 **读 `qualityIssueNum`**。四个字段固定统计当月，与请求里的 `startRoundNo`/ `endRoundNo` 无关；其余字段（`stockOut`、`salesReturnNum` 等）口径不同，首页卡片不要用。

| 字段 | 类型 | 说明 |
|----|----|----|
| `qualityIssueNum` | int | 当月质检问题扎数 = `qualityLackNum` + `qualityReturnNum` + `qualityExchangeNum`，与 `/home/qualityIssue/summary` 按月（`statType=1`、不传月份）的 `lackNum + returnNum + exchangeNum` 一致。**计算失败时为 null**，前端显示「--」，不影响首页其他数据 |
| `qualityLackNum` | int | 当月质检缺货扎数，= 质检汇总页按月 `lackNum` |
| `qualityExchangeNum` | int | 当月换货要求扎数，等于汇总页 exchangeNum，包含已完成与未完成换货 |
| `qualityReturnNum` | int | 当月质检退货扎数，= 质检汇总页按月 `returnNum` |

```text
// 响应 data（仅列出本页相关字段）
{ "stockOut": 3, "...": "...", "qualityIssueNum": 19, "qualityLackNum": 8, "qualityReturnNum": 8, "qualityExchangeNum": 3 }
```

### POST /punish/center/summary

我的处罚统计，按月或按场次。首页「本月扣款」取 `/home/supplierMoney` 的 `monthPunishAmount`，见接口 `POST /home/supplierMoney`。

#### 请求参数

| 字段 | 类型 | 必填 | 说明 |
|----|----|----|----|
| `statType` | int | 否 | 统计方式：1 按月（默认）/ 2 按场次 |
| `month` | string | 否 | 统计月份 `2026-10`，不传 = 当月 |
| `roundNo` | string | 否 | 场次 `2026-10-14`，传了只统计该场次（从质检汇总页跳转时也传它）；`statType=2` 不传时取所选月份最近一个有处罚的场次 |

#### 响应 data

| 字段 | 类型 | 说明 |
|----|----|----|
| `statType` | int | 实际统计方式（传了 `roundNo` 即为 2） |
| `month / startRoundNo / endRoundNo` | string | 实际统计的月份与场次区间（含）；按场次时起止都为该场次 |
| `roundNo` | string | 按场次时实际统计的场次；所选月份无处罚时为 null |
| `roundOptions[]` | array | 所选月份有处罚的场次（倒序），按场次时的场次下拉用 |
| `actualAmount` | number | **实际处罚**（顶部大数字、首页本月扣款）= 总处罚 - 申诉成功 |
| `totalAmount` | number | 总处罚 |
| `appealSuccessAmount` | number | 申诉成功（已退回金额） |
| `totalCount` | int | 共 N 笔 |
| `pendingAmount / pendingCount` | number / int | 待扣款金额/笔数，**不含在上面四个数里**，需要时可单独展示 |
| `categories[]` | array | 固定 3 个一级类目（0 也返回）：`category, categoryName, actualAmount, totalAmount, appealSuccessAmount, totalCount, subCategories[]` |
| `categories[].subCategories[]` | array | 该一级下全部二级（0 也返回）：`subCategory, subCategoryName` + 同样 4 个统计字段 |
| `typeOptions[]` | array | 「全部类型」下拉树：`category, categoryName, subCategories[{subCategory, subCategoryName, types[{sourceType, punishType, name}]}]` |
| `serverTime` | string | 服务器时间 |

#### 示例

```text
// 请求：按月
{ "statType": 1, "month": "2026-10" }
// 请求：按场次（首次切换不传 roundNo，后端取最近场次）
{ "statType": 2, "roundNo": "2026-10-14" }
// 响应 data（按月）
{
  "statType": 1, "month": "2026-10", "roundNo": null, "roundOptions": ["2026-10-14", "2026-10-12"],
  "startRoundNo": "2026-10-01", "endRoundNo": "2026-10-31",
  "actualAmount": 320.00, "totalAmount": 350.00, "appealSuccessAmount": 30.00, "totalCount": 12,
  "pendingAmount": 0.00, "pendingCount": 0,
  "categories": [{
    "category": 1, "categoryName": "质检处罚",
    "actualAmount": 240.00, "totalAmount": 260.00, "appealSuccessAmount": 20.00, "totalCount": 9,
    "subCategories": [
      { "subCategory": 11, "subCategoryName": "缺货", "actualAmount": 60.00, "totalAmount": 60.00, "appealSuccessAmount": 0.00, "totalCount": 3 },
      { "subCategory": 14, "subCategoryName": "降级", "actualAmount": 40.00, "totalAmount": 40.00, "appealSuccessAmount": 0.00, "totalCount": 2 }
    ]
  }],
  "typeOptions": [{ "category": 1, "categoryName": "质检处罚", "subCategories": [
    { "subCategory": 11, "subCategoryName": "缺货", "types": [
      { "sourceType": 1, "punishType": 6, "name": "缺货补货" },
      { "sourceType": 1, "punishType": 9, "name": "缺货不补" } ] } ] }],
  "serverTime": "2026-10-21 10:00:00"
}
```

### POST /punish/center/list

处罚明细。按处罚时间倒序。

#### 请求参数（包含 ① 的 statType / month / roundNo）

| 字段 | 类型 | 必填 | 说明 / 对应页面操作 |
|----|----|----|----|
| `statType / month / roundNo` | int / string | 否 | 同 ①，与统计取同一范围；按场次时建议传 ① 返回的 `roundNo` |
| `category` | int | 否 | Tab：不传 = 全部，1 质检 / 2 迟到 / 3 其他 |
| `subCategory` | int | 否 | 点击二级类目卡 |
| `sourceType` + `punishType` | int | 否 | 「全部类型」下拉选中的三级类型，原样回传 `typeOptions` 里的两个值（售后两项 `punishType` 为 null） |
| `deductStatus` | int | 否 | 「处罚状态」下拉：1 待扣款 / 2 已扣款 / 3 已退回 |
| `appealStatus` | int | 否 | 「申诉状态」下拉：0 未申诉 / 1 申诉中 / 2 申诉通过 / 3 申诉驳回 |
| `pageIndex` | int | 否 | 默认 1 |
| `pageSize` | int | 否 | 默认 20，最大 50 |

#### 响应 data

`{ total, records[], serverTime }`，`records[]` 字段：

| 字段 | 类型 | 说明 |
|----|----|----|
| `itemKey` | string | 列表 key，`sourceType_sourceId` |
| `sourceType / sourceId` | int / long | 来源与来源 ID，进详情、提交申诉都用它 |
| `category / categoryName / subCategory / subCategoryName` |  | 类目 |
| `punishType / typeName` |  | 三级类型（售后处罚 punishType 为 null） |
| `title` | string | 卡片标题 = 二级名 + 处罚，如「退货处罚」「换货配货处罚」「迟到处罚」「售后处罚」 |
| `roundNo / punishTime` | string | 场次 / 处罚时间 |
| `productName / rankNum / num / orderItemUrl` |  | 商品信息：名称、等级（1~5 = A~E）、扎数、图片；可能为空 |
| `skuDesc / productFullName` | string | 规格属性描述 / 商品名 + A~E级 + 属性值；属性按原顺序用空格拼接，忽略规格中的等级和包装 JSON，例如高原红A级不卷边。质检汇总商品行同样返回 |
| `statusTag / statusTagName` | int / string | 统一展示状态，见状态标签表 |
| `reason` | string | 处罚描述（行名显示「处罚描述」） |
| `orderSn` | string | 关联单号（可复制） |
| `punishAmount` | number | 原处罚金额（卡片金额） |
| `returnedAmount` | number | 已退回金额 |
| `actualAmount` | number | 实际处罚 = 原金额 - 已退回 |
| `deductStatus / deductStatusName` |  | 扣款状态 |
| `appealStatus / appealStatusName` |  | 申诉状态 |
| `complainType` | int | 提交申诉时传给 `/comment/complain` 的值（3 / 2 / 6） |
| `canAppeal` | bool | 当前能否申诉（含再次申诉） |
| `nextAppealNo` | int | 可申诉时为第几次申诉（1 首诉 / 2 二诉） |
| `appealDeadline` | string | 本轮申诉截止时间（倒计时用）；已申诉过的售后处罚为 null |
| `canCancelAppeal` | bool | 能否撤回申诉（仅处罚申诉中） |
| `latestComplainId` | long | 最新一条申诉 ID，撤回时用 |
| `latestComplain` | object | 最新一条申诉：`reason, pictures, status, operationContent`（驳回原因/审核说明） `, returnMoney, operatorId`（处理客服 ID，0 或 null 显示「系统」） `, createTime, handleTime` 等；`operatorName` 恒为 null |

#### 示例

```text
// 请求
{ "statType": 1, "month": "2026-10", "category": 1, "sourceType": 1, "punishType": 7,
  "deductStatus": null, "appealStatus": null, "pageIndex": 1, "pageSize": 20 }
// 响应 data
{ "total": 4, "serverTime": "2026-10-21 10:00:00", "records": [{
    "itemKey": "1_922652537", "sourceType": 1, "sourceId": 922652537,
    "category": 1, "categoryName": "质检处罚", "subCategory": 12, "subCategoryName": "退货",
    "punishType": 7, "typeName": "退货不补", "title": "退货处罚",
    "roundNo": "2026-10-09", "punishTime": "2026-10-09 17:16:00",
    "productName": "卡罗拉", "rankNum": 2, "skuDesc": "中熟", "productFullName": "卡罗拉B级中熟",
    "num": 5, "orderItemUrl": "https://…",
    "reason": "花材品质不达标", "orderSn": "T2026100910033425",
    "punishAmount": 45.00, "returnedAmount": 0.00, "actualAmount": 45.00,
    "deductStatus": 2, "deductStatusName": "已扣款", "appealStatus": 0, "appealStatusName": "未申诉",
    "statusTag": 1, "statusTagName": "待申诉",
    "complainType": 3, "canAppeal": true, "nextAppealNo": 1, "appealDeadline": "2026-10-21 17:16:00",
    "canCancelAppeal": false, "latestComplainId": null, "latestComplain": null }] }
```

### POST /punish/center/detail

#### 请求参数

| 字段         | 类型 | 必填 | 说明                  |
|--------------|------|------|-----------------------|
| `sourceType` | int  | 是   | 列表项的 `sourceType` |
| `id`         | long | 是   | 列表项的 `sourceId`   |

#### 响应 data = 列表项全部字段 + 以下字段

| 字段 | 说明 | 原型位置 |
|----|----|----|
| `sourceId` | 处罚编号 | 处罚编号 |
| `deductStatusName` | 顶部标签（待扣款/已扣款/已退回） | 扣款状态标签 |
| `title` | 处罚判定 | 处罚判定 |
| `roundNo` | 场次 | 场次 |
| `shelfDisplay` / `zoneShelf` | 货架号，如 `#07-167`（仅处罚有） | 货架号 |
| `zoneShelfList` | 换货迟到处罚涉及的多个货位号 | 货架号（多个） |
| `num` / `supplierPrice` / `punishProportion` | 处罚数量 / 供货价 / 处罚比例（0-100，%） | 处罚数量、供货价、处罚比例 |
| `reason` | 处罚描述 | 处罚描述 |
| `remark` | 处罚备注（售后为售后单备注） | 处罚备注 |
| `evidencePics` | 相关素材图片：处罚为 `pic_url`（与旧处罚详情 `picUrl` 同格式），售后为买家图片 `pictures` | 相关素材 |
| `platformPics` | 售后平台补充图片（仅退款支出） | 相关素材 |
| `evidenceVideos` | 售后视频（仅退款支出） | 相关素材 |
| `ruleDesc` | 处罚规则说明，为空不展示 | — |
| `sourceDesc` | 处罚来源，如「质检处罚」「系统自动处罚」「售后处罚」「售后单」「缺货赔付（订单 T…）」，不含姓名，为空不展示 | — |
| `operatorId` / `enterTime` | 下发处罚人 ID（质检处罚为质检员）/ 入场时间；显示「客服：ID」，0 或 null 显示「系统」；`operatorName` 恒为 null | 客服 ID |
| `complainHistory[]` | 全部申诉记录，按提交时间正序，`appealNo` 为展示序号；含已撤回记录（`delTime` 非空 = 已撤回）；处理人显示 `operatorId`（「客服：ID」，0 或 null 显示「系统」），`operatorName` 恒为 null | 处罚申诉 - 申诉记录（空数组显示「暂无申诉记录」） |
| `serverTime` | 服务器时间 | 倒计时 |

处罚不存在、不属于本人、已被运营直接撤销、已取消、处罚金额为 0，或为城市仓处罚、供应商拒不开票处罚时返回失败「处罚不存在」。

### POST /home/qualityIssue/summary

#### 请求参数

| 字段 | 类型 | 必填 | 说明 |
|----|----|----|----|
| `statType` | int | 否 | 统计方式：1 按月（默认）/ 2 按场次 |
| `month` | string | 否 | 不传 = 当月；传了 `roundNo` 时取场次所在月 |
| `roundNo` | string | 否 | 按月：「场次质检汇总」下拉选中值，`rounds` 只含该场次，汇总仍为整月；按场次：所选场次，汇总与 `rounds` 都只算该场次，不传取当月最近场次 |

#### 响应 data

| 字段 | 说明 |
|----|----|
| `statType` | 实际统计方式 |
| `month / roundNo` | 统计月份 / 按场次时实际统计的场次（当月无场次时为 null，按月时为 null） |
| `roundCount` | 统计范围内场次数（按场次为 1 或 0） |
| `saleNum / lackNum / returnNum / exchangeNum` | 统计范围内售卖扎数 / 缺货扎数 / 退货扎数 / 换货扎数（缺货/退货为质检数量，含处罚金额为 0 的） |
| `roundOptions[]` | 所选月份内有售卖、缺货、退货或换货的场次（倒序），场次下拉用 |
| `rounds[]` | 场次卡（倒序）：`roundNo, productKinds`（共 X 种商品） `, saleNum`（售卖 Y 扎） `, lackNum, returnNum, exchangeNum`（红标「缺货a扎·退货b扎·换货c扎」） `, punishCount, punishAmount`（共 N 条处罚 已确认 · ¥X：条数为该场次质检处罚，含待扣款；金额仅为已扣款/已退回项的实际处罚，不含 0 元处罚） `, items[]` |
| `rounds[].items[]` | 商品行，按售卖 → 换货 → 退货 → 缺货扎数倒序，全部返回由前端折叠；商品列展示 `productFullName`：`productId, productName, rankNum, skuDesc, productFullName, saleNum, lackNum, returnNum, exchangeNum` |

```text
// 请求：按月
{ "statType": 1, "month": "2026-10" }
// 请求：按场次
{ "statType": 2, "roundNo": "2026-10-14" }
// 响应 data（按月）
{
  "statType": 1, "month": "2026-10", "roundNo": null, "roundCount": 2, "saleNum": 50, "lackNum": 2, "returnNum": 5, "exchangeNum": 3,
  "roundOptions": ["2026-10-14", "2026-10-12"],
  "rounds": [{
    "roundNo": "2026-10-14", "productKinds": 5, "saleNum": 32, "lackNum": 2, "returnNum": 3, "exchangeNum": 2,
    "punishCount": 5, "punishAmount": 120.00,
    "items": [{ "productId": 1, "productName": "高原红", "rankNum": 1, "saleNum": 8, "lackNum": 1, "returnNum": 1, "exchangeNum": 2, "skuDesc": "卷边", "productFullName": "高原红A级卷边" }]
  }]
}
```

- 「查看处罚明细」跳我的处罚并定位质检 Tab：summary 传 `{statType:2, roundNo}`，list 传 `{statType:2, roundNo, category:1}`。列表 total 等于场次卡 punishCount，已扣款/已退回项 actualAmount 之和等于 punishAmount。
- 缺货/退货扎数按类目类型集合统计有效处罚，只统计质检环节处罚（punish_link=1），人工下发、代卖等其他环节的处罚不计入；申诉成功的不计入。缺货类型为 1、6、9；退货类型为 7、8、11、12；与二级类目 11/12 一致。换货独立取 exchange_log 的 SUM(num)，过滤本人、场次区间、status=0、num\>0，包含扫码及批量换货；type 18、19、22、25 的处罚数量不参与扎数统计。只有换货的场次也进入 roundOptions。

### POST /comment/complain

提交申诉。成功后刷新列表/详情/统计。

| 字段 | 类型 | 必填 | 说明 |
|----|----|----|----|
| `id` | long | 是 | 处罚项的 `sourceId` |
| `complainType` | int | 是 | 处罚项的 `complainType`（3 处罚 / 2 退款支出 / 6 缺货赔付） |
| `reason` | string | 是 | 申诉理由，≤255 字 |
| `picUrl` | string | 否 | 申诉凭证（图片/视频地址 JSON 数组字符串），三种类型都 **选填**，最多 6 个（前端限制）；不传、空串或 `[]` 均可提交，后端统一存 `[]` |
| `appealDesc` | string | 否 | 诉求说明 |

#### 缺货赔付申诉（complainType=6）可能的失败提示

- 缺货赔付记录不存在 / 缺货赔付未扣款，不能申诉 / 缺货赔付金额异常
- 缺货赔付扣款后72小时内才可以申诉
- 同条缺货赔付记录最多申诉1次

### POST /comment/complain/cancel

撤回申诉：`{ "id": latestComplainId }`，仅 `canCancelAppeal = true` 时显示按钮（只有处罚申诉支持撤回）。

## 列表/详情展示规则

客服只展示 ID：详情 `operatorId`（下发人）、列表 `latestComplain.operatorId` 与详情 `complainHistory[].operatorId`（处理人）显示「客服：ID」，0 或 null 显示「系统」；`operatorName` 恒为 null；`sourceDesc` 不含客服或质检员姓名。适用于 `/punish/center/*` 与 `/home/qualityIssue/summary`。

#### 状态标签

列表/详情统一读取 statusTag / statusTagName；按下表优先判断待扣款。申诉状态筛选仍用 appealStatus，不增加「已确认」筛选项。驳回后可二诉时仍显示「申诉拒绝」，同时显示再次申诉按钮及倒计时。

| 条件                             | 标签（statusTag / statusTagName） |
|----------------------------------|-----------------------------------|
| `deductStatus = 1`（优先）       | 0 / 空字符串，不展示              |
| `appealStatus = 0 && canAppeal`  | 1 / 待申诉                        |
| `appealStatus = 0 && !canAppeal` | 5 / 已确认                        |
| `appealStatus = 1`               | 2 / 申诉中                        |
| `appealStatus = 2`               | 3 / 申诉成功                      |
| `appealStatus = 3`               | 4 / 申诉拒绝                      |

#### 金额

- 卡片金额显示 `punishAmount`；`returnedAmount > 0` 时加删除线，可附「实扣 ¥ `actualAmount`」（部分退回时实扣不为 0）。

#### 提示条

| 条件 | 提示 |
|----|----|
| `appealStatus = 0 && canAppeal` | 申诉剩余 hh:mm:ss，超时将无法申诉 |
| `appealStatus = 1` | 申诉中，等待客服处理 |
| `appealStatus = 2` | 申诉成功，款项已退回 |
| `appealStatus = 3 && canAppeal` | 首次申诉已拒绝，可再次申诉 + 二次申诉剩余倒计时 |
| `appealStatus = 3 && !canAppeal` | 申诉已拒绝（可展示 `latestComplain.operationContent` 作为原因） |
| `deductStatus = 1` | 待运营审核扣款（不可申诉） |

#### 按钮

| 条件                            | 按钮     |
|---------------------------------|----------|
| 始终                            | 查看详情 |
| `canAppeal && appealStatus = 0` | 去申诉   |
| `canAppeal && appealStatus = 3` | 再次申诉 |
| `canCancelAppeal`               | 撤回申诉 |

> [!NOTE]
> 倒计时归零后按钮自动隐藏即可；以后端为准，用户此时提交会返回超时提示。

## 运营后台

### POST /order/complain/list

#### 入参（申诉来源相关）

| 字段 | 类型 | 说明 |
|----|----|----|
| `complainTypeList` | int\[\] | 「商品申诉」Tab 传 `[1, 2, 6]`；与 `complainType` 同时传时以本字段为准 |
| `complainType` | int | 支持 6（缺货赔付申诉）；「售后申诉」Tab 传 2 |

#### records\[\] 字段（申诉来源相关）

| 字段 | 说明 | 页面列 |
|----|----|----|
| `appealSourceType` | 1 商品申诉 / 2 退款支出申诉 / 3 缺货赔付申诉（其他类型 null） | 申诉类型/来源 |
| `appealSourceStr` | 「商品申诉」「退款支出申诉」「缺货赔付申诉」，直接做彩色标签 | 申诉类型/来源 |
| `orderPaidInfo` | 仅 complainType=6：`id, orderSn, reasonType, reasonTypeStr`（缺货赔付/二次售后/其他） `, paidReason, price, xmCoin, deductMoney, refundStatus, createTime, updateTime` | 关联订单/商品/售后 |
| `deductMoney` | 仅 2/6：实际扣款金额（2 = 该售后单申诉人承担金额，6 = 赔付金额 + 寻梦币） | — |
| `returnedMoney` | 仅 2/6：同一记录已同意退回金额合计 | — |
| `maxReturnMoney` | 仅 2/6：同意时最多可退 = deductMoney - returnedMoney | 同意弹窗「最多可退 ¥x」 |

- 「关联订单/商品/售后」列：1 用 `orderItemComment`，2 用 `refundInfo`，6 用 `orderPaidInfo`。
- 「退回金额」列：已同意时显示 `returnMoney`。
- 城市仓/分销商名称（`wareName / partnerName / platformStr`）对 2 和 6 都会回填。
- 导出（`exportFlag=1`）包含「申诉来源」「实际扣款金额」两列，类型 6 的「关联订单号」取赔付记录订单号。

### POST /order/complain/check

| 操作 | 请求 | 规则 |
|----|----|----|
| 同意（2 / 6） | `{ "id": 1, "status": 2, "returnMoney": "10.00", "reason": "…" }` | `returnMoney` 必填且 \> 0，不能超过 `maxReturnMoney`；缺货赔付同意后退回供应商余额（账变「售后承担退回」） |
| 驳回（2 / 6） | `{ "id": 1, "status": 3, "reason": "…" }` | **`reason` 必填** |

#### 可能的失败提示

- 缺货赔付申诉同意时需要填写退回金额 / 退回金额不能小于等于0
- 退回金额不能超过实际扣款金额，最多可退：x 元
- 驳回申诉必须填写驳回原因
- 缺货赔付记录不存在 / 缺货赔付未扣款，不能退回
- 已有其他已创建、已同意的申诉，无法通过该条申诉 id:x

审核后 12 小时内的「撤销」（`/order/complain/revoke`）也支持类型 6，撤销时会扣回已退金额；`canRevoke` 字段照常返回。

## 联调注意

- **待扣款**（质检处罚待运营审核）会出现在明细里，但不计入实际处罚/总处罚、不可申诉；运营审核通过后变为已扣款，开始 48h 申诉窗口。
- **不展示**：运营直接撤销的处罚、已取消/删除的处罚、处罚金额为 0 的处罚（含退款支出、缺货赔付）、城市仓处罚、供应商拒不开票处罚。申诉成功的处罚会展示为「已退回」。
- **0 元处罚**不计入统计（实际处罚、共 N 笔、待扣款、各类目/二级类目笔数、首页「本月扣款」、质检场次卡「共 N 条处罚 · ¥X」），与明细列表一致。质检汇总的缺货/退货扎数是质检数量，仍包含 0 元处罚对应的扎数。
- **城市仓处罚、供应商拒不开票处罚**不属于「我的处罚」：下拉选项、统计、明细、首页「本月扣款」、质检场次卡均不包含，详情返回「处罚不存在」。
- **售后处罚**（退款支出、缺货赔付）可由后台 Nacos 开关整体关闭；关闭后统计、明细、下拉里都没有 32，前端无需判断。
- **退款支出**不含极速退款（售后类型 1、2、4、5，未发货退款，供应商不承担）、退运费（7）和质检处罚退款（8，已在处罚中体现）；这几类不出现在明细、统计、首页「本月扣款」里。
- 运营端「售后申诉」Tab 用于历史查看；处理统一在「商品申诉」Tab。
- 测试环境：先部署 admin（能审核 6），再部署 supplier（能提交 6）。
