From 544277748924e965d42f8a3a330fa056f45da745 Mon Sep 17 00:00:00 2001 From: API Changelog Bot Date: Tue, 12 May 2026 16:45:23 +0800 Subject: [PATCH] docs(changelog): 12_fix_product_v2_toddler_discount_db_clear_on_update MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR #2111 / Issue #2110 — 修补 #2107 漏 UPDATE 路径; MP NOT_NULL 默认跳过 null 导致 child=0 时 toddler_discount DB 列残留. @TableField(updateStrategy=IGNORED) 强制 UPDATE 写入, 测试服 4 场景 DB 真测通过. 前端无需改动. Co-Authored-By: Claude Opus 4.7 (1M context) --- ..._v2_toddler_discount_db_clear_on_update.md | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 changelogs/2026-05/12_fix_product_v2_toddler_discount_db_clear_on_update.md diff --git a/changelogs/2026-05/12_fix_product_v2_toddler_discount_db_clear_on_update.md b/changelogs/2026-05/12_fix_product_v2_toddler_discount_db_clear_on_update.md new file mode 100644 index 0000000..da429be --- /dev/null +++ b/changelogs/2026-05/12_fix_product_v2_toddler_discount_db_clear_on_update.md @@ -0,0 +1,78 @@ +--- +date: 2026-05-12 +type: backend-fix +service: hl-product-service-v2 +priority: high +notify: ["@mmg"] +status: verified +--- + +# 价格日历: 小童优惠儿童=0 时 DB 列**真正清空**(补 #2107 漏网回归) + +> **PR**: [#2111](https://git.1814.love:8443/wx/HL/pulls/2111) — 已合并 dev + 部署测试服 +> **Issue**: [#2110](https://git.1814.love:8443/wx/HL/issues/2110) +> **接口**: `POST /admin/product/item/{id}/price-calendar/batch` (无 API 变化) +> **状态**: ✅ **测试服已验证** — 4 场景 DB 落库 SQL 真测通过 + +--- + +## 背景 + +#2107 (changelog `12_fix_product_v2_toddler_discount_normalize_and_validate.md`) 修了"儿童=0 时强制 toddlerDiscount=null"逻辑, 单测 + INSERT 场景验证通过, **遗漏了 UPDATE 场景**: + +- 原 DB 已有记录 `child=80, toddler=20` +- 用户改 child=0 保存 (前端把 toddler 输入框 disabled) +- 后端 step 0 把 `req.toddlerDiscount` 归一化为 null ✓ +- `expandToDailyRecords` 兜底 0→null ✓ +- **`batchUpsert` 走 `updateById(item)`, MP 默认 `FieldStrategy.NOT_NULL` 跳过 null 字段**, UPDATE SQL 不下发 `set toddler_discount=NULL`, **DB 列保留原值 20** + +## 修复 + +```java +public class ProductPriceCalendarDO extends BaseDO { + @TableField(updateStrategy = FieldStrategy.IGNORED) // ← 加这一行 + private BigDecimal toddlerDiscount; +} +``` + +`FieldStrategy.IGNORED` 强制 UPDATE 写入此字段, 即使 null 也下发 `set toddler_discount=NULL`, 让 step 0 归一化真正落库。 + +## 测试服真测 (硬性凭证) + +接口: `POST /admin/product/item/{id}/price-calendar/batch` +产品: 2053668473820622849 (test_admin 自有产品) + +``` +Scenario A: 已有 child=80,toddler=20 + 提交 child=0 + → DB seed: toddler_discount=20.00 (insert 阶段) + → DB after: toddler_discount=NULL ✓ (update 阶段强制写入) + +Scenario B: 已有 child=80,toddler=20 + 提交 child=60,toddler=0 + → DB seed: 20.00 + → DB after: NULL ✓ (expandToDailyRecords 0→null 归一化 + UPDATE 真写) + +Scenario C: 已有 child=80,toddler=20 + 提交 child=60,toddler=15 + → DB seed: 20.00 + → DB after: 15.00 ✓ (正常 UPDATE) + +Scenario D: 全新 child=0 + → DB after: NULL ✓ (INSERT 阶段 null 正常写入) +``` + +## 影响面 + +- **API 不变, 仅 DB 写入行为修正**, 前端无需改动 +- `infantPrice/singleRoomDiff` 等其他金额字段**不需要**同样调整 (0=合法值, 无 0→null 归一化语义) +- `deductStock/restoreStock` 走 load → setSold → updateById, toddler_discount 字段从 DB 加载原值原样写回, IGNORED 策略下等价无变化, 安全 + +## 风险 + +- 极小. `@TableField(updateStrategy=IGNORED)` 是 MP 官方文档行为, 仅影响**该单字段**的 UPDATE 写入策略, 不影响 INSERT/SELECT/DELETE。 + +## 历史脏数据 + +DB 里可能存在 `childSellPrice=0 && toddler_discount IS NOT NULL` 的脏数据(#2107 之前的残留)。**下次该区间编辑保存**会被本 PR 真正清掉, 用户无感知, 无需单独迁移脚本。 + +## 前端 @mmg 无需改动 + +仅是后端 UPDATE 写入策略修正, 接口/字段/行为全无变化。本 changelog 仅备案。