From 94bcdb3d9b30ddcd9f43dd09f04e0f494546702e Mon Sep 17 00:00:00 2001 From: mazengfei <331023091@qq.com> Date: Mon, 21 Sep 2026 14:21:17 +0800 Subject: [PATCH] =?UTF-8?q?feat(ratio-scale):=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E7=83=B9=E9=A5=AA=E6=97=B6=E9=95=BF=E9=87=87=E9=9B=86=E5=8F=8A?= =?UTF-8?q?=E8=B0=83=E6=96=99=E6=94=BE=E7=BD=AE=E6=97=B6=E9=97=B4=E6=94=AF?= =?UTF-8?q?=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 配比秤采样提交接口新增字段 cookStart,传递烹饪开始时间 - 食材构成中调料项新增 putTime 字段,记录调料首次使用时间 - 设备端提交采样时食材构造调整,调料按名称匹配 putTime 填充 - 新增对烹饪时长的后端计算功能,时长单位为分钟自动向上取整 - 采样列表接口响应新增 cookStart 与 cookDuration 字段,用于显示烹饪时长 - 配比秤采样模式相关API文档同步更新,废弃制作模式接口及字段 - 烹饪完成接口要求强化传递 cookStart,后端基于此计算并更新烹饪时长 - 关联数据库迁移支持烹饪时长字段持久化及历史记录完善 --- .../shuwei/dish/match/model/SampleModels.kt | 10 +- .../dish/match/ui/SubmitFoodActivity.kt | 7 +- 配比秤-采样模式-API文档.md | 588 +++++------------- 配比秤烹饪时长-设备端对接文档.md | 116 ++++ 4 files changed, 295 insertions(+), 426 deletions(-) create mode 100644 配比秤烹饪时长-设备端对接文档.md diff --git a/app/src/main/java/com/shuwei/dish/match/model/SampleModels.kt b/app/src/main/java/com/shuwei/dish/match/model/SampleModels.kt index 23f3761..04c6a70 100644 --- a/app/src/main/java/com/shuwei/dish/match/model/SampleModels.kt +++ b/app/src/main/java/com/shuwei/dish/match/model/SampleModels.kt @@ -130,7 +130,9 @@ data class ConstituteSaveItem( /** 食材称量用量(g),必须 > 0 */ val useWeight: Double = 0.0, /** 食材分类: 1=主材 / 2=辅材 / 3=调料 */ - val isMain: Int = 0 + val isMain: Int = 0, + /** 调料首次使用时间 yyyy-MM-dd HH:mm:ss,仅调料需要,主辅材留空;字段与制作模式 CookCompleteItem 对齐 */ + val putTime: String? = null ) /** @@ -141,6 +143,8 @@ data class ConstituteSaveItem( data class ConstituteSaveRequest( /** 菜品名称 */ val foodName: String? = null, + /** 烹饪开始时间 yyyy-MM-dd HH:mm:ss,进入提交页面时刻;字段与制作模式 CookCompleteRequest 对齐 */ + val cookStart: String? = null, /** 熟重(g),必须 > 0 */ val foodWeight: Double = 0.0, /** 食材构成列表,至少 1 条 */ @@ -174,6 +178,10 @@ data class SampleHistoryItem( val foodName: String = "", /** 熟重(g) */ val foodWeight: Double = 0.0, + /** 烹饪开始时间,提交时未传则为 null */ + val cookStart: String? = null, + /** 烹饪时长(分钟),后端按 提交时间-烹饪开始时间 向上取整计算;未传 cookStart 则为 null */ + val cookDuration: Int? = null, /** 状态: 1=实验中 / 2=已评测 / 9=已转化 */ val status: Int = 0, /** 状态中文 */ diff --git a/app/src/main/java/com/shuwei/dish/match/ui/SubmitFoodActivity.kt b/app/src/main/java/com/shuwei/dish/match/ui/SubmitFoodActivity.kt index e1a3835..2316797 100644 --- a/app/src/main/java/com/shuwei/dish/match/ui/SubmitFoodActivity.kt +++ b/app/src/main/java/com/shuwei/dish/match/ui/SubmitFoodActivity.kt @@ -563,13 +563,17 @@ class SubmitFoodActivity : BaseActivity() { } // 仅保留用量 > 0 的食材;materId 优先取 materId,为空时回退 goodsId + // 调料(isMain=3)按 goodsName 匹配首次使用时间填入 putTime,主辅材留空 val items = allGoods .filter { (it.useWeight ?: 0.0) > 0.0 } .map { ConstituteSaveItem( materId = it.materId?.takeIf { mid -> mid.isNotEmpty() } ?: it.goodsId, useWeight = it.useWeight ?: 0.0, - isMain = it.materialType + isMain = it.materialType, + putTime = if (it.materialType == 3) { + it.goodsName?.let { name -> seasoningFirstUseMap[name] } + } else null ) } if (items.isEmpty()) { @@ -591,6 +595,7 @@ class SubmitFoodActivity : BaseActivity() { val request = ConstituteSaveRequest( foodName = foodName, + cookStart = cookStartTime, foodWeight = foodWeight, items = items ) diff --git a/配比秤-采样模式-API文档.md b/配比秤-采样模式-API文档.md index f2bd54a..cf4f136 100644 --- a/配比秤-采样模式-API文档.md +++ b/配比秤-采样模式-API文档.md @@ -1,42 +1,45 @@ -# 配比秤(新设备)— 采样与制作模式 API 文档 +# 配比秤(新设备)— 采样模式 API 文档 > Controller: `NutRatioScaleController` > 路径前缀: `/neglect/ratio-scale`(Nacos 白名单 `/nutrition/neglect/**`,无需 Sa-Token) > 设备上下文通过请求头 `X-DEVICE-CODE` 解析(`TerminalContextHelper`,据此拿到食堂 id) -> 日期: 2026-09-09 +> 日期: 2026-09-10 > > **序列化规则**:Long 类型字段响应中均为字符串(Jackson `ToStringSerializer`),`BigDecimal` 为普通数字 > **单位约定**: -> - 提交构成的熟重 `foodWeight` 单位为 **克 (g)**(与 `nut_food_cook.output_weight` 口径一致) +> - 提交采样的熟重 `foodWeight` 单位为 **克 (g)** > - 食材称量用量 `useWeight` 单位为 **克 (g)** -> - 烹制单生重 `rawWeight`、熟重 `cookedWeight` 单位为 **千克 (kg)** -> - 食材实际用量 `actualQty` 单位为 **克 (g)** --- ## 业务背景 -配比秤新增「采样模式」与「制作模式」,两者通过**同一个提交接口** `POST /constitute/save` 的 `cookMode` 字段区分: +配比秤「采样」复用 **味养创新(nut_innovation)** 的人工创新实验流程,不再直接落 `nut_food` 主表。 -| cookMode | 模式 | 说明 | -|:--------:|------|------| -| 2 | 采样模式 | **从零建菜**:称量食材 → 新增菜品(生成构成/营养/宝塔)→ 进入「待审核」→ 后台审核通过后上架 | -| 1 | 制作模式 | **基于已有菜品**:称量食材 → 记录熟重+构成快照(历史参考,不覆盖正式构成)→ 标记菜品为「配比秤制作」 | +### 完整流程(3 步) -### 制作方式枚举 +``` +① 提交采样(设备端) → 落库味养创新实验,状态 = 1 实验中 +② 提交评测(味养创新后台)→ 计算营养 + 完善检验信息,状态 = 2 已评测 +③ 转化餐品(味养创新后台)→ 走 nut_food 新建餐品流程关联,状态 = 9 已转化 +``` -| 值 | 名称 | 菜品对象 | 数据来源 data_source | -|:--:|------|---------|---------------------| -| 1 | 制作模式 | 已有菜品(foodId 必填) | 3(配比秤制作) | -| 2 | 采样模式 | 新增菜品(foodName 必填) | 2(配比秤采样) | +### 设备端职责边界 -### 采样菜品审核状态枚举 +配比秤设备端只负责 **① 提交采样**(称量食材 + 熟重 → 写味养创新实验)。后续的 **② 提交评测**、**③ 转化餐品** 走味养创新已有的后台接口(`/nut/innovation`),不在设备端范围内。 -| 状态码 | 状态名 | 说明 | -|:------:|--------|------| -| 1 | 待审核 | 采样提交后的初始状态,后台可审核 | -| 2 | 已通过 | 审核通过,菜品上架生效 | -| 3 | 已驳回 | 审核驳回,菜品保持下架,可重新采样同名菜品 | +### 采样交互流程 + +``` +搜菜品(food-search) + │ + ├─ 有目标餐品 → 查菜品构成(food/{foodId}/composition)回显 → 可增删构成 + │ + └─ 无目标餐品 → 食材模糊搜索(mater-search)自己添加食材/调料 + │ + ▼ + 提交采样(constitute/save)→ 落库味养创新 +``` ### 食材分类枚举 @@ -52,18 +55,101 @@ | 序号 | 接口 | 路径 | 用途 | |:----:|------|------|------| -| 一 | 食材模糊搜索 | `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/food-search` | 按名称模糊查上架菜品 | +| 二 | 查菜品构成 | `GET /neglect/ratio-scale/food/{foodId}/composition` | 按菜品id查主辅料+调料,回显构成 | +| 三 | 食材模糊搜索 | `POST /neglect/ratio-scale/mater-search` | 采样称量前检索食材 | +| 四 | 提交采样 | `POST /neglect/ratio-scale/constitute/save` | **落库味养创新实验** | +| 五 | 已提交采样列表 | `POST /neglect/ratio-scale/sample-list` | 按日期查已提交采样,默认当天 | --- -## 一、食材模糊搜索 +## 一、搜菜品 + +``` +POST /neglect/ratio-scale/food-search +Content-Type: application/json +X-DEVICE-CODE: <设备编码> +``` + +根据餐厅查餐品名称,供硬件端选择目标菜品(有该餐品时回显其构成)。 + +### 请求体 + +```json +{ + "keyword": "西兰花", // 选填 — 菜品名称或编号模糊搜索 + "pageNum": 1, // 必填 — 页码 + "pageSize": 20 // 必填 — 每页条数 +} +``` + +### 响应 + +```json +{ + "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 查询已有菜品的主辅料+调料构成及熟重,供硬件端回显构成数据。 + +### 响应 + +```json +{ + "code": "00000", + "msg": "操作成功", + "data": { + "foodId": "2091839370283118593", // 菜品id + "foodName": "清炒西兰花", // 菜品名称 + "foodWeight": 450.0, // 熟重(g) + "ingredients": [ + { + "materId": "1979825467306471454", // 食材id + "materName": "樱桃萝卜", // 食材名称 + "useWeight": 300.0, // 用量(g) + "isMain": 1, // 1=主材 / 2=辅材 / 3=调料 + "isMainName": "主材" + } + ] + } +} +``` + +### 逻辑说明 + +- 数据源:`nut_food_composition`(按 `food_id` 查全部构成) +- 食材名称从 `nut_mater_base` **批量反查**(无 N+1) +- `foodWeight` 从 `nut_food_cook.output_weight` 反查 + +--- + +## 三、食材模糊搜索 ``` POST /neglect/ratio-scale/mater-search @@ -105,19 +191,6 @@ X-DEVICE-CODE: <设备编码> } ``` -### 字段说明 - -| 字段 | 类型 | 说明 | -|------|------|------| -| 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` @@ -127,92 +200,7 @@ X-DEVICE-CODE: <设备编码> --- -## 二、搜菜品 - -``` -POST /neglect/ratio-scale/food-search -Content-Type: application/json -X-DEVICE-CODE: <设备编码> -``` - -制作模式下,检索本食堂已上架的菜品,供后厨选择目标菜品。 - -### 请求体 - -```json -{ - "keyword": "西兰花", // 选填 — 菜品名称或编号模糊搜索 - "pageNum": 1, // 必填 — 页码 - "pageSize": 20 // 必填 — 每页条数 -} -``` - -### 响应 - -```json -{ - "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 查询已有菜品的主辅料+调料构成及熟重。 - -### 响应 - -```json -{ - "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 @@ -220,17 +208,16 @@ Content-Type: application/json X-DEVICE-CODE: <设备编码> ``` -**统一提交接口**,通过 `cookMode` 区分采样/制作。 +> ⚠️ **接口变更标注**:原提交接口含 `cookMode`(区分制作/采样),现制作模式已废弃,本接口统一为「提交采样」,入参去掉 `cookMode`、`foodId` 字段。 + +后厨称量食材 + 熟重后提交,后端落库到 **味养创新实验**(`nut_innovation`,人工创新,状态 = 实验中)。 ### 请求体 ```json { - "cookMode": 2, // 必填 — 1=制作模式 / 2=采样模式 - "foodId": 2091839370283118593, // 制作模式必填 — 已有菜品id - "foodName": "清炒西兰花", // 采样模式必填 — 菜品名称 + "foodName": "清炒西兰花", // 必填 — 菜品名称 "foodWeight": 500, // 必填 — 熟重(g),必须 > 0 - "mealType": 2, // 选填 — 餐次: 1=早餐/2=午餐/3=晚餐/4=加餐 "items": [ // 必填 — 食材构成列表,至少1条 { "materId": 20001, // 必填 — 食材id(nut_mater_base.id) @@ -256,29 +243,24 @@ X-DEVICE-CODE: <设备编码> } ``` -### 处理流程(采样模式 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(待审核) +2. 反查食材名称(nut_mater_base) + 一级分类名称(nut_mater_class) +3. 构建味养创新实验 DTO: + - foodName → food_name + - foodWeight → food_weight(熟重) + - items → 食材构成(mater_id + mater_name + mater_cat + amount_g + is_main) + - canteenId → 从 X-DEVICE-CODE 解析 + - startDate → 回填当天(实验开始日期) + - source / deviceCode → 来源标识(2) + 设备编码 + - innovGoal → 默认值「配比秤采样」 +4. 调用味养创新 manualAdd,落库 nut_innovation(status=1 实验中)+ nut_innovation_ing(食材构成) +5. 自动初始化营养:按食材构成 + 熟重计算每100g熟重营养,写入草稿评测事件 nut_innovation_eval(status=0)+ 营养维度 nut_innovation_eval_nutri(7 项:热量/蛋白/碳水/脂肪/维C/膳食纤维/钠,mineral 留空) ``` -### 处理流程(制作模式 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. 不覆盖菜品的正式构成/营养/宝塔(历史参考) -``` +> 注:设备端不传 `foodCategory`(餐品分类),落库为空,后期在味养创新后台维护;`innovGoal`(创新目标)后端填默认值「配比秤采样」,后续可改。 ### 异常场景 @@ -287,54 +269,37 @@ X-DEVICE-CODE: <设备编码> | 食材构成列表为空 | `请完善菜品构成信息` | | 食材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` | 新增 | 制作明细(构成快照) | +| `nut_innovation` | 新增 | 味养创新实验(status=1 实验中,含熟重 food_weight) | +| `nut_innovation_ing` | 新增 | 食材构成(mater_id + mater_name + mater_cat + amount_g + is_main) | +| `nut_innovation_eval` | 新增 | 草稿评测事件(status=0,采样时初始化) | +| `nut_innovation_eval_nutri` | 新增 | 营养维度(7 项自动计算值,mineral 留空) | +| `nut_mater_base` | 查询 | 反查食材名称 + 营养数据(计算营养) | +| `nut_mater_class` | 查询 | 反查一级分类名称 | --- -## 五、采样历史 +## 五、已提交采样列表 ``` -POST /neglect/ratio-scale/sample/history +POST /neglect/ratio-scale/sample-list Content-Type: application/json X-DEVICE-CODE: <设备编码> ``` -查询本食堂(由 `X-DEVICE-CODE` 解析)的采样提交历史,默认查询当天。 +查询本食堂已提交的采样记录(味养创新实验 `source=2`),分页返回、按提交时间倒序,供硬件端回看。 ### 请求体 ```json { - "pageNum": 1, // 必填 — 页码 - "pageSize": 10, // 必填 — 每页条数 - "keyword": "西兰花", // 选填 — 菜品名称模糊搜索 - "auditStatus": 1, // 选填 — 审核状态筛选: 1=待审核 / 2=已通过 / 3=已驳回 - "sampleDate": "2026-09-09" // 选填 — 采样日期,不传默认当天 + "pageNum": 1, // 必填 — 页码 + "pageSize": 20 // 必填 — 每页条数 } ``` @@ -346,14 +311,13 @@ X-DEVICE-CODE: <设备编码> "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" // 提交时间 + "innovId": "2097486806812893186", // 味养创新实验id + "innovNo": "INN-H-2026-001", // 实验编号 + "foodName": "清炒西兰花", // 餐品名称 + "foodWeight": 500.0, // 熟重(g) + "status": 1, // 状态: 1实验中/2已评测/9已转化 + "statusName": "实验中", // 状态中文 + "createTime": "2026-09-11 10:30:00" // 提交时间 } ], "total": 1 @@ -362,250 +326,26 @@ X-DEVICE-CODE: <设备编码> ### 逻辑说明 -- 数据源:`nut_food`(`data_source=2` 配比秤采样) -- `canteenId` 由 `X-DEVICE-CODE` 解析终端后自动限定 -- 排序:`create_time DESC` -- `foodWeight` 从 `nut_food_cook.output_weight` 反查 -- `auditRecordId` 从 `nut_food_audit_record` 反查 +- 数据源:`nut_innovation` +- 过滤:`canteen_id` = 当前食堂 + `source` = 2(配比秤采样,后台手动为 1) +- 排序:`create_time DESC`(查所有,不分日期) --- -## 六、制作历史 - -``` -POST /neglect/ratio-scale/make/history -Content-Type: application/json -X-DEVICE-CODE: <设备编码> -``` - -查询本设备(由 `X-DEVICE-CODE` 解析)的制作记录。 - -### 请求体 - -```json -{ - "pageNum": 1, // 必填 — 页码 - "pageSize": 10, // 必填 — 每页条数 - "day": "2026-09-09" // 选填 — 制作日期,不传查全部 -} -``` - -### 响应 - -```json -{ - "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`),用于前端渲染制作趋势图。 - -### 响应 - -```json -{ - "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: <设备编码> -``` - -按单烹制提交。**改造点**:原逻辑一次性完成(`待烹制 → 已完成`),现支持**任意状态下再次烹制**——每次提交累加份数/生重/熟重并留存一条流水,不校验烹制状态,需求达标后仍可继续烹制(应对菜不够卖接着做的场景)。 - -### 请求体 - -```json -{ - "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 - } - ] -} -``` - -### 响应 - -```json -{ - "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) + │ 称量 mater_id + use_weight(g) + 熟重 food_weight(g) ▼ -POST /constitute/save (cookMode=2) +POST /constitute/save │ - ├── 生成 nut_food (采样来源 + 待审核 + 下架) - ├── 生成 nut_food_cook (熟重 = foodWeight) - ├── 生成 nut_food_composition (食材构成) - ├── 自动计算 nut_food_nutri (每100g营养) + nut_food_pagoda (膳食宝塔) - └── 生成 nut_food_audit_record (待审核) -``` - -## 数据链路图(制作模式 cookMode=1) + ├── 生成 nut_innovation (味养创新实验,status=1 实验中,含 food_weight) + ├── 生成 nut_innovation_ing (食材构成:mater_id + mater_name + mater_cat + amount_g + is_main) + └── 自动算营养 → nut_innovation_eval(草稿) + nut_innovation_eval_nutri(7项) -``` -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 = 已完成(每次提交都置此状态,允许继续烹制累加) +后续(味养创新后台,非设备端): + ├── 提交评测 → 复核营养 + 补味道/口感/色泽,status=2 已评测 + └── 转化餐品 → nut_food(新建餐品流程),convert_food_id 关联,status=9 已转化 ``` diff --git a/配比秤烹饪时长-设备端对接文档.md b/配比秤烹饪时长-设备端对接文档.md new file mode 100644 index 0000000..fa813dc --- /dev/null +++ b/配比秤烹饪时长-设备端对接文档.md @@ -0,0 +1,116 @@ +# 配比秤烹饪时长 - 设备端对接文档 + +> 本次变更:配比秤新增「烹饪时长」采集与计算能力。设备端只需**传烹饪开始时间**和**调料放置时间**,时长由后端自动计算落库,设备端不需要自己算。 +> +> 部署依赖:数据库迁移 `V21__nut_innovation_cook_duration.sql` 需先于服务发布执行。 + +--- + +## 通用说明 + +- 时间格式统一为 `yyyy-MM-dd HH:mm:ss` +- 时长单位:**分钟**,由后端计算,规则:`时长 = 接口调用时间 - 烹饪开始时间`,**向上取整,正数至少 1 分钟**(如 90 秒 → 2 分钟,30 秒 → 1 分钟) +- 烹饪开始时间为空、或晚于当前时间(无效)时,后端**跳过时长计算**,接口正常成功 + +--- + +## 1. 提交采样(新增入参字段) + +**POST** `/nutrition/neglect/ratio-scale/constitute/save` + +### 请求参数(仅列出新增字段,其余字段不变) + +| 字段 | 类型 | 必填 | 说明 | +|------|------|:----:|------| +| cookStart | string | 建议 | **新增**。烹饪开始时间,用于与提交时间计算烹饪时长(分钟) | +| items[].putTime | string | - | **新增**。调料放置时间,仅调料项(`isMain=3`)传,主材/辅材不传 | + +### 完整请求示例 + +```json +{ + "foodName": "低钠宫保鸡丁", + "foodWeight": 1250.5, + "cookStart": "2026-09-21 10:30:00", + "items": [ + { "materId": 1001, "useWeight": 500, "isMain": 1 }, + { "materId": 1002, "useWeight": 200, "isMain": 2 }, + { "materId": 1003, "useWeight": 15, "isMain": 3, "putTime": "2026-09-21 10:32:10" }, + { "materId": 1004, "useWeight": 8, "isMain": 3, "putTime": "2026-09-21 10:35:40" } + ] +} +``` + +### 后端行为 + +- 烹饪开始时间落库 `nut_innovation.cook_start`,时长落库 `nut_innovation.cook_duration`,调料放置时间落库 `nut_innovation_ing.put_time` +- 响应结构不变(`Result`) + +--- + +## 2. 烹饪完成(入参字段无变化,传值要求强化) + +**POST** `/nutrition/neglect/ratio-scale/cook-orders/complete` + +### 相关字段 + +| 字段 | 类型 | 必填 | 说明 | +|------|------|:----:|------| +| cookOrderId | long | ✓ | 烹制单 id(原有) | +| cookStart | string | **强烈建议** | 烹饪开始时间(字段原有)。**本次开始烹饪时务必回传**,否则不计算时长 | +| seasonings[].putTime | string | - | 调料放置时间(字段原有),继续按原逻辑传 | + +### 后端行为(新增) + +- 每次完成按 `本次 cookStart → 接口调用时间` 计算时长,**更新菜品烹饪工艺表 `nut_food_cook.cook_duration`** +- **多次烹饪时长不累计、各算各的**:同一烹制单多次提交时,每次都用本次的时长**覆盖**菜品烹饪时长 +- 同时写一条餐品操作记录(后台可见):操作标题「配比秤烹饪餐品计算」,记录时长从 X 分钟更新为 Y 分钟 +- `cookStart` 为空时跳过以上更新,其余原有逻辑(流水、生熟比、累加)不受影响 + +--- + +## 3. 已提交采样列表(响应新增字段) + +**POST** `/nutrition/neglect/ratio-scale/sample-list` + +### 响应 `data.list[]` 新增字段 + +| 字段 | 类型 | 说明 | +|------|------|------| +| cookStart | string | **新增**。烹饪开始时间;提交时未传则为 null | +| cookDuration | int | **新增**。烹饪时长(分钟);提交时未传 cookStart 则为 null | + +### 响应示例 + +```json +{ + "code": "00000", + "msg": "操作成功", + "data": { + "list": [ + { + "innovId": 1930123456789, + "innovNo": "INN-H-2026-041", + "foodName": "低钠宫保鸡丁", + "foodWeight": 1250.5, + "cookStart": "2026-09-21 10:30:00", + "cookDuration": 12, + "status": 1, + "statusName": "实验中", + "createTime": "2026-09-21 10:42:03" + } + ], + "total": 1 + } +} +``` + +--- + +## 设备端改动清单 + +| 接口 | 改动 | +|------|------| +| constitute/save | 请求新增 `cookStart` + 调料项 `putTime`(开始烹饪时记录时间,放调料时记录时间) | +| cook-orders/complete | 字段无变化,确保每次提交回传 `cookStart`;`seasonings[].putTime` 继续传 | +| sample-list | 响应新增 `cookDuration`,列表可展示「烹饪时长(分钟)」 |