# 打印签单出参重制（自包含通知单 + 字段全中文化）

> 变更类型：**修改接口** ｜ 端类型：**管理后台** ｜ 接口：`GET /v3/admin/order/{id}/sign-voucher`
> 合并自 3 个 PR：#4549（FROM 下沉 + 付款方式 + 经办人）、#4566（订单概要下沉 + 司机/导游）、#4584（房型/结算/付款方式中文化）
> 原始接口见 #4505。本文给出**最终出参契约**，前端按此对接即可。

---

## ① 接口背景

订单详情「打印签单」功能，后端组装「酒店预订通知单 + 景点门票预订通知单」（A4 横向传真单）数据。原型每张单独立打印，发给不同供应商。本次按验收反馈把出参重制为「每张通知单完全自包含」，并把所有字典 code 后端翻成中文。

---

## ② 变更清单

1. **删除共享 `header`**：原 `data.header`（订单概要 + FROM 抬头）整体删除，所有字段下沉到每个 `entries[]`，每条自成一张可独立打印的 A4 单。
2. **订单概要下沉**：`teamNo` / `productName` / `dateRange` / `paxSummary` 移到每个 entry。
3. **FROM 抬头下沉 + 新增经办人**：`fromCompany` / `taxId` 移到每个 entry，新增 `operatorName`（经办人/发件人=订单定制师）。
4. **新增司机/导游**：每个 entry 加 `driverName` / `driverPhone` / `guideName` / `guidePhone`（电话为内网明文，供应商可联系；无则空串）。
5. **新增付款方式**：每个 entry 加 `settleType`（结算方式）/ `paymentMode`（付款方式）。
6. **字段全中文化**：`items[].name`（房型）、`settleType`、`paymentMode` 由原本返字典 code 改为**后端翻好的中文**，前端不再接触机器码。

---

## ③ 接口详情

| 项 | 值 |
|---|---|
| 方法 | GET |
| 路径 | `/v3/admin/order/{id}/sign-voucher` |
| 鉴权 | 管理后台登录态（Gateway JWT） |
| 返回 | `Result<SignVoucherRespVO>` |

---

## ④ 入参

| 参数 | 位置 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
| `id` | path | Long | 是 | 订单 ID |
| `showAmount` | query | Boolean | 否 | 是否展示金额，默认 `false`。`false`=脱敏（所有 unitPrice/amount/totalAmount 置 null，前端渲染 `***` 防成本价外泄给供应商）；`true`=展示金额 |

> 入参无变化（与原始接口一致）。

---

## ⑤ 出参

**顶层**（注意：**已无 `header`**，只有 `entries`）

| 字段 | 类型 | 说明 |
|---|---|---|
| `entries` | array | 预订通知单列表（每条完全自包含一张 A4 单） |

**`entries[]`（每张预订通知单）**

| 字段 | 类型 | 说明 |
|---|---|---|
| `entryId` | string | 条目唯一 ID，酒店 `H{assignmentId}` / 景点 `S{assignmentId}`，前端勾选用 |
| `type` | string | `HOTEL` / `SCENIC` |
| `title` | string | "酒店预订通知单" / "景点门票预订通知单" |
| `teamNo` | string | 团号（订单概要，下沉） |
| `productName` | string | 产品名 |
| `dateRange` | string | 出行日期范围 `depart→return`（任一为空返空串） |
| `paxSummary` | string | 人数摘要，如 "2大1小"（成人=大，儿童+幼童=小） |
| `driverName` | string | 司机姓名（无司机返空串 `""`） |
| `driverPhone` | string | 司机电话（内网明文，无则空串） |
| `guideName` | string | 导游姓名（无导游返空串） |
| `guidePhone` | string | 导游电话（内网明文，无则空串） |
| `fromCompany` | string | FROM 本社名（下沉，每条自带） |
| `taxId` | string | 纳税人识别号（统一社会信用代码） |
| `operatorName` | string | 经办人/发件人（订单定制师 consultant_name） |
| `supplierName` | string | TO 供应商名（酒店名 / 景点名） |
| `contactPerson` | string | 联系人/收件人（取不到返空串） |
| `contactPhone` | string | 联系电话（取不到返空串） |
| `fax` | string | 传真（本期恒 null） |
| `settleType` | string | **结算方式中文**（酒店：现付/签单/公司付款；景点：恒空串） |
| `paymentMode` | string | **付款方式中文**（酒店：预付款/现结/月结；景点：恒空串） |
| `items` | array | 明细行 |
| `totalAmount` | string(可空) | 合计金额（`showAmount=false` 时 null；BigDecimal 序列化为字符串防精度丢失） |

