hl-api-changelog/changelogs/2026-05/12_fix_admin_product-v2-mileage-override-and-child-price-zero.md
API Changelog Bot 3fccc2381b fix: 产品 V2 编辑两项修复(里程被覆盖 + 儿童售价=0)
PR #2067 (commit d30bd995):
- #2064 行程编排日里程手动改后保存被高德自动计算覆盖
- #2066 价格日历批量设置允许儿童售价等于 0(儿童免费)

测试服 admin token round-trip 4 case 全过(2026-05-12 10:14-10:15)。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 10:18:28 +08:00

5.2 KiB

后端 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 入库

关联