# 供应商申诉调整

> 首页质检问题（缺货+退货+换货）、质检汇总换货扎数与场次卡「已确认」口径及跳转、二级类目编码、状态标签、商品全称、客服只展示 ID、申诉凭证选填

- 类型：前端对接
- 分类：供应商
- 端：供应商小程序
- 后端服务：supplier
- 状态：已上测
- 测试分支：supplier/test
- 需求单：XYLZ-3640
- 更新：2026-10-29
- 网页版：https://doc.xfun.fun/supplier-appeal-adjust-api

完整接口字段见 [供应商申诉优化 · 前端对接文档](/supplier-punish-center-api)；本页集中说明本次涉及的字段、口径与展示规则。

## 总览

| 接口 | 页面 | 本页涉及的字段 / 规则 |
|----|----|----|
| `POST /home/supplierPunishCount` | 首页「本月履约情况 —— 质检问题汇总 N 扎」 | `qualityIssueNum / qualityLackNum / qualityReturnNum / qualityExchangeNum` |
| `POST /home/qualityIssue/summary` | 质检问题汇总 | 汇总卡 / 场次卡 / 商品行三级 `exchangeNum`；`lackNum / returnNum` 类型集合；`rounds[].punishCount / punishAmount`；`items[].skuDesc / productFullName`；商品行排序；`roundOptions` |
| `POST /punish/center/summary` | 我的处罚统计、二级类目卡、「全部类型」下拉 | 二级类目编码表、`categories[].subCategories[]`、`typeOptions` |
| `POST /punish/center/list` | 我的处罚 - 处罚明细 | `statusTag / statusTagName`、`skuDesc / productFullName`、`title / typeName`、`reason`（处罚描述）、`latestComplain.operatorId` |
| `POST /punish/center/detail` | 处罚详情 | 列表字段 + `operatorId`（下发人）、`complainHistory[].operatorId`（处理人）、`sourceDesc` |
| `POST /comment/complain` | 申诉提交 | 凭证 `picUrl` 选填（图片 / 视频），`reason` 必填 |

- 全部 `POST` + JSON，返回 `{ "code": 0, "msg": "success", "data": … }`；`code != 0` 时 `msg` 直接 toast。
- 时间格式 `yyyy-MM-dd HH:mm:ss`；金额为保留 2 位小数的数字；扎数为整数。
- 接口路径与入参签名与现有对接文档一致，前端只需按本页处理字段与展示。

## 首页质检问题

### POST /home/supplierPunishCount

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

#### 请求

```text
// 首页已调用的请求，原样即可
{ "startRoundNo": "2026-10-27", "endRoundNo": "2026-10-27" }
```

#### 响应 data（本页相关字段）

| 字段 | 类型 | 说明 |
|----|----|----|
| `qualityIssueNum` | int | 当月质检问题扎数 = `qualityLackNum + qualityReturnNum + qualityExchangeNum`（缺货 + 退货 + 换货）。等于 `/home/qualityIssue/summary` 按月（`statType=1`、不传月份）的 `lackNum + returnNum + exchangeNum` |
| `qualityLackNum` | int | 当月缺货扎数，处罚类型 **1 缺货处罚、6 缺货补货、9 缺货不补** 的处罚数量之和；= 汇总页当月 `lackNum` |
| `qualityReturnNum` | int | 当月退货扎数，处罚类型 **7、8、11、12** 的处罚数量之和；= 汇总页当月 `returnNum` |
| `qualityExchangeNum` | int | 当月换货扎数，取 **换货记录**的换货要求扎数（质检扫码换货 + 运营批量换货，含已完成与未完成，扣除已取消数量）；= 汇总页当月 `exchangeNum` |

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

> [!WARNING]
> **null 处理**：四个字段同时计算，计算失败时 **四个字段都为 null**，接口仍返回成功、其他字段正常。前端「质检问题汇总」显示「--」（或隐藏数字），入口仍可点击进入汇总页；不要把 null 当 0 展示。

## 质检问题汇总

### POST /home/qualityIssue/summary

#### 请求参数

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

#### 扎数口径（三级一致）

