Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

PRD — Package Dropdown on the Inventories Overview (package-scoped calendar)

Status: Drafted from grilling; ready for review Branch: continues perf/hhserver-vendor-availability-major Tracking: EPIC: Package dropdown on inventories overview #8333 - tracked on PR #8274 Audience: HungryHub Engineering + CS-Ops Relationship to prior work: Extends the shipped bookability diagnosis — adds a package selector to the overview Check form so the availability calendar can be scoped to one package (or all).


1. Problem

The inventories overview Check form has Party size + Channel, and both color the 45-day calendar — but only at the restaurant level (“bookable if ANY package is bookable that day”). To see a specific package’s calendar, ops must leave the overview and navigate to the separate per-package page (?restaurant_package_id=X). Feedback: “maybe better add package selection also besides party-size selection?” — so ops can scope the same calendar to one package without leaving the page.

2. Goal

Add a Package dropdown to the overview Check form (alongside Party + Channel). Selecting “All” keeps the current restaurant-level calendar; selecting a package renders that package’s calendar in place on the overview — one page where party + channel + package drive one calendar.

3. Grounded reality (what already exists)

  • Overview Check form (_overview.html.erb): GET form with party + channel, feeding the AJAX-loaded calendar fragment (inventory_calendar_calendar_body).
  • Restaurant-level calendar = date_verdicts(channel:, party:)fresh_payload(checker, date) (no slug) — fast fresh-calc, “any package bookable”.
  • Per-package calendar already exists_restaurant_package_overview.html.erb (reached via ?restaurant_package_id=X), which colors each day via package_verdict(rp, date) — the full gating cascade (config / storefront / agenda / supplier-mismatch / outside-window), giving rich per-day reasons.
  • package_verdict-per-day ×45 is already a proven, accepted pattern (the existing per-package page runs exactly that).
  • all_packages(channel) already filters packages by for_dine_in? / for_delivery?, so a package list is naturally channel-scoped.

4. Solution

4.1 Two verdict paths behind one dropdown

  • “All packages” (default) → the current restaurant-level date_verdicts (fresh-calc). Unchanged.
  • A specific package → per-day package_verdict(rp, date) (the gating cascade — same logic the existing per-package calendar uses). This gives the why per day, which is the point of the diagnosis tool. We do NOT make date_verdicts slug-aware; the dropdown switches which path is correct for the question being asked.

4.2 Unify onto the overview page (B)

The dropdown lives in the overview Check form; submitting re-renders the overview with the package-scoped calendar in place (via the AJAX fragment). The existing _restaurant_package_overview route keeps working as a deep-link (not removed — avoids breaking bookmarks), but the unified overview is the primary UX.

4.3 Package-aware AJAX calendar fragment

  • The Check-form JS builds the fragment data-url including the selected restaurant_package_id (identifier chosen because it’s consistent with the existing per-package route and with package_verdict(rp, ...); the rp is resolved from the already-loaded all_restaurant_packages — no extra query).
  • inventory_calendar reads optional params[:restaurant_package_id], resolves the rp, passes it to _calendar_body.
  • _calendar_body branches: if rp → loop (today..today+45) calling package_verdict(rp, date) (reuse the exact tile markup + logic the existing per-package calendar uses); else → current date_verdicts.
  • Day-tile links carry the package: when a package is selected, clicking a day links to the package-detail page (restaurant_package_id + date), not the restaurant-level date page.

4.4 The dropdown itself

  • Options: “All packages” (default) + every package from all_packages(channel) (channel-scoped; all packages incl. misconfigured ones — diagnosing them is the point).
  • Sort valid-first: packages with active == true OR active_for_third_party_only == true listed before the rest.
  • Label format: "{package.name_en.presence || package.name} {rp.id} - {rp.slug}".

4.5 Channel interaction

  • Form keeps the existing Channel dropdown, default dine_in.
  • The package list is scoped to the selected channel (all_packages(channel)) — so a package can never contradict the channel. Changing channel re-scopes the package list; if the previously-selected package isn’t in the new channel’s list, the selection resets to “All”.

5. Acceptance criteria

  1. The overview Check form shows Party + Channel + Package; Package defaults to “All packages”.
  2. “All” renders the current restaurant-level calendar (unchanged).
  3. Selecting a package renders that package’s calendar in place on the overview, colored via package_verdict per day (rich reasons + the calm/grey states it already produces).
  4. Package options are sorted valid-first and labeled "{name_en||name} {id} - {slug}".
  5. The package list is scoped to the selected channel; changing channel re-scopes it and resets to “All” if the selection no longer applies.
  6. The AJAX calendar fragment receives and honors restaurant_package_id; the initial page load stays shell-only (no perf regression — the compute is still deferred).
  7. Day-tile links carry the selected package (→ package-detail on click).
  8. The existing ?restaurant_package_id=X per-package route still works.
  9. No regression to “All” behavior, party/channel behavior, or the outside-window state.

6. Non-goals (v1)

  • No change to date_verdicts or package_verdict logic — only routing to them.
  • No removal/deprecation of the existing _restaurant_package_overview route.
  • No new per-package availability computation — reuse existing.
  • No special-casing of the outside-window (#8331) state for the package-scoped calendar — package_verdict handles its own boundaries.
  • No customer-facing changes.

7. Risks

  • Perf: the package-scoped calendar runs package_verdict ×45, but it’s in the AJAX fragment (deferred, off the initial page load) and is already the proven per-package pattern — no new perf exposure beyond what exists.
  • Channel/package desync: must reset package→“All” when channel changes and the selection no longer applies (acceptance #5).
  • Label nil-safety: name_en/name/slug may be nil for odd packages — guard with presence + fallbacks.

8. Source-of-truth file map

  • Form + calendar skeleton: app/views/admin/restaurants/inventories/_overview.html.erb
  • AJAX fragment: admin/restaurants_controller#inventory_calendar, _calendar_body.html.erb, setup_bookability_context
  • Verdict paths: BookabilityPresenter#date_verdicts (All), #package_verdict (per package), #all_packages(channel)
  • Existing per-package calendar (reuse markup/logic): _restaurant_package_overview.html.erb