20 KiB
schema, ticket, title, consumer, author, change_type, backend_status, gateway_status, frontend_status, frontend_owner, frontend_ref, target_release, verified_at, status_note, updated_at, base
| schema | ticket | title | consumer | author | change_type | backend_status | gateway_status | frontend_status | frontend_owner | frontend_ref | target_release | verified_at | status_note | updated_at | base |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| hl-changelog/v2 | 7244 | 订单选品接口透出 productType 与团期班期列表 batches | admin | wx(GIT) | 修改接口 | deployed | verified | verified | mmg | ccbbc446 | 2026-09-07 | 前端已交付并验证(功能主体 ccbbc446 + API 契约注释 7894071e):新增 groupBatch utils(可选仅 ENROLLING/NEARLY_FULL、剩房 ?? 不吞 0、期号取 batchLabel、batchId 全程字符串)与 useOrderDeepLink(详情 lineId 拉选品 batches 定位预选,缺期/已满警告转 Step1);Step1Sku GROUP 卡展开班期行、Step2Info batchLocked 锁出发日跳日历反解、Step3Confirm 增「第N期」、index canNext 缺班期拦截+创单 productBatchId 优先 draft、报价 quote 传所选 batchId。order-v2/new 定向 Vitest 29 绿 + checkpoint 全绿(含 Vitest 全量+生产构建)。 | 2026-09-07 | dev-v3 |
订单选品接口: 新增 productType 与内嵌可售班期列表 batches
服务: hl-product-service-v2 | PR: #7259 | Issue: #7244 | 合并提交:
9800cddde影响范围: 管理后台「新建订单 → 选择产品」页
⚠️ 关键变化
新建订单时,团期产品(productType === "GROUP")必须让运营选定一个班期,不选不允许进入下一步。班期数据现在直接内嵌在选品接口的每个产品项里,前端不需要再单独请求班期接口。
响应新增两个字段:productType(产品类型)与 batches(该产品当前可售班期列表)。11 个旧字段的名称、类型、顺序一字未动,不消费新字段的页面不受影响。
一、背景
新建订单页选完产品后直接让运营手填出发日期,团期产品因此可能建出「没有归属班期」或「出发日与任何班期都对不上」的订单,后续团期看板认领不到该单。
要在选品页当场选班期,就需要选品接口把班期带出来。原本可以再加一个「按产品查班期」的接口,但选品页是分页列表,每个产品一次请求会退化成 N+1;因此改为在选品接口内嵌,一次请求把本页所有团期产品的可售班期全部带回。
二、变更接口清单
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|---|---|---|---|---|---|
| 1 | 订单选品列表 | GET | /admin/product/item/order-picker |
响应新增字段 | 每个产品项新增 productType 与 batches[] |
入参、路径、错误码均未变。
三、接口详情
1. 订单选品列表 GET /admin/product/item/order-picker
VO: OrderPickerProductVO(本次新增两个静态嵌套类 OrderPickerProductVO.BatchItem、OrderPickerProductVO.BatchTierPrice)
使用场景
管理后台「新建订单」流程第 1 步「选择产品」页加载与翻页时调用。运营按主题或关键字筛出产品,选中产品后进入第 2 步选档位。本次改动后,团期产品的可售班期随产品一起返回,运营在同一页选定班期,不再需要单独请求班期接口,也不再手填出发日期。
入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
lineId |
query | Long | 否 | - | 主题/线路 ID,按主题筛选产品 |
keyword |
query | String | 否 | - | 产品名关键字 |
page |
query | Integer | 否 | 默认 1 | 页码 |
pageSize |
query | Integer | 否 | 默认 10 | 每页条数 |
出参 Result<PageResult<OrderPickerProductVO>>
data.records[] 的每一项:
| 字段 | 类型 | 说明 |
|---|---|---|
productId |
String | 产品 ID(雪花,字符串序列化)。非空 |
name |
String | 产品名。非空 |
subtitle |
String | 副标题。可空 |
tripDays |
Integer | 行程天数。可空 |
tripNights |
Integer | 行程晚数。可空 |
tags |
String[] | 标签。可空 |
coverImageUrl |
String | 封面图。可空 |
startPrice |
String | 起价(金额字符串)。可空 |
tierPrices |
Object[] | 产品档位起价(tierSeq / tierName / tierDescription / startPrice)。可空 |
hot |
Boolean | 是否热门。非空 |
nextSaleDate |
String | 最近可售日。可空 |
productType |
String | 新增。产品类型,见六.5。非空 |
batches |
Object[] | 新增。当前可售班期列表。非 GROUP 产品恒为 [](空数组,不是 null)。非空 |
batches[] 每一项
| 字段 | 类型 | 可空 | 说明 |
|---|---|---|---|
batchId |
String | 否 | 班期 ID(雪花,字符串序列化)。创建订单时作为 productBatchId 回传 |
batchNo |
String | 否 | 班期编号 |
batchLabel |
String | 否 | 期号,如 "7" 表示第 7 期。按该产品全部未删班期的出发日升序位置计算,与团期看板显示的期号一致 |
batchName |
String | 是 | 班期名(后端已去除前后空格) |
departureDate |
String | 否 | 出发日 yyyy-MM-dd |
endDate |
String | 是 | 返团日 yyyy-MM-dd |
enrollmentDeadline |
String | 是 | 报名截止日 yyyy-MM-dd |
maxRooms |
Integer | 否 | 房间数上限。本接口恒非 null,0 = 不限 |
remainRooms |
Integer | 是 | 剩余房数。null = 不限,0 = 已满。含运营线下占位房数 |
maxParticipants |
Integer | 是 | 人数上限,null 或 0 = 不限 |
remainSlots |
Integer | 是 | 剩余人数。null = 不限,0 = 已满 |
batchStatus |
String | 否 | 班期状态,见六.5 |
batchStatusLabel |
String | 否 | 班期状态中文文案,可直接显示 |
tierPrices |
Object[] | 否 | 该班期的档位价,至少 1 项 |
batches[].tierPrices[] 每一项
| 字段 | 类型 | 可空 | 说明 |
|---|---|---|---|
tierSeq |
Integer | 否 | 档位序号,未配置档位时为 1 |
tierName |
String | 否 | 档位名,未配置档位时为 "默认档" |
adultPrice |
String | 是 | 成人价(金额字符串) |
childPrice |
String | 是 | 儿童价(金额字符串) |
请求示例
GET /admin/product/item/order-picker?lineId=2044248925572919297&page=1&pageSize=20
Authorization: Bearer {token}
响应示例
测试环境实测,团期产品:
{
"code": 200,
"msg": null,
"data": {
"total": 1,
"records": [
{
"productId": "2044306857534636034",
"name": "冻干粉发短信给",
"subtitle": "发短信给对方搞定",
"tripDays": 3,
"tripNights": 2,
"tags": ["亲子", "研学", "露营"],
"coverImageUrl": "https://hlgl-test.oss-cn-beijing.aliyuncs.com/test/material/2026/03/07/ed278d1da74c3827cb06b7f0cf6091fe.jpg",
"startPrice": "1000.00",
"tierPrices": [
{ "tierSeq": 1, "tierName": "轻奢", "tierDescription": null, "startPrice": "1000.00" }
],
"hot": true,
"nextSaleDate": "2026-10-01",
"productType": "GROUP",
"batches": [
{
"batchId": "2052935476557328386",
"batchNo": "Q202610012052935476548939777",
"batchLabel": "7",
"batchName": "没,那你",
"departureDate": "2026-10-01",
"endDate": "2026-10-03",
"enrollmentDeadline": "2026-09-30",
"maxRooms": 80,
"remainRooms": 25,
"maxParticipants": 0,
"remainSlots": null,
"batchStatus": "ENROLLING",
"batchStatusLabel": "报名中",
"tierPrices": [
{ "tierSeq": 1, "tierName": "轻奢", "adultPrice": "2925.00", "childPrice": "2425.00" }
]
},
{
"batchId": "2096631555760807938",
"batchNo": "Q202612012096631555752419329",
"batchLabel": "8",
"batchName": "QA-7189-1201",
"departureDate": "2026-12-01",
"endDate": "2026-12-03",
"enrollmentDeadline": "2026-11-30",
"maxRooms": 4,
"remainRooms": 4,
"maxParticipants": 6,
"remainSlots": 6,
"batchStatus": "ENROLLING",
"batchStatusLabel": "报名中",
"tierPrices": [
{ "tierSeq": 1, "tierName": "轻奢", "adultPrice": "1000.00", "childPrice": "600.00" }
]
}
]
}
]
}
}
空数据 / 降级响应
非团期产品的 batches 是空数组而不是 null(测试环境实测):
{
"productId": "2049754271968047106",
"name": "的风格发多少",
"productType": "CORE",
"batches": []
}
该产品全部班期都已返团或已取消时,batches 同样是 [],产品本身仍出现在列表里。
整页无匹配产品时:
{ "code": 200, "msg": null, "data": { "total": 0, "records": [] } }
订单侧班期统计服务不可用时,本接口不失败:该批班期按「无活跃订单」计算,remainRooms / remainSlots 可能偏大(偏向显示为可订),响应结构与 code 不变,服务端记 ERROR 日志。
错误响应
未新增错误码,沿用既有:
{
"code": 401,
"msg": "未登录或登录已过期",
"data": null
}
{
"code": 403,
"msg": "无权限访问该数据",
"data": null
}
业务边界
- 鉴权: 需登录;
@DataScope行级校验产品数据权限,越权返 403。 - 只读: 无任何写操作,不产生数据变更,可重复调用。
- 班期可见性: 只返回「未取消 且 返团日未过(返团日为空视为未过)」的班期;已返团、取消中、已取消、已软删的班期不出现。满员(
FULL)的班期仍然返回,由前端灰显。 - 期号口径:
batchLabel按该产品全部未删班期的出发日升序位置计算,再做可售过滤——所以它与数组下标不对应,与团期看板显示的期号一致。 - 空值语义:
remainRooms/remainSlots为null表示不限,为0表示已满;maxRooms恒非 null,0表示不限。 - 降级: 订单侧统计不可用时按无活跃订单计算并记 ERROR,不 500、不阻断页面。
- 兼容: 11 个旧字段的名称、类型、顺序完全未变,不消费新字段的调用方无需改动。
四、契约约束与正确调用方式
本接口是只读 GET,无请求体。约束体现在怎么读响应,下面是三条读法对照。
✅ 正确 / ❌ 错误 读法对照
| 场景 | 读法 |
|---|---|
| ✅ 判断班期能否选中 | ["ENROLLING", "NEARLY_FULL"].includes(batch.batchStatus) |
| ❌ 判断班期能否选中 | batch.remainRooms > 0 → 不限容量时 remainRooms 为 null,null > 0 为 false,会把不限的班期误判成不可选 |
| ✅ 显示剩余房数 | batch.remainRooms ?? "不限" |
| ❌ 显示剩余房数 | batch.remainRooms || "不限" → 0(已满)会被显示成「不限」 |
| ✅ 取期号 | batch.batchLabel |
| ❌ 取期号 | index + 1 → batches 已过滤掉已返团班期,下标与真实期号对不上 |
| ✅ 回传班期 | productBatchId: String(batch.batchId) |
| ❌ 回传班期 | productBatchId: Number(batch.batchId) → 雪花 ID 超出 JS 安全整数范围,精度丢失 |
哪些班期会出现在 batches 里
后端已过滤,只返回「未取消 且 返团日未过(返团日为空视为未过)」的班期。已返团、取消中、已取消、已软删的班期不会出现,前端不需要再过滤一次。
五、数据库行为
本接口为只读查询,无任何写操作,不产生数据变更。
六、边界行为
- 未登录 → 401(网关拦截)
- 无该产品数据权限 → 403(
@DataScope行级校验) - 该产品无任何可售班期(全部已返团/已取消)→
batches为[],产品本身仍出现在列表里 - 非 GROUP 产品 →
batches恒为[],不是null - 班期未配置档位 →
tierPrices返回一项tierSeq=1、tierName="默认档"的兜底档 - 订单侧统计服务降级 → 该批班期按「无活跃订单」计算,剩余数可能偏大,接口不 500 不阻断页面(服务端记 ERROR 日志)
- 本页可售班期总数超过 500 → 服务端记 warn,不截断,仍全量返回
六.5、枚举 / 数据字典
productType(com.hulalv.enums.ProductTypeEnum)
所属字段: records[].productType | 类型: String
| 值 | 中文 | 说明 |
|---|---|---|
CORE |
核心产品 | batches 恒为 [] |
GROUP |
小蒙马(即团期产品) | batches 为可售班期列表,创建订单必须选定其一 |
CUSTOM |
私人定制 | batches 恒为 [] |
(中文名取自枚举定义本身,与后台产品管理页的类型显示一致。)
batchStatus(com.hulalv.enums.BatchStatusEnum)
所属字段: records[].batches[].batchStatus | 类型: String
| 值 | 中文 | 说明 |
|---|---|---|
ENROLLING |
报名中 | 可选中下单;会出现在 batches |
NEARLY_FULL |
即将满额 | 可选中下单;会出现在 batches |
FULL |
已满额 | 不可选(灰显);仍会出现在 batches |
FINISHED |
已结束 | 不可选;一般不会出现(返团日已过会被过滤) |
CANCELLING |
取消中 | 不可选;不会出现(后端已过滤) |
CANCELLED |
已取消 | 不可选;不会出现(后端已过滤) |
六.6、修改前后对比
字段级对比
| 字段 | 改前 | 改后 |
|---|---|---|
records[].productType |
不存在 | 新增,CORE / GROUP / CUSTOM |
records[].batches |
不存在 | 新增,可售班期数组(非 GROUP 恒 []) |
| 其余 11 个字段 | 有 | 完全不变(名称、类型、顺序、取值均未动) |
行为级对比
| 行为 | 改前 | 改后 |
|---|---|---|
| 选品页拿班期 | 拿不到,需另外调接口或让运营手填出发日 | 一次请求随产品带回 |
| 班期期号 | 无 | batchLabel,与团期看板一致 |
| 请求次数 | 1 次 | 仍是 1 次(班期用一次 IN 查询取回,不随产品数增长) |
六.7、影响评估
- 是否破坏向后兼容: 否。11 个旧字段完全未动,只追加两个新字段。
- 前端是否必须同步上线: 否(不上线则维持现状,选品页看不到班期)。但团期订单必须选班期这条业务要求要靠前端落地,见八.5。
- 前端 workaround 清理点: 若前端此前为团期产品单独请求过班期接口或让用户手填出发日,改用本接口的
batches后可撤除。
七、不影响范围
- 仅影响: 管理后台「新建订单 → 选择产品」页调用的
GET /admin/product/item/order-picker - 零影响:
- 订单创建接口
POST /v3/admin/order(本次未改 order-v3 任何代码) - 报价
POST /admin/product/item/{id}/quote、价格日历pricing-calendar - 团期看板全部读端点(
/v3/admin/order/group-batch系列) - 产品侧班期管理接口(
/admin/product/item/{id}/schedule系列) - 小程序 C 端全部接口
- 存量数据(不做任何数据迁移,班期名的前后空格只在输出时去除,不回写库)
- 订单创建接口
八、测试环境已验证
2026-09-07 测试环境(api.test.1814.love)网关实测 15/15 通过:
GET /admin/product/item/order-picker?lineId=2044248925572919297&page=1&pageSize=20 → 200 ✓
productType=GROUP,batches 14 个字段齐全 ✓
各字段与 GET /admin/product/item/2044306857534636034/schedule/list 逐字段一致 ✓
6 个已返团班期被过滤,10-01 与 12-01 期在列,按 departureDate 升序 ✓
期号 10-01="7"、12-01="8",与 /v3/admin/order/group-batch/board?scope=ALL 逐一相等 ✓
班期名 " 没,那你" → "没,那你"(已去空格) ✓
11 个旧字段一个不少 ✓
线下占位: 容量 8 房 + 占位 3 房 + 线上 0 单 → remainRooms=5 ✓
不限容量: maxRooms=0 → remainRooms=null, remainSlots=null, batchStatus=ENROLLING ✓
满员: 容量 1 房 + 占位 1 房 → remainRooms=0, FULL, "已满额", 且仍在 batches 里 ✓
取消与软删的班期不再出现在 batches ✓
非 GROUP 产品 batches 为 [] 而非 null ✓
验证产品: productId=2044306857534636034(冻干粉发短信给,GROUP,8 期)、productId=2049754271968047106(的风格发多少,CORE)
单元测试: mvn -pl hl-product-service-v2 test → Tests run: 1574, Failures: 0, Errors: 0, Skipped: 0
八.5、前端配套(mmg 待办)
2026-09-07 13:50 复测结论:后端接口已就绪。
GET /admin/product/item/order-picker?lineId=2044248925572919297对产品「冻干粉发短信给」返回productType=GROUP与 2 期可售班期——第 7 期「没,那你」2026-10-01→2026-10-03 剩 25 房 ENROLLING;第 8 期「QA-7189-1201」2026-12-01→2026-12-03 剩 4 房 ENROLLING。但此刻管理后台「新建订单 → 选择产品」页展开产品后仍只显示档位、没有班期列表,经查origin/master的src/views/order-v2/new/components/Step1Sku.vue中batches/productType/batchLabel/productBatchId均未出现(0 次命中),即前端尚未消费本契约。这不是后端缺陷,下面 5 条待办完成后该页即可选班期。
Step1Sku.vue:productType === "GROUP"的产品卡展开显示batches[],每行建议格式第{batchLabel}期 · {batchName} · {departureDate} 到 {endDate} · 剩 {remainRooms ?? "不限"} 房 · {batchStatusLabel};batchStatus不属于{ENROLLING, NEARLY_FULL}的班期灰显不可选。选中档位与班期后 emit 带上productBatchId与batchDepartureDate。- GROUP 未选班期不允许进入第 3 步。第 3 步的出发日直接取所选班期的
departureDate(不再让用户手选日期),报价POST /admin/product/item/{id}/quote传batchId。 - 创建订单 payload:
productBatchId = String(batch.batchId);非 GROUP 产品不传该字段。 - 深链: 带
?productBatchId=…进入时,在batches中预选该期;若该期不在列表里(已结束或已取消),给出提示而不是静默忽略。 - 金额只显示后端报价结果,
batches[].tierPrices仅用于展示参考价,不参与前端算价。
九、相关历史 PR
| PR | Issue | 说明 | 是否仍有效 |
|---|---|---|---|
| #7219 | #7189 | 团期看板以产品全班期为基底 + scope 筛选,batchLabel 期号口径在此确立 |
✅ 有效 |
| #7256 | #7245 | 班期名去空格 + 不限容量 remain 统一 null + board 库存同源 | ✅ 有效(本接口沿用同一口径) |
| 本 PR #7259 | #7244 | 选品接口透出 productType 与 batches |
✅ 最新 |
十、相关文档
- 关联 Issue: wx/HL#7244
- 关联 PR: wx/HL#7259
- 期号口径来源:
changelogs-v2/2026-09/07_7189_团期看板产品全班期基底与scope范围筛选-修改接口-管理后台.md - 剩余库存口径来源:
changelogs-v2/2026-09/07_7245_团期看板班期名与不限容量口径修正-修改接口-管理后台.md