# 产品编辑 订金设置 固定金额/比例 严格互斥

> **服务**: hl-product-service-v2 (端口 8083)
> **PR**: #819
> **Issue**: #816
> **日期**: 2026-04-18
> **影响范围**: 产品编辑 Step1 基础信息 → 订金设置

---

## 一、变更说明

此前后端未对订金"固定金额"和"比例"做互斥校验，允许两字段同时有值写入数据库；下单报价时按「固定额 > 0 优先」的逻辑处理，导致比例字段在同时有值时永远不生效，产生脏数据。

现在后端强制两字段**任意时刻最多只有一个有值**：

1. 同时提交两个字段（都 > 0）→ 后端 **400 拒绝**，返回提示"订金固定金额与比例互斥，只能填写其中一个"
2. 保存时若 `depositAmount` 有值 → 数据库 `deposit_ratio` 显式置 NULL
3. 保存时若 `depositRatio` 有值 → 数据库 `deposit_amount` 显式置 NULL
4. 两者都为 null（或 0）→ 合法（视为全款模式 PaymentType=FULL）

---

## 二、前端必须配合

**切换 tab 时清空对方字段再提交**，否则后端直接拒绝：

| 前端场景 | 必须提交的 payload |
|---------|-------------------|
| 用户选"固定金额"填 500 | `{ "depositAmount": 500, "depositRatio": null }` |
| 用户从"固定金额"切到"比例"填 10% | `{ "depositAmount": null, "depositRatio": 10 }` |
| 用户选"全款支付"（无订金） | `{ "paymentType": "FULL", "depositAmount": null, "depositRatio": null }` |
| ❌ 错误示例（会被 400） | `{ "depositAmount": 500, "depositRatio": 10 }` |

**关键点**：切换 tab 时**必须把对方字段在提交前 reset 成 null**，不要只是隐藏输入框。前端 state 里残留另一方的历史值会让后端拒绝。

---

## 三、变更接口清单

| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|---|------|------|------|---------|------|
| 1 | 保存产品基础信息 | POST | `/admin/product-basic/save` | 请求体新增校验 | `depositAmount`/`depositRatio` 不得同时 > 0 |

---

## 四、接口详情

### 1. 保存产品基础信息 `POST /admin/product-basic/save`

**VO**: `ProductBasicSaveReqVO`

**相关字段**（字段本身没改，**校验行为改了**）：

| 字段 | 类型 | 必填 | 说明 | 变化 |
|------|------|------|------|------|
| `paymentType` | String | 是 | 支付方式：`FULL`=全款, `DEPOSIT`=订金+尾款 | 无 |
| `depositAmount` | BigDecimal | 否 | 订金固定金额（元/人），与 `depositRatio` **互斥** | 互斥校验 |
| `depositRatio` | Integer | 否 | 订金比例(%)，与 `depositAmount` **互斥** | 互斥校验 |

**互斥规则**：
- `depositAmount` 和 `depositRatio` 任意时刻最多只有一个 > 0
- 两者都 null / 都 0 → 合法
- 其中一个有值，另一个必须为 null 或 0

**违反互斥的错误响应**：

```json
{
  "code": 400,
  "message": "depositMutex: 订金固定金额与比例互斥，只能填写其中一个",
  "success": false,
  "data": null
}
```

---

## 五、数据库行为

保存成功后数据库 `product_basic` 表：

| 前端提交 | `deposit_amount` 列 | `deposit_ratio` 列 |
|---------|--------------------|--------------------|
| `depositAmount=500, depositRatio=null` | `500.00` | `NULL` |
| `depositAmount=null, depositRatio=10` | `NULL` | `10` |
| 都为 null | `NULL` | `NULL` |

即使前端不传"对方字段"，后端也会**显式 SET NULL**，不会保留数据库里的历史残留值。

---

## 六、下单/报价逻辑未变

下单报价（`InternalProductService.fillPaymentInfo`）规则保持不变：

```
paymentType = 'DEPOSIT' 时：
  if depositAmount > 0: 使用固定金额
  else if depositRatio > 0: depositAmount = totalPrice × depositRatio / 100
  else: depositAmount = 0
```

因为现在产品保存阶段已经保证了两字段互斥，下单阶段永远走其中一条分支，不会再出现"两个都有值但比例被忽略"的脏数据场景。

---

## 七、影响范围

- 仅影响**管理后台产品编辑**的订金设置表单
- 小程序端 C 端算价、订单创建、订单详情等接口**零影响**
- 历史数据（已经同时有两字段值的产品）：后端读取逻辑不变，仍按固定额优先，不会异常。下次编辑保存时才会触发互斥校验
