- 新增手动录入食材信息的UI布局,包含食材名称搜索、主辅材选择及用量输入 - 整合采样模式下的食材搜索弹窗,支持食材模糊查询与净材种类选择 - 新增相关网络接口调用,实现食材搜索、菜品搜索、菜品构成查询及采样历史等功能 - 优化食材列表展示,支持采样历史审核状态和熟重显示,增加颜色区分 - 修改PrepareFoodActivity,采样模式启用手动新增食材功能,包含添加、重置操作 - 调整Adapter和ViewModel,兼容新接口数据模型,支持分页加载和状态回调 - 修正接口路径和网络请求地址,统一使用nutrition/neglect路径前缀 - 删除冗余代码和未使用的旧接口代码,简化搜索逻辑及界面响应机制
19 KiB
19 KiB
配比秤(新设备)— 采样与制作模式 API 文档
Controller:
NutRatioScaleController路径前缀:/neglect/ratio-scale(Nacos 白名单/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/save 的 cookMode 字段区分:
| 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_name和alias - 排序:
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_food(status=1上架) - 过滤:
canteen_id= 当前食堂 - 关键词模糊匹配
food_name和food_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, // 必填 — 食材id(nut_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_food:data_source=2(配比秤采样) / audit_status=1(待审核) / status=0(下架)
5. 保存熟重 nut_food_cook:output_weight = foodWeight
6. 保存食材构成 nut_food_composition:mater_id + use_weight + is_main
7. 根据构成自动计算营养指标 nut_food_nutri + 膳食宝塔 nut_food_pagoda
8. 发起审核记录 nut_food_audit_record:audit_status=1(待审核)
处理流程(制作模式 cookMode=1)
1. 校验食材构成非空 + materId 非空且唯一
2. 校验菜品存在(foodId 必填)
3. 标记菜品 nut_food.data_source = 3(配比秤制作)
4. 保存制作记录 nut_food_matching_use:device_code + food_id + food_name + meal_type + day + food_weight(熟重)
5. 保存制作明细 nut_food_matching_use_item:mater_id + mater_name + use_weight + is_main(构成快照)
6. 不覆盖菜品的正式构成/营养/宝塔(历史参考)
异常场景
| 场景 | 错误信息 |
|---|---|
| 食材构成列表为空 | 请完善菜品构成信息 |
| 食材id为空 | 菜品食材不能为空 |
| 食材id重复 | 菜品食材不能重复,重复食材id:xxx |
| 制作方式非法 | 不支持的制作方式: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_food(data_source=2配比秤采样) canteenId由X-DEVICE-CODE解析终端后自动限定- 排序:
create_time DESC foodWeight从nut_food_cook.output_weight反查auditRecordId从nut_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 / 1000,g→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 = 已完成(每次提交都置此状态,允许继续烹制累加)