文件
hl-api-changelog/changelogs-v2/2026-09/18_7741_订单用餐信息与用餐模版接口-新增接口-管理后台.md
T
2026-09-20 16:00:55 +08:00

36 KiB
原始文件 Blame 文件历史

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 7741 订单用餐信息与用餐模版接口(查询、生成、整单保存、重置、查模版、存模版、套模版) admin lc(GIT) 新增接口 deployed verified verified mmg 061b728af421904d6f275292273814c206d374f2 2026-09-20 新增 7 个接口:订单详情「用餐」页签的用餐信息查询 / 按产品快照生成 / 整单一次保存 / 重置,以及用餐模版的查询 / 保存 / 套用。前端需按本文契约对接;人数与桌数二选一、类别字典过滤 SELF 两点见「四、契约约束」。前端已交付(mmg@061b728a):订单详情「用餐」页签(自动生成/整单保存全集语义/人数桌数互锁/金额同公式/589613 静默重拉/只读态)+存为模版/套用模版弹窗,类别下拉过滤 SELF;3 新 spec 18/18,scoped checkpoint 全绿。 2026-09-18 dev-v3

order-v3: 订单用餐信息与用餐模版接口

服务: hl-order-service-v3(订单库新建两张表,随服务部署由 Flyway 创建) PR: #7945(主体)、#7954(3.1 团期编号回显补充) Issue: #7741、#7953 日期: 2026-09-18 影响范围: 管理后台订单详情「用餐」页签(新接口,老接口不变)


⚠️ 关键变化

🔴 新增 7 个接口,路径前缀 /v3/admin/order/meal-info 与 /v3/admin/order/meal-template。

🔴 订单用餐的增、删、改只有一个接口:3 号「整单保存」。 页面上加行、改值、删行都不调后端,点「保存」时把这张订单页面上的全部行一次提交:带 mealInfoId 的整行覆盖,不带的新增,原来有而这次没传上来的行被删除。

🔴 金额由前端按公式算好传上来,后端逐行重算核对,对不上返回 589613,整批不保存。 桌数为 0 按人算,不为 0 按桌算(公式见 3 号接口)。

🔴 类别字典 meal_type 在 TEST 有 BREAKFAST / LUNCH / DINNER / SELF 四个值,接口只接受前三个,页面下拉请过滤掉 SELF。


一、背景

订单详情新增「用餐」页签:按订单产品快照里每天早午晚餐的安排,自动生成需要安排的用餐行;运营逐行选餐厅、餐食,填桌数或人数,保存金额;排好的几天可以存成「用餐模版」,以后套到别的订单上。

生成规则(快照里行程天的早 / 午 / 晚餐取值,字典 meal_option):

取值 中文 是否生成用餐行
HOTEL 含(酒店) 不生成
SELF 自理 不生成
CAMP 含(营地) 生成这一天这一餐的一行空行
SPECIAL 含(特色餐) 生成这一天这一餐的一行空行
REGULAR 含(正餐) 生成这一天这一餐的一行空行

二、变更接口清单

# 接口 方法 路径 变更类型 说明
1 查询用餐信息列表 GET /v3/admin/order/meal-info/list 新增 页签查询行和合计
2 生成用餐信息 POST /v3/admin/order/meal-info/generate 新增 按产品快照生成空行,已有行不重复生成
3 保存用餐信息(整单一次保存) POST /v3/admin/order/meal-info/save 新增 增、改、删一次提交
4 重置用餐信息 POST /v3/admin/order/meal-info/reset 新增 删掉全部行后按快照重新生成空行
5 查询用餐模版 GET /v3/admin/order/meal-template/list 新增 按模版ID或模版名查,按模版分组
6 保存为用餐模版 POST /v3/admin/order/meal-template/save 新增 选几行存成一个新模版
7 套用用餐模版 POST /v3/admin/order/meal-template/apply 新增 把模版套到一张订单上

三、接口详情

1. 查询用餐信息列表 GET /v3/admin/order/meal-info/list

VO: OrderMealInfoListReqVO → OrderMealInfoListRespVO

使用场景

订单详情「用餐」页签打开时传当前订单的 orderId,取这张订单的全部用餐行、合计金额和订单信息;generated=false 且 orderEditable=true 时再调 2 号接口生成。按团期查询时传 groupBatchId 或 batchNo。

