diff --git a/changelogs-v2/2026-09/21_8093_套用用餐模版改只读返回组合结果与模版桌人创建人搜索删除-修改接口-管理后台.md b/changelogs-v2/2026-09/21_8093_套用用餐模版改只读返回组合结果与模版桌人创建人搜索删除-修改接口-管理后台.md new file mode 100644 index 00000000..12a069a9 --- /dev/null +++ b/changelogs-v2/2026-09/21_8093_套用用餐模版改只读返回组合结果与模版桌人创建人搜索删除-修改接口-管理后台.md @@ -0,0 +1,408 @@ +--- +schema: "hl-changelog/v2" +ticket: "8093" +title: "套用用餐模版改为只读返回组合结果,模版加桌数人数与创建人、模糊搜索与删除" +consumer: "admin" +author: "lc(GIT)" +change_type: "修改接口" +backend_status: "deployed" +gateway_status: "verified" +frontend_status: "pending" +frontend_owner: "" +frontend_ref: "" +target_release: "" +verified_at: "" +status_note: "套用用餐模版由写库改为只读:只返回「模版+订单出发日」组合出来的行,不含订单原有行、库里一行不动;订单原有行由前端把这些行原样提交整单保存时按现有规则软删。行上人数、桌数、价钱、桌/人、金额后端已算好。模版列表新增创建人姓名与行上桌数、人数、桌/人;模版名由精确改模糊并新增创建人模糊搜索;新增删除模版接口。前端必须按本文改调用方式。" +updated_at: "2026-09-21" +base: "dev-v3" +--- + +# order-v3: 套用用餐模版改为只读返回组合结果,模版加桌数人数与创建人、模糊搜索与删除 + +> **服务**: hl-order-service-v3 +> **PR**: #8105 +> **Issue**: #8093 +> **日期**: 2026-09-21 +> **影响范围**: 管理后台订单详情「用餐」页签的套用模版与模版列表;新增删除模版 + +--- + +## ⚠️ 关键变化 + +- 🔁 **套用模版不再写库(破坏性)**:`POST /v3/admin/order/meal-template/apply` 由写接口变为只读接口。调用它**不会**把模版内容存进订单,返回里也**不再包含**这张订单原来的用餐行,只返回「模版 + 订单出发日」组合出来的行。原来的行要等前端把这些行**原样提交整单保存**(`POST /v3/admin/order/meal-info/save`)时,才按现有「原来有而这次没传上来的软删」规则去掉。**前端如果沿用「套用成功后重新查一次列表就当保存好了」的做法,用户的套用结果不会入库。** +- ✅ **套用返回的行后端已算好**:人数、桌数、价钱、桌/人、金额都由后端给出,免人数 / 免金额 / 其他成本为 0,前端直接展示,不需要再算金额。 +- 🆕 **套用返回的行新增 `priceUnit`(桌/人)**:由桌数是否为 0 派生,取值 `person` / `table`,不存库。3.1–3.4 的行**没有**这个字段。 +- 🔁 **模版名搜索由精确改为模糊**:`templateName` 改为「包含即命中」。原来传全名的调用仍能命中,但会一并带出名称含该串的其他模版。 +- 🆕 **模版列表新增创建人**:模版级新增 `creatorName`(中文姓名),并新增同名入参按创建人模糊搜索。 +- 🆕 **新增删除模版接口** `POST /v3/admin/order/meal-template/delete`。 + +--- + +## 二、变更接口清单 + +| # | 接口 | 方法 | 路径 | 变更类型 | 说明 | +|---|---|---|---|---|---| +| 1 | 套用用餐模版 | POST | `/v3/admin/order/meal-template/apply` | 修改 | 由写库改为只读;返回内容变化;行上新增 `priceUnit` | +| 2 | 查询用餐模版 | GET | `/v3/admin/order/meal-template/list` | 修改 | 新增入参 `creatorName`;`templateName` 改模糊;出参新增 `creatorName` 与行上 `tableCount`/`personCount`/`priceUnit` | +| 3 | 保存为用餐模版 | POST | `/v3/admin/order/meal-template/save` | 修改 | 入参不变;模版一并存下源用餐行的桌数、人数;出参行新增上述三个字段 | +| 4 | 删除用餐模版 | POST | `/v3/admin/order/meal-template/delete` | 新增 | 按模版ID软删该模版全部行 | + +3.1 查询、3.2 生成、3.3 整单保存、3.4 重置的入参与出参**一个字段都没变**。 + +--- + +## 三、接口详情 + +### 1. 套用用餐模版 `POST /v3/admin/order/meal-template/apply` + +**VO**: `MealTemplateApplyReqVO` → `OrderMealInfoListRespVO` + +#### 使用场景 + +订单详情「用餐」页签点「套用模版」,把某个模版的内容按订单出发日铺开,**展示给用户预览**;用户确认后再点保存。 + +#### 入参字段表 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| orderId | Body | String | 是 | 雪花 ID | 订单 ID(不变) | +| templateId | Body | String | 是 | 雪花 ID | 模版 ID(不变) | + +#### 出参字段表 + +| 字段 | 类型 | 说明 | +|------|------|------| +| items | Array | **内容变化**。只含这个模版套出来的行,**不含**该订单原来的用餐行 | +| items[].mealInfoId | String | **恒为 `null`**:这些行还没入库 | +| items[].dayNumber | Integer | 模版的第几日 | +| items[].mealDate | String | 订单出发日 + dayNumber − 1(出发日是第 1 日) | +| items[].restaurantId / restaurantName / dishId / dishName / unitPrice / settleType / settleTypeName | — | 取模版,原样返回 | +| items[].tableCount | Integer | **后端算好**。取模版的桌数 | +| items[].personCount | Integer | **后端算好**。桌数为 0(按人)取订单人数;桌数不为 0(按桌)为 0 | +| items[].priceUnit | String | **新增**。`person`(桌数为 0)/ `table`(桌数不为 0),派生值,不存库 | +| items[].freePersonCount / freeAmount / otherCost | — | **恒为 0** | +| items[].amount | Number | **后端算好**。按人 = 单价 ×(人数 − 免人数)− 免金额 + 其他成本;按桌 = 单价 × 桌数 − 免金额 + 其他成本 | +| totalAmount | Number | 本次返回各行金额之和 | +| orderId / groupBatchId / batchNo / departDate / returnDate / personCount / orderEditable / generated | — | 口径与 3.1 相同,未变 | + +#### 请求示例 + +```http +POST /v3/admin/order/meal-template/apply +Authorization: Bearer +Content-Type: application/json + +{ "orderId": "2101846199160188929", "templateId": "2101952572174860290" } +``` + +#### 响应示例 + +订单出发日 `2026-11-10`、订单人数 2,模版 3 行(早餐默认行、午餐按人 30 元、晚餐按桌 888 元 × 2 桌): + +```json +{ + "code": 200, + "message": "成功", + "success": true, + "data": { + "orderId": "2101846199160188929", + "departDate": "2026-11-10", + "personCount": 2, + "orderEditable": true, + "generated": true, + "totalAmount": 1836.00, + "items": [ + { + "mealInfoId": null, + "mealType": "BREAKFAST", "dayNumber": 1, "mealDate": "2026-11-10", + "restaurantName": "HL8093-TEST-默认餐厅", "dishName": "HL8093-TEST-默认餐", + "unitPrice": 0.00, "tableCount": 0, "personCount": 2, "priceUnit": "person", + "freePersonCount": 0, "freeAmount": 0.00, "otherCost": 0.00, "amount": 0.00 + }, + { + "mealInfoId": null, + "mealType": "LUNCH", "dayNumber": 1, "mealDate": "2026-11-10", + "restaurantName": "HL8093-TEST-按人餐厅", "dishName": "HL8093-TEST-按人餐", + "unitPrice": 30.00, "tableCount": 0, "personCount": 2, "priceUnit": "person", + "freePersonCount": 0, "freeAmount": 0.00, "otherCost": 0.00, "amount": 60.00, + "settleType": "sign", "settleTypeName": "签单" + }, + { + "mealInfoId": null, + "mealType": "DINNER", "dayNumber": 1, "mealDate": "2026-11-10", + "restaurantName": "HL8093-TEST-按桌餐厅", "dishName": "HL8093-TEST-按桌餐", + "unitPrice": 888.00, "tableCount": 2, "personCount": 0, "priceUnit": "table", + "freePersonCount": 0, "freeAmount": 0.00, "otherCost": 0.00, "amount": 1776.00, + "settleType": "cash", "settleTypeName": "现付" + } + ] + } +} +``` + +#### 空数据 / 降级响应 + +- 模版一行都没有:`items` 为 `[]`、`totalAmount` 为 `0.00`,`code` 仍为 200。 +- 订单没有出发日期:无法对天,`items` 为 `[]`,`code` 200,`message` 为「订单缺少出发日期」提示(589602 文案)。 + +#### 错误响应 + +```json +{ "code": 589607, "message": "订单已完成或已取消,不能修改用餐信息", "success": false, "data": null } +``` + +订单不可写按 581045 / 581008;订单不存在按订单模块现有错误码。这些判断与改动前一致。 + +#### 业务边界 + +- **调用套用不改库**:调用前后用 3.1 按该订单查询,返回逐字段完全一致。 +- 模版第 d 日超出订单行程天数时不做限制,照落在出发日 + d − 1。 +- 模版值原样带出,不核对餐食、餐厅是否还存在或已下架。 + +### 2. 查询用餐模版 `GET /v3/admin/order/meal-template/list` + +**VO**: `MealTemplateListReqVO` → `List` + +#### 使用场景 + +「套用模版」弹层里选模版,支持按模版名、创建人搜索。 + +#### 入参字段表 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| templateId | Query | String | 否 | 雪花 ID | 精确查;**传了就忽略下面两个条件** | +| templateName | Query | String | 否 | ≤ 500 字符 | **由精确匹配改为模糊匹配**(包含即命中) | +| creatorName | Query | String | 否 | ≤ 100 字符 | **新增**。按创建人中文姓名模糊匹配(包含即命中,忽略大小写) | + +组合口径:`templateName` 与 `creatorName` 同传取**同时满足**的模版;只传一个按该条件筛;都不传返回全部;传 `templateId` 时按 ID 精确查且不受这两个条件影响。 + +#### 出参字段表 + +| 字段 | 类型 | 说明 | +|------|------|------| +| [].templateId / templateName | — | 不变 | +| [].creatorName | String | **新增**。创建人中文姓名:企微姓名,没绑企微取登录名;取不到时为 `null` | +| [].items[].tableCount | Integer | **新增**。桌数,0 表示按人算 | +| [].items[].personCount | Integer | **新增**。人数(模版内容回显用) | +| [].items[].priceUnit | String | **新增**。`person` / `table`,由桌数派生,不存库 | +| [].items[] 其余字段 | — | `restaurantId`、`restaurantName`、`dishId`、`dishCode`、`dishName`、`mealType`、`dayNumber`、`unitPrice`、`settleType`、`settleTypeName` 均不变 | + +#### 请求示例 + +```http +GET /v3/admin/order/meal-template/list?templateName=HL8093&creatorName=%E5%88%98 +Authorization: Bearer +``` + +`creatorName` 是中文时必须按 URL 编码传。 + +#### 响应示例 + +```json +{ + "code": 200, + "message": "成功", + "success": true, + "data": [ + { + "templateId": "2101952572174860290", + "templateName": "HL8093-TEST-桌人模版", + "creatorName": "刘畅", + "items": [ + { + "mealType": "LUNCH", "dayNumber": 1, + "restaurantName": "HL8093-TEST-按人餐厅", "dishName": "HL8093-TEST-按人餐", + "unitPrice": 30.00, "tableCount": 0, "personCount": 4, "priceUnit": "person", + "settleType": "sign", "settleTypeName": "签单" + }, + { + "mealType": "DINNER", "dayNumber": 1, + "restaurantName": "HL8093-TEST-按桌餐厅", "dishName": "HL8093-TEST-按桌餐", + "unitPrice": 888.00, "tableCount": 2, "personCount": 0, "priceUnit": "table", + "settleType": "cash", "settleTypeName": "现付" + } + ] + } + ] +} +``` + +#### 空数据 / 降级响应 + +- `data` 为 `[]`:没有同时满足条件的模版。 +- 用户服务取不到姓名时,该模版 `creatorName` 为 `null`,列表照常返回;此时若传了 `creatorName` 条件,该模版不会被返回。 + +#### 错误响应 + +```json +{ "code": 400, "message": "创建人姓名最多100字符", "success": false, "data": null } +``` + +#### 业务边界 + +- 按模版 ID 分组,重名的是各自独立的模版;列表按模版 ID 升序(保存先后)。 +- 模版名片段里的 `%`、`_` 已做转义,不会被当通配符放大匹配范围。 + +### 3. 保存为用餐模版 `POST /v3/admin/order/meal-template/save` + +**VO**: `MealTemplateSaveReqVO` → `MealTemplateRespVO` + +#### 入参字段表 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| templateName | Body | String | 是 | ≤ 500 字符 | 不变 | +| mealInfoIds | Body | Array | 是 | 非空 | 不变 | + +**入参没有变化**,前端不需要多传字段。 + +#### 出参字段表 + +| 字段 | 类型 | 说明 | +|------|------|------| +| creatorName | String | **新增**。当前操作人的中文姓名 | +| items[].tableCount / personCount / priceUnit | — | **新增**,口径同 4.1 | + +#### 业务边界 + +- 模版现在会一并存下源用餐行的**桌数、人数**;改动前不存,所以**已有的老模版这两项都是 0**(等同「按人算、人数未知」),套用时按人的行人数仍取订单人数,金额不受影响。 +- 每次保存生成新的模版 ID,不覆盖已有模版。 + +### 4. 删除用餐模版 `POST /v3/admin/order/meal-template/delete`(新增) + +**VO**: `MealTemplateDeleteReqVO` → `Result` + +#### 使用场景 + +模版列表上删除某个模版。 + +#### 入参字段表 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| templateId | Body | String | 是 | 雪花 ID | 要删除的模版 ID | + +#### 请求示例 + +```http +POST /v3/admin/order/meal-template/delete +Authorization: Bearer +Content-Type: application/json + +{ "templateId": "2101953000000000001" } +``` + +#### 响应示例 + +```json +{ "code": 200, "message": "成功", "success": true, "data": null } +``` + +#### 错误响应 + +```json +{ "code": 400, "message": "模版ID不能为空", "success": false, "data": null } +``` + +#### 业务边界 + +- **幂等**:模版不存在或已经删过,同样返回成功,前端不需要处理「已删除」的失败分支。 +- 软删该模版的全部行;**已经保存到订单上的用餐行不受影响**。 +- 权限门槛与查询、保存一致(房务 / 组长角色 581045)。 + +--- + +## 四、契约约束与正确调用方式 + +- **套用后必须再调一次整单保存**,流程改为:点「套用模版」→ 调 4.3 拿到组合结果 → 在页面上展示(可让用户继续改)→ 用户点「保存」→ 把这些行(连同用户的修改)提交 `POST /v3/admin/order/meal-info/save`。只调 4.3 不调 3.3,什么都不会入库。 +- 提交 3.3 时这些行**不带 `mealInfoId`**(为 `null`),后端按新增处理;订单原来的行因为没被传上来,按现有规则软删。 +- `priceUnit` 是只读派生字段,提交 3.3 时带上也会被后端忽略;不要把它当成可编辑项,也不要用它反推桌数。 +- 判断一行「按人还是按桌」只看 `tableCount` 是否为 0,`priceUnit` 只是同一判断的展示形式,两者不会互相矛盾。 +- 套用返回的金额已经算好,**前端不要再算一遍**;用户在页面上改了人数 / 桌数 / 单价后仍按原有方式由前端试算、以 3.3 保存后的后端结果为准。 +- `creatorName` 作为 query 参数传中文时必须 URL 编码。 +- 老模版的 `tableCount` / `personCount` 都是 0,属于正常数据,不是异常。 + +--- + +## 六、边界行为 + +- 模版第 d 日超出订单行程天数:不拦截,照落在出发日 + d − 1。 +- 订单没有出发日期:返回 `items: []` 并带提示文案,不报错。 +- 模版为空:返回 `items: []`、`totalAmount: 0.00`。 +- 创建人姓名取不到(用户服务异常或查不到该管理员):`creatorName` 为 `null`,列表仍正常返回;传了创建人条件时这类模版不返回。 + +--- + +## 六.6、修改前后对比 + +| 项 | 改动前 | 改动后 | +|---|---|---| +| 套用是否写库 | 是(覆盖 / 新建) | **否,只读** | +| 套用返回内容 | 该订单全部用餐行(含原有行) | **只有模版套出来的行** | +| 套出来的行桌数 / 人数 / 金额 | 都是 0 | **后端算好**(桌数取模版,按人行人数取订单人数,金额按公式) | +| 行上 `priceUnit` | 无 | 套用返回的行与模版行有(3.1–3.4 仍无) | +| 模版名搜索 | 精确匹配 | **模糊匹配** | +| 创建人 | 不返回、不能搜 | **返回 `creatorName`,可模糊搜** | +| 模版行 `tableCount` / `personCount` | 不存、不返回 | **存并返回** | +| 删除模版 | 无接口 | **新增 4.4** | + +## 六.7、影响评估 + +- **是否破坏向后兼容**: 是。4.3 不再写库且返回内容变化,沿用旧流程会导致套用结果不入库 +- **前端是否必须同步上线**: 是,必须按第四节改套用流程 +- **前端 workaround 清理点**: 去掉「套用成功后直接重查列表」的假设;去掉前端自己算套用行金额的代码 + +## 七、不影响范围 + +- **仅影响**: 用餐模版 4.1 / 4.2 / 4.3 与新增的 4.4 +- **零影响**: + - 3.1 查询、3.2 生成、3.3 整单保存、3.4 重置的入参、出参与行为 + - `order_meal_info` 表结构与金额公式 + - 订单、团期、结算、房务等其他模块 + +--- + +## 八、测试环境已验证 + +部署提交 `cc4fb69ed`,经 Gateway 用真实 TEST 身份(SUPER_ADMIN)由固定脚本实测 32 项全部通过: + +``` +套用零写入(订单 2101846199160188929,原有 10 行) → 调用前后 3.1 整份 JSON 逐字段一致 ✓ +套用返回行数 → 3 行 = 模版行数,订单原有 10 行不在返回里 ✓ +套用返回行ID → mealInfoId 全为 null ✓ +第几日 / 用餐日期 → dayNumber=1、mealDate=2026-11-10(出发日+0)✓ +按人行(桌数 0,单价 30) → 人数=订单人数 2、priceUnit=person、金额 60.00 ✓ +按桌行(桌数 2,单价 888) → 人数=0、priceUnit=table、金额 1776.00 ✓ +免人数 / 免金额 / 其他成本 → 全为 0 ✓ +totalAmount → 1836.00 = 各行金额之和 ✓ +原样回提整单保存(假订单) → 原 3 行全部软删,只剩套用的 3 行,内容一致 ✓ +4.1 创建人 → 7 个模版全部返回中文姓名「刘畅」✓ +4.2 桌数人数快照 → 早/午/晚三行与源用餐行逐项一致(0/0、0/4、2/0)✓ +模版名模糊 templateName=HL8093 → 只返回名称含该片段的模版 ✓ +创建人模糊 creatorName=刘 → 7/7 命中;传对不上的片段返回空 ✓ +两条件同传 → 取交集;任一条件对不上返回空 ✓ +传 templateId → 精确查,忽略另外两个条件 ✓ +删除模版 → 删后查不到、其他模版不变、重复删除仍成功 ✓ +已取消订单套用 → 589607 ✓ +部署前已有 4 个模版 → 原有字段逐字段不变 ✓ +``` + +--- + +## 十、相关文档 + +- 关联 Issue: [wx/HL#8093](https://git.1814.love:8443/wx/HL/issues/8093) +- 关联 PR: [wx/HL#8105](https://git.1814.love:8443/wx/HL/pulls/8105) + +## 关联 / 联系人 + +### 链接 + +- **Issue**: [#8093](https://git.1814.love:8443/wx/HL/issues/8093) +- **PR**: [wx/HL#8105](https://git.1814.love:8443/wx/HL/pulls/8105) +- **Merge commit**: [cc4fb69ed](https://git.1814.love:8443/wx/HL/commit/cc4fb69ed505a5966ae995f2d7746c086c802d79) + +### 联系人 + +- 后端: @lc