Grocy 1.14.0 新特性深度解析:Recipes 菜谱模块如何打通库存与购物清单
【免费下载链接】grocyERP beyond your fridge - Grocy is a web-based self-hosted groceries & household management solution for your home项目地址: https://gitcode.com/GitHub_Trending/gr/grocy
Grocy 1.14.0(2018-07-15)是本项目发展史上具有里程碑意义的一次发布——它首次引入了Recipes(菜谱)功能,将"库存管理"与"居家做饭"两个场景打通:你可以把一组产品、数量与说明文字组织成一份菜谱,随时查看所需原料是否全部在库,并把缺失的原料一键加入购物清单。本文以 changelog/27_1.14.0_2018-07-15.md 为骨架,结合当前仓库的数据库迁移脚本与 Recipes 相关源码,还原该版本的核心改动,并补充可直接落地的使用与扩展细节。
版本速览:1.14.0 带来了什么
对照变更日志,本次版本的核心内容可归纳为四类:
| 类别 | 具体改动 |
|---|---|
| 核心新功能 | 引入Recipes(菜谱):把产品、数量与说明组织成菜谱;一键查看原料库存匹配情况;一键把缺失原料加入购物清单 |
| 本地化 | 新增挪威语(Norwegian)翻译,由社区贡献者 @BlizzWave 提供 |
| UI 改进 | 表格列可拖拽重排;购物清单页内嵌日历;列排序与排序状态被记住;侧边栏折叠状态被记住;修复日期时间选择器边框;激活子菜单时保持父级菜单展开 |
| 自定义机制 | 自定义 JS/CSS 文件名发生变更(详见下文第 5 节) |
其中Recipes 是整个 Grocy 从"库存工具"走向"家庭管理平台"的关键一步,也是本文展开的重点。
Recipes 功能全景:从菜谱定义到一键补货
数据模型:菜谱与菜谱原料行
从 migrations/0025.sql 可以看到该功能最初落库时的两张核心表:
recipes:菜谱主表,字段为id、name(菜谱名)、description(说明文字)、row_created_timestamp;recipes_pos:菜谱原料行,字段为id、recipe_id(所属菜谱)、product_id(对应产品)、amount(数量)、note(备注)。
也就是说,1.14.0 的菜谱模型就是"菜谱 = 一个名称 + 一段描述 + 若干原料行",原料行通过product_id与产品主数据(stockoverview 中的产品)关联,amount即所需数量。这份结构延续至今,后续版本在此基础上不断加列增强(见第 6 节)。
库存匹配逻辑:recipes_fulfillment 视图
菜谱"原料是否齐备"的判断并非写死在 PHP 里,而是由数据库视图完成。同一迁移文件 migrations/0025.sql 中定义了recipes_fulfillment视图,其核心计算逻辑为:
recipe_amount:菜谱要求的数量;stock_amount:当前库存量(来自stock_current视图);need_fulfilled:stock_amount >= recipe_amount时为 1,否则为 0(即"该原料是否满足需求");missing_amount:stock_amount - recipe_amount为负时的绝对值,即缺口数量;amount_on_shopping_list:该产品当前在购物清单上的数量(含amount_autoadded自动追加部分);need_fulfilled_with_shopping_list:库存加购物清单合计是否满足需求。
配套的recipes_fulfillment_sum视图则对每份菜谱做聚合:只要有一行原料未满足,整份菜谱的need_fulfilled就为 0,并统计missing_products_count(缺失原料种类数)。这就是"一眼看出菜谱是否齐备"的底层来源。
一键补缺:把缺失原料加入购物清单
变更日志承诺的"把缺失的东西一键放到购物清单上"由 RecipesService::AddNotFulfilledProductsToShoppingList 实现,其流程为:
- 取该菜谱所有解析后的原料行(
recipes_pos_resolved),过滤出属于当前菜谱、且不在excludedProductIds排除列表中的行; - 计算需采购量:
missing_amount - amount_on_shopping_list(已在清单上的部分不再重复加); - 对购物清单上已存在的该产品条目执行"累加金额"更新,否则新建条目(记录
product_id、amount、qu_id)。
对外暴露的 API 端点为POST /api/recipes/{recipeId}/add-not-fulfilled-products-to-shoppinglist(见 routes.php 与 RecipesApiController.php),该接口要求调用者具备PERMISSION_SHOPPINGLIST_ITEMS_ADD权限,请求体可携带excludedProductIds数组实现"部分排除"。
消耗菜谱与"自产"记账
除了"看缺什么",1.14.0 的 Recipes 还支持按菜谱消耗库存。核心实现位于 RecipesService::ConsumeRecipe:
- 整个过程包裹在数据库事务中(
beginTransaction/commit/rollback),任一原料消耗失败即整体回滚; - 遍历每行原料,当
stock_amount > 0时调用StockService::ConsumeProduct消耗库存;若库存不足(stock_amount < recipe_amount),则只消耗当前实际库存量; - 若菜谱配置了"产出产品"(
product_id),随后会通过StockService::AddProduct以TRANSACTION_TYPE_SELF_PRODUCTION(自产)事务类型把成品加回库存,并写入菜谱名作为来源备注——这为后续"自制果酱""自制面包"等场景奠定了数据基础。
从 routes.php 看,其 API 端点为POST /api/recipes/{recipeId}/consume,并校验PERMISSION_STOCK_CONSUME权限(见 RecipesApiController.php)。
配套页面与路由
1.14.0 同期提供了完整的菜谱 UI,当前仓库中对应页面包括:
GET /recipes:菜谱概览页 recipes.blade.php,展示菜谱及其原料匹配状态;GET /recipe/{recipeId}:菜谱编辑页 recipeform.blade.php;GET /recipe/{recipeId}/pos/{recipePosId}:原料行编辑页 recipeposform.blade.php;GET /recipessettings:菜谱设置页 recipessettings.blade.php;GET /recipe/{recipeId}/grocycode:生成菜谱 Grocycode(二维码)图片,实现"扫一个码定位一份菜谱"(见 RecipesController::RecipeGrocycodeImage)。
路由注册集中在 routes.php,页面渲染逻辑见 RecipesController。
本次 UI 改进:可记忆、可重排的表格体验
1.14.0 的 UI 改动集中在交互记忆与表格操作上:
- 表格列可重排:各列表格的列顺序支持拖拽调整;
- 排序状态被记住:列排序方向与顺序在页面刷新后依然保留;
- 侧边栏折叠状态被记住:收起/展开侧边栏后,下次访问保持上次状态;
- 父级菜单保持展开:当激活页面是某个子菜单项时,其父级菜单项保持展开,避免导航层级跳变;
- 购物清单页新增日历:变更日志提到作者本人也认为"有用"的内嵌日历,为购物计划提供日期参考;
- 修复日期时间选择器边框:修正控件外观细节。
这些属于前端交互层的打磨(对应 public/viewjs 下的前端逻辑),核心收益是让高频操作页面(库存、购物清单、菜谱)用起来更顺手。
自定义 CSS/JS 注入机制与文件名变更
1.14.0 变更了自定义样式与脚本的文件名。按当前 README.md 的说明,该机制的最终形态为:
- 当
data/custom_css.html文件存在时,其内容会被注入到每个页面的</head>之前; - 当
data/custom_js.html文件存在时,其内容会被注入到每个页面的</body>之前。
页面布局层 default.blade.php 与 default.blade.php 分别通过file_exists(GROCY_DATAPATH . '/custom_css.html')与custom_js.html判断并include注入。这意味着:无需修改应用本体代码,即可在data目录放置自定义 HTML/CSS/JS 片段实现品牌化、埋点或样式覆盖,升级 Grocy 时自定义内容也不会丢失。1.14.0 正是把这些文件的命名约定固定下来的版本。
多语言支持:挪威语加入
1.14.0 新增了挪威语翻译(由 @BlizzWave 贡献)。Grocy 的本地化采用 gettext 体系,翻译文件以.po形式存放于 localization 目录(当前仓库已包含no/语言包,内含strings.po、locales.po、permissions.po等),后续版本也持续沿用了"社区贡献翻译 + 独立 demo 站点"的协作模式。
从 1.14.0 到当前版本:Recipes 模块的持续演进
需要说明的是:本文所引用的迁移脚本与源码来自当前仓库,属于该功能历经多个版本演进后的形态。从源码结构看,1.14.0 之后 Recipes 模块至少经历了以下增强,可作为你阅读 services/RecipesService.php 与 migrations 目录时的参考线索:
- 原料行支持独立计量单位与换算(migrations/0045.sql 为
recipes_pos加入qu_id、only_check_single_unit_in_stock(仅检查是否有任意数量在库)、ingredient_group(原料分组)、not_check_stock_fulfillment(不参与库存匹配检查),并通过触发器在新增原料行时自动回填产品的qu_id_stock); - 菜谱嵌套(
recipes_nestings,一份菜谱包含另一份菜谱)与"产出产品/按份数"(recipes.product_id、base_servings/desired_servings)能力,从 RecipesService::CopyRecipe 的复制逻辑可窥一斑; - 用餐计划(Meal Plan)模块:
RecipesService中定义了RECIPE_TYPE_MEALPLAN_DAY、RECIPE_TYPE_MEALPLAN_WEEK、RECIPE_TYPE_MEALPLAN_SHADOW等内部菜谱类型(services/RecipesService.php),对应的日历视图见 mealplan.blade.php; - 其他 API 能力:菜谱复制
POST /api/recipes/{recipeId}/copy、匹配状态查询GET /api/recipes/{recipeId}/fulfillment、标签打印GET /api/recipes/{recipeId}/printlabel(路由见 routes.php)。
小结
Grocy 1.14.0 用一套简洁的"菜谱 + 原料行"数据模型,撬动了库存查询、缺料补单、消耗记账三个核心流程,其设计骨架(视图计算匹配、事务化消耗、API 化补单)在此后多个版本中持续复用与扩展。对于希望深入理解 Grocy 的读者,建议按以下路径继续探索:
- 先读 migrations/0025.sql 与 migrations/0045.sql,掌握菜谱相关表与视图的演进;
- 再读 services/RecipesService.php 与 controllers/Api/RecipesApiController.php,理解补单、消耗、复制等操作的服务层实现;
- 最后对照 routes.php 与 RecipesController.php 把"页面 → 路由 → 服务 → 数据库"这条调用链串起来。
若想在自建实例上体验,只需将 Grocy 部署后访问/recipes创建菜谱并添加原料行,再回到库存页补足库存,即可直观看到recipes_fulfillment视图中need_fulfilled与missing_amount字段的实时变化。
【免费下载链接】grocyERP beyond your fridge - Grocy is a web-based self-hosted groceries & household management solution for your home项目地址: https://gitcode.com/GitHub_Trending/gr/grocy
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考