入参

字段 位置 类型 必填 约束 说明
orderId Query String 否 正数 订单ID
groupBatchId Query String 否 正数 团期ID
batchNo Query String 否 ≤64 团期编号,精确匹配
mealType Query String 否 BREAKFAST / LUNCH / DINNER 类别
mealDateStart Query String 否 yyyy-MM-dd 用餐日期起(含)
mealDateEnd Query String 否 yyyy-MM-dd,≥ mealDateStart 用餐日期止(含)
dayNumber Query Integer 否 ≥1 第几天
settleType Query String 否 cash / sign / company 付款方式
keyword Query String 否 ≤64 模糊匹配餐厅名称、餐食名称;%、_ 按字面量匹配
limit Query Integer 否 1–500,默认 200 最大返回条数

出参 Result<OrderMealInfoListRespVO>

字段 类型 说明
orderId String 入参订单ID;没传为 null
groupBatchId String 订单所属团期ID;订单没挂团期或没传订单ID时为入参团期ID;都没有为 null
batchNo String 与 groupBatchId 对应的团期编号;没有为 null
departDate String 订单出发日期 yyyy-MM-dd;没传订单ID为 null
returnDate String 订单返回日期;没传订单ID为 null
personCount Integer 订单人数:成人 + 儿童 + 幼童 + 婴儿;没传订单ID为 null
orderEditable Boolean 订单是否可改:已完成、已取消为 false;没传订单ID为 null
generated Boolean 该订单是否已有用餐行(不受筛选条件影响);没传订单ID为 null
totalAmount Number 本次返回各行金额之和
items[].mealInfoId String 用餐信息ID
items[].orderId String 订单ID,可为 null
items[].groupBatchId String 团期ID,可为 null
items[].batchNo String 团期编号,可为 null
items[].restaurantId String 餐厅ID,可为 null
items[].restaurantName String 餐厅名称,可为 null
items[].dishId String 餐食ID,没选餐食为 null
items[].dishCode String 餐食编码,没选餐食为 null
items[].dishName String 餐食名称,没选餐食为 null
items[].mealType String 类别 BREAKFAST / LUNCH / DINNER
items[].mealDate String 用餐日期 yyyy-MM-dd
items[].dayNumber Integer 第几天;手动添加的行为 null
items[].unitPrice Number 单价(按人时是每人单价,按桌时是每桌单价)
items[].tableCount Integer 桌数;0 表示按人算
items[].personCount Integer 人数
items[].freePersonCount Integer 免人数
items[].freeAmount Number 免金额
items[].otherCost Number 其他成本
items[].amount Number 金额
items[].settleType String 付款方式编码 cash / sign / company,可为 null
items[].settleTypeName String 付款方式中文,可为 null
items[].createTime String 创建时间 yyyy-MM-dd HH:mm:ss

请求示例

GET /v3/admin/order/meal-info/list?orderId=2100864597505286145
Authorization: Bearer <admin token>

响应示例

TEST 实测返回(节选 1 行):

{
  "code": 200,
  "message": "成功",
  "success": true,
  "data": {
    "orderId": "2100864597505286145",
    "groupBatchId": null,
    "batchNo": null,
    "departDate": "2026-10-23",
    "returnDate": "2026-10-25",
    "personCount": 2,
    "orderEditable": true,
    "generated": true,
    "totalAmount": 60.00,
    "items": [
      {
        "mealInfoId": "2100883161750687746",
        "orderId": "2100864597505286145",
        "groupBatchId": null,
        "batchNo": null,
        "restaurantId": "2023382108491247617",
        "restaurantName": "七间房全羊馆",
        "dishId": "2100864423122898946",
        "dishCode": "HL7741A",
        "dishName": "HL7741-TEST-餐食A",
        "mealType": "LUNCH",
        "mealDate": "2026-10-20",
        "dayNumber": 1,
        "unitPrice": 30.00,
        "tableCount": 0,
        "personCount": 0,
        "freePersonCount": 0,
        "freeAmount": 0.00,
        "otherCost": 0.00,
        "amount": 0.00,
        "settleType": "sign",
        "settleTypeName": "签单",
        "createTime": "2026-09-18 17:42:33"
      }
    ]
  }
}

空数据 / 降级响应

订单还没有用餐行时 generated=false、items=[]、totalAmount=0,其余订单字段照常返回。按团期编号查不到团期时返回空列表。

