hl-api-changelog/changelogs/2026-05/08_feat_single_room_diff.md
API Changelog Bot d240ef0e96 feat: 价格日历加单房差字段 (PR #1901)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 21:31:20 +08:00

9.5 KiB

价格日历加单房差字段:成人=1 时计入总价

服务: hl-product-service-v2(8093)+ hl-order-service-v2(8094) PR: #1901 Issue: #1900 日期: 2026-05-08 影响范围: 管理端价格日历(所有产品类型)+ GROUP 班期编辑器 + 小程序订单确认页价格 / 明细面板 + 管理端订单详情明细面板


⚠️ 关键变化(管理端 + 小程序必读)

价格日历 / 班期新增 singleRoomDiff 必填字段(单房差):

  • 管理端"编辑价格区间"弹窗 + GROUP"班期编辑"弹窗:4 个价格输入框旁加一列"单房差 ¥xxx"必填输入框(0 = 不收)
  • 小程序订单确认页:adultCount == 1 触发(严格 == 1,与儿童/小童/幼童数量无关),后端把 singleRoomDiff 累加到 totalPrice
  • 订单详情明细面板:singleRoomSurcharge > 0 时多出一行"单房差 ¥xxx",= 0 或老订单缺字段时不展示
  • 定金不变:比例定金按 baseTotal(不含单房差)算;固定/人定金不动

一、背景

1 个成人单住一房时,实际成本高于双人均摊 — 管理员需要为此场景配置单房差。本期实现:

  1. 价格日历(product_price_calendar)+ 班期表(group_tour_batch)各加 single_room_diff DECIMAL(12,2) NOT NULL DEFAULT 0
  2. 算价时严格 adultCount == 1 触发,累加到 totalPrice + 写入 priceBreakdown JSON
  3. 订单明细多一行展示
  4. 定金算法不变(具体见下文 D2/D3)

二、变更接口清单

# 接口 方法 路径 变更类型 说明
1 管理端批量保存价格日历 POST /admin/product/{id}/price-calendar/batch 请求体加必填字段 singleRoomDiff 必填(@NotNull @DecimalMin("0.00"))
2 管理端查询价格日历 GET /admin/product/{id}/price-calendar 响应加字段 每条记录回传 singleRoomDiff
3 管理端 GROUP 班期保存 POST/PUT (Schedule 系列) 请求体加必填字段 singleRoomDiff 必填
4 管理端 GROUP 班期查询 GET (Schedule 系列) 响应加字段 回传 singleRoomDiff
5 内部简单报价 GET /internal/product/{productId}/simple-quote 响应加 2 字段 singleRoomDiff(配置值)+ singleRoomSurcharge(本次实际加价,0 或 = diff)
6 内部 GROUP 报价 GET /internal/product/{productId}/group-quote 响应加 2 字段 同上
7 小程序订单详情 GET /mp/order/{id} 行为变更 priceBreakdown.items[]singleRoomSurcharge > 0 时多出一行 "单房差"
8 管理端订单详情 GET /admin/order/{id} 无变化 admin 端不读 priceBreakdown,只读汇总字段
9 创建订单(C 端 / admin) POST /mp/order/create 行为变更 OrderInfo.totalPrice 已包含单房差;OrderInfo.priceBreakdown JSON 包含 singleRoomDiff + singleRoomSurcharge 字段

三、决策点

D1 触发条件(严格)

if (adultCount == 1) {
    singleRoomSurcharge = singleRoomDiff;
} else {
    singleRoomSurcharge = 0;
}

只看 adultCount 是否严格等于 1,与儿童 / 小童 / 幼童数量无关。adultCount == 0 不触发(下单本来就要求至少 1 成人,@Min(1));adultCount >= 2 不触发。

D2 比例定金基数(不含单房差)

比例定金 = baseTotal × ratio / 100
其中 baseTotal = totalPrice - singleRoomSurcharge

保证"定金计算逻辑不变":不含单房差的基础价 × 比例 = 定金;尾款 = totalPrice - 定金(自然就含了单房差)。

D3 固定/人定金不动

固定定金 = depositAmount/人 × 定金人头(成人 + 儿童 + 小童)

与单房差完全解耦。

D7 落库归属(不进 surcharge_amount)

字段 / 表 是否含单房差
order_info.total_price (含单房差)
order_info.price_breakdown JSON (singleRoomSurcharge 字段)
order_info.surcharge_amount (语义=后期附加费,与单房差解耦)
order_surcharge (不写单房差行)

D9 修订单 adminEditOrder 改 4 count 不重算单房差

管理端"编辑订单"弹窗改成人/儿童/小童/幼童数后,后端不会重新计算 totalPrice / depositAmount / 单房差(维持现有契约,Line 140 的 manualPrice 注释已说明:"调价统一走加优惠/加附加费接口")。

影响场景:

  • 用户原下单成人=1 → 含单房差 200,定制师改为成人=2 → totalPrice 仍含 200(应不含),客户多付 200
  • 反之改为成人=1,totalPrice 缺 200(应含),少收 200

应对:定制师改人数后,如需调整单房差,通过"加附加费"接口手动落 order_surcharge 一笔(正负皆可)。本期不开发自动重算路径,后续单独工单再做。


四、接口详情

1. 管理端批量保存价格日历 POST /admin/product/{id}/price-calendar/batch

VO: PriceCalendarBatchReqVO

入参变化

新增字段:

{
  "startDate": "2026-04-01",
  "endDate": "2026-04-30",
  "tierSeq": 1,
  "priceType": "NORMAL",
  "adultSellPrice": 4105,
  "childSellPrice": 1865,
  "toddlerDiscount": -500,
  "infantPrice": 0,
  "singleRoomDiff": 200,
  "dailyStock": null
}
字段 必填 校验 说明
singleRoomDiff 必填 @NotNull @DecimalMin("0.00") 不传 → 400;< 0 → 400

5. 内部简单报价 GET /internal/product/{productId}/simple-quote

响应 VO 新增字段:

{
  "adultPrice": 4105,
  "childPrice": 1865,
  ...,
  "adultTotal": 4105,
  ...,
  "singleRoomDiff": 200,
  "singleRoomSurcharge": 200,
  "totalPrice": 4305,
  "depositAmount": 100,
  "stock": 99
}
  • singleRoomDiff — 价格日历配置的单房差(无论 adultCount 是多少都返回)
  • singleRoomSurcharge — 本次报价实际加价金额:adultCount == 1 ? singleRoomDiff : 0
  • totalPrice 已经包含 singleRoomSurcharge
  • depositAmount(比例定金时)按 (totalPrice - singleRoomSurcharge) × ratio

7. 小程序订单详情 GET /mp/order/{id} 价格明细行为变更

priceBreakdown.items[] 数组结构:

{
  "items": [
    {"label": "成人", "unitPrice": 4105, "quantity": 1, "amount": 4105},
    {"label": "儿童", "unitPrice": 1865, "quantity": 0, "amount": 0},
    {"label": "小童", "unitPrice": 1365, "quantity": 0, "amount": 0},
    {"label": "幼童", "unitPrice": 0, "quantity": 0, "amount": 0},
    {"label": "单房差", "unitPrice": null, "quantity": null, "amount": 200}
  ]
}

单房差行的特殊处理:

  • unitPrice / quantitynull(整笔费用,不按人头)
  • amount 即为本次单房差金额
  • 仅当 singleRoomSurcharge > 0 时才出现此行;= 0 或老订单缺字段时不展示

五、前端改动建议(mmg)

管理端

价格日历 - "编辑价格区间"弹窗

参考用户截图布局,在"幼童"输入框右侧加一列必填输入框:

[成人 *] [儿童 *] [小童优惠额] [幼童] [单房差 *]
  • placeholder: ¥ 必填,0=不收
  • 提示文案 / hover tip:"成人=1 人下单时叠加到总价"
  • 校验:必填 + 不能为负数

GROUP 班期编辑

同上,在班期价格区域加"单房差"必填输入框。

小程序

订单确认页

  • 总价 / 定金:直接展示后端返回的 totalPrice / depositAmount,前端不要自己加单房差(防止双计)
  • 明细面板:从后端返回的 priceBreakdown.items[] 直接渲染,如有 label === "单房差" 一行,展示 amount,无 unitPrice / quantity

不需要改造

  • 选择人数控件:不需要改(adultCount = 1 时由后端自动加单房差,前端无感)
  • 定金 / 尾款显示:无变化

管理端订单详情

  • 不读 priceBreakdown,只读 totalPrice 汇总字段,无需改造

六、向后兼容

  • 老 calendar / batch 数据:single_room_diff 列 DEFAULT 0,行为=关闭单房差,与原逻辑一致
  • 老订单:priceBreakdown JSON 缺 singleRoomSurcharge 字段时,解析端 isMissingNode() 跳过,不报错不展示空行
  • 老 App 仍传 singleRoomDiff 不会(本来就没此字段)
  • 新建/编辑必填:管理员每次保存价格区间或班期都得显式填(@NotNull),不允许默认 0

七、@mmg 关注点

  1. 管理端价格日历"编辑价格区间"弹窗加单房差必填输入框
  2. 管理端 GROUP 班期编辑器加同款输入框
  3. 小程序订单确认页明细面板渲染"单房差"行(条件出现)
  4. D9 注意:管理端"编辑订单"弹窗改成人/儿童/小童/幼童数后,后端不会重算单房差。建议在该弹窗加文案提示:"如改人数导致单房差变化,请通过『加附加费』接口手动调整"(可选,本期不强制)

八、错误码

无新增错误码。校验失败统一走 Spring Validation 默认 400 message。


九、影响范围(经核确认无影响)

系统 是否影响 说明
早鸟优惠 order_discount 表减尾款,与定金无关;min(totalPrice) 上限保护
微信支付 公式 totalPrice - discount + surcharge 自洽
退款 refundAmount 是 admin 手输入,代码层不依赖 totalPrice 拆分
起步价 / 列表"¥xxx 起" 不算单房差(展示用)
保险费 按人头 × 保单单价
12301 上报 团费按 totalPrice 上报,自洽
合同 PDF 团费金额 = totalPrice
C 端价格日历视图 只展示成人/儿童/小童/幼童 4 价
兜底对账 totalPrice - discount + surcharge 公式自洽
order_surcharge 表 / 后期附加费 单房差不进此表(D7)