From 97608cfca6f89ce238e006007d9a52a5414cf362 Mon Sep 17 00:00:00 2001 From: API Changelog Bot Date: Mon, 7 Sep 2026 12:34:59 +0800 Subject: [PATCH] =?UTF-8?q?docs(changelog):=20#7244=20=E9=80=89=E5=93=81?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3=E9=80=8F=E5=87=BA=20productType=20=E4=B8=8E?= =?UTF-8?q?=E5=9B=A2=E6=9C=9F=E7=8F=AD=E6=9C=9F=E5=88=97=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01N7Xpgcv9nncadAXQtWhg4P --- ...€�出产品类型与团期班期列表-修改接口-管理后台.md | 428 ++++++++++++++++++ 1 file changed, 428 insertions(+) create mode 100644 changelogs-v2/2026-09/07_7244_选品接口透出产品类型与团期班期列表-修改接口-管理后台.md diff --git a/changelogs-v2/2026-09/07_7244_选品接口透出产品类型与团期班期列表-修改接口-管理后台.md b/changelogs-v2/2026-09/07_7244_选品接口透出产品类型与团期班期列表-修改接口-管理后台.md new file mode 100644 index 00000000..b9efb583 --- /dev/null +++ b/changelogs-v2/2026-09/07_7244_选品接口透出产品类型与团期班期列表-修改接口-管理后台.md @@ -0,0 +1,428 @@ +--- +schema: "hl-changelog/v2" +ticket: "7244" +title: "订单选品接口透出 productType 与团期班期列表 batches" +consumer: "admin" +author: "wx(GIT)" +change_type: "修改接口" +backend_status: "deployed" +gateway_status: "verified" +frontend_status: "pending" +frontend_owner: "mmg" +frontend_ref: "" +target_release: "" +verified_at: "2026-09-07" +status_note: "PR #7259 已合入 dev-v3(9800cddde);2026-09-07 测试环境网关实测 15/15 通过(AC-1/1b/2/3/4/5/6)。前端待办:新建订单选择 GROUP 产品时必须在选品页选定班期,未选不允许进入下一步——详见第八.5 节。" +updated_at: "2026-09-07" +base: "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>` + +`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 | 是 | 儿童价(金额字符串) | + +#### 请求示例 + +```http +GET /admin/product/item/order-picker?lineId=2044248925572919297&page=1&pageSize=20 +Authorization: Bearer {token} +``` + +#### 响应示例 + +测试环境实测,团期产品: + +```json +{ + "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`(测试环境实测): + +```json +{ + "productId": "2049754271968047106", + "name": "的风格发多少", + "productType": "CORE", + "batches": [] +} +``` + +该产品全部班期都已返团或已取消时,`batches` 同样是 `[]`,产品本身仍出现在列表里。 + +整页无匹配产品时: + +```json +{ "code": 200, "msg": null, "data": { "total": 0, "records": [] } } +``` + +订单侧班期统计服务不可用时,本接口**不失败**:该批班期按「无活跃订单」计算,`remainRooms` / `remainSlots` 可能偏大(偏向显示为可订),响应结构与 `code` 不变,服务端记 ERROR 日志。 + +#### 错误响应 + +未新增错误码,沿用既有: + +```json +{ + "code": 401, + "msg": "未登录或登录已过期", + "data": null +} +``` + +```json +{ + "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 待办) + +1. **`Step1Sku.vue`**: `productType === "GROUP"` 的产品卡展开显示 `batches[]`,每行建议格式 + `第{batchLabel}期 · {batchName} · {departureDate} 到 {endDate} · 剩 {remainRooms ?? "不限"} 房 · {batchStatusLabel}`; + `batchStatus` 不属于 `{ENROLLING, NEARLY_FULL}` 的班期灰显不可选。选中档位与班期后 emit 带上 `productBatchId` 与 `batchDepartureDate`。 +2. **GROUP 未选班期不允许进入第 3 步**。第 3 步的出发日直接取所选班期的 `departureDate`(不再让用户手选日期),报价 `POST /admin/product/item/{id}/quote` 传 `batchId`。 +3. **创建订单 payload**: `productBatchId = String(batch.batchId)`;非 GROUP 产品不传该字段。 +4. **深链**: 带 `?productBatchId=…` 进入时,在 `batches` 中预选该期;若该期不在列表里(已结束或已取消),给出提示而不是静默忽略。 +5. **金额只显示后端报价结果**,`batches[].tierPrices` 仅用于展示参考价,不参与前端算价。 + +--- + +## 九、相关历史 PR + +| PR | Issue | 说明 | 是否仍有效 | +|----|-------|------|------------| +| #7219 | #7189 | 团期看板以产品全班期为基底 + scope 筛选,`batchLabel` 期号口径在此确立 | ✅ 有效 | +| #7256 | #7245 | 班期名去空格 + 不限容量 remain 统一 null + board 库存同源 | ✅ 有效(本接口沿用同一口径) | +| **本 PR #7259** | **#7244** | 选品接口透出 `productType` 与 `batches` | ✅ 最新 | + +--- + +## 十、相关文档 + +- 关联 Issue: [wx/HL#7244](https://git.1814.love:8443/wx/HL/issues/7244) +- 关联 PR: [wx/HL#7259](https://git.1814.love:8443/wx/HL/pulls/7259) +- 期号口径来源: `changelogs-v2/2026-09/07_7189_团期看板产品全班期基底与scope范围筛选-修改接口-管理后台.md` +- 剩余库存口径来源: `changelogs-v2/2026-09/07_7245_团期看板班期名与不限容量口径修正-修改接口-管理后台.md` + +## 关联 / 联系人 + +### 链接 + +- **Issue**: [#7244](https://git.1814.love:8443/wx/HL/issues/7244) +- **PR**: [#7259](https://git.1814.love:8443/wx/HL/pulls/7259) +- **Merge commit**: [9800cddde](https://git.1814.love:8443/wx/HL/commit/9800cddde) + +### 联系人 + +- **后端负责人**: @wx +- **前端负责人**: @mmg