错误响应

{ "code": 400, "message": "用餐日期起不能晚于用餐日期止", "success": false, "data": null }

业务边界

  • 只查未删除的行,各条件之间是「并且」;排序:用餐日期升序 → 早、午、晚 → 创建时间升序。
  • 传 orderId:订单不存在按订单模块现有错误返回;房务角色返回 581045;需对该订单有查看权限。
  • 不传 orderId、传团期ID或团期编号:需要团期查看权限(不足 589507),范围是挂在该团期上的行。
  • 三个都不传:房务角色返回 581045;只查「没挂订单也没挂团期」的行。

2. 生成用餐信息 POST /v3/admin/order/meal-info/generate

VO: OrderMealInfoGenerateReqVO → OrderMealInfoListRespVO

使用场景

1 号接口返回 generated=false 且订单可改时调用,按订单产品快照生成空行(规则见「一、背景」表)。生成不读模版,要模版的调 7 号接口。

入参

字段 位置 类型 必填 约束 说明
orderId Body String ✅ 正数 订单ID

出参 Result<OrderMealInfoListRespVO>

字段 类型 说明
data Object 生成后按该订单查询的结果,字段同 1 号接口出参

请求示例

{ "orderId": "2100864614022451201" }

响应示例

TEST 实测(订单 B 生成时返回,节选 1 行):第 1 天午餐 = 含(营地),生成的行餐厅、餐食、付款方式为空,数值全为 0:

{
  "code": 200,
  "message": "成功",
  "success": true,
  "data": {
    "orderId": "2100864614022451201",
    "departDate": "2026-10-20",
    "returnDate": "2026-10-22",
    "personCount": 2,
    "orderEditable": true,
    "generated": true,
    "totalAmount": 0.00,
    "items": [
      {
        "mealInfoId": "2100882946096353281",
        "orderId": "2100864614022451201",
        "restaurantId": null,
        "restaurantName": null,
        "dishId": null,
        "dishCode": null,
        "dishName": null,
        "mealType": "LUNCH",
        "mealDate": "2026-10-20",
        "dayNumber": 1,
        "unitPrice": 0.00,
        "tableCount": 0,
        "personCount": 0,
        "freePersonCount": 0,
        "freeAmount": 0.00,
        "otherCost": 0.00,
        "amount": 0.00,
        "settleType": null,
        "settleTypeName": null,
        "createTime": "2026-09-18 17:41:42"
      }
    ]
  }
}

空数据 / 降级响应

订单出发或返回日期为空时不生成,返回成功(code 200),message 为「订单出发或返回日期为空,无法生成用餐信息」(错误码 589602 的文案,只作提示),data.items 为 []。快照里没有需要生成的餐时也返回成功、items 为空。

错误响应

{ "code": 589607, "message": "订单已完成或已取消,不能修改用餐信息", "success": false, "data": null }

业务边界

  • 用餐日期 = 出发日期 + d − 1,第几天 = d;天数按出发到返回日期算,快照里超出的天不生成。
  • 幂等:该订单已有未删除的行时不再生成,直接返回现有列表。
  • 订单不存在、对订单无操作权限按订单模块现有错误返回;已完成或已取消 589607;返回日期早于出发日期 589612。

3. 保存用餐信息(整单一次保存) POST /v3/admin/order/meal-info/save

VO: OrderMealInfoBatchSaveReqVO → OrderMealInfoListRespVO

使用场景

「用餐」页签点「保存」,把这张订单页面上的全部行一次提交。页面上的「添加」「删除」和行内改值都不调后端。

入参