| 扎数 | 字段 | 口径 | 对应「我的处罚」二级类目 |
|----|----|----|----|
| 售卖 | `saleNum` | 入场明细售卖扎数 | — |
| 缺货 | `lackNum` | 处罚类型 **1、6、9** 的处罚数量 | 11 缺货 |
| 退货 | `returnNum` | 处罚类型 **7、8、11、12** 的处罚数量 | 12 退货 |
| 换货 | `exchangeNum` | 换货记录的换货要求扎数（质检扫码换货 + 运营批量换货，含已完成与未完成，扣除已取消） | 15 换货配货 |

- 汇总卡、场次卡红标、商品行三级都返回 `exchangeNum`，上级 = 下级之和。
- 缺货 / 退货的类型集合与「我的处罚」二级类目 11 / 12 相同。换货未换（类型 25）只计入换货扎数，不计入退货；类型 1「缺货处罚」包含人工下发记录，归缺货并计入缺货扎数。
- 扎数是问题花材数量，包含处罚金额为 0 的记录；二级类目统计的是处罚条数 / 金额，两者度量不同。

#### 响应 data

| 字段 | 类型 | 说明 |
|----|----|----|
| `statType / month / roundNo` | int / string | 实际统计方式、月份、按场次时实际场次（按月时 `roundNo` 为 null） |
| `roundCount` | int | 统计范围内场次数 |
| `saleNum / lackNum / returnNum / exchangeNum` | int | 汇总卡「售卖 / 缺货 / 退货 / 换货」4 列 |
| `roundOptions[]` | string\[\] | 所选月份内有售卖、缺货、退货 **或换货**的场次（倒序），只有换货的场次也在内；场次下拉用 |
| `rounds[]` | array | 场次卡（倒序），见下表 |

#### rounds\[\] 场次卡

| 字段 | 类型 | 说明 |
|----|----|----|
| `roundNo` | string | 场次 |
| `productKinds / saleNum` | int | 共 X 种商品 / 售卖 Y 扎 |
| `lackNum / returnNum / exchangeNum` | int | 红标「缺货 a 扎 · 退货 b 扎 · 换货 c 扎」 |
| `punishCount` | int | 该场次 **质检处罚**（category=1）条数，**含待扣款**；等于跳转后 `/punish/center/list {statType:2, roundNo, category:1}` 的 `total` |
| `punishAmount` | number | 该场次质检处罚 **已确认金额**：已扣款 / 已退回项的实际处罚（`actualAmount`）之和，**不含待扣款** |
| `items[]` | array | 商品行，全部返回由前端折叠（默认 3 行，「展开更多(n)」n = `items.length - 3`） |

卡片底部文案：**「查看处罚明细 —— 共 `punishCount` 条处罚 已确认 · ¥ `punishAmount` \>」**。

#### rounds\[\].items\[\] 商品行

