Files
DishMatch/配比秤-采样模式-API文档.md
T
mazengfei 6d3c5490a4 feat(food-prepare): 增加食材信息录入卡及相关接口支持
- 新增手动录入食材信息的UI布局,包含食材名称搜索、主辅材选择及用量输入
- 整合采样模式下的食材搜索弹窗,支持食材模糊查询与净材种类选择
- 新增相关网络接口调用,实现食材搜索、菜品搜索、菜品构成查询及采样历史等功能
- 优化食材列表展示,支持采样历史审核状态和熟重显示,增加颜色区分
- 修改PrepareFoodActivity,采样模式启用手动新增食材功能,包含添加、重置操作
- 调整Adapter和ViewModel,兼容新接口数据模型,支持分页加载和状态回调
- 修正接口路径和网络请求地址,统一使用nutrition/neglect路径前缀
- 删除冗余代码和未使用的旧接口代码,简化搜索逻辑及界面响应机制
2026-09-10 13:41:35 +08:00

19 KiB
Raw Blame History

配比秤(新设备)— 采样与制作模式 API 文档

Controller: NutRatioScaleController 路径前缀: /neglect/ratio-scaleNacos 白名单 /nutrition/neglect/**,无需 Sa-Token) 设备上下文通过请求头 X-DEVICE-CODE 解析(TerminalContextHelper,据此拿到食堂 id 日期: 2026-09-09

序列化规则:Long 类型字段响应中均为字符串(Jackson ToStringSerializer),BigDecimal 为普通数字 单位约定

  • 提交构成的熟重 foodWeight 单位为 克 (g)(与 nut_food_cook.output_weight 口径一致)
  • 食材称量用量 useWeight 单位为 克 (g)
  • 烹制单生重 rawWeight、熟重 cookedWeight 单位为 千克 (kg)
  • 食材实际用量 actualQty 单位为 克 (g)

业务背景

配比秤新增「采样模式」与「制作模式」,两者通过同一个提交接口 POST /constitute/savecookMode 字段区分:

cookMode 模式 说明
2 采样模式 从零建菜:称量食材 → 新增菜品(生成构成/营养/宝塔)→ 进入「待审核」→ 后台审核通过后上架
1 制作模式 基于已有菜品:称量食材 → 记录熟重+构成快照(历史参考,不覆盖正式构成)→ 标记菜品为「配比秤制作」

制作方式枚举

名称 菜品对象 数据来源 data_source
1 制作模式 已有菜品(foodId 必填) 3(配比秤制作)
2 采样模式 新增菜品(foodName 必填) 2(配比秤采样)

采样菜品审核状态枚举

状态码 状态名 说明
1 待审核 采样提交后的初始状态,后台可审核
2 已通过 审核通过,菜品上架生效
3 已驳回 审核驳回,菜品保持下架,可重新采样同名菜品

食材分类枚举

分类 名称 说明
1 主材 菜品主要食材
2 辅材 菜品辅助食材
3 调料 油盐酱醋等

接口清单

序号 接口 路径 用途
食材模糊搜索 POST /neglect/ratio-scale/mater-search 称量前检索食材(设备对应食堂食材库)
搜菜品 POST /neglect/ratio-scale/food-search 制作模式选已有菜品(按名称模糊查上架菜品)
查菜品构成 GET /neglect/ratio-scale/food/{foodId}/composition 制作模式查看已有菜品的主辅料+调料构成
提交构成 POST /neglect/ratio-scale/constitute/save 统一提交,cookMode 区分制作/采样
采样历史 POST /neglect/ratio-scale/sample/history 查询本食堂采样提交历史(默认当天)
制作历史 POST /neglect/ratio-scale/make/history 按设备+日期查询制作记录分页
制作历史图表 GET /neglect/ratio-scale/make/history/{foodId} 按菜品id查制作历史图表数据
烹饪完成 POST /neglect/ratio-scale/cook-orders/complete 按单烹制,任意状态提交均累加

一、食材模糊搜索

POST /neglect/ratio-scale/mater-search
Content-Type: application/json
X-DEVICE-CODE: <设备编码>

称量食材前,从食材库检索食材(设备对应食堂食材)。

请求体

{
  "keyword": "萝卜",          // 选填 — 模糊匹配食材名称和别名
  "pageNum": 1,              // 必填 — 页码
  "pageSize": 20             // 必填 — 每页条数
}

响应

{
  "code": "00000",
  "msg": "操作成功",
  "data": [
    {
      "id": "1979825467306471454",        // 食材id
      "materName": "樱桃萝卜",             // 食材名称
      "materCode": "04041012",            // 食材编码
      "alias": null,                      // 别名
      "materFirstClass": "04",            // 食材一级分类
      "materSecondClass": "043",          // 食材二级分类
      "materUrl": null,                   // 食材图片url
      "canteenId": "2"                    // 归属食堂id
    }
  ],
  "total": 1
}

字段说明

字段 类型 说明
id Long → String 食材id(提交构成 items.materId 使用)
materName String 食材名称
materCode String 食材编码(一级+二级+自编码,全局唯一)
alias String 别名
materFirstClass String 食材一级分类
materSecondClass String 食材二级分类
materUrl String 食材图片url
canteenId Long → String 归属食堂id

逻辑说明

  • 数据源:nut_mater_base
  • 过滤:canteen_id = 当前食堂(由 X-DEVICE-CODE 解析)
  • 关键词模糊匹配 mater_namealias
  • 排序:mater_name ASC

二、搜菜品

POST /neglect/ratio-scale/food-search
Content-Type: application/json
X-DEVICE-CODE: <设备编码>

制作模式下,检索本食堂已上架的菜品,供后厨选择目标菜品。

请求体

{
  "keyword": "西兰花",       // 选填 — 菜品名称或编号模糊搜索
  "pageNum": 1,              // 必填 — 页码
  "pageSize": 20             // 必填 — 每页条数
}

响应

{
  "code": "00000",
  "msg": "操作成功",
  "data": [
    {
      "foodId": "2091839370283118593",   // 菜品id
      "foodCode": "FOOD00012",           // 菜品编号
      "foodName": "清炒西兰花"            // 菜品名称
    }
  ],
  "total": 1
}

逻辑说明

  • 数据源:nut_foodstatus=1 上架)
  • 过滤:canteen_id = 当前食堂
  • 关键词模糊匹配 food_namefood_code
  • 排序:id DESC

三、查菜品构成

GET /neglect/ratio-scale/food/{foodId}/composition
X-DEVICE-CODE: <设备编码>

制作模式下,根据菜品 id 查询已有菜品的主辅料+调料构成及熟重。

响应

{
  "code": "00000",
  "msg": "操作成功",
  "data": {
    "foodId": "2091839370283118593",      // 菜品id
    "foodName": "清炒西兰花",              // 菜品名称
    "foodWeight": 450.0,                  // 熟重(g),从 nut_food_cook.output_weight 反查
    "ingredients": [
      {
        "materId": "1979825467306471454", // 食材id
        "materName": "樱桃萝卜",           // 食材名称
        "useWeight": 300.0,               // 用量(g)
        "isMain": 1,                      // 1=主材 / 2=辅材 / 3=调料
        "isMainName": "主材"
      }
    ]
  }
}

逻辑说明

  • 数据源:nut_food_composition(按 food_id 查全部构成)
  • 食材名称从 nut_mater_base 批量反查

四、提交构成(cookMode 区分采样/制作)

POST /neglect/ratio-scale/constitute/save
Content-Type: application/json
X-DEVICE-CODE: <设备编码>

统一提交接口,通过 cookMode 区分采样/制作。

请求体

{
  "cookMode": 2,                // 必填 — 1=制作模式 / 2=采样模式
  "foodId": 2091839370283118593, // 制作模式必填 — 已有菜品id
  "foodName": "清炒西兰花",       // 采样模式必填 — 菜品名称
  "foodWeight": 500,            // 必填 — 熟重(g),必须 > 0
  "mealType": 2,                // 选填 — 餐次: 1=早餐/2=午餐/3=晚餐/4=加餐
  "items": [                    // 必填 — 食材构成列表,至少1条
    {
      "materId": 20001,         // 必填 — 食材idnut_mater_base.id
      "useWeight": 300,         // 必填 — 食材称量用量(g),必须 > 0
      "isMain": 1               // 必填 — 食材分类: 1=主材 / 2=辅材 / 3=调料
    },
    {
      "materId": 30001,
      "useWeight": 50,
      "isMain": 3
    }
  ]
}

响应

{
  "code": "00000",
  "msg": "操作成功",
  "data": null
}

处理流程(采样模式 cookMode=2)

1. 校验食材构成非空 + materId 非空且唯一
2. 校验菜品名称在同食堂下唯一(排除已驳回菜品,允许驳回后重新采样同名)
3. 生成菜品编号(FOOD + 5位序号)
4. 保存菜品 nut_fooddata_source=2(配比秤采样) / audit_status=1(待审核) / status=0(下架)
5. 保存熟重 nut_food_cookoutput_weight = foodWeight
6. 保存食材构成 nut_food_compositionmater_id + use_weight + is_main
7. 根据构成自动计算营养指标 nut_food_nutri + 膳食宝塔 nut_food_pagoda
8. 发起审核记录 nut_food_audit_recordaudit_status=1(待审核)

处理流程(制作模式 cookMode=1)

1. 校验食材构成非空 + materId 非空且唯一
2. 校验菜品存在(foodId 必填)
3. 标记菜品 nut_food.data_source = 3(配比秤制作)
4. 保存制作记录 nut_food_matching_usedevice_code + food_id + food_name + meal_type + day + food_weight(熟重)
5. 保存制作明细 nut_food_matching_use_itemmater_id + mater_name + use_weight + is_main(构成快照)
6. 不覆盖菜品的正式构成/营养/宝塔(历史参考)

异常场景

场景 错误信息
食材构成列表为空 请完善菜品构成信息
食材id为空 菜品食材不能为空
食材id重复 菜品食材不能重复,重复食材idxxx
制作方式非法 不支持的制作方式:xxx
采样:同食堂已存在同名菜品(非驳回) 该食堂下已存在同名餐品
采样:食材id无效 存在无效的食材id
制作:菜品不存在 菜品不存在
熟重/用量 ≤ 0 参数校验:熟重必须大于0 / 食材用量必须大于0

涉及数据表

采样模式(cookMode=2

表名 操作 说明
nut_food 新增 采样菜品主表(采样来源 + 待审核 + 下架)
nut_food_cook 新增 保存熟重
nut_food_composition 新增 食材构成
nut_food_nutri 新增 每100g营养指标(自动计算)
nut_food_pagoda 新增 膳食宝塔(自动计算)
nut_food_audit_record 新增 待审核记录

制作模式(cookMode=1

表名 操作 说明
nut_food 更新 标记 data_source=3(配比秤制作)
nut_food_matching_use 新增 制作记录(熟重记录)
nut_food_matching_use_item 新增 制作明细(构成快照)

五、采样历史

POST /neglect/ratio-scale/sample/history
Content-Type: application/json
X-DEVICE-CODE: <设备编码>

查询本食堂(由 X-DEVICE-CODE 解析)的采样提交历史,默认查询当天。

请求体

{
  "pageNum": 1,                    // 必填 — 页码
  "pageSize": 10,                  // 必填 — 每页条数
  "keyword": "西兰花",             // 选填 — 菜品名称模糊搜索
  "auditStatus": 1,                // 选填 — 审核状态筛选: 1=待审核 / 2=已通过 / 3=已驳回
  "sampleDate": "2026-09-09"       // 选填 — 采样日期,不传默认当天
}

响应

{
  "code": "00000",
  "msg": "操作成功",
  "data": [
    {
      "foodId": "2097486806812893186",     // 菜品id
      "foodCode": "FOOD00030",             // 菜品编号
      "foodName": "清炒西兰花",             // 菜品名称
      "foodWeight": 500.0,                 // 熟重(g)
      "auditStatus": 1,                    // 审核状态: 1=待审核 / 2=已通过 / 3=已驳回
      "auditStatusName": "待审核",          // 审核状态中文
      "auditRecordId": "2097486809375612929", // 审核记录id
      "createTime": "2026-09-09 08:46:39"  // 提交时间
    }
  ],
  "total": 1
}

逻辑说明

  • 数据源:nut_fooddata_source=2 配比秤采样)
  • canteenIdX-DEVICE-CODE 解析终端后自动限定
  • 排序:create_time DESC
  • foodWeightnut_food_cook.output_weight 反查
  • auditRecordIdnut_food_audit_record 反查

六、制作历史

POST /neglect/ratio-scale/make/history
Content-Type: application/json
X-DEVICE-CODE: <设备编码>

查询本设备(由 X-DEVICE-CODE 解析)的制作记录。

请求体

{
  "pageNum": 1,                    // 必填 — 页码
  "pageSize": 10,                  // 必填 — 每页条数
  "day": "2026-09-09"              // 选填 — 制作日期,不传查全部
}

响应

{
  "code": "00000",
  "msg": "操作成功",
  "data": [
    {
      "id": "2097499999999999999",     // 制作记录id
      "foodId": "2091839370283118593", // 菜品id
      "foodName": "清炒西兰花",         // 菜品名称
      "foodWeight": 500.0,             // 熟重(g)
      "mealType": 2,                   // 餐次: 1=早餐/2=午餐/3=晚餐/4=加餐
      "mealTypeName": "午餐",
      "day": "2026-09-09",             // 制作日期
      "createTime": "2026-09-09 08:46:39"
    }
  ],
  "total": 1
}

逻辑说明

  • 数据源:nut_food_matching_use
  • 过滤:device_code = 当前设备(由 X-DEVICE-CODE 解析)
  • 排序:create_time DESC

七、制作历史图表(按菜品id

GET /neglect/ratio-scale/make/history/{foodId}
X-DEVICE-CODE: <设备编码>

根据菜品 id 查询该菜品的所有制作记录,返回图表数据(对齐借鉴项目 queryGoodsInfoByFoodIdMatchingList),用于前端渲染制作趋势图。

响应

{
  "code": "00000",
  "msg": "操作成功",
  "data": [
    {
      "xAxes": [
        {"goodId": "", "goodName": "时间"},
        {"goodId": "", "goodName": "熟重"},
        {"goodId": "20001", "goodName": "樱桃萝卜"},
        {"goodId": "30001", "goodName": "酱油"}
      ],
      "yAxes": [
        {"value": "2026-09-09 08:46:39"},
        {"value": 500.0},
        {"value": 300.0},
        {"value": 50.0}
      ]
    }
  ]
}

字段说明

字段 说明
xAxes 横轴模板:时间、熟重、该菜品所有出现过的食材(去重保序),所有记录共用
xAxes[].goodId 食材id(时间/熟重项为空字符串)
xAxes[].goodName 项名称(时间/熟重/食材名称)
yAxes 纵轴值,与 xAxes 一一对应
yAxes[].value 值:时间项为时间戳,其余为数值(缺失食材补 0)

逻辑说明

  • 数据源:nut_food_matching_use(主表)+ nut_food_matching_use_item(子表)
  • 主表按 food_id 过滤,create_time DESC 排序
  • 子表按 matching_use_id 批量查询并分组(消除 N+1
  • 时间项取 create_time,熟重项取 food_weight,食材项取 use_weight(缺失补 0

八、烹饪完成(支持再次烹制)

POST /neglect/ratio-scale/cook-orders/complete
Content-Type: application/json
X-DEVICE-CODE: <设备编码>

按单烹制提交。改造点:原逻辑一次性完成(待烹制 → 已完成),现支持任意状态下再次烹制——每次提交累加份数/生重/熟重并留存一条流水,不校验烹制状态,需求达标后仍可继续烹制(应对菜不够卖接着做的场景)。

请求体

{
  "cookOrderId": 10001,                 // 必填 — 烹制单id
  "portions": 10,                       // 选填 — 本次烹制份数,不传则按申领单/需求份数兜底
  "cookStart": "2026-09-09 10:00:00",   // 选填 — 本次烹制开始时间
  "cookedWeight": 3.5,                  // 必填 — 本次熟重(kg)
  "items": [                            // 必填 — 主辅材实际用量列表,至少1条
    {
      "materId": 20001,                 // 必填 — 食材id
      "actualQty": 5000                 // 必填 — 实际用量(g)
    }
  ],
  "seasonings": [                       // 选填 — 调料用量列表
    {
      "materId": 30001,
      "actualQty": 15.0
    }
  ]
}

响应

{
  "code": "00000",
  "msg": "操作成功",
  "data": null
}

状态流转规则(改造核心)

每次提交后(不校验状态,任何状态下提交均累加):
  cooked_portions 累加 += 本次份数
  raw_weight 累加 += 本次生重
  cooked_weight 累加 += 本次熟重
  cook_status → 2(已完成,每次提交都置此状态,允许继续烹制累加)
  生熟比 = 累计熟重 / 累计生重(用累加后的值)

时间语义:
  cook_start = 首次烹制开始时间(后续烹制不覆盖)
  cook_end   = 最后一次烹制结束时间(每次更新)

处理流程

1. 校验烹制单存在 + 食堂权限(不校验烹制状态,任何状态下提交均累加数据)
2. 本次烹制份数 = 入参 portions(优先)→ 申领单 quantity → 需求份数 → 1
3. 首次烹制(cooked_portions=0)才完善组配任务并完成
4. 计算本次生重(Σ actualQty / 1000g→kg
5. 计算生熟比 = 累计熟重 / 累计生重(用累加后的值)
6. 写烹制流水 nut_prod_cook_flow(本次生重/熟重/份数/起止时间)
7. 保存食材明细 + 调料(首次更新预生成明细,再次插入新明细,均关联 flow_id)
8. 累加主表:份数/生重/熟重累加 + 状态置已完成 + 起止时间

异常场景

场景 错误信息
烹制单不存在 烹制单不存在
首次烹制未关联组配任务 烹制单未关联组配任务,无法开始烹制
组配任务不存在 组配任务不存在,comboNo=xxx

涉及数据表

表名 操作 说明
nut_prod_cook_order 查询 + 更新 累加份数/生重/熟重 + 状态置已完成
nut_prod_cook_flow 新增 烹制流水,每次烹制一条
nut_prod_cook_ingredient 查询 + 更新/新增 首次更新预生成明细,再次插入新明细,均带 flow_id
nut_prod_combo_task / _item 查询 + 更新 仅首次烹制时完善组配
nut_sup_internal_supply / nut_sup_meal_package / _ingredient 查询 解析溯源码与份数
nut_mater_base / nut_food_composition 查询 食材编码/名称/分类

数据链路图(采样模式 cookMode=2)

nut_mater_base (食材库)
    │
    │ 采样称量 mater_id + use_weight(g)
    ▼
POST /constitute/save (cookMode=2)
    │
    ├── 生成 nut_food (采样来源 + 待审核 + 下架)
    ├── 生成 nut_food_cook (熟重 = foodWeight)
    ├── 生成 nut_food_composition (食材构成)
    ├── 自动计算 nut_food_nutri (每100g营养) + nut_food_pagoda (膳食宝塔)
    └── 生成 nut_food_audit_record (待审核)

数据链路图(制作模式 cookMode=1)

nut_food (已有菜品)
    │
    │ 制作称量 mater_id + use_weight(g) + 熟重
    ▼
POST /constitute/save (cookMode=1)
    │
    ├── 标记 nut_food.data_source = 3 (配比秤制作)
    ├── 生成 nut_food_matching_use (制作记录:熟重/餐次/日期/设备)
    └── 生成 nut_food_matching_use_item (构成快照,历史参考)
          (不覆盖 nut_food_composition / nut_food_nutri / nut_food_pagoda

数据链路图(再次烹制)

nut_prod_cook_order (烹制单,累加数据)
    │
    ├── POST /cook-orders/complete (每次烹制)
    │       ├── 生成 nut_prod_cook_flow (流水,每次一条)
    │       └── 生成/更新 nut_prod_cook_ingredient (明细,带 flow_id)
    │
    └── 主表累加: cooked_portions / raw_weight / cooked_weight
              cook_start = 首次开始 / cook_end = 末次结束
              cook_status = 已完成(每次提交都置此状态,允许继续烹制累加)