字段 位置 类型 必填 约束 说明
orderId Body String ✅ 正数 订单ID
groupBatchId Body String 否 正数 传了须与订单所属团期一致,否则 589601
items Body Array ✅ 可为空数组 这张订单保存后应有的全部行;空数组表示一行都不留
items[].mealInfoId Body String 否 — 传了更新这一行,不传新增一行
items[].restaurantId Body String 否 — 餐厅ID(来自餐厅选项接口),原样保存
items[].restaurantName Body String 否 ≤128 餐厅名称,原样保存
items[].dishId Body String 否 — 餐食ID(来自餐食下拉),没选餐食不传
items[].dishCode Body String 否 ≤20 餐食编码,原样保存
items[].dishName Body String 否 去首尾空格后 ≤500 餐食名称,原样保存
items[].mealType Body String 否 BREAKFAST / LUNCH / DINNER,默认 LUNCH 类别
items[].mealDate Body String ✅ yyyy-MM-dd 用餐日期
items[].unitPrice Body Number 否 0–99999999.99,两位小数,默认 0 单价
items[].tableCount Body Integer 否 0–999,默认 0 桌数;0 按人算,不为 0 按桌算
items[].personCount Body Integer 否 0–9999,默认 0 人数
items[].freePersonCount Body Integer 否 ≥0,默认 0 免人数;不校验与人数的大小关系
items[].freeAmount Body Number 否 0–99999999.99,两位小数,默认 0 免金额
items[].otherCost Body Number 否 0–99999999.99,两位小数,默认 0 其他成本
items[].amount Body Number 否 0–99999999.99,两位小数,默认 0 金额,前端按公式算好传上来
items[].settleType Body String 否 cash / sign / company 付款方式编码
items[].settleTypeName Body String 否 ≤32 付款方式中文,原样保存

出参 Result<OrderMealInfoListRespVO>

字段 类型 说明
data Object 保存后按该订单查询的结果,字段同 1 号接口出参;新增行是新生成的 mealInfoId,dayNumber 为 null

请求示例

示例(金额取自 TEST 实测数据;节选:改一行按人、改一行按桌、新增一行,没传上来的行被删除):

{
  "orderId": "2100864597505286145",
  "items": [
    {
      "mealInfoId": "2100883161750687746",
      "restaurantId": "2023382108491247617", "restaurantName": "七间房全羊馆",
      "dishId": "2100864423122898946", "dishCode": "HL7741A", "dishName": "HL7741-TEST-餐食A",
      "mealType": "LUNCH", "mealDate": "2026-10-20",
      "unitPrice": 30, "tableCount": 0, "personCount": 4, "freePersonCount": 1,
      "freeAmount": 0, "otherCost": 10, "amount": 100.00,
      "settleType": "sign", "settleTypeName": "签单"
    },
    {
      "mealInfoId": "2100883161754882049",
      "restaurantId": "2023382108491247617", "restaurantName": "七间房全羊馆",
      "dishId": "2100864435710009346", "dishCode": "HL7741B", "dishName": "HL7741-TEST-餐食B",
      "mealType": "DINNER", "mealDate": "2026-10-20",
      "unitPrice": 800, "tableCount": 2, "personCount": 0, "freePersonCount": 0,
      "freeAmount": 100, "otherCost": 50, "amount": 1550.00,
      "settleType": "cash", "settleTypeName": "现付"
    },
    {
      "restaurantId": "2023382108491247617", "restaurantName": "七间房全羊馆",
      "dishId": "2100864423122898946", "dishCode": "HL7741A", "dishName": "HL7741-TEST-餐食A",
      "mealType": "BREAKFAST", "mealDate": "2026-10-21",
      "unitPrice": 30, "personCount": 2, "amount": 60.00,
      "settleType": "sign", "settleTypeName": "签单"
    }
  ]
}

响应示例

{
  "code": 200,
  "message": "成功",
  "success": true,
  "data": {
    "orderId": "2100864597505286145",
    "generated": true,
    "orderEditable": true,
    "totalAmount": 1710.00,
    "items": [
      { "mealInfoId": "2100883161750687746", "mealType": "LUNCH", "mealDate": "2026-10-20", "dayNumber": 1, "tableCount": 0, "personCount": 4, "freePersonCount": 1, "amount": 100.00 },
      { "mealInfoId": "2100883161754882049", "mealType": "DINNER", "mealDate": "2026-10-20", "dayNumber": 1, "tableCount": 2, "personCount": 0, "freePersonCount": 0, "amount": 1550.00 },
      { "mealInfoId": "2100883192117448705", "mealType": "BREAKFAST", "mealDate": "2026-10-21", "dayNumber": null, "tableCount": 0, "personCount": 2, "freePersonCount": 0, "amount": 60.00 }
    ]
  }
}

(items 每行实际返回 1 号接口出参的全部字段,此处省略餐厅、餐食等字段。)

空数据 / 降级响应

items 传空数组表示这张订单一行都不留:原有行全部删除,返回 items=[]、totalAmount=0、generated=false。