| 字段 | 类型 | 说明 |
|----|----|----|
| `productId / productName / rankNum` | long / string / int | 商品 ID、商品名、等级（1~5 = A~E，6 = O）；行按 `(productId, rankNum)` 区分 |
| `skuDesc` | string | 规格属性描述，如「卷边」「不卷边」；无属性时为空串，见 [商品全称](#商品全称-skudesc-productfullname) |
| `productFullName` | string | 商品全称，如「高原红A级卷边」，商品列直接展示 |
| `saleNum / lackNum / returnNum / exchangeNum` | int | 商品表「售卖 / 缺货 / 退货 / 换货」列 |

商品行排序：售卖扎数倒序 → 换货扎数倒序 → 退货扎数倒序 → 缺货扎数倒序。前端按返回顺序展示即可。

#### 示例

```text
// 请求：按月
{ "statType": 1, "month": "2026-10" }
// 请求：按场次
{ "statType": 2, "roundNo": "2026-10-21" }
// 响应 data（按月）
{
  "statType": 1, "month": "2026-10", "roundNo": null,
  "roundCount": 2, "saleNum": 86, "lackNum": 7, "returnNum": 5, "exchangeNum": 1,
  "roundOptions": ["2026-10-21", "2026-10-14"],
  "rounds": [{
    "roundNo": "2026-10-21", "productKinds": 6, "saleNum": 52,
    "lackNum": 7, "returnNum": 5, "exchangeNum": 1,
    "punishCount": 5, "punishAmount": 120.00,
    "items": [
      { "productId": 399968, "productName": "高原红", "rankNum": 1, "skuDesc": "卷边",
        "productFullName": "高原红A级卷边", "saleNum": 20, "lackNum": 3, "returnNum": 2, "exchangeNum": 1 },
      { "productId": 400017, "productName": "高原红", "rankNum": 1, "skuDesc": "不卷边",
        "productFullName": "高原红A级不卷边", "saleNum": 20, "lackNum": 4, "returnNum": 3, "exchangeNum": 0 },
      { "productId": 401203, "productName": "卡罗拉", "rankNum": 6, "skuDesc": "",
        "productFullName": "卡罗拉", "saleNum": 12, "lackNum": 0, "returnNum": 0, "exchangeNum": 0 }
    ]
  }]
}
```

## 场次卡「查看处罚明细」跳转

质检问题汇总 · 场次卡 → 我的处罚（按场次，定位「质检处罚」Tab）

| 跳转后调用 | 入参 |
|----|----|
| `POST /punish/center/summary` | `{ "statType": 2, "roundNo": "2026-10-21" }` |
| `POST /punish/center/list` | `{ "statType": 2, "roundNo": "2026-10-21", "category": 1, "pageIndex": 1, "pageSize": 20 }` |

- 页面切到「按场次」、场次下拉选中 `roundNo`、Tab 定位到「质检处罚」（`category=1`）。
- 列表 `total` = 场次卡 `punishCount`；列表中已扣款 / 已退回项的 `actualAmount` 之和 = 场次卡 `punishAmount`。

## 我的处罚二级类目编码

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

质检类型（1、6、7、8、9、11、12、13、15）只有质检环节（`punish_link=1`）才归入下表的质检类目，其他环节的同类型处罚归「其他处罚 · 34」，详见 [供应商申诉优化 · 前端对接](/supplier-punish-center-api)。

二级类目 `subCategory` 的 **有效编码仅以下 9 个**，按下表顺序下发（`summary.categories[].subCategories[]` 与 `typeOptions` 均按此顺序）：

| category | subCategory | 三级类型（sourceType / punishType：typeName） | 卡片标题 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 超时转代卖，及其他未归类类型 | 其他处罚 |

- 二级卡、「全部类型」下拉都按接口下发渲染，**不要写死编码与名称**；列表 / 详情的 `title`（二级名 + 处罚）与 `typeName`（三级名）直接展示。
- 「售后处罚」由后台开关关闭时，`subCategories` 与 `typeOptions` 中都没有 32。
- 筛选回传：点二级卡传 `subCategory`；下拉选三级时原样回传 `sourceType + punishType`。

#### 示例：/punish/center/summary

```text
// 请求
{ "statType": 1, "month": "2026-10" }
// 响应 data（节选）
{
  "statType": 1, "month": "2026-10", "roundNo": null, "roundOptions": ["2026-10-21", "2026-10-14"],
  "startRoundNo": "2026-10-01", "endRoundNo": "2026-10-31",
  "actualAmount": 320.00, "totalAmount": 350.00, "appealSuccessAmount": 30.00, "totalCount": 12,
  "pendingAmount": 15.00, "pendingCount": 1,
  "categories": [
    { "category": 1, "categoryName": "质检处罚",
      "actualAmount": 240.00, "totalAmount": 260.00, "appealSuccessAmount": 20.00, "totalCount": 8,
      "subCategories": [
        { "subCategory": 11, "subCategoryName": "缺货", "actualAmount": 60.00, "totalAmount": 60.00, "appealSuccessAmount": 0.00, "totalCount": 3 },
        { "subCategory": 12, "subCategoryName": "退货", "actualAmount": 120.00, "totalAmount": 140.00, "appealSuccessAmount": 20.00, "totalCount": 3 },
        { "subCategory": 14, "subCategoryName": "降级", "actualAmount": 40.00, "totalAmount": 40.00, "appealSuccessAmount": 0.00, "totalCount": 1 },
        { "subCategory": 15, "subCategoryName": "换货配货", "actualAmount": 20.00, "totalAmount": 20.00, "appealSuccessAmount": 0.00, "totalCount": 1 } ] },
    { "category": 2, "categoryName": "迟到处罚",
      "actualAmount": 50.00, "totalAmount": 50.00, "appealSuccessAmount": 0.00, "totalCount": 2,
      "subCategories": [
        { "subCategory": 21, "subCategoryName": "配货迟到", "actualAmount": 0.00, "totalAmount": 0.00, "appealSuccessAmount": 0.00, "totalCount": 0 },
        { "subCategory": 22, "subCategoryName": "换货迟到", "actualAmount": 20.00, "totalAmount": 20.00, "appealSuccessAmount": 0.00, "totalCount": 1 },
        { "subCategory": 23, "subCategoryName": "迟到处罚", "actualAmount": 30.00, "totalAmount": 30.00, "appealSuccessAmount": 0.00, "totalCount": 1 } ] },
    { "category": 3, "categoryName": "其他处罚",
      "actualAmount": 30.00, "totalAmount": 40.00, "appealSuccessAmount": 10.00, "totalCount": 2,
      "subCategories": [
        { "subCategory": 32, "subCategoryName": "售后处罚", "actualAmount": 10.00, "totalAmount": 20.00, "appealSuccessAmount": 10.00, "totalCount": 1 },
        { "subCategory": 34, "subCategoryName": "其他处罚", "actualAmount": 20.00, "totalAmount": 20.00, "appealSuccessAmount": 0.00, "totalCount": 1 } ] }
  ],
  "typeOptions": [
    { "category": 1, "categoryName": "质检处罚", "subCategories": [
      { "subCategory": 15, "subCategoryName": "换货配货", "types": [
        { "sourceType": 1, "punishType": 25, "name": "换货未换" },
        { "sourceType": 1, "punishType": 18, "name": "换货" },
        { "sourceType": 1, "punishType": 19, "name": "配货错误处罚" },
        { "sourceType": 1, "punishType": 22, "name": "先扫码后配货处罚" } ] } ] },
    { "category": 2, "categoryName": "迟到处罚", "subCategories": [
      { "subCategory": 23, "subCategoryName": "迟到处罚", "types": [
        { "sourceType": 1, "punishType": 3, "name": "迟到处罚" } ] } ] }
  ],
  "serverTime": "2026-10-27 10:00:00"
}
```

## 处罚明细

### POST /punish/center/list

#### 本页相关的 records\[\] 字段

| 字段 | 类型 | 卡片展示 |
|----|----|----|
| `title` | string | 卡片标题，二级名 + 处罚，如「换货配货处罚」「迟到处罚」 |
| `subCategory / subCategoryName / typeName` | int / string | 二级 / 三级类型，编码见「我的处罚二级类目编码」 |
| `statusTag / statusTagName` | int / string | 卡片右上角标签，见 [状态标签](#状态标签-statustag) |
| `punishTime` | string | 处罚时间 |
| `productFullName` + `num` | string / int | 商品信息：「`productFullName` · `num` 扎」；需要等级标红时用 `productName` + 等级（`rankNum`）+ `skuDesc` 自行拼 |
| `reason` | string | 行名显示为 **「处罚描述」**，值取 `reason` |
| `orderSn` | string | 关联单号 |
| `deductStatus / deductStatusName` | int / string | 扣款状态 |
| `latestComplain.operatorId` | long | 最新申诉的处理客服 ID；`latestComplain.operatorName` 恒为 null |

#### 示例

```text
// 请求
{ "statType": 2, "roundNo": "2026-10-21", "category": 1, "pageIndex": 1, "pageSize": 20 }
// 响应 data
{ "total": 5, "serverTime": "2026-10-27 10:00:00", "records": [
  { "itemKey": "1_922652537", "sourceType": 1, "sourceId": 922652537,
    "category": 1, "categoryName": "质检处罚", "subCategory": 12, "subCategoryName": "退货",
    "punishType": 7, "typeName": "退货不补", "title": "退货处罚",
    "roundNo": "2026-10-21", "punishTime": "2026-10-21 17:16:00",
    "productName": "高原红", "rankNum": 1, "skuDesc": "卷边", "productFullName": "高原红A级卷边",
    "num": 2, "orderItemUrl": "https://…",
    "reason": "花头受损，品质不达标", "orderSn": "T2026102110033425",
    "punishAmount": 45.00, "returnedAmount": 0.00, "actualAmount": 45.00,
    "deductStatus": 2, "deductStatusName": "已扣款", "appealStatus": 3, "appealStatusName": "申诉驳回",
    "statusTag": 4, "statusTagName": "申诉拒绝",
    "complainType": 3, "canAppeal": true, "nextAppealNo": 2, "appealDeadline": "2026-10-23 11:00:00",
    "canCancelAppeal": false, "latestComplainId": 88123,
    "latestComplain": { "id": 88123, "appealNo": 1, "reason": "花头受损为运输导致", "pictures": "[]",
      "status": 3, "operationContent": "质检照片显示到货即受损", "operatorId": 10086, "operatorName": null,
      "createTime": "2026-10-22 09:30:00", "handleTime": "2026-10-22 11:00:00" } },
  { "itemKey": "1_922652601", "sourceType": 1, "sourceId": 922652601,
    "category": 1, "categoryName": "质检处罚", "subCategory": 15, "subCategoryName": "换货配货",
    "punishType": 25, "typeName": "换货未换", "title": "换货配货处罚",
    "roundNo": "2026-10-21", "punishTime": "2026-10-21 22:00:00",
    "productName": "卡罗拉", "rankNum": 6, "skuDesc": "", "productFullName": "卡罗拉",
    "num": 1, "reason": "场次换货未换", "orderSn": "T2026102110039876",
    "punishAmount": 20.00, "returnedAmount": 0.00, "actualAmount": 20.00,
    "deductStatus": 2, "deductStatusName": "已扣款", "appealStatus": 0, "appealStatusName": "未申诉",
    "statusTag": 5, "statusTagName": "已确认",
    "complainType": 3, "canAppeal": false, "nextAppealNo": null, "appealDeadline": "2026-10-23 22:00:00",
    "canCancelAppeal": false, "latestComplainId": null, "latestComplain": null } ] }
```

## 处罚详情

### POST /punish/center/detail

响应 = 列表项全部字段（含 `statusTag / skuDesc / productFullName`）+ 详情字段。本页相关：

| 字段 | 类型 | 说明 |
|----|----|----|
| `operatorId` | long | 下发处罚人 ID（质检处罚为质检员）：处罚取处罚记录、退款支出取售后单、缺货赔付取赔付记录的操作人 ID。展示为「客服：`operatorId`」，**0 或 null 显示「系统」** |
| `operatorName` | string | 恒为 null，不展示 |
| `sourceDesc` | string | 处罚来源，如「质检处罚」「系统自动处罚」「售后处罚」「售后单」「缺货赔付（订单 T…）」，不含任何姓名；为空不展示 |
| `reason` | string | 行名「处罚描述」 |
| `complainHistory[].operatorId` | long | 每条申诉的处理客服 ID，显示「客服：ID」，0 或 null 显示「系统」；`operatorName` 恒为 null |

```text
// 请求
{ "sourceType": 1, "id": 922652537 }
// 响应 data（节选）
{
  "sourceType": 1, "sourceId": 922652537, "title": "退货处罚", "typeName": "退货不补",
  "productName": "高原红", "rankNum": 1, "skuDesc": "卷边", "productFullName": "高原红A级卷边",
  "num": 2, "reason": "花头受损，品质不达标",
  "deductStatus": 2, "deductStatusName": "已扣款", "statusTag": 4, "statusTagName": "申诉拒绝",
  "operatorId": 20231, "operatorName": null, "sourceDesc": "质检处罚",
  "complainHistory": [
    { "id": 88123, "appealNo": 1, "reason": "花头受损为运输导致", "pictures": "[]", "status": 3,
      "operationContent": "质检照片显示到货即受损", "operatorId": 10086, "operatorName": null,
      "createTime": "2026-10-22 09:30:00", "handleTime": "2026-10-22 11:00:00", "delTime": null } ],
  "serverTime": "2026-10-27 10:00:00"
}
```

## 状态标签 statusTag

列表、详情都返回 `statusTag / statusTagName`，前端直接展示 `statusTagName`，不用自行组合扣款 / 申诉状态。判断顺序：先判待扣款，再按申诉状态。

| statusTag | statusTagName | 条件 |
|----|----|----|
| 0 | （空串，不展示标签） | `deductStatus = 1` 待扣款 |
| 1 | 待申诉 | `appealStatus = 0 && canAppeal` |
| 2 | 申诉中 | `appealStatus = 1` |
| 3 | 申诉成功 | `appealStatus = 2` |
| 4 | 申诉拒绝 | `appealStatus = 3`；可二诉时同样显示「申诉拒绝」，另显示「再次申诉」按钮与二诉倒计时 |
| 5 | 已确认 | `appealStatus = 0 && !canAppeal`（申诉期已过且未申诉） |

- 「申诉状态」筛选下拉仍用 `appealStatus`（0 未申诉 / 1 申诉中 / 2 申诉通过 / 3 申诉驳回），「已确认」不作为筛选项。
- 按钮、倒计时规则仍按 `canAppeal / canCancelAppeal / appealDeadline` 判断。

## 商品全称 skuDesc / productFullName

返回位置：质检问题汇总 `rounds[].items[]`、我的处罚列表 `records[]`、处罚详情。

| 字段 | 规则 |
|----|----|
| `skuDesc` | 取商品规格属性值，按原顺序以空格连接；规格中的「等级」与包装 JSON 不展示；无属性时为空串 `""` |
| `productFullName` | `productName` + 等级名 + `skuDesc`；等级名仅 `rankNum` 1~5 对应 **A级~E级**，其他取值（如 6 = O 级、0、null） **不拼等级字样** |

| productName | rankNum   | 规格               | skuDesc  | productFullName |
|-------------|-----------|--------------------|----------|-----------------|
| 高原红      | 1         | 生熟度:卷边        | 卷边     | 高原红A级卷边   |
| 高原红      | 1         | 生熟度:不卷边      | 不卷边   | 高原红A级不卷边 |
| 卡罗拉      | 2         | 生熟度:中熟,等级:B | 中熟     | 卡罗拉B级中熟   |
| 卡罗拉      | 6（O 级） | （空）             | （空串） | 卡罗拉          |

`productName / rankNum` 仍返回，需要把等级标红时可用 `productName` + 等级（标红）+ `skuDesc` 自行拼接。

## 客服只展示 ID

| 位置 | 字段 | 展示 |
|----|----|----|
| 处罚详情 · 下发处罚人 | `detail.operatorId` | 「客服：`operatorId`」；0 或 null 显示「系统」 |
| 处罚列表 · 最新申诉处理人 | `latestComplain.operatorId` | 「客服：`operatorId`」；0 或 null 显示「系统」 |
| 处罚详情 · 申诉记录处理人 | `complainHistory[].operatorId` | 「客服：`operatorId`」；0 或 null 显示「系统」 |

- `operatorName`（详情、`latestComplain`、`complainHistory[]`） **恒为 null**，前端不要读取。
- `sourceDesc` 不含客服 / 质检员姓名。
- 适用范围：`/punish/center/*` 与 `/home/qualityIssue/summary`。

## 提交申诉

### POST /comment/complain

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

```text
// 请求：不带凭证
{ "id": 922652537, "complainType": 3, "reason": "花头受损为运输导致" }
// 请求：带图片和视频
{ "id": 922652537, "complainType": 3, "reason": "花头受损为运输导致",
  "picUrl": "[\"https://img.xm.com/a.jpg\",\"https://img.xm.com/b.mp4\"]" }
// 响应
{ "code": 0, "msg": "success", "data": true }
```

## 联调与发版注意

- **前后端需同时发版**：二级类目编码、场次卡 `punishCount / punishAmount` 口径、状态标签、客服 ID 展示均依赖本次后端版本。前端若写死二级类目编码会显示异常，统一按接口下发渲染。
- **首页数字口径**：「质检问题汇总 N 扎」= 缺货 + 退货 + 换货；缺货按类型 1、6、9，退货按类型 7、8、11、12，换货按换货记录。首页与质检问题汇总页的数字以此口径为准，**需告知测试与产品**。
- 场次卡「共 N 条处罚」含待扣款，金额「已确认 · ¥X」只算已扣款 / 已退回项，两者口径不同属正常。
- 自测对照（测试环境 supplier 24562，场次 2026-10-21）：缺货 7、退货 5、换货 1；两行高原红分别显示「高原红A级卷边」「高原红A级不卷边」；场次卡 `punishCount` = 跳转后列表 `total`。
- 后端只部署 supplier，运营后台无变化。