**`entries[].items[]`（明细行）**

| 字段 | 类型 | 说明 |
|---|---|---|
| `name` | string | **中文名**：酒店=房型中文（大床房/标间…，未知房型回退「其他房型」）；景点=固定"门票" |
| `date` | string(date) | 日期，如 `2026-07-01` |
| `qty` | int | 数量：酒店=房间数；景点=订单总人数（成人+儿童，婴幼儿不计） |
| `unit` | string | "间夜" / "张" |
| `unitPrice` | string(可空) | 单价（`showAmount=false` 时 null） |
| `amount` | string(可空) | 金额（`showAmount=false` 时 null） |
| `remark` | string | 备注（酒店取配房备注；景点恒 null） |

> 金额字段（unitPrice/amount/totalAmount）均为 BigDecimal，经 ToStringSerializer 序列化为**字符串**。

---

## ⑥ 枚举 / 数据字典

**`settleType`（结算方式，后端已翻中文，前端直接展示）**

| 后端返回（中文） | 含义 |
|---|---|
| 现付 | 现场付款 |
| 签单 | 签单挂账 |
| 公司付款 | 公司统一付款 |

**`paymentMode`（付款方式，后端已翻中文）**

| 后端返回（中文） | 含义 |
|---|---|
| 预付款 | 预付 |
| 现结 | 现场结算 |
| 月结 | 按月结算 |

**`items[].name`（房型，后端已翻中文）**：标间 / 单人间 / 双床房 / 大床房 / 豪华大床 / 套房 / 家庭房 / 蒙古包 / 特色房 / 亲子房 …；字典未命中回退「其他房型」。

> ⚠️ 前端**不再收到** `sign` / `prepay` / `BIG_BED` 等机器码，settleType/paymentMode/房型一律为中文。景点的 settleType/paymentMode 本期恒空串（景点资源未建结算字段）。

---

## ⑦ 错误码

| 场景 | code | 说明 |
|---|---|---|
| 订单不存在 | 581007 | ORDER_NOT_FOUND |
| 未登录 | 401 | 网关鉴权 |

> 无新增业务错误码。

---

## ⑧ 示例

**典型（`showAmount=false`，1 酒店 + 1 景点）**

```json
{
  "code": 200,
  "message": "成功",
  "data": {
    "entries": [
      {
        "entryId": "H2070758427717525506",
        "type": "HOTEL",
        "title": "酒店预订通知单",
        "teamNo": "26-1590",
        "productName": "呼伦贝尔7日游",
        "dateRange": "2026-07-01→2026-07-07",
        "paxSummary": "2大1小",
        "driverName": "全广存",
        "driverPhone": "18614805444",
        "guideName": "",
        "guidePhone": "",
        "fromCompany": "内蒙古呼籁国际旅行社有限公司",
        "taxId": "91150702MACYFE851R",
        "operatorName": "张定制",
        "supplierName": "呼伦贝尔香格里拉大酒店",
        "contactPerson": "王经理",
        "contactPhone": "0470-8888888",
        "fax": null,
        "settleType": "签单",
        "paymentMode": "预付款",
        "items": [
          { "name": "大床房", "date": "2026-07-01", "qty": 1, "unit": "间夜", "unitPrice": null, "amount": null, "remark": "含双早" }
        ],
        "totalAmount": null
      },
      {
        "entryId": "S2071200000000000001",
        "type": "SCENIC",
        "title": "景点门票预订通知单",
        "teamNo": "26-1590",
        "productName": "呼伦贝尔7日游",
        "dateRange": "2026-07-01→2026-07-07",
        "paxSummary": "2大1小",
        "driverName": "全广存", "driverPhone": "18614805444",
        "guideName": "", "guidePhone": "",
        "fromCompany": "内蒙古呼籁国际旅行社有限公司",
        "taxId": "91150702MACYFE851R",
        "operatorName": "张定制",
        "supplierName": "呼和诺尔草原旅游区",
        "contactPerson": "", "contactPhone": "", "fax": null,
        "settleType": "", "paymentMode": "",
        "items": [
          { "name": "门票", "date": "2026-07-03", "qty": 2, "unit": "张", "unitPrice": null, "amount": null, "remark": null }
        ],
        "totalAmount": null
      }
    ]
  }
}
```