错误响应

{ "code": 589613, "message": "金额与明细对不上,请刷新后重试", "success": false, "data": null }

业务边界

  • 金额公式:桌数为 0 时 金额 = 单价 ×(人数 − 免人数)− 免金额 + 其他成本;桌数不为 0 时 金额 = 单价 × 桌数 − 免金额 + 其他成本;算出负数存 0,四舍五入到两位小数。后端按同一公式逐行核对,任何一行对不上返回 589613,整批不保存。
  • 带 mealInfoId 的行整行覆盖:可选字段不传按清空处理(数值取 0,类别取 LUNCH);dayNumber 保持原值。
  • mealInfoId 必须是这张订单未删除的行:查不到或已删除 589606;原来挂的不是这张订单 589611。
  • 餐厅、餐食、单价、付款方式按页面传的原样保存,不回查资源服务;保存前餐食被下架或删除也照常保存。
  • 订单已完成或已取消 589607;groupBatchId 与订单所属团期不一致 589601;整批一个事务,任何错误都不写入。

4. 重置用餐信息 POST /v3/admin/order/meal-info/reset

VO: OrderMealInfoResetReqVO → OrderMealInfoListRespVO

使用场景

把这张订单的用餐行全部删掉(含人工改过的、手动加的),再按产品快照重新生成一套空行,结果与第一次调 2 号接口一致。要模版的,重置后再调 7 号接口。

入参

字段 位置 类型 必填 约束 说明
orderId Body String ✅ 正数 订单ID

出参 Result<OrderMealInfoListRespVO>

字段 类型 说明
data Object 重置后按该订单查询的结果,字段同 1 号接口出参;行ID全部是新的

请求示例

{ "orderId": "2100864597505286145" }

响应示例

{
  "code": 200,
  "message": "成功",
  "success": true,
  "data": {
    "orderId": "2100864597505286145",
    "generated": true,
    "totalAmount": 0.00,
    "items": [
      { "mealInfoId": "2100883161750687746", "mealType": "LUNCH", "mealDate": "2026-10-20", "dayNumber": 1, "dishName": null, "restaurantName": null, "unitPrice": 0.00, "tableCount": 0, "personCount": 0, "amount": 0.00 }
    ]
  }
}

(TEST 实测,节选 1 行与部分字段,字段同 1 号接口。)

空数据 / 降级响应

订单出发或返回日期为空时:原有行照样删除、不生成新行,返回成功,message 为 589602 的提示文案,items=[]。

错误响应

{ "code": 589607, "message": "订单已完成或已取消,不能修改用餐信息", "success": false, "data": null }

业务边界

  • 删除和重新生成在一个事务里;返回日期早于出发日期 589612,此时不删也不生成。
  • 不受 2 号接口「已有行就不再生成」的限制。
  • 订单不存在、无操作权限按订单模块现有错误返回;已完成或已取消 589607。

5. 查询用餐模版 GET /v3/admin/order/meal-template/list

VO: MealTemplateListReqVO → List<MealTemplateRespVO>

使用场景

「用餐」页签选择模版时,列出模版及其内容。

入参

字段 位置 类型 必填 约束 说明
templateId Query String 否 — 模版ID精确匹配;传了忽略 templateName
templateName Query String 否 ≤500 模版名精确匹配;两个都不传返回全部模版

出参 Result<List<MealTemplateRespVO>>

字段 类型 说明
[].templateId String 模版ID;同一个模版的行共用
[].templateName String 模版名;允许重名,重名的是各自独立的模版
[].items[].id String 模版行ID
[].items[].dayNumber Integer 第几日,从 1 开始
[].items[].mealType String 餐次 BREAKFAST / LUNCH / DINNER
[].items[].restaurantId String 餐厅ID,可为 null
[].items[].restaurantName String 餐厅名称,可为 null
[].items[].dishId String 餐食ID,可为 null
[].items[].dishCode String 餐食编码,可为 null
[].items[].dishName String 餐食名称,可为 null
[].items[].unitPrice Number 价钱
[].items[].settleType String 付款方式编码,可为 null
[].items[].settleTypeName String 付款方式中文,可为 null

请求示例

GET /v3/admin/order/meal-template/list?templateId=2100883046935748610
Authorization: Bearer <admin token>

响应示例

TEST 实测返回(节选 1 行):

