HH Menu V3 and Comparing package
Description / Background
It’s difficult to compare menus between packages because each package needs the menu written separately, even when they’re mostly the same, just with different quantities. This takes time and can lead to mistakes. A better solution would allow using the same menu in different packages, only changing the quantities. This would save time and make it much easier to compare and manage packages.
Glossary
https://app.clickup.com/9003122396/docs/8ca1fpw-35796/8ca1fpw-41516
Objectives
- Admin can see the menu master list and menu group on admin dashboard
- Admin can set the menu master list on their own page
- Admin can create, edit, and manage menu groups
- Admin can filter the menu master list and menu group by various parameters (menu status, upload time, category, image availability, tags)
- Admin can print the menu group and it will be downloaded as PDF
- Admin can duplicate the menu group
- Admin can pick HH Menu V3 on edit package → menu type
- Admin can pick the menu group and it will show the menu sections
- Admin can set the quantity of each menu section for how many items can be selected by the user from each menu section
- Admin can set the unlimited or free flow drink for each menu section
- Admin can set the time limit minutes on the edit package on the package comparison section

- Admin can generate AI images for specific menus using pre-configured prompts based on restaurant data (name, description, dining style, cuisine, city, menu details)
- Admin can generate AI images for all menus in a restaurant/branch
- Admin can generate AI images only for menus missing images
- Admin can customize the AI generation prompt before generating (one-time use)
- Admin can regenerate AI images for menus that already have AI-generated images
- Admin can see confirmation prompts when replacing real images with AI-generated ones
- Admin can mark menus with “AI-generated image” flag via checkbox
- Admin can control the visibility of “AI-generated” label on front-end via “Show AI Generated label” checkbox in menu settings
- Admin can see the used and unused menu items on Menu Master List
- Admin can view upload time for each menu (timestamp when menu was first created/submitted)
- Admin can sort menus with priority: Used menus first, then Unused menus
- Admin can delete menus by upload time (shows all unique upload times in dropdown)
- Admin can delete all unused menus at once
- Admin can filter menus by status (Used/Unused), upload time, category, image availability, and tags
- The system will show “unlimited” when the menu section type is food
- The system will show “free flow drink” when the menu section type is beverage
- The system will hide the menu from user (on detail package and view menu) when the quantity is 0
- The system will automatically determine “Used” status when menu is: not expired AND linked to packages
- The system will automatically determine “Unused” status when menu is: expired (regardless of link status) OR not linked to any packages
- The system will display AI-generated images with visible watermark saying “AI-generated”
- The system will show disclaimer text on menu pages: “📌 AI-generated image: for illustrative purposes only…” when at least one menu has the “Show AI Generated label” enabled
- The system will reuse previously generated AI images if the same menu name exists (no regeneration needed)
- User can see the package comparison for each package type (AYCE, PP, XP)
- User can only see the package comparison when the package is active or upcoming package
- User can see AI-generated watermark and disclaimer on menus where enabled
- User can view menus with clear indication when images are AI-generated (both in QR menu and web/app views)
- Admin can activate/deactivate the menu on menu master list of each restaurant setting
- When creating the menu at the first time, the status will be active
- All the menus created before, will become active
- Active (by Default)
- Show to QR menu page / view menu page on HH menu v2, v3, imenupro
- Show menu master list on menu group
- If the previous status is
Used, no need to change the status. keep itUsed - If the previous status is
Unusedno need to change to beUsed, unless it meets the requirement to beUsed. check here: Used & Unused Status (https://app.clickup.com/9003122396/docs/8ca1fpw-11562/8ca1fpw-49076) - Only menu with active status will be displayed on menu sections on menu group setting (HH menu V2/V3)
- Inactive
- Don’t Show to QR menu page / view menu page on HH menu v2, v3, imenupro
- Don’t Show menu master list on menu group
Usedstatus will be changed toUnusedstatus
Scope
HH menu
Sequence Diagram / Flow
Sequence: Package page loads + “Comparing packages” (AYCE/PP)
Sequence: Admin manages Master Menu & Menu Group
Component/Context diagram
ERD

Backend Implementation
- Main Form:
app/javascript/admin/pages/packages/MainForm.vue- Integrates a new
MenuTypecomponent. - Handles
selectedMenuTypeFromResponseand propagates it to child components. - Standardizes and simplifies form structure.
- Adds logic to normalize
package_typefor PartyPack and Ayce.
- Integrates a new
MenuType.vue: (New) Handles menu type selection.menuItem/MenuV1.vueandmenuItem/MenuV3.vue: (New) Support for menu management, including dynamic menu group and section selection, editing, and quantity/unlimited toggles.app/services/menu_data_service.rb: Fetches and processes menu data from external APIs, supports both Ayce and PartyPack.app/services/comparing_package_service.rb: Compares packages for a menu group and restaurant, returns formatted comparison data.app/services/branch_list_service.rb: Handles branch listing, filtering, and pagination.app/services/restaurant_list_service.rb: Handles restaurant listing, filtering, and pagination.app/controllers/hh_menu/comparing_package_lists_controller.rb: (New)- New endpoint for comparing packages (PartyPack & Ayce) by menu group and restaurant.
- Validates and normalizes
package_type. - Uses the new
ComparingPackageService.
app/controllers/hh_menu/packages_controller.rb- Normalizes
package_typeparameter for PartyPack and Ayce. - No longer relies on ambiguous values like
pp/ayce.
- Normalizes
app/controllers/hh_menu/restaurants_controller.rb- Adds new actions:
branch: Get branch details.branches: Paginated, filtered branch list usingBranchListService.restaurant_lists: Paginated, filtered restaurant list usingRestaurantListService.
- Now includes
ResponseCacheConcernfor caching.
- Adds new actions:
- Adds a new “Menu” dropdown for quick links to Menu Master List and Menu Groups in the admin panel.
- Utility for serializing restaurant packages, with optional inclusion of the restaurant.
- Wherever branches were filtered by their associated restaurants, the code used both
.activeand.not_expiredscopes together. Example:branch.restaurants.active.not_expired.count > 0- For master menu filtering:
branch.restaurants.active.not_expired
- The set of restaurants returned by this service is now broader, as restaurants do not need to be “active”—only “not expired” to be included.
- Adds support for
menu_v3. - If the package uses
menu_v3, it calls a new method:calculateOriginalPriceV3(). - Updates the warning to include
'HH Menu V3'. - New Helper Methods:
getMenuSectionsV3()- Fetches menu sections for the selected menu group (includes menus).
- Handles errors (menu group ID not found, API error).
getMenuGroupDetailsV3()- Fetches details for the menu group, including sections, menu group sections, and menus.
- Handles errors similarly.
- This change likely ensures that the rewards page for a specific user in the admin section uses the correct route, probably one that is scoped or protected for admin users.
- Adds a new attribute package_status that:
-
- Returns
activeif today’s date is within (or after) the package’s start_date (and before or on the end_date if it exists).
- Returns
-
- Returns
inactiveotherwise (start date is in the future, end date is in the past, or dates are missing).
- Returns
-
- The logic prioritizes:
- If both start_date and end_date are present:
- Active if today is between those dates (inclusive)
- The logic prioritizes:
-
- If only start_date is present:
- Active if today is on or after the start date
- Otherwise, returns inactive https://github.com/hungryhub-team/hh-server/pull/7544
- If only start_date is present:
HH MENU REPO:
- Adds new admin dashboard pages for managing
MasterMenuandMenuGroupresources, using the Administrate gem. - New filters and search options for master menus and menu groups (by ID, outlet, date, etc.).
- Modal UI for duplicating menu masters with outlet/branch selection.
- Custom table views and improved navigation.
admin/master_menus_controller.rbandadmin/menu_groups_controller.rbprovide CRUD, search, filtering, and duplication actions for admins.- Outlet/branch info is fetched from external APIs for display.
- Duplicating a master menu or group involves copying all associated menus/sections.
- New admin base controller with an access token check, and an
AuthControllerfor OAuth2 admin login/logout flows. - Adds
Api::V1::MenuGroupsController,Api::V1::MasterMenusController,Api::V1::BranchesController, etc., supporting CRUD, serialization, and bulk operations. - New API endpoints for creating, updating, and importing menu groups and master menus.
- Enhanced
SectionsControllerto support menu group associations and new attributes (likeunlimited). - Bulk import endpoints for menu groups/master menus via Excel files.
- New Models:
MenuGroup,MenuGroupSection, and supporting serializers and dashboards.
- Adds new columns to existing tables:
branch_idtomaster_menus,category_menus, etc.custom_menu_idtomenus.menu_group_id,section_type, andunlimitedtosections.main_language,optional_language,package_typetomenu_groups.
- Enforces at least one of
restaurant_idorbranch_idfor menu groups/master menus. - Updates model relationships for new entities.
- Adds new and customized ERB partials for the admin panel (navigation, sidebar, collection tables, modals, etc.).
- Improved flash messaging and layout structure.
- Gemfile: Adds the
administrategem for the new admin UI. - Dockerfile: Updates asset precompilation for Tailwind CSS.
- Routes: Extensive new admin and API routes for all new features.
- The controller responsible for admin master menus (
master_menus_controller.rb) was updated so its search now strictly uses only the outlet name. - This is enforced by initializing the GraphQL search input with
{ searchNameOnly: true }. - This prevents other search criteria from interfering, making results more precise and predictable.
- The code now contains explicit comments and inline documentation explaining the use of the
searchNameOnlyparameter. - The GraphQL query is documented to show how
$input: SearchInput!is expected and howsearchNameOnlyshould be passed. - This helps anyone maintaining or extending the code to avoid misuse or misunderstanding of the search input.
- Adds a
deleted_atcolumn (datetime) to thesectionstable. - Adds an index on this column for better query performance.
- Adds logic to set the
deleted_attimestamp when soft-deleting. - Adds scopes:
not_deleted: Returns sections wheredeleted_atis null.deleted: Returns sections wheredeleted_atis not null.
- Overrides the default
destroymethod to perform a soft-delete. - Adds a method to restore a deleted section by setting
deleted_atto null. - Updates the destroy action to perform a soft-delete instead of a hard delete.
- Adds a new
restoreaction to allow recovery of soft-deleted sections. - Ensures that deleted sections are excluded from standard queries and responses.
- Filters out soft-deleted sections when fetching menu groups.
- Adds support for branch filtering and section/package filters.
- Updates associations to use the new
not_deletedscope, ensuring soft-deleted sections are excluded unless specified. - Adds
all_sectionsassociation to get all sections including deleted ones if needed. - When returning restaurant IDs, only includes those associated with non-deleted sections.
- Adds caching hints for improved performance.
- Adds new routes to support the restore functionality (
PUT /restore) for sections under both packages and groups. - Updates schema comments to document the new
deleted_atfield. - Ensures API responses properly exclude deleted sections and reflect restoration status.
- The PR addresses an issue related to searching for outlets and branches in the codebase.
- It modifies 2 files, adding 14 lines of code and no deletions.
- The changes are likely focused on improving or correcting the search functionality for outlets and branches.
- The
menu_groups_controller.rbadds a destroy action that checks for linked sections before allowing deletion. If any sections are linked, deletion is blocked with an alert. - The
imports_controller.rbensures that when importing, if amenu_group_idormaster_menu_idis missing, the system automatically creates the required MenuGroup or MasterMenu and includes their IDs in the response. - improves restaurant and branch filtering for creating master menus.
- New boolean column active to menus table (
default: true, not null). - Scopes (
active,inactive) and methods (activate!,deactivate!) in the Menu model. -
- New boolean column active to menus table (
default: true, not null). - Scopes (
active,inactive) and methods (activate!,deactivate!) in the Menu model.
- New boolean column active to menus table (
- Added import_at:
- New datetime column import_at to menus table for tracking import time.
- Sets import_at to current time when importing menus in various import controller actions.
- Updated permitted and serialized menu attributes to include active and import_at.
- Updated factory and spec for menus to support new attributes.
- New service class to check if a menu is “used” (included in
selected_menusand has active restaurant/package).- Uses external API calls to determine restaurant & package status, result is cached (2 min TTL).
- Provides public class methods for clearing the relevant cache.
- MenuSerializer:
- Adds used_menu attribute, using the above service to expose usage status per menu.
- Frontend is advised (in controller code comments) to filter menus by used_menu client-side for performance, rather than server-side.
- In all places where selected_menus are created, updated, or destroyed, now call cache invalidation for affected menus (using MenuUsageCheckerService).
- Sections are “touched” (timestamp updated) after selected_menu changes to help with caching.
- selected_menu.rb, my_image.rb, and section.rb now call Rails.cache.delete for affected menu keys whenever related records are changed (after_save, after_destroy).
- Section model now clears usage cache for all its selected_menus when saved or destroyed.
- When a menu is set to inactive (menu.active = false) via create or update, it is automatically removed from all
selected_menus. - Comments added to explain that server-side filtering by used_menu is not supported (performance).
- Comments and logic improved for handling and clearing caches, updating section modification time (touch), and managing selected menu assignments.
- MenuSerializer and MyImageSerializer updated to expose new fields (active, import_at, created_at).
- Factory file for Menu updated: support for active (default true), import_at, and nil category_menu_id.
- add
activeto menus table (boolean, default true, not null) - add
import_atto menus table (datetime)
https://github.com/hungryhub-team/hh-server/pull/7078/files
https://github.com/hungryhub-team/hh-menu/pull/236
https://github.com/hungryhub-team/hh-menu/pull/263/files
https://github.com/hungryhub-team/hh-menu/pull/264/files
https://github.com/hungryhub-team/hh-server/pull/6142/files
https://github.com/hungryhub-team/hh-menu/pull/268/files
https://github.com/hungryhub-team/hh-server/pull/7191/files
https://github.com/hungryhub-team/hh-server/pull/7096/files
https://github.com/hungryhub-team/hh-menu/pull/265/files
https://github.com/hungryhub-team/hh-server/pull/7106/files
https://github.com/hungryhub-team/hh-menu/pull/267/files
https://github.com/hungryhub-team/hh-server/pull/7078/files
https://github.com/hungryhub-team/hh-menu/pull/281
Frontend Implementation
- New
/v3routes are added insrc/router/index.js:/v3/master/createand/v3/master/:idfor master menu creation and editing under the new flow./v3/package/:id,/v3/menu-group/create,/v3/menu-group/:idfor package and menu group handling./v3/layoutand/v3/printfor new layout and print views.
SectionMenuListCategory.vue(new): Displays menu sections grouped by category, including support for multi-language and price options.MenuCategory.vue(new): Simple component to display a category header.SectionComparison.vue(new): Provides a comparison table for packages (e.g., “All You Can Eat” vs. “Party Pack”), supporting dynamic columns based on package type.views/layout/v3/IndexV3.vue(new): The main container for the new v3 layout/print page. It loads and coordinates header, footer, package comparison, categories, and menu list, fetching required data from APIs.- Handles loading states, API calls, and error notifications.
stores/menuGroup.js(new): Manages the state for menu groups, including form data for branch, restaurant, name, and sections.stores/packageList.js: Adds asection_typefield (default “food”) to section state for v3 handling.stores/master.js: Adds acustom_menu_idfield for menu item identification.views/master/MasterList.vue:Conditional rendering based on the route (checks if in v3 mode).- New logic for handling outlet/branch selection (
FormOutletSetting.vue). - Ability to create a master menu and its items in a single API call (
createNewMasterAndMenu), and redirect after creation. - The form now includes and persists
custom_menu_idfor each menu item. FormOutletSetting.vue: Allows users to select either an outlet or branch and search for them via API.views/package/FormSection.vue:- Adds a
section_typeradio selector (“food” or “beverage”) for v3. - Adjusts logic for add/update section to support both save-and-edit and non-edit (for v3).
- Handles menu selection and availability based on new menu group data structure.
- Adds a
views/print/PrintMenu.vue:- Adds v3 support for template selection (package/group based) and group naming for print output.
- Changes parameter passing and URL construction logic for print/preview in v3 mode.
- Integrates new restaurant selection logic for v3 menu groups and branches.
SelectLang.vue: Button styling now rotates when open.FormLanguageSetting.vue: AddsusePriceanduseTranslateprops to control optional UI.- Many components: Conditional logic and props to control v3 vs. legacy flows.
- General code tidying: Use of
vue-typesfor prop validation, improved comments, and clearer method names. - Adds logic for handling ‘Other’ menu categories if items do not have a
category_menu_id. - Adds random key generation for list rendering stability in v3 components.
- Handles error and loading states gracefully in new and updated components.
- Generalizes several API endpoints to work with either branch or restaurant context.
- Menu images and gallery images now load reliably from the correct domains.
- The code is cleaner, with fewer hardcoded values and more flexible helpers.
- Added: date-fns@^4.1.0 for date operations.
- Removed: moment@^2.29.3 (previously used for date/time formatting).
- Applied max height and scroll to dropdown (SelectLang.vue) for better usability.
- Added visual toggle and status tags in FormMenu.vue for menu
activestate andused/unusedlabelling. - Added filters for menu status, import time, category, image (with/without), and tags (popular, vegetarian, chef-recommended, long-cooking).
- Filtered lists update the .show property on each menu to control visibility.
- Shows a “No result” message if nothing matches the filters applied.
- Add Delete by imported time: Prompts for a timestamp to delete all menus imported at that time.
- Add Delete all unused menus: Deletes all menus not marked as
used. - Add Checkbox toggle per menu for setting whether a menu is
active. - Menu displays, menu grouping, and print/export views now only show items with active: true.
- In multiple places (MenuGroup, Print, layout Index and v3 layout), menu lists filter and group only
activeitems. - All date manipulations in FormSection.vue migrated from moment.js to date-fns, including checks, formatting, and submission of seasonal menu ranges.
https://github.com/hungryhub-team/hh-menu-frontend-new/pull/143
https://github.com/hungryhub-team/hh-pegasus/pull/2039/files
https://github.com/hungryhub-team/hh-pegasus/pull/2039/files
https://github.com/hungryhub-team/hh-pegasus/pull/2030/files
https://github.com/hungryhub-team/hh-pegasus/pull/2039/files
https://github.com/hungryhub-team/hh-menu-frontend-new/pull/190
PRD & Task
PRD: https://app.clickup.com/9003122396/docs/8ca1fpw-7922/8ca1fpw-42456 Active & Inactive Menu : https://app.clickup.com/9003122396/v/dc/8ca1fpw-11562/8ca1fpw-53936
TASK: https://app.clickup.com/t/86cuu81rm https://app.clickup.com/t/86d157268 (https://app.clickup.com/t/86czkyeq3)[https://app.clickup.com/t/86czkyeq3]
Design
API Blueprint
| Method | Path | URL | Description | Payload |
|---|---|---|---|---|
| GET | /api/v1/comparing_package_lists | API endpoint to compare package lists by menu group and package type. | menu_group_id, package_type, restaurant_id | |
| GET | /api/v1/menu_groups/check_data | to fetch detailed package-section data. | - | |
| GET | /api/v1/restaurants/branch/:id | Returns info (id, name) for a specific branch by ID. | id (in path) | |
| GET | /api/v1/branches | Returns a paginated list of branches with active, not-expired restaurants. Supports filtering by name and pagination parameters. | name_like, page, per_page | |
| GET | /api/v1/restaurant_lists | Returns a paginated list of active, not-expired restaurants. Supports name filtering, pagination, and caching. | name_like, page, per_page | |
| - | - | - | - | - |
| GET | /hh_menu/restaurant_lists.json | name_like (string, query) | ||
| GET | /hh_menu/restaurants/{id} | id (path) | ||
| GET | /hh_menu/branches.json | name_like (string, query) | ||
| GET | /hh_menu/restaurants/branch/{id} | id (path) | ||
| POST | /api/v1/master_menus/create_master_menu | { branch_id, restaurant_id, main_language, menu: [...] } (body, JSON) | ||
| GET | /api/v1/menu_groups/{id}/detail | id (path) | ||
| GET | /api/v1/branches/{id}/master_menu | id (path) | ||
| GET | /api/v1/restaurants/{id}/master_menu | id (path) |
New Query
N/A
DB Schema / Database Migration
- Creates the
userstable for Devise authentication. - Adds admin functionality and Devise “confirmable” to users
- Creates the
menu_groupstable. - Adds a
branch_idcolumn to themaster_menustable and an index on it. - Adds a
custom_menu_idcolumn to themenustable and an index on it. - Adds
menu_group_idto thesectionstable. - Adds an
unlimitedboolean column (default: false) tosections, indicating if the section has unlimited quantity. - A data migration that seeds two admin users.
- Adds
main_languageandoptional_language` tomenu_groups - Adds a
branch_idcolumn (bigint) and index tocategory_menus, enabling menus to be associated with a specific branch. - Creates a new table called
menu_group_sectionsto represent a many-to-many relationship betweenMenuGroupandSection. - Adds a
menu_group_idcolumn to all relevant package tables (e.g.,party_packs,ayce,hungry_sets, etc.) to relate packages to menu groups. - add
activeto menus table (boolean, default true, not null) - add
import_atto menus table (datetime)
Improvement:
N/A