**边界（无酒店无景点）**：`data.entries` = `[]`（空数组）。

**异常（订单不存在）**：

```json
{ "code": 581007, "message": "订单不存在", "data": null }
```

---

## ⑨ 业务边界

- 一个订单一个本社：所有 entry 的 `fromCompany`/`taxId`/`operatorName` 相同（同一定制师/本社）。
- 司机/导游各取本单首条 DRIVER/GUIDE 角色人员；未配则四字段空串。
- 酒店按 `hotelId` 分组，每酒店一张通知单（多晚合为一组多条 item）；每条景点一张通知单。
- 自定义景点（无 scenicId）联系人留空。
- `showAmount=false`（默认）下所有金额 null，前端渲染 `***`。

---

## ⑩ 修改前后对比

| 维度 | 改前 | 改后 |
|---|---|---|
| 结构 | `data.header`（共享概要+FROM）+ `data.entries[]` | **只有 `data.entries[]`**，header 删除，所有字段下沉 |
| FROM | header 一份 | 每个 entry 自带 fromCompany/taxId/**operatorName(新增)** |
| 订单概要 | header 里 | 每个 entry 里 teamNo/productName/dateRange/paxSummary |
| 司机/导游 | 无（header.driverName 恒 null） | 每个 entry driverName/driverPhone/guideName/guidePhone |
| 付款方式 | 无 | 每个 entry settleType/paymentMode |
| settleType/paymentMode/房型 | 返字典 code（sign/prepay/BIG_BED） | 返中文（签单/预付款/大床房） |

---

## ⑪ 影响评估 / 回滚

- **前端必改**：原读 `data.header.xxx` 的代码全部改读 `data.entries[i].xxx`（header 已删，读 header 会拿到 undefined）。
- settleType/paymentMode/房型原本若前端自己做 code→中文映射，可删除该映射逻辑（后端已翻好）。
- 回滚：接口为纯读组装，无 DDL、无状态写入，回滚仅需后端版本回退。

---

## ⑫ 注意事项

- 司机/导游电话为**内网明文全号**（供应商需联系司机/导游，与酒店联系人电话同口径），前端按需展示。
- 景点 `settleType`/`paymentMode` 本期恒空串（景点资源侧未建结算字段，待后续）。
- 房型/结算/付款中文依赖数据字典（`room_category`/`hotel_settle_type`/`hotel_payment_mode`），字典缺值时：房型回退「其他房型」，结算/付款回退空串——前端做空值兜底即可。

---

## ⑬ 关联 / 联系人

- Issue：[#4549](https://git.1814.love:8443/wx/HL/issues/4549) / [#4566](https://git.1814.love:8443/wx/HL/issues/4566) / [#4582](https://git.1814.love:8443/wx/HL/issues/4582)（原始 [#4505](https://git.1814.love:8443/wx/HL/issues/4505)）
- PR：[#4550](https://git.1814.love:8443/wx/HL/pulls/4550) / [#4567](https://git.1814.love:8443/wx/HL/pulls/4567) / [#4584](https://git.1814.love:8443/wx/HL/pulls/4584)
- 负责人：腰苏图（后端）
