# 后端 fix 通知 — 产品 V2 编辑两项修复(里程被覆盖 + 儿童售价=0)

**日期**: 2026-05-12
**类型**: 后端修复 (PR #2067, merge commit `d30bd995`)
**模块**: 产品 V2 编辑 → 行程编排 / 价格日历批量
**通知**: @mmg
**前端是否需改动**: **可选**(后端兼容现有前端行为, 修复后即生效; 若要支持"取消手动锁回到自动计算"则需小调整, 见下)
**严重性**: P1(行程里程) + P2(儿童售价=0)

---

## 修复 1: 行程编排「当日里程」手动改后保存被高德覆盖 (Closes #2064)

### 现象
产品 V2 编辑页 → 第 2 步「行程编排」→ DAY N → 修改「里程」输入框为某个值(如 180 km)→ 保存 → 刷新页面 → 里程又变回了高德自动计算的数字。

### 根因
后端有 `mileageManualOverride` 开关字段标记是否人工覆盖。前端编辑 `dailyMileage` 时只传数值未带 override=true, 后端 MP `updateById` NOT_NULL 策略跳过 null 字段, DB 中 override 保持 0, 事务提交后 `ItineraryAutoCalculator` 调高德 API 重新覆盖用户输入。

### 修复
后端 `ProductItineraryService.convertDay` 兜底: 当前端传了 `dailyMileage` 但未显式传 `mileageManualOverride` 时, 自动视为手动覆盖(置为 TRUE), 阻断 autocalc 覆盖。

### 前端 mmg 完全无需改动
前端继续按当前逻辑只提交 `dailyMileage` 即可, 后端兜底已覆盖。

### 如果要支持「重置回自动计算」(可选, 不是必做)
若想加一个"刷新/重置"按钮让用户撤销手动锁、回到高德自动值, 需:
1. 该天 SaveReqVO 显式传 `dailyMileage: null` **且** `mileageManualOverride: false`
2. 保存后等 1-2 秒, 后端 autocalc 会重算并填入

截图里看到里程字段旁有圆形刷新图标和 × 按钮, 如果这两个图标已绑此语义, 检查一下提交时是否真的清空 dailyMileage + 显式 override=false。

---

## 修复 2: 价格日历批量设置允许儿童售价 = 0 (Closes #2066)

### 现象
价格日历批量设置中, 儿童售价填 `0` 报「儿童售价必须大于0」无法保存。业务上 `0` 表示儿童免费应允许。

### 修复
后端 4 处放宽:
1. `PriceCalendarBatchReqVO.childSellPrice` `@DecimalMin` 0.01 → 0.00, 文案改"儿童售价不能为负数"
2. `ProductPricingService` 第一道校验拆分: null → 原 REQUIRED 错误码; <0 → 新错误码 `PRICE_CHILD_SELL_PRICE_NEGATIVE` (410112)
3. `ProductPricingService` 固定订金模式: `childSellPrice=0` 时跳过"必须>订金"校验
4. `ProductBasicService` 反向校验对称处理: 调订金时若现存价格日历/班期 `childPrice=0` 视为免费跳过, 避免"正向能设 0、反向改订金又被卡"

成人售价校验维持 `>0` 不变, 不允许成人售价 0。

### 前端 mmg 行为变化
- 儿童售价输入框: 现在允许填 `0`(免费)
- 儿童售价 = 负数: 仍报错, 文案变成「儿童售价不能为负数」(原"必须大于0")
- 成人售价: 维持 `>0`, 不可填 0

如果前端有"儿童售价 ≥ 0.01"的本地校验, **请放宽到 ≥ 0**(允许 0.00, 不允许负数)。

---

## ✅ 测试服真测(2026-05-12 10:04 部署, 10:14-10:15 真测)

测试服 admin token (test_admin) 经网关 `api.test.1814.love:9443` 真测, 用产品 `2053662735618506754`:

### Case 1: BUG #2064 修复验证

| 步骤 | 操作 | 结果 |
|------|------|------|
| baseline | DAY 2 (dayId=2053662735677227011) `daily_mileage=140.0` `override=1` | — |
| 改动 | PUT `/admin/product/item/{id}/itinerary` DAY 2 `dailyMileage=888.0`, **不传 override** | HTTP 200, code=200, msg="行程保存成功" |
| 等 8s | autocalc 在 10:14:11 跑完 `自动计算每日里程完成, 天数=7` | — |
| 验证 | SQL `daily_mileage=888.0` `mileage_manual_override=1` | ✅ 888 未被高德覆盖 |

### Case 2: BUG #2066 修复验证

| 步骤 | 操作 | 结果 |
|------|------|------|
| 改动 | POST `/admin/product/item/{id}/price-calendar/batch`, `childSellPrice=0, adultSellPrice=1999, date=2026-12-15, tier=1, NORMAL` | HTTP 200, code=200, msg="价格日历设置成功" |
| 验证 | SQL `child_sell_price=0.00, adult_sell_price=1999.00, price_type=NORMAL` | ✅ child=0 落库 |

### Case 3: 边界 — 儿童售价 = -1 仍拦

- POST 同上 `childSellPrice=-1` → HTTP 200, **code=400** msg="**儿童售价不能为负数**" success=false ✅

### Case 4: 旁证 — autocalc 在 override=false 时正常生效

- DAY 3 显式传 `mileageManualOverride=false` 且不传 `dailyMileage` → 8s 后 SQL `daily_mileage=0.0 override=0`, 日志 10:15:36 `自动计算每日里程完成` — autocalc 正常生效(该天集散坐标相同高德返 0km, 是预期结果)

---

## 影响面 / 兼容性

- 接口签名零变化 — 任何前端代码不改也能直接用
- 已部署: 测试服 product-v2 双实例(8083 + 8183) 已是含修复的版本
- 历史已被高德覆盖的天: 下次用户手动改并保存即可锁住, 无需脏数据修复
- 已有 `mileage_manual_override=1` 的天不受影响
- 历史已有 `childSellPrice` 数据不受影响, 仅前向允许 0 入库

## 关联

- PR: https://git.1814.love:8443/wx/HL/pulls/2067
- Issue #2064: https://git.1814.love:8443/wx/HL/issues/2064
- Issue #2066: https://git.1814.love:8443/wx/HL/issues/2066
