- #5599/#5567/#5633(yst 8-06~8-07 推送时未部署测试服): 2026-08-10 复核确认代码已随 order-v3 8-10 部署上测试服,补验证证据章节(部署点位+DB 列+网关实测),backend_status 回填 deployed/gateway verified - #5444、#5730-5732: backend_status released→deployed(枚举合法化,状态语义不变) - #5784 补验证证据章节、#5788 章节结构规范化、#5797 清理 not_required 残留前端字段 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
15 KiB
schema, ticket, title, consumer, author, change_type, backend_status, gateway_status, frontend_status, frontend_owner, frontend_ref, verified_at, target_release, updated_at, base, status_note
| schema | ticket | title | consumer | author | change_type | backend_status | gateway_status | frontend_status | frontend_owner | frontend_ref | verified_at | target_release | updated_at | base | status_note |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| hl-changelog/v2 | 5599 | 核单餐食餐厅下拉值独立存储 | admin | yst | 修改接口 | deployed | verified | implemented | mmg | hl-admin@01bd3875046cd32e9497e2f00a9eb0f782a197dd | 2026-08-10 | dev-v3 | PR #5600 已合并 dev-v3;2026-08-10 复核确认测试服已部署并验证(见文末验证证据章节)。restaurantId 关联资源服务餐厅下拉接口 PR #5583 |
核单餐食餐厅下拉值独立存储(不覆盖餐食名称)
- 变更类型:修改接口
- 端类型:管理后台
- 服务:hl-order-service-v3
- 日期:2026-08-06
1. 接口背景
核单「餐食」Tab 原来只有手动录入的 mealName(餐食名称)一个文本字段承载餐食/餐厅信息。业务上需要把「从餐厅资源库选择的餐厅」作为独立结构化数据存下来,便于后续按餐厅维度统计与对账,且不能覆盖运营手动填写的餐食名称。
本次变更:餐食费用的「新增 / 修改 / 回显」三个接口同时新增 restaurantId + restaurantName 两个字段,与 mealName 完全独立、互不联动。
2. 变更清单
| # | 接口 | 变更 | 说明 |
|---|---|---|---|
| 1 | POST /v3/admin/order/{orderId}/settlement/meals | 入参 +2 字段 | 新增 restaurantId / restaurantName(成对、非必填) |
| 2 | PUT /v3/admin/order/{orderId}/settlement/meals/{settlementId} | 入参 +2 字段 | 新增 restaurantId / restaurantName(成对、非必填) |
| 3 | GET /v3/admin/order/{orderId}/settlement/meals | 出参 +2 字段 | 回显新增 restaurantId / restaurantName |
无删除字段、无改名字段、无枚举值变化。
3. 接口详情
3.1 新增餐食费用
- 方法/路径:
POST /v3/admin/order/{orderId}/settlement/meals - 接口名:新增餐食费用
- 认证:管理后台 JWT(网关统一鉴权)
- 幂等性:非幂等(重复提交会产生多条餐食记录)
- 限流:走网关默认限流,无接口级特殊限流
3.2 修改餐食费用
- 方法/路径:
PUT /v3/admin/order/{orderId}/settlement/meals/{settlementId} - 接口名:修改餐食费用
- 认证:管理后台 JWT(网关统一鉴权)
- 幂等性:幂等(相同 body 重复 PUT 结果一致)
- 限流:走网关默认限流
3.3 查询餐食费用(回显)
- 方法/路径:
GET /v3/admin/order/{orderId}/settlement/meals - 接口名:查询餐食费用
- 认证:管理后台 JWT(网关统一鉴权)
- 幂等性:只读接口
- 限流:走网关默认限流
4. 接口入参
4.1 路径参数
| 参数 | 位置 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
| orderId | path | Long | 是 | 订单ID(三个接口均有) |
| settlementId | path | Long | 是 | 餐食费用记录ID(仅 PUT 修改接口) |
无 Query 参数。
4.2 请求体字段(POST / PUT 共用同一请求结构)
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| mealType | String | 是 | 餐型:BREAKFAST / LUNCH / DINNER |
| mealDate | Date(yyyy-MM-dd) | 否 | 发生日期 |
| mealName | String | 是 | 餐食名称,手动录入,最长 200 字符;不受餐厅选择影响、不被覆盖 |
| quantity | Integer | 是 | 数量,1-10000 |
| unitPrice | Number | 是 | 单价,≥ 0,最多 8 位整数 + 2 位小数;unitPrice × quantity ≤ 99999999.99 |
| paymentMethod | String | 是 | 付款类型:CASH_PAID / COMPANY_PAID / SIGNED |
| sourceType | String | 是 | 来源类型:MEAL_ASSIGNMENT / MANUAL / SYSTEM |
| settlementConfirmStatus | String | 是 | 确认状态:UNCONFIRMED / CONFIRMED |
| voucherUrls | String[] | 否 | 凭证 URL 列表,最多 9 个,仅支持 http/https,单个最长 1024 字符 |
| restaurantId | Long | 否 | 本次新增。餐厅资源ID,与 restaurantName 成对出现;不选餐厅则两者都不传 |
| restaurantName | String | 否 | 本次新增。餐厅名称快照,与 restaurantId 成对出现,最长 200 字符 |
| remark | String | 否 | 备注,最长 512 字符 |
成对校验(关键业务规则):restaurantId 与 restaurantName 必须同时传或同时不传(restaurantName 为空白字符串视为未传)。只传其一 → 400,message 为「restaurantId 与 restaurantName 必须成对出现」或「餐食费用字段超出允许范围」(错误码 584094,MEAL_EXPENSE_REQUEST_INVALID)。两字段与手动录入的 mealName 完全独立、不联动——选了餐厅也不会改动/覆盖 mealName。
5. 出参字段
- POST / PUT 响应:
Result<SettlementMealRespVO>(单条记录) - GET 响应:
Result<List<SettlementMealRespVO>>(列表,元素结构相同)
SettlementMealRespVO 字段表:
| 字段 | 类型 | 说明 |
|---|---|---|
| id | String | 餐食费用ID,雪花ID序列化为字符串 |
| mealType | String | 餐型 |
| mealDate | Date(yyyy-MM-dd) | 发生日期 |
| mealName | String | 餐食名称 |
| restaurantId | String | null | 本次新增。餐厅资源ID,雪花ID序列化为字符串(防 JS 精度丢失);未选餐厅为 null |
| restaurantName | String | null | 本次新增。餐厅名称快照(选中时记录,餐厅改名/删除不影响已存核单记录);未选餐厅为 null |
| quantity | Integer | 数量 |
| unitPrice | String | 单价(BigDecimal 序列化为字符串) |
| actualAmount | String | 实际金额 = unitPrice × quantity(BigDecimal 序列化为字符串) |
| paymentMethod | String | 付款类型 |
| sourceType | String | 来源类型 |
| sourceTypeName | String | 来源类型名称 |
| voucherUrls | String[] | 凭证 URL 列表 |
| settlementConfirmStatus | String | 确认状态 |
| settlementConfirmStatusName | String | 确认状态名称 |
| remark | String | 备注 |
外层为统一响应结构:{ "code": 200, "message": "...", "data": ..., "traceId": ..., "success": ... };业务失败时 code 为错误码、data 为 null。
6. 枚举 / 数据字典
本次无新增、无变更枚举。以下现有枚举取值保持不变:
| 字段 | 取值 | 说明 |
|---|---|---|
| mealType | BREAKFAST / LUNCH / DINNER | 早餐 / 午餐 / 晚餐 |
| paymentMethod | CASH_PAID / COMPANY_PAID / SIGNED | 现金垫付 / 公司支付 / 签单 |
| sourceType | MEAL_ASSIGNMENT / MANUAL / SYSTEM | 餐食安排 / 手动录入 / 系统生成 |
| settlementConfirmStatus | UNCONFIRMED / CONFIRMED | 未确认 / 已确认 |
restaurantId 的取值来源:资源服务「餐厅资源下拉选项」接口 GET /admin/resource-options/restaurants(PR #5583 已上线)返回的 resourceId。餐厅选项是业务数据,不是枚举/字典。
7. 错误码
| HTTP | 错误码 | message | 触发条件 |
|---|---|---|---|
| 400 | 584094 | 餐食费用字段超出允许范围 | restaurantId 与 restaurantName 只传其一(不成对),service 层抛 MEAL_EXPENSE_REQUEST_INVALID |
| 400 | (参数校验失败) | restaurantId 与 restaurantName 必须成对出现 | 同上场景,bean validation 层先行拦截时的提示文案 |
| 400 | (参数校验失败) | mealType 必须是 BREAKFAST / LUNCH / DINNER 之一 等 | 既有字段校验失败(本次不变) |
| 401 | - | 未登录 / token 失效 | 未携带或携带无效的管理后台 JWT |
8. 示例
8.1 典型成功:选择餐厅保存 + 回显
请求:
POST /v3/admin/order/1951234567890123456/settlement/meals
Authorization: Bearer {adminToken}
Content-Type: application/json
{
"mealType": "LUNCH",
"mealDate": "2026-08-10",
"mealName": "团队桌餐(10人标)",
"quantity": 10,
"unitPrice": 68.00,
"paymentMethod": "COMPANY_PAID",
"sourceType": "MANUAL",
"settlementConfirmStatus": "UNCONFIRMED",
"voucherUrls": ["https://oss.example.com/voucher/a1.jpg"],
"restaurantId": 1889900112233445566,
"restaurantName": "海拉尔XX手把肉餐厅",
"remark": "导游现场确认"
}
响应(200):
{
"code": 200,
"message": "成功",
"data": {
"id": "1952345678901234567",
"mealType": "LUNCH",
"mealDate": "2026-08-10",
"mealName": "团队桌餐(10人标)",
"restaurantId": "1889900112233445566",
"restaurantName": "海拉尔XX手把肉餐厅",
"quantity": 10,
"unitPrice": "68.00",
"actualAmount": "680.00",
"paymentMethod": "COMPANY_PAID",
"sourceType": "MANUAL",
"sourceTypeName": "手动录入",
"voucherUrls": ["https://oss.example.com/voucher/a1.jpg"],
"settlementConfirmStatus": "UNCONFIRMED",
"settlementConfirmStatusName": "未确认",
"remark": "导游现场确认"
}
}
回显:GET /v3/admin/order/1951234567890123456/settlement/meals 返回 data 为列表,元素结构同上(含 restaurantId / restaurantName)。
8.2 边界:不选餐厅,纯手动录入(两字段都不传)
请求:
POST /v3/admin/order/1951234567890123456/settlement/meals
Authorization: Bearer {adminToken}
Content-Type: application/json
{
"mealType": "BREAKFAST",
"mealDate": "2026-08-11",
"mealName": "酒店自助早餐",
"quantity": 10,
"unitPrice": 0,
"paymentMethod": "SIGNED",
"sourceType": "MANUAL",
"settlementConfirmStatus": "CONFIRMED"
}
响应(200):data 中 restaurantId: null、restaurantName: null,其余字段正常填充。存量历史餐食记录回显同样两字段为 null。
8.3 业务失败:只传 restaurantId、缺 restaurantName(不成对)
请求:
{
"mealType": "DINNER",
"mealName": "涮羊肉",
"quantity": 10,
"unitPrice": 88.00,
"paymentMethod": "CASH_PAID",
"sourceType": "MANUAL",
"settlementConfirmStatus": "UNCONFIRMED",
"restaurantId": 1889900112233445566
}
响应(400):
{
"code": 584094,
"message": "餐食费用字段超出允许范围",
"data": null
}
(若 bean validation 层先行拦截,message 为「restaurantId 与 restaurantName 必须成对出现」。反向场景——只传 restaurantName 不传 restaurantId——同样 400。)
9. 业务边界
适用:
- 核单「餐食」Tab 新增 / 编辑费用时,从餐厅资源下拉中选择餐厅,结构化保存 restaurantId + restaurantName
- 运营纯手动录入餐食(不关联餐厅):两字段都不传即可,行为与本次变更前完全一致
不适用:
- restaurantId 只接受「餐厅资源下拉选项」接口返回的 resourceId,不接受其他类型资源ID
- 已提交核单(终态)订单不可再改餐食费用(核单提交守卫为既有逻辑,本次不变)
特殊边界:
- 快照语义:restaurantName 是选中那一刻的名称快照。之后餐厅在资源库改名 / 删除,不影响已存核单记录的 restaurantName
- 与 mealName 完全独立:选了餐厅也不会改动 / 覆盖 mealName;mealName 仍是必填手动录入字段
- 存量数据:历史餐食费用记录两字段均为 null,回显正常、无需迁移
10. 修改前后对比
字段级对比
| 接口 | 维度 | 修改前 | 修改后 |
|---|---|---|---|
| POST / PUT 保存 | 入参 | 无餐厅字段 | +restaurantId(Long,可选,成对)+restaurantName(String,最长 200,可选,成对) |
| GET 回显 | 出参 | 无餐厅字段 | +restaurantId(String,可 null)+restaurantName(String,可 null) |
| mealName | 语义 | 餐食/餐厅信息只能挤在这一个文本字段里 | 仍为必填手动录入;餐厅信息改由独立字段承载,不再占用 mealName |
行为级对比
| 场景 | 修改前 | 修改后 |
|---|---|---|
| 保存餐食费用 | 想记餐厅只能写进 mealName 文本 | 可结构化传 restaurantId + restaurantName,mealName 不被覆盖 |
| 回显 | 无法区分「餐食名称」和「餐厅」 | 两类信息独立字段回显,前端可分开展示 |
| 入参校验 | 无成对概念 | restaurantId / restaurantName 不成对 → 400 |
11. 影响评估 / 回滚
- 破坏性兼容:无。两个字段均为可选新增;存量请求不传两字段时行为完全不变;存量数据两列为 NULL
- 前端同步上线:不强制同步。前端未接新字段前,保存 / 回显行为与之前一致(回显多两个 null 字段,不读即可);前端接新字段需等后端部署后联调
- 后端部署状态:PR #5600 已合并 dev-v3,测试服已部署(2026-08-10 复核确认,见文末验证证据章节)
- 回滚方案:后端回滚 = 下线两字段即可。回滚时前端需同步停止传这两字段;存量数据两列本就为 NULL 或历史快照值,无数据迁移、无回滚 SQL
12. 注意事项
restaurantId出参是 String(雪花 ID 序列化),前端不要用 Number 解析,防 JS 精度丢失unitPrice/actualAmount出参同样是 String(BigDecimal 序列化),金额展示直接渲染字符串即可- 编辑时「清掉已选餐厅」= 两字段都不传(或都传 null),保持成对空态
- restaurantName 是快照不是实时关联,餐厅后续改名 / 删除不影响历史核单记录显示,属预期
- 餐厅下拉数据源接口
GET /admin/resource-options/restaurants属资源服务(PR #5583),不在本变更范围内 - 本变更只涉及接口契约;后端代码已合并 dev-v3,测试服部署时间以后端通知为准
13. 关联 / 联系人
- Issue:wx/HL#5599
- PR:wx/HL#5600
- Commit:
9884585a29 - 关联依赖:餐厅资源下拉接口 PR #5583(资源服务,已上线)
- 后端负责人:yst(腰苏图)
验证证据(2026-08-10 复核回填,wx)
本条 changelog 2026-08-06 推送时后端尚未部署测试服(原 status_note 自述),违反「测试服部署+实测后才通知前端」流程。2026-08-10 复核补齐部署与验证证据如下:
- 测试服 hl-order-service-v3 运行版本为 2026-08-10 15:10 构建(dev-v3),晚于 PR #5600 合并点(2026-08-06 14:56),本变更代码已在运行实例中。
- 测试服 DB
hl_order_service_v3.order_settlement_meal已存在restaurant_id/restaurant_name两列(Flyway 已执行)。 SettlementMealRespVO含restaurantId/restaurantName字段(dev-v3 源码核对)。GET /v3/admin/order/{orderId}/settlement/meals经网关 9443 + 真 admin token 实测返回code=200(空列表订单);测试库现有餐食记录的订单因核单数据权限(581008 无权查看此订单)未能以 SUPER_ADMIN 直接观测到带值行,字段存在性以 DB 列 + VO 源码 + 部署点位三重证据确认。