{
  "code": 200,
  "message": "成功",
  "success": true,
  "data": [
    {
      "templateId": "2100883046935748610",
      "templateName": "HL7741-TEST-含营地",
      "items": [
        {
          "id": "2100883046935748611",
          "dayNumber": 1,
          "mealType": "LUNCH",
          "restaurantId": "2023382108491247617",
          "restaurantName": "七间房全羊馆",
          "dishId": "2100864423122898946",
          "dishCode": "HL7741A",
          "dishName": "HL7741-TEST-餐食A",
          "unitPrice": 30.00,
          "settleType": "sign",
          "settleTypeName": "签单"
        }
      ]
    }
  ]
}

空数据 / 降级响应

{ "code": 200, "message": "成功", "success": true, "data": [] }

错误响应

{ "code": 581045, "message": "房务角色无权查看订单详情,房务仅可配房", "success": false, "data": null }

业务边界

  • 按模版ID分组,模版之间按保存先后排序;同一个模版内按第几日升序、早午晚排序。
  • 模版里存的是快照:餐食或餐厅后来被下架、删除,照常返回存下的名称和价钱。
  • 模版不存用餐日期、桌数、人数、免人数、免金额、其他成本、金额。

6. 保存为用餐模版 POST /v3/admin/order/meal-template/save

VO: MealTemplateSaveReqVO → MealTemplateRespVO

使用场景

在「用餐」页签把已排好的几行存成模版,模版名自己取。

入参

字段 位置 类型 必填 约束 说明
templateName Body String ✅ 去首尾空格后 1–500 模版名;允许和已有模版重名
mealInfoIds Body Array<String> ✅ 非空,自动去重 要存进模版的用餐信息行ID

出参 Result<MealTemplateRespVO>

字段 类型 说明
data Object 新模版,结构同 5 号接口的一个元素(含新生成的 templateId)

请求示例

{
  "templateName": "HL7741-TEST-含营地",
  "mealInfoIds": ["2100883161750687746", "2100883161754882049", "2100883192117448705", "2100883161759076354"]
}

响应示例

{
  "code": 200,
  "message": "成功",
  "success": true,
  "data": {
    "templateId": "2100883046935748610",
    "templateName": "HL7741-TEST-含营地",
    "items": [
      { "id": "2100883046935748611", "dayNumber": 1, "mealType": "LUNCH", "dishName": "HL7741-TEST-餐食A", "unitPrice": 30.00, "settleType": "sign", "settleTypeName": "签单" },
      { "id": "2100883047007051777", "dayNumber": 1, "mealType": "DINNER", "dishName": "HL7741-TEST-餐食B", "unitPrice": 800.00, "settleType": "cash", "settleTypeName": "现付" },
      { "id": "2100883047032217601", "dayNumber": 2, "mealType": "BREAKFAST", "dishName": "HL7741-TEST-餐食A", "unitPrice": 30.00, "settleType": "sign", "settleTypeName": "签单" },
      { "id": "2100883047007051778", "dayNumber": 2, "mealType": "LUNCH", "dishName": "HL7741-TEST-餐食A", "unitPrice": 30.00, "settleType": "sign", "settleTypeName": "签单" }
    ]
  }
}

(请求为示例;响应行取自 TEST 实测模版,节选字段,实际返回 5 号接口 items[] 的全部字段。第 2 日早餐是手动加的行,按最早日期补成第 2 日。)

空数据 / 降级响应

mealInfoIds 一行都取不到(都已删除或不存在)时返回 589606,不建模版;部分取不到时只存取到的行。

错误响应

{ "code": 400, "message": "用餐信息行ID不能为空; 模版名不能为空", "success": false, "data": null }

业务边界

  • 每次保存都新建一个模版(新的 templateId),不覆盖任何已有模版,名字一样也一样。
  • 第几日:行上有第几天直接用;没有(手动加的行)时以这批行里最早的用餐日期为第 1 日,按实际相差天数补。
  • 同一日同一餐出现多条返回 400,提示「第 d 日某餐重复」。
  • 存餐厅、餐食、餐次、第几日、价钱、付款方式(编码和中文);房务角色 581045。

7. 套用用餐模版 POST /v3/admin/order/meal-template/apply

VO: MealTemplateApplyReqVO → OrderMealInfoListRespVO

使用场景

