docs(product-v2): 订金固定金额/比例 严格互斥校验 (PR #819)

通知前端:切换 tab 时必须清空对方字段再提交,两字段同时有值后端 400 拒绝
这个提交包含在:
API Changelog Bot 2026-04-18 14:50:19 +08:00
父节点 b212728b86
当前提交 f2c0dcd460

查看文件

@ -0,0 +1,112 @@
# 产品编辑 订金设置 固定金额/比例 严格互斥
> **服务**: 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 端算价、订单创建、订单详情等接口**零影响**
- 历史数据(已经同时有两字段值的产品):后端读取逻辑不变,仍按固定额优先,不会异常。下次编辑保存时才会触发互斥校验