「用餐」页签选一个模版套到当前订单上。

入参

字段 位置 类型 必填 约束 说明
orderId Body String ✅ 正数 订单ID
templateId Body String ✅ — 模版ID

出参 Result<OrderMealInfoListRespVO>

字段 类型 说明
data Object 套用后按该订单查询的结果,字段同 1 号接口出参

请求示例

{ "orderId": "2100864597505286145", "templateId": "2100883046935748610" }

响应示例

TEST 实测:模版第 1 日午餐覆盖到出发日那天已有的午餐行(同一行ID),模版第 2 日早餐订单上没有,新建一行:

{
  "code": 200,
  "message": "成功",
  "success": true,
  "data": {
    "orderId": "2100864597505286145",
    "generated": true,
    "items": [
      { "mealInfoId": "2100883161750687746", "mealType": "LUNCH", "mealDate": "2026-10-20", "dayNumber": 1, "dishName": "HL7741-TEST-餐食A", "unitPrice": 30.00, "settleType": "sign", "tableCount": 0, "personCount": 0, "amount": 0.00 },
      { "mealInfoId": "2100883192117448705", "mealType": "BREAKFAST", "mealDate": "2026-10-21", "dayNumber": 2, "dishName": "HL7741-TEST-餐食A", "unitPrice": 30.00, "settleType": "sign", "tableCount": 0, "personCount": 0, "amount": 0.00 },
      { "mealInfoId": "2100883161759076355", "mealType": "DINNER", "mealDate": "2026-10-21", "dayNumber": 2, "restaurantName": "HL7741-TEST-原行", "dishName": null, "unitPrice": 20.00, "settleType": null, "tableCount": 0, "personCount": 3, "amount": 60.00 }
    ]
  }
}

(TEST 实测,节选字段与行:模版没有第 2 日晚餐,订单原来的那一行不动。)

空数据 / 降级响应

订单出发日期为空时不套用,返回成功,message 为 589602 的提示文案,行不变。模版ID不存在或模版没有行时什么都不改,返回订单当前的行。

错误响应

{ "code": 589607, "message": "订单已完成或已取消,不能修改用餐信息", "success": false, "data": null }

业务边界

  • 订单出发日是第 1 日,模版第 d 日落到出发日 + d − 1。
  • 模版第 d 日某餐有安排:订单上已有那天那一餐的行,覆盖餐厅(ID、名称)、餐食(ID、编码、名称)、价钱、付款方式(编码、中文),金额按新价钱用同一公式重算;没有那一行就新建一行,桌数、人数、免人数、免金额、其他成本为 0。
  • 模版里没有的第 d 日、或第 d 日没有的那一餐,订单原来的行不动。
  • 模版里的餐食、餐厅被下架或删除,仍用模版里存的名称和价钱。订单已完成或已取消 589607。

四、契约约束与正确调用方式

✅ 正确 / ❌ 错误 payload 对照

场景 payload 结果
✅ 按人算 { "unitPrice": 30, "tableCount": 0, "personCount": 4, "freePersonCount": 1, "otherCost": 10, "amount": 100.00 } 保存成功
✅ 按桌算 { "unitPrice": 800, "tableCount": 2, "personCount": 0, "freePersonCount": 0, "freeAmount": 100, "otherCost": 50, "amount": 1550.00 } 保存成功
✅ 算出负数 { "unitPrice": 30, "personCount": 2, "freeAmount": 100, "amount": 0 } 保存成功,金额 0
❌ 金额对不上 { "unitPrice": 30, "personCount": 4, "freePersonCount": 1, "otherCost": 10, "amount": 99.99 } 589613,整批不保存
❌ 类别传 SELF { "mealType": "SELF", "mealDate": "2026-10-20" } 400

需要前端配合的两点

  1. 人数与桌数二选一:人数不为 0 时,桌数不能点击并设成 0;桌数不为 0 时,人数、免人数不能点击并设成 0。后端只按「桌数是否为 0」选公式。
  2. 类别下拉过滤 SELF:字典 meal_type 的 SELF 值不要出现在类别下拉里。

保存时的整单语义

3 号接口的 items 必须是页面上这张订单的全部行。只提交改动过的行会把其余行删掉。


五、数据库行为

前端操作 结果
2 号生成 该订单没有用餐行时写入一批空行;已有行时不写
3 号保存 带ID的行整行覆盖,不带ID的新增,没传上来的原有行软删除;任一行失败整批不写
4 号重置 软删除该订单全部用餐行,再写入一批新空行;一个事务
6 号存模版 新增一组模版行(新模版ID),不改任何已有模版
7 号套模版 覆盖对应日期对应餐次的行、新建缺少的行;其余行不动

被删除的行不会再出现在任何查询结果里。订单后来改出发日期或人数,已生成的用餐行不会跟着变(需要时调 4 号重置)。


六、边界行为

  • 未登录 → 401(网关拦截)。
  • 房务角色调 1、5、6 号及写接口 → 581045。
  • 订单不存在 → 订单模块现有「订单不存在」错误。
  • 订单已完成或已取消 → 2、3、4、7 号返回 589607,不写入;1 号照常可查,orderEditable=false。
  • 餐食、餐厅被下架或删除 → 已保存的用餐行与模版照常显示存下的名称和价钱。

六.5、枚举 / 数据字典

mealType(类别 / 餐次)

所属字段: mealType(3、5、6、7 号入参或出参) | 类型: String

值 中文 说明
BREAKFAST 早餐 —
LUNCH 午餐 3 号接口不传时的默认值
DINNER 晚餐 —

settleType(付款方式,字典 resource_settle_type)

所属字段: settleType / settleTypeName | 类型: String

值 中文 说明
cash 现付 —
sign 签单 —
company 公司付款 —

错误码(hl-order-service-v3,段位 589600–589699)

code message 触发接口
589601 订单不属于该团期 3
589602 订单出发或返回日期为空,无法生成用餐信息 2、4、7(只作提示:code 200,文案放在 message)
589606 用餐信息不存在 3、6
589607 订单已完成或已取消,不能修改用餐信息 2、3、4、7
589611 已挂订单或团期的用餐信息不能改挂或清空 3
589612 返回日期早于出发日期,无法生成用餐信息 2、4
589613 金额与明细对不上,请刷新后重试 3

七、不影响范围

  • 仅影响:订单详情「用餐」页签(全新接口)。
  • 零影响:
    • 订单、团期、核单等现有接口的入参出参;
    • 餐食管理、餐厅选项接口(本单只读用它们的数据);
    • 历史订单:不会自动生成用餐行,打开页签时才按 2 号接口生成。

八、测试环境已验证

部署 hl-order-service-v3(dev-v3,最终部署提交 6a121038b,含 #7945 与 #7954),经 Gateway 用管理员身份逐项实测,假数据名称均带 HL7741-TEST:

POST /v3/admin/order/meal-info/generate   → 只为含营地/特色餐/正餐生成 6 行空行,含酒店、自理不生成;再调不重复 ✓
POST /v3/admin/order/meal-info/save       → 新增+改+少传一行:新增有、改的变、少传的软删;按人 100.00、按桌 1550.00、负数存 0、免人数>人数照存 ✓
POST /v3/admin/order/meal-info/save       → 金额写错 → 589613,库里行不变 ✓;餐食下架/删除后照存页面值 ✓
POST /v3/admin/order/meal-info/save       → 行ID已删除/不存在 → 589606;别的订单的行 → 589611,两单行不变 ✓
POST /v3/admin/order/meal-template/save   → 模版只存模版名/第几日/餐次/餐厅/餐食/价钱/付款方式;同名再存是新模版,老模版不变 ✓
POST /v3/admin/order/meal-info/reset      → 原有行(含改过的、手动加的)全部软删,重新生成与首次一致 ✓
POST /v3/admin/order/meal-template/apply  → 第 1 日落出发日;已有行覆盖、缺的新建、模版缺的不动;餐食下架/删除后仍用模版名称和价钱 ✓
POST /v3/admin/order/{id}/adjustment/submit(改出发日期)→ 已生成的用餐行不变 ✓
已取消 / 已完成订单调 2、3、4、7 号 → 589607,零写入 ✓
GET  /v3/admin/order/meal-info/list(没挂团期的订单 + 团期ID / 团期编号)→ 回显同一团期的ID和编号 ✓

验证订单:2100864597505286145(HL7741-TEST-orderA)、2100864614022451201(HL7741-TEST-orderB,已取消)。


十、相关文档

关联 / 联系人

链接

联系人

  • 后端负责人: @lc