From d1116dab24e8d8283f598e34cbd61edfc8a6ffb8 Mon Sep 17 00:00:00 2001 From: Hussain Nagaria Date: Sat, 11 Jul 2026 19:56:16 +0530 Subject: [PATCH] docs: add v2 tracer-bullet specs, trim repo docs Buzz v2 planning: teams/multi-tenancy foundation (specs/v2/00-teams, 5 steps) and unified manager dashboard to Desk parity (specs/v2/01-dashboard, phases A-D + Pulse analytics). Each step is a PR-sized vertical slice with API contracts, permission notes, and merge-gating acceptance criteria (unit/integration tests + agent-browser flows). Speaker remodel captured as an open decision spec. - Replace CLAUDE.md guidance with slim implementation rules - Drop ARCHITECTURE.md, entities.png, custom-forms worktree gitlink (stale against the v2 direction) Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01TaWUCqE5kHgE2PCcXdsD7V --- ARCHITECTURE.md | 249 ------------ CLAUDE.md | 384 ++---------------- entities.png | Bin 452207 -> 0 bytes specs/v2/00-teams/00-plan.md | 64 +++ .../00-teams/01-team-doctypes-and-core-api.md | 141 +++++++ .../02-team-field-on-events-and-backfill.md | 113 ++++++ specs/v2/00-teams/03-permission-hooks.md | 166 ++++++++ specs/v2/00-teams/04-team-invitations.md | 112 +++++ specs/v2/00-teams/05-team-settings.md | 152 +++++++ specs/v2/01-dashboard/00-plan.md | 93 +++++ .../01-app-shell-and-events-list.md | 116 ++++++ .../02-event-workspace-and-ticket-types.md | 102 +++++ .../03-event-overview-and-editing.md | 96 +++++ specs/v2/01-dashboard/04-coupons.md | 78 ++++ specs/v2/01-dashboard/05-ticket-add-ons.md | 72 ++++ .../06-sponsorship-tiers-and-sponsors.md | 77 ++++ .../01-dashboard/07-bookings-and-attendees.md | 100 +++++ specs/v2/01-dashboard/08-checkin-desk.md | 100 +++++ .../01-dashboard/09-venues-hosts-library.md | 78 ++++ .../10-schedule-tracks-and-talks.md | 101 +++++ specs/v2/01-dashboard/11-event-templates.md | 92 +++++ .../12-event-settings-payments-and-forms.md | 99 +++++ .../13-team-settings-members-and-invites.md | 117 ++++++ specs/v2/01-dashboard/14-pulse-analytics.md | 115 ++++++ specs/v2/02-speaker-remodel.md | 116 ++++++ specs/v2/README.md | 100 +++++ worktrees/custom-forms | 1 - 27 files changed, 2433 insertions(+), 601 deletions(-) delete mode 100644 ARCHITECTURE.md delete mode 100644 entities.png create mode 100644 specs/v2/00-teams/00-plan.md create mode 100644 specs/v2/00-teams/01-team-doctypes-and-core-api.md create mode 100644 specs/v2/00-teams/02-team-field-on-events-and-backfill.md create mode 100644 specs/v2/00-teams/03-permission-hooks.md create mode 100644 specs/v2/00-teams/04-team-invitations.md create mode 100644 specs/v2/00-teams/05-team-settings.md create mode 100644 specs/v2/01-dashboard/00-plan.md create mode 100644 specs/v2/01-dashboard/01-app-shell-and-events-list.md create mode 100644 specs/v2/01-dashboard/02-event-workspace-and-ticket-types.md create mode 100644 specs/v2/01-dashboard/03-event-overview-and-editing.md create mode 100644 specs/v2/01-dashboard/04-coupons.md create mode 100644 specs/v2/01-dashboard/05-ticket-add-ons.md create mode 100644 specs/v2/01-dashboard/06-sponsorship-tiers-and-sponsors.md create mode 100644 specs/v2/01-dashboard/07-bookings-and-attendees.md create mode 100644 specs/v2/01-dashboard/08-checkin-desk.md create mode 100644 specs/v2/01-dashboard/09-venues-hosts-library.md create mode 100644 specs/v2/01-dashboard/10-schedule-tracks-and-talks.md create mode 100644 specs/v2/01-dashboard/11-event-templates.md create mode 100644 specs/v2/01-dashboard/12-event-settings-payments-and-forms.md create mode 100644 specs/v2/01-dashboard/13-team-settings-members-and-invites.md create mode 100644 specs/v2/01-dashboard/14-pulse-analytics.md create mode 100644 specs/v2/02-speaker-remodel.md create mode 100644 specs/v2/README.md delete mode 160000 worktrees/custom-forms diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md deleted file mode 100644 index e8b1539b..00000000 --- a/ARCHITECTURE.md +++ /dev/null @@ -1,249 +0,0 @@ -# Buzz App Architecture Notes - -## Overview -- Buzz is a Frappe app for event management with a Vue 3 (FrappeUI) dashboard and Frappe Builder/public pages. -- Backend is organized into Frappe modules: `Buzz`, `Events`, `Ticketing`, `Proposals`. -- Frontend dashboard lives in `dashboard/`, built with Vite and published into `buzz/public/dashboard` and `buzz/www/dashboard.html`. - -## Backend Structure -- App bootstrap and hooks: `buzz/hooks.py` - - Requires `frappe/payments`. - - Scheduler: `buzz.tasks.unpublish_ticket_types_after_last_date` (daily). - - Doc events: assigns `Buzz User` role on user creation; syncs Speaker Profile display name on User update. - - App icon entry in Desk apps screen. -- Core API: `buzz/api.py` - - Booking, tickets, add-ons, sponsorship, check-in, languages, translations. -- Payments integration: `buzz/payments.py` - - Creates `Event Payment` records and generates payment links via the Payments app. -- Utilities: `buzz/utils.py` - - App-install guard decorator, custom field helpers, role assignment. -- Install/fixtures: - - `buzz/install.py` creates default Event Categories and zoom integration fields if installed. - - `buzz/fixtures` and `buzz/patches` for setup and migrations. - -## Key DocTypes (Data Model) - -### Events Module -- `Buzz Event` (`buzz/events/doctype/buzz_event`) - - Primary entity; holds schedule, category, venue, ticketing/payment settings, sponsorship settings. - - Creates default `Sponsorship Tier` and `Event Ticket Type` after insert. - - Optional Zoom webinar creation/updates when `zoom_integration` is installed. -- `Event Category`, `Event Venue`, `Event Host` - - Category slug creation; venue location geojson and map embed cleanup. -- `Event Talk`, `Talk Speaker`, `Speaker Profile`, `Event Track` - - Talks can be generated from proposals; speaker profiles tied to User records. -- `Event Sponsor`, `Sponsorship Tier`, `Sponsorship Deck Item` - - Sponsor uniqueness enforced per enquiry. -- `Event Check In` - - Used for check-in records and attendance reporting. -- `Event Payment Gateway` - - Child table for multiple payment gateways per event. -- `Additional Event Page` - - Event-specific extra pages with route validation. - -### Ticketing Module -- `Event Booking` - - Holds attendees, pricing, tax, UTM parameters, custom fields. - - On submit: generates `Event Ticket` documents and applies add-ons/custom fields. -- `Event Ticket` - - Generates QR code, sends ticket email + print format attachment. - - Creates Zoom webinar registration (if enabled). - - Supports transfer/cancellation flows. -- `Event Ticket Type` - - Inventory logic; tracks max tickets and remaining count. -- `Ticket Add-on` + `Ticket Add-on Value` + `Attendee Ticket Add-on` - - Add-on definitions and per-ticket selections. -- `Bulk Ticket Coupon` - - Auto-generates code; limits usage via claimed count. -- `Ticket Cancellation Request` + `Ticket Cancellation Item` - - Cancel booking or specific tickets on acceptance. -- `Additional Field` - - Generic key/value for ticket/booking custom fields. - -### Proposals Module -- `Talk Proposal` - - Maps into `Event Talk` and auto-creates users/speaker profiles if needed. -- `Sponsorship Enquiry` - - Handles approval/payment flow; creates `Event Sponsor` on payment completion. - -### Buzz Module -- `Buzz Custom Field` - - Event-specific custom fields applied to bookings or tickets. -- `Buzz Settings` - - Central configuration for transfer/add-on/cancellation windows. - -## Backend Flows - -### Booking + Payment -1. Dashboard calls `buzz.api.get_event_booking_data` to load ticket types, add-ons, custom fields, and payment gateways. -2. `buzz.api.process_booking` creates an `Event Booking` with attendees, add-ons, custom fields, and UTM parameters. -3. If total is 0, booking is auto-submitted; otherwise `Event Payment` is created and a payment URL is returned. -4. On payment authorization, `Event Booking.on_payment_authorized` marks the payment received and submits the booking. -5. Booking submission creates `Event Ticket` records and triggers QR + email flow. - -### Ticket Lifecycle -- Ticket creation generates QR code file and email (with print format attachment). -- Transfers are handled via `buzz.api.transfer_ticket` with window checks from `Buzz Settings`. -- Add-on preference changes use `buzz.api.change_add_on_preference` with window checks. -- Cancellation requests are created via `buzz.api.create_cancellation_request` and are accepted/rejected in Desk. - -### Sponsorships -- Web form submits `Sponsorship Enquiry`. -- Approval moves status to `Payment Pending` and triggers email notification. -- Payment link is generated via `buzz.api.create_sponsorship_payment_link`. -- Payment authorization creates `Event Sponsor` and marks enquiry as paid. - -### Check-in -- Dashboard scanner validates tickets with `buzz.api.validate_ticket_for_checkin`. -- Successful check-in creates `Event Check In` and returns event/ticket context. - -## API Surface (Whitelisted) -- Booking: `get_event_booking_data`, `process_booking`, `get_booking_details`, `create_cancellation_request`. -- Ticket actions: `get_ticket_details`, `transfer_ticket`, `change_add_on_preference`. -- Sponsorships: `get_sponsorship_details`, `get_user_sponsorship_inquiries`, `create_sponsorship_payment_link`, `withdraw_sponsorship_enquiry`. -- Check-in: `validate_ticket_for_checkin`, `checkin_ticket`. -- Payments: `get_event_payment_gateways` (plus payment helpers in `buzz/payments.py`). -- User + i18n: `get_user_info`, `get_enabled_languages`, `update_user_language`, `get_translations`. - -## Reports -- Events: - - `Event Overview` (tickets sold, add-ons sold, sales) in `buzz/events/report/event_overview`. - - `Event Attendance Summary` with dynamic per-day check-ins and chart in `buzz/events/report/event_attendance_summary`. -- Ticketing: - - `Event Add-Ons Overview` in `buzz/ticketing/report/event_add_ons_overview`. - - `Detailed Event Registrations` with dynamic custom fields, add-ons, UTM params in `buzz/ticketing/report/detailed_event_registrations`. - -## Public Pages + Web Forms -- Dashboard is served via `buzz/www/dashboard.html` (built from `dashboard/` output). -- Web forms: - - `Apply for Sponsorship` and `Propose a Talk` under `buzz/buzz/web_form/`. -- Frappe Builder assets are under `buzz/builder_files` and `buzz/public/builder_files` (currently empty stubs/asset containers). - -## Frontend (Vue Dashboard) - -### Entry + Build -- Entry: `dashboard/src/main.js` mounts `App.vue` with router, resources, translation plugin, and socket. -- Build: `dashboard/vite.config.js` outputs to `buzz/public/dashboard` and updates `buzz/www/dashboard.html`. -- Base URL: `/dashboard` (router history uses `createWebHistory("/dashboard")`). - -### Routing -- Public-like flows: booking (`/book-tickets/:eventRoute`) and check-in (`/check-in`). -- Account area under `/account`: - - bookings list/details, tickets list/details, sponsorships list/details. -- Guard: `router.beforeEach` checks `buzz.api.get_user_info` and redirects to `/login` if unauthenticated. - -### Data Access Pattern -- Uses `frappe-ui` `createResource` and `createListResource` for API calls. -- API endpoints map directly to backend whitelisted methods (see API Surface above). -- Session state in `dashboard/src/data/session.js` handles login/logout and cookie-based session user. - -### Key UI Components -- Booking flow: `BookingForm.vue`, `PaymentGatewayDialog.vue`, `BookingSummary.vue`. -- Ticket management: `TicketDetails.vue`, `TicketTransferDialog.vue`, `AddOnPreferenceDialog.vue`, `CancellationRequestDialog.vue`. -- Sponsorship management: `SponsorshipDetails.vue`, `SponsorshipPaymentDialog.vue`, `SponsorLogoUploader.vue`. -- Check-in: `CheckInScanner.vue`, `EventSelector.vue`, `QRScanner.vue`. -- Shared: `Navbar.vue`, `LanguageSwitcher.vue`, `SuccessMessage.vue`. - -### Notable Composables + Utils -- `useTicketValidation` handles check-in validation flow and audio feedback. -- `usePaymentSuccess` handles success banners + confetti and URL cleanup. -- `useBookingFormStorage` keeps draft booking data in localStorage per event. -- `useLanguage` changes user language and reloads translations. - -## Integrations -- Payments app required (gateway link generation + payment receipt recording). -- Zoom integration optional; `Buzz Event` can create/update webinars and register attendees. - -## Permissions + Roles -- Roles: `Buzz User`, `Frontdesk Manager` (fixtures in hooks). -- Check-in APIs restricted to `Frontdesk Manager`. -- Sponsorship details restricted to enquiry owner or users with permission. - -## Key Paths for Feature Work -- Booking logic: `buzz/api.py`, `buzz/ticketing/doctype/event_booking`, `dashboard/src/components/BookingForm.vue`. -- Ticket lifecycle: `buzz/ticketing/doctype/event_ticket`, `dashboard/src/pages/TicketDetails.vue`. -- Sponsorship lifecycle: `buzz/proposals/doctype/sponsorship_enquiry`, `dashboard/src/pages/SponsorshipDetails.vue`. -- Check-in flow: `buzz/api.py`, `dashboard/src/pages/CheckInScanner.vue`. -- Event configuration: `buzz/events/doctype/buzz_event` and related event doctypes. - -## Notes / Gotchas -- Payment gateway selection is event-scoped via `Event Payment Gateway` child rows. -- Event routes are auto-generated when publishing (`Buzz Event`, `Additional Event Page`). -- Custom fields are event-scoped (`Buzz Custom Field`) and can apply to booking or ticket. -- Ticket type availability is enforced at booking time, plus daily auto-unpublish by scheduler. - -## Data Model Diagram (DocTypes + Relationships) -```mermaid -erDiagram - BUZZ_EVENT ||--o{ EVENT_TICKET_TYPE : has - BUZZ_EVENT ||--o{ TICKET_ADD_ON : has - BUZZ_EVENT ||--o{ EVENT_BOOKING : has - BUZZ_EVENT ||--o{ EVENT_SPONSOR : has - BUZZ_EVENT ||--o{ SPONSORSHIP_TIER : has - BUZZ_EVENT ||--o{ BUZZ_CUSTOM_FIELD : has - BUZZ_EVENT ||--o{ EVENT_CHECK_IN : has - BUZZ_EVENT ||--o{ ADDITIONAL_EVENT_PAGE : has - EVENT_BOOKING ||--o{ EVENT_BOOKING_ATTENDEE : has - EVENT_BOOKING ||--o{ ADDITIONAL_FIELD : has - EVENT_BOOKING ||--o{ UTM_PARAMETER : has - EVENT_BOOKING ||--o{ EVENT_TICKET : generates - EVENT_TICKET ||--o{ TICKET_ADD_ON_VALUE : has - EVENT_TICKET ||--o{ ADDITIONAL_FIELD : has - EVENT_TICKET_TYPE ||--o{ EVENT_TICKET : sold_as - ATTENDEE_TICKET_ADD_ON ||--o{ TICKET_ADD_ON_VALUE : has - TICKET_CANCELLATION_REQUEST ||--o{ TICKET_CANCELLATION_ITEM : includes - TICKET_CANCELLATION_REQUEST }o--|| EVENT_BOOKING : cancels - EVENT_CHECK_IN }o--|| EVENT_TICKET : for - SPONSORSHIP_ENQUIRY }o--|| BUZZ_EVENT : for - SPONSORSHIP_ENQUIRY ||--o{ EVENT_SPONSOR : creates - TALK_PROPOSAL }o--|| BUZZ_EVENT : for - TALK_PROPOSAL ||--o{ EVENT_TALK : maps_to -``` - -## Frontend Page -> API Map -- `dashboard/src/pages/BookTickets.vue` - - `buzz.api.get_event_booking_data` to load event booking config. - - `buzz.api.process_booking` via `dashboard/src/components/BookingForm.vue`. -- `dashboard/src/pages/BookingDetails.vue` - - `buzz.api.get_booking_details` for booking + ticket data. - - `buzz.api.create_cancellation_request` via `dashboard/src/components/CancellationRequestDialog.vue`. - - `buzz.api.transfer_ticket` via `dashboard/src/components/TicketsSection.vue`. -- `dashboard/src/pages/TicketDetails.vue` - - `buzz.api.get_ticket_details`. - - `buzz.api.change_add_on_preference` via `dashboard/src/components/AddOnPreferenceDialog.vue`. - - `buzz.api.transfer_ticket` via `dashboard/src/components/TicketTransferDialog.vue`. -- `dashboard/src/pages/SponsorshipsList.vue` - - `buzz.api.get_user_sponsorship_inquiries`. -- `dashboard/src/pages/SponsorshipDetails.vue` - - `buzz.api.get_sponsorship_details`. - - `buzz.api.withdraw_sponsorship_enquiry`. - - `buzz.api.get_event_payment_gateways` and `buzz.api.create_sponsorship_payment_link` via `dashboard/src/components/SponsorshipPaymentDialog.vue`. -- `dashboard/src/pages/CheckInScanner.vue` - - `buzz.api.validate_ticket_for_checkin` and `buzz.api.checkin_ticket` via `dashboard/src/composables/useTicketValidation.js`. -- `dashboard/src/data/user.js` - - `buzz.api.get_user_info` for session user details and roles. -- `dashboard/src/composables/useLanguage.js` - - `buzz.api.get_enabled_languages`, `buzz.api.update_user_language`. -- `dashboard/src/translation.js` - - `buzz.api.get_translations`. - -## Feature Development Checklist (Common Changes) -- Tickets / booking changes - - Update `buzz/api.py` for API shape changes and `Event Booking`/`Event Ticket` doctypes for business logic. - - Keep `BookingForm.vue` payload mapping in sync with backend expectations. - - Update reports if new fields should be exported (`Detailed Event Registrations`). -- Add-on or custom field changes - - Check `Buzz Custom Field` logic and `Additional Field` usage in booking/tickets. - - Update frontend rendering in `CustomFieldInput.vue` and `BookingForm.vue`. -- Payment flow changes - - Update `buzz/payments.py` and any event-scoped gateway selection logic. - - Ensure payment redirects still land on `/dashboard/...?...success=true`. -- Sponsorship flow changes - - Update `Sponsorship Enquiry` for status transitions and `SponsorshipDetails.vue` for UI state. - - Verify sponsor creation in `on_payment_authorized`. -- Check-in changes - - Update `buzz.api.validate_ticket_for_checkin` and `CheckInScanner.vue`/`useTicketValidation.js`. - - Confirm role gating for `Frontdesk Manager` remains consistent. -- Event publishing or public pages - - Review `Buzz Event` route generation and `Additional Event Page` route uniqueness. - - If using Builder, update assets under `buzz/builder_files` and publish to `buzz/public/builder_files`. diff --git a/CLAUDE.md b/CLAUDE.md index dce9c33d..15808650 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,368 +1,50 @@ -# CLAUDE.md +## Implementation -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. +Guidelines for writing good code for a developer: -At the start of every session, read `.claude/memory/memory.md` to load project context. -After completing significant work (new patterns, architectural decisions, solved problems), -update `.claude/memory/memory.md`. Keep it under 300 lines — summarize when it grows. +1. Choose clean code over clever code. +2. Write object oriented code as much as possible. +3. Keep function sizes small, ideally 10 lines. +4. Try and keep files between 100 and 300 lines. +5. Don't keep too many files in a folder or module. Try and keep it under 15. +6. Avoid abbreviations. +7. Use standard API as much as possible. +8. Reuse. Write as little code as possible. +9. Use Frappe UI, espresso design system for UI styling. +10. Always write tests, and make sure they work. +11. Build the minimum working app, then iterate towards your goals. +12. Keep the verbosity less in new changes (inline comments, docstrings erc). + Explain only what's absolutely needed in inline comments. + Actual changes explanation can be part of commit message. ---- +## DocType -## This defines the best practices to write backend code in the Frappe Framework +* Use column breaks and tab breaks to create user friendly doctype forms -* Frappe Framework is a full-stack web application framework that contains all the necessary components for building modern web applications. -* It provides background workers using Redis, real-time updates using sockets, and a database layer using MariaDB. -* Bench is the official command-line tool for managing Frappe applications. +## Skills -## Backend Development +Always load frappe-app-dev and frappe-ui skills before any implementation -### JSON & Request Handling +## Planning -* Always use built-in functions for parsing JSON: +For creating specs use tracer bullet approach. - * `frappe.parse_json` (handles dicts, lists, and JSON strings safely) +> Tracer bullets comes from the Pragmatic Programmer. When building systems, you want to write code that gets you feedback as quickly as possible. Tracer bullets are small slices of functionality that go through all layers of the system, allowing you to test and validate your approach early. This helps in identifying potential issues and ensures that the overall architecture is sound before investing significant time in development. -* Never use `json.loads` directly on request data. +Create specs in `specs/`. Maintain a `PROGRESS.md` file to track progress of implementation phases. -* For outbound HTTP requests (calling external APIs), use: +## Commit / PR - * `frappe.integration.utils.make_get_request` - * `frappe.integration.utils.make_post_request` - * `frappe.integration.utils.make_put_request` - * `frappe.integration.utils.make_patch_request` +* Always use conventional commits +* Commit the spec before you begin +* Reconcile the specs after implementation -### Datatype Conversion & Utilities +## Testing -* For converting datatypes (e.g. str → int, str → float, etc.) use built-in helpers: +Use agent-browser for quick manual e2e checks. - * `frappe.utils.data.cint` - * `frappe.utils.data.cstr` - * `frappe.utils.data.flt` - * `frappe.utils.data.getdate` - * `frappe.utils.data.get_datetime` +Automated e2e uses Playwright (root `package.json`, specs in `e2e/`). -* `frappe.utils.data` contains most conversion and formatting helpers you will ever need: +## Credentials - * date / datetime parsing - * currency formatting - * number formatting - -* Do NOT create custom utility functions for these conversions. - -* If unsure, ask before implementing. - -### DocType Access Patterns - -* When fetching an existing DocType, prefer: - - * `frappe.get_cached_doc` - -* Use `frappe.get_doc` when: - - * creating a new document - - * To create a new doc go to bench console via bench --site sitename console and use frappe.new_doc("DocType") and then create the doc, don't create the doc via json as the validations doesn't run - - -### Optimization - -* Don't use get_doc or get_cached_doc inside for loop it creates n+1 db problem use frappe.get_all with all the params required and then loop over that list - -### Database Access - -* Prefer ORM methods: - - * `frappe.get_all` - * `frappe.get_list` - * `frappe.db.get_value` - -* Avoid raw SQL absolutely. - - -### Permissions & Security - -* Always respect user permissions. -* Use `ignore_permissions=True` only when absolutely required and justified. - -### Background Jobs & Performance - -* For long-running or heavy operations, always use: - - * `frappe.enqueue` - -* Never block request-response cycles with heavy business logic. - -### Error Handling & Logging - -* Use `frappe.throw` or specific exceptions like `frappe.ValidationError` for user-facing errors. -* Use `frappe.log_error` for unexpected or system-level exceptions. -* Avoid bare `except:` blocks. - -### General Guidelines - -* Prefer framework conventions over custom implementations. -* Keep business logic out of controllers where possible. -* Write readable, predictable, and maintainable code. - - - -## Frontend Development - -1. Always use async/await; avoid callback-based patterns and nested promises. - -2. Use Frappe-provided APIs for server calls: `frappe.call` with `async: true`. Prefer Promise-based usage over callbacks. - -3. Use Frappe's global JS helpers instead of native JS equivalents: - * `cstr()` instead of `String()` - * `cint()` instead of `parseInt()` - * `flt()` instead of `parseFloat()` - * `is_null()` instead of manual null/undefined/empty checks - * `format_currency()` for currency formatting - - -## Crawling - -Always use gemini as much as possible for getting the context, to get the help use gemini --help - -For checking if the site works you can use the agent-browser use agent-browser --help to get the context for it - - -## Commands - -### Frontend (Dashboard) -```bash -# dev server -yarn dev # or: cd dashboard && yarn dev - -# build for production -yarn build # outputs to buzz/public/dashboard + buzz/www/dashboard.html - -# lint/format frontend -cd dashboard && yarn lint -``` - -### Backend (Python) - -Always run bench migrate after doctype schema changes. - -```bash -# linting/formatting (via pre-commit) -pre-commit run --all-files - -# run ruff directly -ruff check buzz/ -ruff format buzz/ - -# install app to site -bench --site [site-name] install-app buzz -``` - -Use bench --help to see how to work with frappe bench, e.g. bench execute, bench console, etc. are very useful - -### Testing - -There are unit tests, run using bench run-tests. Site name is buzz.localhost, but if not found, ask user for it. The credentials are Administrator/admin. - -* To test in UI, use agent-browser. -* For frontend changes use :8080 since yarn dev server is running. -* Use in headed mode unless specified - -## Architecture - -**Three-tier stack:** -1. **Backend**: Frappe Framework (Python) - DocTypes, API, permissions, scheduler -2. **Dashboard**: Vue 3 + FrappeUI + Vite - attendee/sponsor/checkin UI - -**Core entity**: `Buzz Event` DocType drives everything (tickets, sponsors, schedule, payments). - -**Main modules** (inside `buzz/`): -- `events/` - Event, Venue, Category, Talks, Sponsors, Check-ins -- `ticketing/` - Bookings, Tickets, Add-ons, Cancellations, Coupons -- `proposals/` - Talk Proposals, Sponsorship Enquiries -- `buzz/` - Settings, Custom Fields -- `api.py` - whitelisted API methods for dashboard -- `payments.py` - integration with frappe/payments app - -**Frontend structure** (inside `dashboard/`): -- `src/pages/` - route components (BookTickets, TicketDetails, CheckInScanner, etc) -- `src/components/` - BookingForm, dialogs, shared UI -- `src/composables/` - reusable logic (useTicketValidation, usePaymentSuccess, etc) -- `src/data/` - frappe-ui resources for API calls -- Vite builds to `buzz/public/dashboard/`, router base is `/dashboard` - -**Key flows:** -- Booking: load event data → fill form → create booking → generate payment link → on payment auth → submit booking → generate tickets + QR + email -- Ticket actions: transfer, cancel, change add-on (window checks from Buzz Settings) -- Sponsorship: enquiry → approval → payment link → payment auth → create sponsor record -- Check-in: scan QR → validate → create check-in record (requires Frontdesk Manager role) - -**Integrations:** -- `frappe/payments` required for payment gateways -- `buildwithhussain/zoom_integration` optional for webinar creation/registration - -## Key Paths for Common Tasks - -**Booking changes**: `buzz/api.py`, `buzz/ticketing/doctype/event_booking/`, `dashboard/src/components/BookingForm.vue` - -**Ticket lifecycle**: `buzz/ticketing/doctype/event_ticket/`, `dashboard/src/pages/TicketDetails.vue` - -**Sponsorships**: `buzz/proposals/doctype/sponsorship_enquiry/`, `dashboard/src/pages/SponsorshipDetails.vue` - -**Check-in**: `buzz/api.py` (validate_ticket_for_checkin, checkin_ticket), `dashboard/src/pages/CheckInScanner.vue` - -**Event config**: `buzz/events/doctype/buzz_event/` - -**Reports**: `buzz/events/report/` and `buzz/ticketing/report/` - - -## Joining or creating report - "Never write `frappe.db.sql` again" -=========================================================== - -1. **Ban `frappe.db.sql` in new code** - * Add a pre-commit rule or CI step that greps for `\.db\.sql` and fails the build. - * Legacy code => wrap in `frappe.db.sql("...", as_dict=1)` and add a `# TODO-QB` comment so the next refactor is trackable. - -2. **Use the typed entry point** - ```python - from frappe.query_builder import DocType, Field - from frappe.query_builder.functions import Count, Sum, Coalesce, Date - ``` - Never `import pypika` directly; the `frappe.qb` namespace already returns the correct `MariaDB/PostgreSQL` dialect. - -3. **Parameterise, never interpolate** - ```python - # Bad - frappe.db.sql(f"... {user_input}") # injection bomb - # Good - frappe.qb.from_(...).where(table.field == user_input) # auto-escaped - ``` - -4. **Prefer joins over N+1** - ```python - so = DocType("Sales Order") - si = DocType("Sales Invoice") - query = ( - frappe.qb.from_(so) - .left_join(si) - .on(so.name == si.sales_order) - .select(so.name, si.name) - .where(so.customer == customer) - ) - ``` - One round-trip, no loops. - -5. **Sub-queries > raw SQL strings** - Need *"latest row per group"*? - ```python - latest = ( - frappe.qb.from_(si) - .select(si.name) - .where(si.sales_order == so.name) - .orderby(si.creation, order=Order.desc) - .limit(1) - ) - query = frappe.qb.from_(so).where(so.name == latest) - ``` - Keeps everything composable and dialect-agnostic. - -6. **Use `case` for conditional aggregates** - ```python - from frappe.query_builder.functions import Case - paid_amt = Sum( - Case() - .when(si.status == "Paid", si.grand_total) - .else_(0) - ) - ``` - -7. **Respect Frappe field casing** - * SQL column: `grand_total` - * Frappe field: `grand_total` - * No back-ticks needed; QB adds the correct quotes per DB. - -8. **Use `as_dict=True` or ORM objects** - ```python - rows = query.run(as_dict=True) # list[dict] - docs = query.run(as_dict=False) # list[tuple] - obj = frappe.get_doc("Doctype", pk) # when you need the full DocType hooks - ``` - -9. **Pagination with `limit_page_length` and `limit_start`** - ```python - query = query.limit(limit_page_length).offset(limit_start) - ``` - Same pattern the REST API uses. - -10. **Index-friendly WHERE order** - Put indexed columns first (`company`, `customer`, `status`) so MariaDB/PostgreSQL can use composite indexes. - -11. **Avoid `SELECT *` in reports** - Explicit list of fields keeps wire-size small and prevents breaking changes when new fields are added. - -13. **Cache heavy aggregations** - ```python - @frappe.whitelist() - @redis_cache(ttl=300) - def get_dashboard_stats(company): - inv = DocType("Sales Invoice") - total = frappe.qb.from_(inv).select(Sum(inv.grand_total)).where(inv.company == company).run() - return total[0][0] or 0 - ``` - - -Quick migration template ----------------------- - -Legacy: -```python -rows = frappe.db.sql(""" - select name, grand_total - from `tabSales Invoice` - where customer = %s - and docstatus = 1 -""", customer, as_dict=1) -``` - -QB equivalent: -```python -si = DocType("Sales Invoice") -rows = ( - frappe.qb.from_(si) - .select(si.name, si.grand_total) - .where((si.customer == customer) & (si.docstatus == 1)) - .run(as_dict=True) -) -``` - - -### Report Patterns -- Entry: `def execute(filters): return get_columns(), get_data(filters)` -- QB imports: `from frappe.query_builder import DocType` + `functions.Sum, Case, Count` -- Build lookup maps first, then loop + merge (avoid N+1) -- Caching: `@redis_cache(ttl=seconds)` for conversion factors - -### Token-Saving Workflow -- Default to `/model haiku` for routine edits, `/model sonnet` for moderate tasks -- Use `/model opus` ONLY for architecture, debugging complex issues -- Use `/compact` after completing each subtask -- Use `gemini -p "prompt"` via stdin to read/summarize files without burning Claude tokens -- Scope tasks narrowly: one feature/fix per session -- Dump progress to `.claude/memory/scratch.md` before session ends -- At session START: read `.claude/memory/scratch.md` — if it has content, resume from there -- At session END (or when user says "dump progress"): fill in scratch.md with current task state -- After resuming from scratch.md, clear it once the task is complete - - -### Variable and Function naming convention - -- always use full names for variables don't use abbreviations for ex: use -"for row in rows" instead of "for r in rows" -proxy_sku = DocType("Proxy SKU") instead of ps = DocType("Proxy SKU") -- avoid starting python functions with underscore "_" unless it's a private version behind a whitelisted function (e.g. `get_dial_codes` (whitelisted) -> `_get_dial_codes` (cached logic)) -- use camelCase in JS and follow the surrounding code style in the project -- always put imports at the top of the file, never inside functions - -## Notes - -- Read `ARCHITECTURE.md` for comprehensive details on data model, API surface, flows +The site is buzz.localhost:8000 (Administrator/admin) \ No newline at end of file diff --git a/entities.png b/entities.png deleted file mode 100644 index 95dbcb1641d90997b961c2b48f244e80f646721c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 452207 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�d9O-(zF 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 VjLa16O+xTvG1rQnYU;H0{{ZO`6$t Buzz Team` — creates team (+ owner membership via controller). +- `get_teams_for_user(user: str) -> list[dict]` — Active memberships joined to team + (name, team_name, slug, logo, team_role) via `frappe.get_all` on membership + team fields. +- `get_membership(team: str, user: str) -> dict | None` — Active membership row or None. +- `sync_frappe_roles(user: str)` — see above. +- `create_default_team_for(user: str)` — idempotent; used by install hook. + +### `buzz/api/teams.py` (new API module — organizer APIs get their own files, do not grow `buzz/api/__init__.py`) + +All `@frappe.whitelist()`, no guest access. + +### `Event User Preferences` change + +Add field `active_team` (Link → Buzz Team). Doctype currently has a single `user` field and is +already per-user. `set_active_team` validates the caller has an Active membership in that team. + +### Install & fixtures + +- `fixtures/role.json`: add `Event Manager` (desk_access 0 in v2 posture; keep existing entries). +- `buzz/install.py` `after_install` + a patch `buzz/patches/create_default_teams.py` + (registered in `patches.txt`): for each enabled user holding `Event Manager`, call + `create_default_team_for(user)` with team name `{first_name}'s Team`. Idempotent. + +## Frontend changes + +None. (Consumed by `01-dashboard/01`.) + +## API contract + +| Endpoint | Method | Params | Returns | Auth | +|---|---|---|---|---| +| `buzz.api.teams.create_team` | POST | `team_name` | `{name, team_name, slug}` | any logged-in user | +| `buzz.api.teams.get_my_teams` | GET | — | `[{name, team_name, slug, logo, team_role}]` | logged-in | +| `buzz.api.teams.get_active_team` | GET | — | `{name, team_name, slug, logo, team_role}` or null; falls back to first membership | logged-in | +| `buzz.api.teams.set_active_team` | POST | `team` | `{ok: true}` | member of `team` | + +## Permissions notes + +- Doctype perms: `Buzz Team` + `Buzz Team Membership` readable by `Buzz User` role (row-level + scoping arrives in step 03); write via API only (`System Manager` full access in Desk). +- `create_team` is open to any logged-in user — this is the self-serve entry point. + +## Demo script + +1. `bench --site buzz.localhost console`: create users `alice@x.com`, `bob@x.com`. +2. As alice (API): `create_team("Alice Events")` → team + Owner membership exist. +3. As alice: `get_my_teams()` → exactly one team, `team_role == "Owner"`; alice now holds + `Event Manager` role. +4. As bob: `get_my_teams()` → `[]`; `set_active_team()` → throws. +5. `bench migrate` on a site with an existing Event Manager user → default team created once; + re-running the patch is a no-op. + +## Acceptance criteria (merge gate) + +### Automated tests — `buzz/tests/test_teams.py` (new) +`bench --site buzz.localhost run-tests --module buzz.tests.test_teams` +- `create_team` creates the team + Owner membership + grants `Event Manager` to creator. +- Duplicate `(team, user)` membership insert raises. +- Role sync: adding Frontdesk membership grants `Frontdesk Manager`; demoting the only Owner / + disabling the last Active Owner membership raises. +- `get_my_teams` returns only the caller's Active memberships (two-user isolation case). +- `set_active_team` for a non-member raises; for a member persists to `Event User Preferences`. +- `create_default_team_for` is idempotent (call twice → one team). +- Patch `create_default_teams` run twice → no duplicates. + +### Browser checks (agent-browser) +None — no UI in this step (covered by `01-dashboard/01` checks). + +## Dependencies & risks + +- `Event Manager` fixture must not clobber existing site roles — fixture export merges by name. +- Role sync is additive-only by design; revocation handled in `sync_frappe_roles` recompute. diff --git a/specs/v2/00-teams/02-team-field-on-events-and-backfill.md b/specs/v2/00-teams/02-team-field-on-events-and-backfill.md new file mode 100644 index 00000000..6bcbe75a --- /dev/null +++ b/specs/v2/00-teams/02-team-field-on-events-and-backfill.md @@ -0,0 +1,113 @@ +# 02 — Tenant field on events & backfill + +| Phase | Depends on | Status | +|---|---|---| +| Foundation | 00-teams/01 | Draft | + +## Goal + +Every `Buzz Event` belongs to a team, and `team` is denormalized onto all event descendants so the +permission layer (step 03) is a single-column filter on every doctype. + +## Demo criteria (definition of done) + +`bench migrate` on a site with existing data leaves every `Buzz Event` and descendant row with +`team` set; creating an `Event Ticket Type` in Desk auto-stamps `team` from its event; creating a +`Buzz Event` without `team` fails validation. + +## Scope + +### In +- `team` field on all tenant doctypes (inventory in `00-plan.md`) +- Cascade helper + `doc_events` wiring +- Backfill patch + +### Out (deferred to) +- Enforcement (hooks) → `03-permission-hooks.md` +- Per-team settings split of `Buzz Settings` → future (site-wide Single stays global for now) + +## Backend changes + +### Field additions + +- **`Buzz Event`**: `team` Link → Buzz Team, **reqd**, `in_standard_filter`, indexed + (`search_index: 1`). Placed in the first section next to `title`. +- **Team-direct doctypes** (`Event Venue`, `Event Host`, `Event Template`, `Buzz Campaign`): + `team` Link → Buzz Team, reqd, indexed. +- **Derived-from-event doctypes** (all 18 in `00-plan.md` inventory): `team` Link → Buzz Team, + read-only, hidden, indexed. Stamped, never user-entered. + +### Cascade helper — `buzz/teams.py` + +```python +def inherit_team_from_event(doc, method=None): + """doc_events handler: stamp doc.team from its linked event.""" +``` + +- Resolves the event link field (`event` on most doctypes) → `frappe.db.get_value("Buzz Event", + event, "team")` and sets `doc.team`. Throws if the event has no team. +- Wired in `hooks.py` `doc_events` `before_insert` for every derived doctype. `validate` also + re-stamps when the `event` link changed (ticket transfers between events don't exist, but coupon + `event` is editable). +- Child tables (`Schedule Item`, `Buzz Event Form`, etc.) need **no** team field — they are + reachable only through their parent and inherit its permissions. + +### `Buzz Event` controller + +`validate`: `team` required (schema-level reqd covers Desk; keep an explicit check for +`ignore_mandatory` code paths). New-event creation from the dashboard always passes the active team. + +### Backfill patch — `buzz/patches/assign_default_team.py` (register in `patches.txt` after `create_default_teams`) + +1. Site-default team: reuse the default team of the site's first System Manager/Event Manager + (created by step 01's patch); create one (`"Default Team"`) if none exists. +2. `UPDATE tabBuzz Event SET team = WHERE IFNULL(team, '') = ''` via + `frappe.db.set_value`-style bulk (`frappe.qb.update` — no raw SQL). +3. Cascade to each derived doctype with a query-builder update joining on its `event` column. +4. Idempotent; safe to re-run. + +> Decision recorded: **one site-default team** for backfill (not per-`Event Host` teams). Existing +> installs are single-tenant in practice; owners can reassign later from Desk. + +## Frontend changes + +None. + +## API contract + +No new endpoints. + +## Permissions notes + +None enforced yet — `team` is inert data until step 03. Ship 02 and 03 in quick succession; a site +sitting on 02 alone behaves exactly as today. + +## Demo script + +1. `bench --site buzz.localhost migrate`. +2. In Desk, open any pre-existing `Buzz Event` → `team` populated with the default team. +3. `frappe.db.count("Event Booking", {"team": ("is", "not set")})` → 0 (repeat per derived doctype). +4. Create a new `Event Ticket Type` for that event in Desk → `team` auto-stamped, read-only. +5. Try inserting a `Buzz Event` without `team` in console → `frappe.MandatoryError`. + +## Acceptance criteria (merge gate) + +### Automated tests — `buzz/tests/test_team_cascade.py` (new) +`bench --site buzz.localhost run-tests --module buzz.tests.test_team_cascade` +- Inserting `Event Ticket Type` / `Event Booking` / `Event Sponsor` for an event stamps `team` + from the event (one parametrized case per derived doctype in the inventory). +- Changing a coupon's `event` link re-stamps `team` on validate. +- Inserting `Buzz Event` without `team` raises `MandatoryError` (both Desk path and + `ignore_mandatory` guard). +- Backfill patch: seed legacy rows with NULL team → run patch → zero NULL-team rows across every + doctype in the inventory; run patch again → no change (idempotent). + +### Browser checks (agent-browser) +None — no UI in this step. + +## Dependencies & risks + +- Patch ordering: must run **after** doctype sync and step 01's `create_default_teams` + (patches.txt order), and **before** step 03's hooks are exercised — NULL `team` under active + hooks hides rows from everyone but System Manager. +- `bench migrate` required after schema change (per project convention). diff --git a/specs/v2/00-teams/03-permission-hooks.md b/specs/v2/00-teams/03-permission-hooks.md new file mode 100644 index 00000000..6d039d5d --- /dev/null +++ b/specs/v2/00-teams/03-permission-hooks.md @@ -0,0 +1,166 @@ +# 03 — Permission hooks (row-level isolation) + +| Phase | Depends on | Status | +|---|---|---| +| Foundation | 00-teams/02 | Draft | + +## Goal + +Team isolation actually enforced: a member of Team A can never list or open Team B's records — +in Desk, `frappe.client`, REST, and frappe-ui `useList` alike. Replaces the commented-out stubs at +`buzz/hooks.py:161-167`. + +## Demo criteria (definition of done) + +Two managers in two teams: Desk/API list views show only their own team's events and bookings; +direct URL to the other team's doc → 403. Simultaneously, a Guest completes a booking on a +published event and an attendee still sees their own booking at `/dashboard/account/bookings` — +zero regression. + +## Scope + +### In +- `buzz/permissions.py` with query-condition + has_permission implementations +- `hooks.py` wiring for every tenant doctype + `Buzz Team`/`Buzz Team Membership` +- Guest/attendee endpoint audit + regression test suite + +### Out (deferred to) +- UI 403 page → `01-dashboard/01` +- Frontdesk check-in scoping niceties → `01-dashboard/08` + +## Backend changes + +### `buzz/permissions.py` (new, Gameplan pattern — one central module) + +```python +TEAM_SCOPED_WRITE_ROLES = {"Owner", "Admin", "Manager"} +TEAM_SCOPED_DELETE_ROLES = {"Owner", "Admin"} + +def team_query_conditions(user=None, doctype=None, **kwargs) -> str | None: + """permission_query_conditions hook, shared by all tenant doctypes. + + Called by db_query as fn(user, doctype=doctype). Returns namespaced SQL: + `tab{doctype}`.`team` in (select `team` from `tabBuzz Team Membership` + where `user` = %(user)s and `status` = 'Active') + Built with frappe.qb / pypika criterion (framework renders + escapes it). + System Manager / Administrator -> None (unrestricted). + """ + +def membership_query_conditions(user=None, **kwargs) -> str | None: + """For Buzz Team Membership itself: rows of teams the user belongs to.""" + +def team_doc_query_conditions(user=None, **kwargs) -> str | None: + """For Buzz Team: teams the user is an Active member of.""" + +def team_has_permission(doc, ptype="read", user=None, **kwargs) -> bool: + """has_permission hook — DENY-ONLY (composes over role perms). + + read/select/print/email/export/report -> any Active membership on doc.team + write/create/submit/cancel/amend -> team_role in TEAM_SCOPED_WRITE_ROLES + delete -> team_role in TEAM_SCOPED_DELETE_ROLES + System Manager -> True. doc.team unset -> True (pre-backfill tolerance). + """ +``` + +Notes: +- Signatures follow framework calling conventions verified in + `frappe/model/db_query.py:get_permission_query_conditions` (`fn(user, doctype=...)`) and + `frappe/permissions.py:has_controller_permissions` (`fn(doc=, ptype=, user=, debug=)`); + `**kwargs` absorbs extras. +- One generic implementation, not per-doctype copies. Membership subquery built once via + `frappe.qb` and rendered with `with_namespace=True`. +- **Attendee carve-out**: for `Event Booking`, `Event Ticket`, `Event Payment`, + `Ticket Cancellation Request` the query condition is + `( OR owner = user)` — attendees list *their own* docs today via + framework `owner`/`if_owner` perms and existing API checks; team scoping must widen, not narrow, + that access. + +### `hooks.py` wiring + +```python +permission_query_conditions = { + "Buzz Team": "buzz.permissions.team_doc_query_conditions", + "Buzz Team Membership": "buzz.permissions.membership_query_conditions", + "Buzz Event": "buzz.permissions.team_query_conditions", + # ... every doctype in the 00-plan.md tenant inventory +} +has_permission = { + "Buzz Team": "buzz.permissions.team_doc_has_permission", + "Buzz Event": "buzz.permissions.team_has_permission", + # ... same inventory +} +``` + +### Guest & attendee flow audit (hard requirement of this step) + +Every whitelisted endpoint in `buzz/api/__init__.py` is enumerated in the PR description with its +verdict. The critical ones: + +| Endpoint | Path today | Verdict | +|---|---|---| +| `get_event_booking_data`, `validate_coupon` | guest-allowed reads of published event config | reads use `frappe.get_all` with explicit filters — must switch to `ignore_permissions=True` reads of *published* events only (guest has no membership) | +| `process_booking` | creates booking/attendees as guest or user | doc inserts already run with explicit flows; inserts get `ignore_permissions=True` where the actor is a non-member by design | +| `get_booking_details`, `get_ticket_details` | owner-checked | keep explicit owner checks; owner carve-out in query conditions keeps list views working | +| `transfer_ticket`, `change_add_on_preference`, `create_cancellation_request` | owner + window checks | unchanged; inserts stamped with team via cascade | +| `validate_ticket_for_checkin`, `checkin_ticket` | `frappe.only_for("Frontdesk Manager")` | additionally verify frontdesk user has Active membership (any role with check-in capability) in the ticket's team | + +### Tests — `buzz/tests/test_team_permissions.py` (new) + +1. Cross-team isolation: `frappe.get_list("Buzz Event")` as member A excludes team B's events; + `frappe.get_doc(...).check_permission("read")` raises for B's event. +2. Role matrix: Viewer can read, cannot write; Manager can write, cannot delete; Admin can delete. +3. Guest booking end-to-end on a published event (existing test helpers) still passes. +4. Attendee reads own booking/tickets without membership. +5. Frontdesk of team A cannot check in team B's ticket. + +## Frontend changes + +None. + +## API contract + +No new endpoints. + +## Permissions notes + +This step **is** the permission layer — matrix in `00-teams/00-plan.md`. `has_permission` hooks can +only deny, never grant; base doctype role perms (`Event Manager` etc.) still gate capability. + +## Demo script + +1. Create teams A (alice) and B (bob), one event each. +2. As alice in Desk: Buzz Event list shows only A's event; open B's event URL → 403. +3. As alice via REST: `/api/resource/Buzz Event` → only A's. +4. Incognito guest: book a free ticket on B's published event → success; ticket email received. +5. Attendee logs in → `/dashboard/account/bookings` shows the booking. +6. `bench --site buzz.localhost run-tests --module buzz.tests.test_team_permissions` → green. + +## Acceptance criteria (merge gate) + +### Automated tests — `buzz/tests/test_team_permissions.py` (the suite defined above, expanded) +`bench --site buzz.localhost run-tests --module buzz.tests.test_team_permissions` +- Cross-team isolation: `frappe.get_list` excludes other team's rows for **every** doctype in the + tenant inventory (parametrized); `check_permission("read")` raises cross-team. +- Role matrix: Viewer read-only; Manager write-not-delete; Admin delete; System Manager + unrestricted. +- Owner carve-out: attendee (non-member) lists own `Event Booking`/`Event Ticket` rows. +- Frontdesk of team A cannot `checkin_ticket` for team B's ticket. +- NULL-team tolerance: doc with unset team readable by System Manager only; no exception raised. +- Guest API surface: `get_event_booking_data`, `validate_coupon`, `process_booking` (free ticket) + succeed as Guest on a published event with hooks active. + +### Browser checks (agent-browser) +1. Fresh session (no login) → open `/dashboard/book-tickets/` → complete a + free-ticket guest booking (OTP flow) → success screen; ticket email queued. +2. Log in as that attendee → `/dashboard/account/bookings` → booking visible; open ticket detail. +3. Log in as team-A manager → open Desk `/app/buzz-event` → assert list shows only team A's + events; navigate to team B's event URL → 403/permission error page. + +## Dependencies & risks + +- **Highest-risk step in v2.** The attendee/guest carve-outs are the sharp edge — the test suite is + a merge blocker, not optional. +- NULL `team` tolerance (`doc.team unset -> True` in has_permission; query condition still filters + NULL out for non-System-Managers) keeps a half-migrated site from hard-breaking. +- Performance: membership subquery per list query; Frappe's role check stays Redis-cached. If it + shows up in profiles, cache team IDs per user in `frappe.cache` keyed on membership modification. diff --git a/specs/v2/00-teams/04-team-invitations.md b/specs/v2/00-teams/04-team-invitations.md new file mode 100644 index 00000000..3f9ac78b --- /dev/null +++ b/specs/v2/00-teams/04-team-invitations.md @@ -0,0 +1,112 @@ +# 04 — Team invitations + +| Phase | Depends on | Status | +|---|---|---| +| Foundation | 00-teams/03 | Draft | + +## Goal + +Owners/Admins invite members by email with a team role, reusing Frappe's built-in +`User Invitation` (`frappe/core/doctype/user_invitation/`) — no bespoke invite doctype. + +## Demo criteria (definition of done) + +API-invite a fresh email with `team_role=Manager`; recipient accepts the emailed link as a brand-new +user → Active `Buzz Team Membership` created, `Event Manager` role granted, `get_my_teams` includes +the team on their first login. + +## Scope + +### In +- `user_invitation` hook extension (`extra_invite_params`, `after_accept`) +- `invite_member` wrapper API + pending-invite listing + revoke +- Inviter authorization (Owner/Admin only) + +### Out (deferred to) +- Members/invites UI → `01-dashboard/13` +- Attendee-facing invitations (marketing) → future marketing pillar + +## Backend changes + +### `hooks.py` + +```python +user_invitation = { + "allowed_roles": {"Event Manager": ["Buzz User"], "Buzz User": ["Buzz User"]}, + "extra_invite_params": ["team", "team_role"], + "after_accept": ["buzz.teams.on_invitation_accepted"], +} +``` + +### `buzz/teams.py` + +- `on_invitation_accepted(invitation, user, user_inserted)` — reads `team` + `team_role` from the + invitation's extra params; creates Active `Buzz Team Membership` (idempotent: upsert on + `(team, user)`, re-activate if previously Disabled). Role sync happens via the membership + controller from step 01. +- Validation at *invite time*, not accept time: the wrapper API checks the inviter. + +### `buzz/api/teams.py` additions + +- `invite_member(team, email, team_role)` — validates caller has `team_role in {Owner, Admin}` for + `team`; `team_role` must not be `Owner` (ownership transfer is a separate explicit action); + delegates to `frappe.core.api.user_invitation.invite_by_email` with + `extra_params={"team": team, "team_role": team_role}` and `redirect_to_path="/dashboard"`. +- `get_pending_invitations(team)` — Owner/Admin only; pending `User Invitation` rows for the team. +- `revoke_invitation(team, invitation)` — Owner/Admin only. + +## Frontend changes + +None (UI in `01-dashboard/13`). + +## API contract + +| Endpoint | Method | Params | Returns | Auth | +|---|---|---|---|---| +| `buzz.api.teams.invite_member` | POST | `team, email, team_role` | `{invitation}` | Owner/Admin of team | +| `buzz.api.teams.get_pending_invitations` | GET | `team` | `[{name, email, team_role, status, creation}]` | Owner/Admin | +| `buzz.api.teams.revoke_invitation` | POST | `team, invitation` | `{ok}` | Owner/Admin | + +## Permissions notes + +- Inviting grants at most `Admin` — never `Owner`. +- An invited email that already has an account: `after_accept` fires with `user_inserted=False`; + membership upsert covers both paths. +- Invited existing member: `invite_member` short-circuits with a friendly `frappe.throw`. + +## Demo script + +1. As alice (Owner of team A): `invite_member(A, "carol@x.com", "Manager")` → invitation email + (or console link in dev). +2. `get_pending_invitations(A)` → 1 row. +3. Open the accept link in incognito → account created → redirected to `/dashboard`. +4. As carol: `get_my_teams()` → team A with `team_role: Manager`; carol holds `Event Manager`. +5. As bob (non-member): `invite_member(A, ...)` → throws. + +## Acceptance criteria (merge gate) + +### Automated tests — `buzz/tests/test_team_invitations.py` (new) +`bench --site buzz.localhost run-tests --module buzz.tests.test_team_invitations` +- `invite_member` as Owner creates a pending `User Invitation` with extra params + `{team, team_role}`. +- `invite_member` as Manager/non-member raises; `team_role="Owner"` raises. +- Accept for a brand-new email: User created, Active membership with invited role, + `Event Manager`/`Frontdesk Manager` role granted per mapping. +- Accept for an existing user (`user_inserted=False`): membership upserted; previously Disabled + membership re-activated, not duplicated. +- Inviting an existing Active member throws the friendly error. +- `revoke_invitation` as Admin works; as Manager raises. + +### Browser checks (agent-browser) +1. As Owner (console/API) create an invitation; grab the accept URL (dev mode prints it). +2. Fresh browser session → open the accept URL → account/password flow → assert final redirect + lands on `/dashboard`. +3. Log in as the new user → assert `get_my_teams` (via any team-aware page once + `01-dashboard/01` exists; until then assert via API response in the browser session) includes + the team with the invited role. + +## Dependencies & risks + +- `User Invitation` app-key: invitations are namespaced per app via the hook config — verify the + accept flow passes `app_name=buzz` end to end on this frappe version. +- Email delivery in dev: dev mode prints the accept URL; demo uses that. diff --git a/specs/v2/00-teams/05-team-settings.md b/specs/v2/00-teams/05-team-settings.md new file mode 100644 index 00000000..0862608c --- /dev/null +++ b/specs/v2/00-teams/05-team-settings.md @@ -0,0 +1,152 @@ +# 05 — Buzz Team Settings + +| Phase | Depends on | Status | +|---|---|---| +| Foundation | 00-teams/01–03 | Draft | + +## Goal + +Split tenant-specific configuration out of the site-wide `Buzz Settings` Single into a per-team +**`Buzz Team Settings`** doctype (regular doctype, one row per team — not a Single, since it is +per tenant). `Buzz Settings` remains for truly global, admin-level, backend-only config. + +## Demo criteria (definition of done) + +Two teams set different ticket-transfer windows; each team's events enforce their own window in +the attendee flow (`can_transfer_ticket`). `bench migrate` seeds every existing team's settings +from current global values — attendee-visible behavior unchanged on upgrade day. + +## Scope + +### In +- `Buzz Team Settings` doctype + auto-create on team creation + seed patch +- Resolution helpers + rewiring existing read sites (audit table below) +- API for the frontend settings dialog +### Out (deferred to) +- Settings UI → `01-dashboard/13` (Team Settings dialog gets a "Defaults" section) +- Per-event overrides of these values (event-level fields already exist for some, e.g. + `ticket_email_template` on Buzz Event — that hierarchy stays: event field → team settings) + +## Field split decision + +### Moves to `Buzz Team Settings` (per team) + +| fieldname | type | today read at | +|---|---|---| +| `allow_transfer_ticket_before_event_start_days` | Int | `buzz/api/__init__.py:151` (`can_transfer_ticket`) | +| `allow_add_ons_change_before_event_start_days` | Int | `buzz/api/__init__.py:166` (`can_change_add_ons`) | +| `allow_ticket_cancellation_request_before_event_start_days` | Int | `buzz/api/__init__.py:191` (`can_request_cancellation`) | +| `support_email` | Data | ticket emails / public pages | +| `default_ticket_email_template` | Link Email Template | `event_ticket.py:106` | +| `auto_send_pitch_deck` | Check | `sponsorship_enquiry.py:82` | +| `default_sponsor_deck_email_template` | Link Email Template | `sponsorship_enquiry.py:82` | +| `default_sponsor_deck_reply_to` | Data | same | +| `default_sponsor_deck_cc` | Small Text | same | +| `default_webinar_template` | Link | `buzz_event.py:198` (verify field exists in Buzz Settings JSON — read in code, missing from schema dump; reconcile while implementing) | + +### Stays in `Buzz Settings` (site-wide, Desk/System Manager only) + +| fieldname | why global | +|---|---| +| `login_banner` | site login page (`auth.py:13`) | +| `accept_event_proposals`, `allow_guest_event_proposals`, `event_proposal_banner_title`, `event_proposal_success_title`, `event_proposal_success_message` | inbound event-proposal funnel is site-level (pre-team) — `forms.py:367` | +| `custom_fields_go_after_this` | form-layout plumbing | + +## Backend changes + +### New doctype: `Buzz Team Settings` (module: Events) + +- `team` Link → Buzz Team, reqd, **unique** — one settings row per team. +- All "moves" fields above. +- `autoname: field:team` (row addressable as the team name). +- Not a child table, not a Single — plain doctype so permission hooks from `00-teams/03` apply + (add it to the tenant inventory: query condition = its own `team` column; write requires + Owner/Admin — settings are team administration, stricter than Manager). + +### Lifecycle + +- `Buzz Team.after_insert` → `buzz.teams.create_team_settings(team)` seeding field values from + current `Buzz Settings` globals (copy-on-create; **no runtime fallback chain** — one read, + predictable; changing a global later never silently mutates existing teams). +- Patch `buzz/patches/create_team_settings_for_existing_teams.py`: idempotent seed for teams + created before this step. + +### Resolution helpers — `buzz/teams.py` + +```python +def get_team_settings(team: str) -> Document: + """frappe.get_cached_doc('Buzz Team Settings', team); creates+seeds if missing (self-heal).""" + +def get_event_team_settings(event: str) -> Document: + """Resolve event -> team -> settings. The standard entry point for attendee-flow code.""" +``` + +### Rewire call sites (audit — the heart of this step) + +| Site | Change | +|---|---| +| `buzz/api/__init__.py:151,166,191` | `frappe.get_single("Buzz Settings")` → `get_event_team_settings(event)` (event in scope at each site) | +| `buzz/ticketing/doctype/event_ticket/event_ticket.py:106` | event-level `ticket_email_template` stays first; fallback → team settings default | +| `buzz/proposals/doctype/sponsorship_enquiry/sponsorship_enquiry.py:82` | sponsor-deck defaults → team settings (enquiry → event → team) | +| `buzz/events/doctype/buzz_event/buzz_event.py:198` | webinar template default → team settings | +| `buzz/api/auth.py:13`, `buzz/api/forms.py:367` | unchanged (global) | + +Add regression tests: per-team window enforcement + upgrade parity (seeded values equal old +globals). + +## Frontend changes + +None here. `01-dashboard/13` Team Settings dialog gains a **Defaults** section (windows, support +email, email-template Links, sponsor-deck defaults) bound to `useDoc("Buzz Team Settings", team)`. +Owner/Admin editable, others read-only. + +## API contract + +No bespoke endpoints — `useDoc` CRUD under permission hooks suffices (row addressable by team +name via `autoname: field:team`). + +## Permissions notes + +- Read: any team member (attendee-flow reads happen server-side, unaffected). +- Write: Owner/Admin only (stricter than the Manager write default — add a doctype-specific + branch in `buzz/permissions.py:team_has_permission` or a dedicated `has_permission` mapping). +- `Buzz Settings` keeps System-Manager-only Desk access; never surfaced in the dashboard. + +## Demo script + +1. `bench migrate` → every team has a seeded `Buzz Team Settings` row; values match old globals. +2. Team A sets transfer window 7 days; Team B leaves 2. +3. Attendee with ticket to A's event (5 days out) → transfer blocked; B's event (5 days out) → + allowed. +4. New team created via onboarding → settings row exists immediately. +5. As Manager-role member: `useDoc` save on team settings → server denies. + +## Acceptance criteria (merge gate) + +### Automated tests — `buzz/tests/test_team_settings.py` (new) +`bench --site buzz.localhost run-tests --module buzz.tests.test_team_settings` +- Creating a `Buzz Team` auto-creates its `Buzz Team Settings` row seeded from `Buzz Settings` + values (assert field-by-field for the moved set). +- `get_team_settings` self-heals a missing row. +- Window enforcement: team A window 7 days, team B 2 days → `can_transfer_ticket` / + `can_change_add_ons` / `can_request_cancellation` return opposite verdicts for tickets on + events 5 days out. +- Ticket email template resolution order: event field → team settings default (both branches). +- Sponsor-deck defaults resolved from the enquiry's event's team. +- Write perms: Manager-role member save raises; Owner/Admin save succeeds; cross-team read + excluded (list isolation). +- Seed patch idempotent; seeded values equal pre-migration globals (upgrade-parity case). + +### Browser checks (agent-browser) +1. Attendee with a ticket to team A's event 5 days out (window 7) → ticket detail page: transfer + action absent/disabled; same-aged ticket on team B's event (window 2) → transfer action + available. + +## Dependencies & risks + +- **Decision recorded: copy-on-create, no fallback chain.** Trade-off: platform admin changing a + global default doesn't propagate to existing teams. Revisit only if that becomes a real need. +- `default_webinar_template` schema/code mismatch (read in `buzz_event.py:198`, absent from the + current Buzz Settings field dump) — reconcile during implementation. +- Update `00-teams/00-plan.md` tenant inventory + `01-dashboard/13` scope when this lands (done in + this spec's PR). diff --git a/specs/v2/01-dashboard/00-plan.md b/specs/v2/01-dashboard/00-plan.md new file mode 100644 index 00000000..7427a3b9 --- /dev/null +++ b/specs/v2/01-dashboard/00-plan.md @@ -0,0 +1,93 @@ +# 01-dashboard — Plan + +Unified dashboard: one SPA serving attendees (existing `/account/*` + public booking routes, +untouched) **and** event managers (new `/manage/*` area). Goal of phases A–D: **Desk parity** — +an organizer never opens `/app`. + +## Step 0 — prerequisite gate (frappe-ui v1 beta) + +All specs below are written against **frappe-ui v1 beta** APIs. `dashboard/package.json` currently +pins `frappe-ui ^0.1.257` — **Phase A must not start until the migration is merged.** Verification +checklist: + +- [ ] `package.json` pins the v1 beta release; `yarn build` green +- [ ] `import { DesktopShell } from 'frappe-ui'` resolves and renders in a scratch page +- [ ] `frappe-ui/list` subpath (`List`, `ListRow`) importable +- [ ] `useCall` / `useList` / `useDoc` available +- [ ] Semantic tokens compile (`bg-surface-base`, `text-ink-gray-9`, `border-outline-gray-2`) +- [ ] `Dialog v-model:open`, imperative `toast` / `dialog` helpers work +- [ ] `telemetryPlugin` exported from `frappe-ui/frappe` (needed by step 14) +- [ ] Existing attendee pages still function (booking flow smoke test on :8080) + +## Conventions (binding for all steps) + +**Routing** — manager area under `/manage/*` in `dashboard/src/router.ts`, lazy-loaded chunk +(`() => import(...)`), guarded by `meta: { requiresTeam: true }`; the router guard resolves +`get_my_teams` once (cached) — no teams → onboarding screen; not logged in → existing +`LoginRequired` handling. No team slug in URLs: team identity = active-team preference. Deep link +into another team's event → auto-switch active team if member, else 403 page. + +**Data layer** — all new manager code uses `useList` / `useDoc` / `useCall` composables, organized +per domain in `dashboard/src/data/` (`teams.ts`, `events.ts`, `tickets.ts`, `bookings.ts`, …). +**No new inline `createResource`.** The 68 existing inline attendee resources are explicitly out of +scope — do not refactor them in these steps. + +**UI language** — frappe-ui components only (`Button`, `Dialog`, `FormControl`, `List`, `Badge`, +`Dropdown`, `Tabs`, `FileUploader`, `Breadcrumbs`); semantic tokens only; icons via +`lucide-*` classes; espresso design per the frappe-ui skill `DESIGN.md` (shell anatomy, data-table +archetype, settings-dialog archetype). + +**Backend** — plain doctype CRUD goes through `frappe.client` via `useList`/`useDoc` under the +team permission hooks (`00-teams/03`). Bespoke endpoints only for aggregation/validation, in +per-domain modules `buzz/api/.py`. + +## Phases + +| Phase | Steps | Outcome | +|---|---|---| +| **A — tracer bullet** | [01](01-app-shell-and-events-list.md), [02](02-event-workspace-and-ticket-types.md) | Shell + team-scoped events list + ticket type CRUD, end to end | +| **B — core workspace** | [03](03-event-overview-and-editing.md), [04](04-coupons.md), [05](05-ticket-add-ons.md), [06](06-sponsorship-tiers-and-sponsors.md) | Event fully configurable without Desk | +| **C — operations** | [07](07-bookings-and-attendees.md), [08](08-checkin-desk.md) | Money + people + door | +| **D — parity long tail** | [09](09-venues-hosts-library.md), [10](10-schedule-tracks-and-talks.md), [11](11-event-templates.md), [12](12-event-settings-payments-and-forms.md), [13](13-team-settings-members-and-invites.md) | Full Desk parity + team admin | +| **cross-cutting** | [14](14-pulse-analytics.md) | Pulse product analytics (init early, instrument as steps land) | + +## Event workspace tab map + +`/manage/events/:eventId` — tabs registered up front (step 02), enabled as steps land: + +| Tab | Step | Manages | +|---|---|---| +| Overview | 03 | KPIs, core `Buzz Event` fields, publish | +| Tickets | 02 | `Event Ticket Type` | +| Add-ons | 05 | `Ticket Add-on` | +| Coupons | 04 | `Buzz Coupon Code` | +| Sponsors | 06 | `Sponsorship Tier`, `Event Sponsor`, enquiries (read) | +| Bookings | 07 | `Event Booking`, `Event Payment` | +| Attendees | 07 | `Event Booking Attendee` / `Event Ticket` | +| Check-in | 08 | `Event Check In` + scanner | +| Schedule | 10 | `Schedule Item`, `Event Track`, `Event Talk`, proposals inbox | +| Settings | 12 | gateways, tax, registration windows, custom fields/forms, extra pages | + +## Desk-parity matrix + +| Doctype | Home in dashboard | Step | +|---|---|---| +| Buzz Event | Events list + workspace | 01–03 | +| Event Ticket Type | Tickets tab | 02 | +| Buzz Coupon Code | Coupons tab | 04 | +| Ticket Add-on | Add-ons tab | 05 | +| Sponsorship Tier / Event Sponsor / Sponsorship Enquiry | Sponsors tab | 06 | +| Event Booking / Event Payment | Bookings tab | 07 | +| Event Ticket / Event Booking Attendee | Attendees tab | 07 | +| Event Check In | Check-in tab | 08 | +| Event Venue / Event Host | Library pages | 09 | +| Schedule Item / Event Track / Event Talk / Talk Proposal | Schedule tab | 10 | +| Event Template (+ children) | Templates page | 11 | +| Event Payment Gateway / Offline Payment Method / Buzz Custom Field / Buzz Event Form / Additional Event Page | Settings tab | 12 | +| Buzz Team / Buzz Team Membership / User Invitation / Buzz Team Settings | Team settings | 13 | + +**Phase E candidates (not covered, stay Desk-only for now — parity claims carve these out):** +`Buzz Settings` (site-wide Single — global admin config only; tenant-facing fields move to +`Buzz Team Settings` per `00-teams/05`), `Event Featured Speaker`, +`Speaker Profile`, `Buzz Campaign` + `UTM Parameter`, `Event Feedback` (read-only report later), +`Event Proposal` moderation, `Ticket Cancellation Request` approval queue. diff --git a/specs/v2/01-dashboard/01-app-shell-and-events-list.md b/specs/v2/01-dashboard/01-app-shell-and-events-list.md new file mode 100644 index 00000000..dd71309e --- /dev/null +++ b/specs/v2/01-dashboard/01-app-shell-and-events-list.md @@ -0,0 +1,116 @@ +# 01 — App shell & events list + +| Phase | Depends on | Status | +|---|---|---| +| A | 00-teams/01–03, Step-0 gate | Draft | + +## Goal + +First pixels of the manager app: frappe-ui app shell with sidebar + team switcher, and a +team-scoped events list with event creation. Proves teams, permissions, routing, and the new +frappe-ui stack in one slice. + +## Demo criteria (definition of done) + +A manager logs in, lands on `/manage/events`, sees only their active team's events, switches team +from the sidebar and the list swaps, creates an event from a dialog and it appears in Desk with the +correct team. A user with no team gets the "Create your team" onboarding. Attendee routes +unaffected. + +## Scope + +### In +- `ManagerLayout` shell (desktop + mobile), sidebar nav skeleton, team switcher +- Events list page + New Event dialog +- No-team onboarding screen; 403 page for non-member deep links +- `data/teams.ts`, `data/events.ts` + +### Out (deferred to) +- Event workspace → step 02 +- Sidebar items Venues/Hosts/Templates/Team Settings render disabled → steps 09/11/13 + +## Backend changes + +None. Consumes `buzz.api.teams.*` (00-teams/01); list scoping comes free from permission hooks. + +## Frontend changes + +### Routes (`dashboard/src/router.ts`) + +| Path | Name | Meta | +|---|---|---| +| `/manage` | redirect → `/manage/events` | `requiresTeam` | +| `/manage/events` | `ManageEventsList` | `requiresTeam` | +| `/manage/onboarding` | `TeamOnboarding` | logged-in only | +| `/manage/403` | `ManageForbidden` | | + +All under a parent route rendering `ManagerLayout` as a lazy chunk. Router guard: resolve +`data/teams.ts` `myTeams` once → empty ⇒ redirect onboarding. + +### Components + +- `src/layouts/ManagerLayout.vue` — `DesktopShell` / `MobileShell` split per DESIGN.md shell + anatomy. `Sidebar` (~14rem): header = **team switcher** `Dropdown` (team logo `Avatar`, + `team_name`, chevron; items from `myTeams`, active check, "Create team" action) → + `set_active_team` then invalidate all manager lists; nav `SidebarItem`s: Events + (`lucide-calendar`), Venues, Hosts, Templates, Team Settings (disabled until their steps); + footer: "Attendee view" link → `/account/bookings`, user `Dropdown` (logout). +- `src/pages/manage/EventsList.vue` — data-table archetype: `PageHeader` (title "Events", primary + `Button variant="solid"` "New Event"), `List` from `frappe-ui/list` with columns: title, + `start_date` (formatted), venue, status `Badge` (`is_published` → green "Live" / gray "Draft"), + tickets sold. Row click → step 02 workspace (until then, no-op). `#empty` slot: illustration + + "Create your first event" CTA. +- `NewEventDialog.vue` — `Dialog v-model:open`; `FormControl`s: title (reqd), start_date / + end_date (`DatePicker`), category (Link to `Event Category`), medium Select. Insert via + `events.ts` with `team: activeTeam` stamped; `toast.success` + route to the new event. +- `src/pages/manage/TeamOnboarding.vue` — centered card: team name input → `create_team` → + redirect `/manage/events`. + +### Data layer + +- `src/data/teams.ts` — `useCall` wrappers: `myTeams`, `activeTeam` (+ `setActiveTeam(team)` that + awaits the POST then invalidates manager caches). +- `src/data/events.ts` — `useList({ doctype: "Buzz Event", fields: [...], filters: { team } })` + + `insertEvent`. + +## API contract + +Consumes `buzz.api.teams.get_my_teams`, `get_active_team`, `set_active_team`, `create_team` only. + +## Permissions notes + +List scoping enforced server-side by `00-teams/03` — the `team` filter in `useList` is UX +(active-team narrowing), not security. Non-member deep link → server 403 → render `ManageForbidden`. + +## Demo script + +1. `yarn dev` (:8080), log in as alice (Owner, team A with 2 events) → `/manage/events` lists 2. +2. Sidebar switcher → team X (alice also member) → list swaps without reload. +3. "New Event" → fill dialog → toast, event visible; Desk shows it with `team = X`. +4. Log in as dave (no teams) → `/manage` → onboarding → create team → empty events list. +5. `/account/bookings` and public `/book-tickets/:route` unchanged. + +## Acceptance criteria (merge gate) + +### Automated tests +None new (no backend). Teams suites (`test_teams`, `test_team_permissions`) must still pass. + +### Browser checks (agent-browser, dev server :8080) +1. Log in as team-A manager → `http://localhost:8080/dashboard/manage/events` → assert sidebar + renders (team switcher shows team A name; nav items Events enabled, Venues/Hosts/Templates/ + Team Settings disabled) and list shows exactly team A's seeded events. +2. Team switcher → select team X → assert events list swaps without full reload (URL unchanged). +3. "New Event" → fill title + start date → submit → toast appears, row appears; assert via Desk + API that the doc's `team` == active team. +4. Log in as user with no teams → `/dashboard/manage` → assert redirect to onboarding screen; + create team → lands on empty events list with CTA. +5. Log in as team-B manager → paste team-A workspace URL → assert 403 page (not blank/error). +6. Regression: `/dashboard/account/bookings` renders; public `/dashboard/book-tickets/` + booking form loads. +7. Mobile viewport (390px): shell switches to `MobileShell`; nav reachable; list usable. + +## Dependencies & risks + +- Step-0 gate (frappe-ui v1 beta) is a hard blocker. +- Team switcher cache invalidation: central `invalidateManagerData()` helper in `teams.ts` from + day 1, or stale cross-team data will leak into the UI (cosmetic, not security). diff --git a/specs/v2/01-dashboard/02-event-workspace-and-ticket-types.md b/specs/v2/01-dashboard/02-event-workspace-and-ticket-types.md new file mode 100644 index 00000000..af9e4ebf --- /dev/null +++ b/specs/v2/01-dashboard/02-event-workspace-and-ticket-types.md @@ -0,0 +1,102 @@ +# 02 — Event workspace & ticket types + +| Phase | Depends on | Status | +|---|---|---| +| A | 01-dashboard/01 | Draft | + +## Goal + +Complete the tracer bullet: open an event into a tabbed workspace and manage `Event Ticket Type` +CRUD without Desk — the first full manage-to-public loop. + +## Demo criteria (definition of done) + +Manager opens their event → Tickets tab → creates "Early Bird ₹499, 100 available" → opens the +public `/dashboard/book-tickets/:route` page → the new ticket type is purchasable. A manager from +another team opening this event's URL gets 403. + +## Scope + +### In +- Event workspace frame with full tab registry (only Overview-stub + Tickets enabled) +- Ticket types table + create/edit dialog + publish toggle + delete +### Out (deferred to) +- Real Overview content → step 03; every other tab → its step + +## Backend changes + +**None.** Plain `frappe.client` CRUD through `useList`/`useDoc`, protected by role perms + team +hooks. The spec asserts this deliberately: if a bespoke endpoint proves necessary for basic CRUD, +that's a smell in the permission layer — fix `00-teams/03` instead. + +## Frontend changes + +### Routes + +| Path | Name | Meta | +|---|---|---| +| `/manage/events/:eventId` | `EventWorkspace` (redirects to `overview`) | `requiresTeam` | +| `/manage/events/:eventId/:tab` | child views | `requiresTeam` | + +### Components + +- `src/pages/manage/EventWorkspace.vue` — header: `Breadcrumbs` (Events / {title}), status `Badge`, + "View page" `Button variant="ghost"` (→ public route, new tab). `Tabs` bound to the route param; + full registry from `00-plan.md` tab map, tabs whose step hasn't landed render disabled with a + "soon" tooltip. Loads the event once via `useDoc("Buzz Event", eventId)` and provides it to tabs. +- `src/pages/manage/event/OverviewTab.vue` — stub: event title/date summary card only. +- `src/pages/manage/event/TicketTypesTab.vue` — `List`: title, price (`tabular-nums`, formatted + via `format_currency`), sold/available (`tickets_sold` / `max_tickets_available`), remaining, + published `Badge`; row actions `Dropdown`: Edit, Publish/Unpublish, Delete (imperative + `dialog.confirm` → `toast`). Header `Button` "New Ticket Type". +- `TicketTypeDialog.vue` — `Dialog v-model:open`; `FormControl`s mapped to real fields: + `title` (reqd), `price` + `currency` (Link Currency), `max_tickets_available` (Int), + `auto_unpublish_after` (DatePicker), `is_published` (Switch). Create + edit modes share it. + +### Data layer + +- `src/data/tickets.ts` — `useList({ doctype: "Event Ticket Type", filters: { event } })` with + insert/update/delete helpers. + +## API contract + +No new endpoints. + +## Permissions notes + +- Manager/Admin/Owner can CRUD; Viewer sees the tab read-only (hide mutating buttons by + `team_role` from `activeTeam`); server denies regardless. +- `tickets_sold` / `remaining_tickets` are system-maintained — read-only in the dialog. + +## Demo script + +1. `/manage/events` → click event → workspace opens on Overview stub, Tickets tab enabled. +2. New Ticket Type: "Early Bird", 499 INR, 100 available, published → row appears, toast. +3. Public booking page for the event → Early Bird listed and bookable (complete a test booking). +4. Unpublish it → gone from public page; sold counters unaffected. +5. As bob (team B): open the same workspace URL → 403 page. + +## Acceptance criteria (merge gate) + +### Automated tests +None new (step asserts zero bespoke endpoints). `test_team_permissions` parametrized case for +`Event Ticket Type` covers the CRUD authorization; extend it if the tracer bullet exposes gaps. + +### Browser checks (agent-browser, dev server :8080) +1. Manager → open event → assert workspace header (breadcrumbs, status badge) + tab strip with + Tickets enabled and roadmap tabs disabled. +2. Create ticket type "Early Bird", 499 INR, 100 available, published → row appears with correct + formatted price and 0/100 sold. +3. Public page `/dashboard/book-tickets/` (fresh session) → "Early Bird" listed; complete + a booking → Tickets tab shows sold count 1. +4. Unpublish via row action → public page no longer offers it. +5. Edit dialog: `tickets_sold`/`remaining_tickets` rendered read-only. +6. Viewer-role member → Tickets tab visible, no New/Edit/Delete controls; direct mutation via + console `useList.insert` equivalent (REST call) → server 403. +7. Team-B manager → workspace URL → 403 page. + +## Dependencies & risks + +- Delete of a ticket type with sold tickets: framework link validation will throw — + surface the server message via `toast.error`, don't pre-empt client-side. +- **Tracer bullet exit criteria:** after this step, architecture review before Phase B fan-out. diff --git a/specs/v2/01-dashboard/03-event-overview-and-editing.md b/specs/v2/01-dashboard/03-event-overview-and-editing.md new file mode 100644 index 00000000..cb25d3f6 --- /dev/null +++ b/specs/v2/01-dashboard/03-event-overview-and-editing.md @@ -0,0 +1,96 @@ +# 03 — Event overview & editing + +| Phase | Depends on | Status | +|---|---|---| +| B | 01-dashboard/02 | Draft | + +## Goal + +The Overview tab becomes the event's home: KPI strip + editable core fields + publish control — +day-to-day event setup no longer needs Desk. + +## Demo criteria (definition of done) + +Manager edits title, dates, venue, and banner from Overview; the public event page reflects it. +KPI numbers match Desk counts. Publish/unpublish from the header works. + +## Scope + +### In +- KPI strip (bookings, tickets sold, gross revenue, check-ins) +- Editable core `Buzz Event` fields + banner upload + publish/unpublish + danger zone +### Out (deferred to) +- Sponsor/email/tax/gateway settings → step 12; schedule → step 10; featured speakers → Phase E + +## Backend changes + +`buzz/api/events.py` (new module): + +```python +@frappe.whitelist() +def get_event_stats(event: str) -> dict: + """{bookings, tickets_sold, gross_revenue, currency, checked_in} + Aggregates via frappe.qb (Count/Sum) over Event Booking (docstatus 1), + Event Ticket, Event Payment (payment_received=1), Event Check In. + Permission: frappe.has_permission("Buzz Event", "read", event) explicit check. + """ +``` + +## Frontend changes + +- `OverviewTab.vue` (replaces stub): + - KPI strip — dashboard archetype: 4 stats in a `divide-x divide-outline-gray-2` row, values + `text-2xl font-semibold tabular-nums`, labels `text-sm text-ink-gray-5`; `useCall` to + `get_event_stats`. + - Details form — `useDoc("Buzz Event", eventId)` bound `FormControl`s: `title`, + `short_description`, `about` (`Editor` from `frappe-ui/editor`), `start_date`/`end_date` + + `start_time`/`end_time`, `time_zone` (Combobox), `medium` Select, `venue` (Link → Event Venue, + team-scoped by hooks), `category`, `host` (Link → Event Host), `route` (slug input with + prefix hint), `banner_image` + `card_image` + `meta_image` via `FileUploader`. + Save via `doc.save.submit()` on a sticky footer `Button` (dirty-state aware). + - Header publish control: `is_published` toggle `Button` (solid green "Publish" / subtle + "Unpublish") with `dialog.confirm` when unpublishing an event with sold tickets. + - Danger zone card: cancel event (writes status; framework delete stays Desk-only). +- `data/events.ts`: add `useEventDoc(eventId)` + `useEventStats(eventId)`. + +## API contract + +| Endpoint | Method | Params | Returns | Auth | +|---|---|---|---|---| +| `buzz.api.events.get_event_stats` | GET | `event` | `{bookings, tickets_sold, gross_revenue, currency, checked_in}` | read perm on event (team member) | + +## Permissions notes + +Viewer: form read-only (disable inputs by `team_role`), stats visible. Write path enforced +server-side by team hooks. + +## Demo script + +1. Overview shows KPIs; cross-check numbers in Desk report views. +2. Change title + upload banner → save → public event page shows both. +3. Unpublish → public page 404s/hides; publish again. +4. Viewer-role member sees stats + disabled form. + +## Acceptance criteria (merge gate) + +### Automated tests — `buzz/tests/test_event_stats.py` (new) +`bench --site buzz.localhost run-tests --module buzz.tests.test_event_stats` +- Fixture: event with 2 bookings (one paid, one pending), 3 tickets, 1 check-in → + `get_event_stats` returns exact counts; gross revenue sums only `payment_received=1`. +- Zero-activity event → all zeros, no division/None errors. +- Cross-team caller → `PermissionError`. + +### Browser checks (agent-browser, dev server :8080) +1. Overview tab → KPI strip values match the fixture counts. +2. Edit title + upload banner → save → public event page shows new title + banner image request + 200. +3. Unpublish (event with sold tickets) → confirm dialog appears → public page hides/404s the + event; publish again → restored. +4. Viewer-role member → form inputs disabled, KPIs visible, save button absent. +5. Dirty-state: edit a field, navigate tabs → unsaved-changes guard (or explicit sticky save + still shows dirty state). + +## Dependencies & risks + +- Revenue definition: sum of `Event Payment.amount` where `payment_received=1` — document the + formula in the endpoint docstring; taxes make "gross" ambiguous (note for spec 12). diff --git a/specs/v2/01-dashboard/04-coupons.md b/specs/v2/01-dashboard/04-coupons.md new file mode 100644 index 00000000..f42ce6c5 --- /dev/null +++ b/specs/v2/01-dashboard/04-coupons.md @@ -0,0 +1,78 @@ +# 04 — Coupons + +| Phase | Depends on | Status | +|---|---|---| +| B | 01-dashboard/02 | Draft | + +## Goal + +`Buzz Coupon Code` management in the event workspace — create, edit, activate/deactivate, monitor +usage. + +## Demo criteria (definition of done) + +Manager creates a 10%-off coupon; attendee applies it at public checkout (existing +`validate_coupon`) and sees the discounted total; the Coupons tab shows `times_used` tick up. + +## Scope + +### In +- Coupons tab: table + create/edit dialog covering the discount path +- Free-tickets coupon type (`coupon_type`, `number_of_free_tickets`) +### Out (deferred to) +- `free_add_ons` child rows in the dialog → step 05 (needs add-ons UI patterns); until then that + child table stays Desk-editable +- Cross-event coupons (`applies_to`/`event_category` scope) → render read-only note if set + +## Backend changes + +None — plain CRUD under team hooks (coupon has `event` link; team stamped via cascade). + +## Frontend changes + +- `src/pages/manage/event/CouponsTab.vue` — `List`: `code` (mono), type (discount/free-tickets + `Badge`), value (`discount_value` + `discount_type` %/flat, or `number_of_free_tickets`), + usage `times_used`/`max_usage_count`, validity `valid_from`–`valid_till`, `is_active` Switch + inline. Header "New Coupon". +- `CouponDialog.vue` — `FormControl`s on real fields: `code` (reqd, uppercase), `coupon_type` + Select, then conditional: discount → `discount_type`, `discount_value`, + `maximum_discount_amount`, `minimum_order_value`; free tickets → `number_of_free_tickets`, + `ticket_type` (Link, filtered to this event). Common: `valid_from`/`valid_till`, + `max_usage_count`, `max_usage_per_user`, `is_active`. +- `data/coupons.ts` — `useList({ doctype: "Buzz Coupon Code", filters: { event } })`. + +## API contract + +No new endpoints (checkout reuses existing `buzz.api.validate_coupon`). + +## Permissions notes + +Manager+ CRUD; Viewer read-only. Code uniqueness errors surfaced from server via `toast.error`. + +## Demo script + +1. Create `LAUNCH10`, 10% off, max 100 uses, active. +2. Public checkout → apply `LAUNCH10` → total drops 10%. +3. Complete booking → tab shows `times_used = 1`. +4. Toggle inactive → checkout rejects the code. + +## Acceptance criteria (merge gate) + +### Automated tests +Extend existing coupon tests (or add `buzz/tests/test_coupons.py`) only for gaps exposed: +- `validate_coupon` respects `is_active=0`, `valid_till` past, `max_usage_count` reached, + `minimum_order_value` unmet (skip cases already covered by existing suite — audit first). +- Team cascade: coupon created via dashboard carries the event's team (covered by + `test_team_cascade`; assert once here if not parametrized). + +### Browser checks (agent-browser, dev server :8080) +1. Create coupon `LAUNCH10` (10%, active) → row appears with usage 0/100. +2. Fresh session → public checkout → apply `LAUNCH10` → total reduced 10%; complete booking. +3. Coupons tab → `times_used` shows 1. +4. Toggle `is_active` off inline → checkout rejects the code with visible error message. +5. Create dialog: switching `coupon_type` swaps discount vs free-tickets field groups. +6. Duplicate code creation → server error surfaced as toast, dialog stays open. + +## Dependencies & risks + +- Coupon validation logic stays server-side in `validate_coupon` — the dialog never duplicates it. diff --git a/specs/v2/01-dashboard/05-ticket-add-ons.md b/specs/v2/01-dashboard/05-ticket-add-ons.md new file mode 100644 index 00000000..00f8aa38 --- /dev/null +++ b/specs/v2/01-dashboard/05-ticket-add-ons.md @@ -0,0 +1,72 @@ +# 05 — Ticket add-ons + +| Phase | Depends on | Status | +|---|---|---| +| B | 01-dashboard/02 | Draft | + +## Goal + +`Ticket Add-on` CRUD in the workspace, including option lists (e.g. T-shirt sizes). + +## Demo criteria (definition of done) + +Manager adds "T-shirt (S/M/L), ₹299"; the public booking flow offers it with the size selector; +an attendee's selection lands in `Attendee Ticket Add-on`. + +## Scope + +### In +- Add-ons tab: table + create/edit dialog with options editor, enable/disable +- Coupons dialog follow-up: `free_add_ons` child rows editor (deferred from step 04) +### Out (deferred to) +- Attendee-side add-on change flow (already exists) — untouched + +## Backend changes + +None. + +## Frontend changes + +- `src/pages/manage/event/AddOnsTab.vue` — `List`: `title`, price (`format_currency`), options + summary (chips), `enabled` Switch inline. +- `AddOnDialog.vue` — real fields: `title` (reqd), `price` + `currency`, `description`, + `user_selects_option` (Switch) revealing an **options editor**: tag-style input writing + newline-separated values into the `options` Small Text field (matches current backend parsing), + `enabled`. +- Coupons: extend `CouponDialog.vue` with `free_add_ons` child-table editor (add-on Link + + option) now that add-on selectors exist. +- `data/addons.ts`. + +## API contract + +No new endpoints. + +## Permissions notes + +Manager+ CRUD; Viewer read-only. + +## Demo script + +1. Create "T-shirt", ₹299, options S/M/L, user_selects_option on, enabled. +2. Public booking → add-on appears with size Select; book with "M". +3. Desk: `Attendee Ticket Add-on` row records the selection. +4. Disable the add-on → hidden from new bookings. + +## Acceptance criteria (merge gate) + +### Automated tests — `buzz/tests/test_add_ons.py` (new or extend existing) +- Options round-trip: save "S\nM\nL" via the editor payload → stored `options` string is exactly + newline-delimited with no trailing newline; booking-side parser yields `["S","M","L"]`. +- Disabled add-on excluded from `get_event_booking_data` payload. + +### Browser checks (agent-browser, dev server :8080) +1. Create "T-shirt" ₹299, options S/M/L via tag editor, enabled → row shows option chips. +2. Public booking flow → add-on card with size `Select` (3 options) → book with "M". +3. Assert `Attendee Ticket Add-on` records "M" (Desk/REST check). +4. Toggle `enabled` off → new booking session no longer shows the add-on. +5. Coupon dialog now offers `free_add_ons` rows referencing the add-on. + +## Dependencies & risks + +- `options` is newline-delimited Small Text — the editor must round-trip exactly (no trailing + newline drift) or attendee selects break. diff --git a/specs/v2/01-dashboard/06-sponsorship-tiers-and-sponsors.md b/specs/v2/01-dashboard/06-sponsorship-tiers-and-sponsors.md new file mode 100644 index 00000000..ccf8136e --- /dev/null +++ b/specs/v2/01-dashboard/06-sponsorship-tiers-and-sponsors.md @@ -0,0 +1,77 @@ +# 06 — Sponsorship tiers & sponsors + +| Phase | Depends on | Status | +|---|---|---| +| B | 01-dashboard/02 | Draft | + +## Goal + +Sponsors tab: manage `Sponsorship Tier` and `Event Sponsor`, and see incoming +`Sponsorship Enquiry` records — the sponsorship pipeline visible where the event lives. + +## Demo criteria (definition of done) + +Manager creates a "Gold ₹50k" tier and adds a sponsor with logo; both render on the public event +page's sponsors section. Incoming enquiries appear read-only with status. + +## Scope + +### In +- Tiers CRUD (`title`, `price`, `currency`) +- Sponsors CRUD grouped by tier (`company_name`, `tier`, `company_logo`, `website`, `country`) +- Enquiries list (read-only) with status badges +### Out (deferred to) +- Enquiry approval/payment-link workflow (exists via attendee flow + Desk) — surfacing actions + here is a follow-up once payments v2 lands (future payments pillar) +- Pitch-deck email settings → step 12 + +## Backend changes + +None. + +## Frontend changes + +- `src/pages/manage/event/SponsorsTab.vue` — three sections: + 1. **Tiers** — compact `List` (title, price) + `TierDialog` (3 fields). + 2. **Sponsors** — grouped by tier; each sponsor: `Avatar`-style logo, company_name, website + link; `SponsorDialog`: `company_name` (reqd), `tier` (Select from this event's tiers), + `company_logo` `FileUploader`, `website`, `country` (Link). + 3. **Enquiries** — read-only `List` of `Sponsorship Enquiry` for the event: company, tier, + status `Badge`, created; row → slide-over detail (`Dialog` right placement) showing the + enquiry; no mutations. +- `data/sponsors.ts` — three `useList`s keyed by event. + +## API contract + +No new endpoints. + +## Permissions notes + +Manager+ CRUD tiers/sponsors; enquiries read-only for all team roles except Frontdesk. +Existing attendee-side enquiry endpoints untouched. + +## Demo script + +1. Create tier "Gold" ₹50,000. +2. Add sponsor "Acme" with logo under Gold → public event page sponsors section shows logo. +3. Submit a sponsorship enquiry from the public flow → appears in Enquiries with "Pending Approval". + +## Acceptance criteria (merge gate) + +### Automated tests +None new — CRUD under hooks; `test_team_cascade`/`test_team_permissions` parametrized cases cover +`Sponsorship Tier`/`Event Sponsor`/`Sponsorship Enquiry`. Existing enquiry-dedup test +(fix `8eba8cb`) must stay green. + +### Browser checks (agent-browser, dev server :8080) +1. Create tier "Gold" ₹50,000 → appears in Tiers section. +2. Add sponsor "Acme" (logo upload) under Gold → sponsors section groups it under Gold with logo. +3. Public event page → sponsors section renders Acme logo (image request 200). +4. Fresh session → submit sponsorship enquiry via public flow → Enquiries section shows it with + "Pending Approval" badge; slide-over opens read-only. +5. Frontdesk-role member → Sponsors tab hidden/inaccessible. + +## Dependencies & risks + +- `Event Sponsor.enquiry` dedup validation (recent fix `8eba8cb`) lives server-side — dialog just + surfaces errors. diff --git a/specs/v2/01-dashboard/07-bookings-and-attendees.md b/specs/v2/01-dashboard/07-bookings-and-attendees.md new file mode 100644 index 00000000..3a08c6de --- /dev/null +++ b/specs/v2/01-dashboard/07-bookings-and-attendees.md @@ -0,0 +1,100 @@ +# 07 — Bookings & attendees + +| Phase | Depends on | Status | +|---|---|---| +| C | 01-dashboard/03 | Draft | + +## Goal + +See the money and the people: Bookings tab (payments, offline confirmation) and Attendees tab +(search, filter, export). + +## Demo criteria (definition of done) + +A test booking completed publicly appears in the Bookings tab; manager confirms an offline +payment; finds an attendee by name in Attendees and exports CSV. + +## Scope + +### In +- Bookings tab: list + detail panel + confirm-offline-payment action +- Attendees tab: merged ticket/attendee list, search, filters, CSV export +### Out (deferred to) +- Refunds (needs future payments pillar); cancellation-request approval queue → Phase E +- Manual check-in from attendee rows → step 08 + +## Backend changes + +`buzz/api/bookings.py` (new): + +```python +@frappe.whitelist() +def confirm_offline_payment(booking: str) -> dict: + """Marks the booking's Event Payment as received + submits booking + (mirrors current Desk flow on Event Payment / Event Booking). + Guard: write perm on the booking (team Manager+).""" + +@frappe.whitelist() +def export_attendees(event: str) -> str: + """CSV string of attendees (name, email, ticket type, add-ons, checked-in, booking id). + Built with frappe.get_all joins — one query, no N+1. Read perm on event.""" +``` + +## Frontend changes + +- `src/pages/manage/event/BookingsTab.vue` — `List`: booking id, booker (name/email), amount + (`tabular-nums`), payment status `Badge` (Paid green / Pending orange / Failed red), gateway or + "Offline", created (`relative time`). Filter `Select` by payment status. Row → detail panel + (right slide-over `Dialog`): attendees, `Event Payment` info (gateway, id, amount, tax), + applied coupon, action `Button` "Mark payment received" (offline, unpaid only; + `dialog.confirm`). +- `src/pages/manage/event/AttendeesTab.vue` — `List` over `Event Ticket` joined attendee fields: + name, email, ticket type, add-ons summary, checked-in `Badge`. Debounced search `FormControl` + (name/email), filters: ticket type `Select`, checked-in `Select`. Header: count summary + + "Export CSV" `Button` → `export_attendees` → client download. +- `data/bookings.ts`, `data/attendees.ts`. + +## API contract + +| Endpoint | Method | Params | Returns | Auth | +|---|---|---|---|---| +| `buzz.api.bookings.confirm_offline_payment` | POST | `booking` | `{ok, payment_status}` | team Manager+ | +| `buzz.api.bookings.export_attendees` | GET | `event` | CSV text | team member (read) | + +## Permissions notes + +Bookings/tickets query conditions include the owner carve-out (00-teams/03) — manager lists here +are filtered by `event`, so only team data shows. Viewer read-only (no confirm button). + +## Demo script + +1. Complete a public test booking (offline method) → Bookings tab shows Pending. +2. "Mark payment received" → confirm → Badge flips Paid; attendee gets ticket email. +3. Attendees tab → search attendee name → row found; filter by ticket type. +4. Export CSV → opens with correct columns/rows. + +## Acceptance criteria (merge gate) + +### Automated tests — `buzz/tests/test_bookings_api.py` (new) +`bench --site buzz.localhost run-tests --module buzz.tests.test_bookings_api` +- `confirm_offline_payment`: pending offline booking → payment marked received, booking + submitted, ticket emails queued; already-paid booking raises; gateway (non-offline) booking + raises; Viewer-role caller raises `PermissionError`. +- `export_attendees`: CSV header + row count match fixture; add-ons column aggregates values; + cross-team caller raises; query count bounded (no N+1 — assert with `frappe.db` query counter + or `assertNumQueries`-style helper). + +### Browser checks (agent-browser, dev server :8080) +1. Fresh session → offline-method booking on the event → Bookings tab (manager session) shows + Pending badge. +2. Row → detail panel → "Mark payment received" → confirm → badge flips Paid without reload. +3. Attendees tab → search by attendee name → row filtered; filter by ticket type works. +4. "Export CSV" → file downloads; parse: header columns as specced, row for the new attendee. +5. Viewer-role member → no "Mark payment received" button; tabs render read-only. + +## Dependencies & risks + +- `confirm_offline_payment` must reuse the existing `mark_payment_as_received`/submit logic from + `buzz/payments.py` + booking controller — no duplicate state machine. +- Realtime: `useList` auto-refresh via socket (`refetch_resource` pattern) is a nice-to-have; + manual refresh button is the fallback. diff --git a/specs/v2/01-dashboard/08-checkin-desk.md b/specs/v2/01-dashboard/08-checkin-desk.md new file mode 100644 index 00000000..56d402b4 --- /dev/null +++ b/specs/v2/01-dashboard/08-checkin-desk.md @@ -0,0 +1,100 @@ +# 08 — Check-in desk + +| Phase | Depends on | Status | +|---|---|---| +| C | 01-dashboard/07 | Draft | + +## Goal + +Check-in surfaced in the event workspace with live stats and manual check-in; Frontdesk-role +members get a focused, check-in-only experience. + +## Demo criteria (definition of done) + +Scan (or paste) a ticket → checked in, stats tick up, recent-check-ins feed updates. Manual +check-in from an attendee row works. A `Frontdesk`-role member logs in and sees only the check-in +surface. + +## Scope + +### In +- Check-in tab: stats, embedded scanner, recent check-ins feed, manual check-in +- Frontdesk-role navigation trimming +### Out (deferred to) +- Ticket-tear animation / confetti delight pass → future "check-in redesign" pillar +- Multi-day/date-wise check-in config UI → Phase E + +## Backend changes + +`buzz/api/events.py` addition: + +```python +@frappe.whitelist() +def get_checkin_stats(event: str) -> dict: + """{total_tickets, checked_in, by_ticket_type: [{ticket_type, total, checked_in}]}""" +``` + +`checkin_ticket` / `validate_ticket_for_checkin` (existing) gain the team-membership guard from +`00-teams/03` — no signature change. + +## Frontend changes + +- `src/pages/manage/event/CheckInTab.vue`: + - Stats strip: checked-in/total overall + per ticket type (progress bars, + `bg-surface-gray-2` track / `bg-surface-gray-7` fill). + - Embedded scanner: reuse `CheckInScanner.vue` internals (html5-qrcode + existing + `validate_ticket_for_checkin` → `checkin_ticket` flow) refactored into a + `components/checkin/ScannerPanel.vue` shared by both surfaces. + - Recent check-ins feed: last 20 `Event Check In` rows (attendee, ticket type, time), socket or + poll refresh. + - Manual check-in: search attendee (reuses Attendees data) → `Button` "Check in" per row. +- Attendees tab (step 07): row action "Check in" appears for roles with check-in capability. +- Frontdesk navigation: `ManagerLayout` reads `team_role`; `Frontdesk` sees sidebar with Events → + event list rows deep-link straight to `/manage/events/:id/check-in`; other tabs hidden. +- Existing standalone `/check-in/:eventName` route kept (redirects to the workspace tab when the + user has team access). + +## API contract + +| Endpoint | Method | Params | Returns | Auth | +|---|---|---|---|---| +| `buzz.api.events.get_checkin_stats` | GET | `event` | stats dict | team member w/ check-in capability | + +## Permissions notes + +Check-in capability: Owner/Admin/Manager/Frontdesk (matrix in `00-teams/00-plan.md`). Global +`Frontdesk Manager` Frappe role still required by the existing endpoints — granted automatically +by membership role sync. + +## Demo script + +1. Open Check-in tab → stats show 0/N. +2. Paste a valid ticket id in scanner input → success state, stats 1/N, feed shows the entry. +3. Duplicate scan → "already checked in" warning state. +4. Manual check-in from attendee search → works. +5. Log in as frontdesk-role user → sidebar trimmed; can check in; cannot open Tickets tab. + +## Acceptance criteria (merge gate) + +### Automated tests — `buzz/tests/test_checkin.py` (new or extend existing check-in tests) +`bench --site buzz.localhost run-tests --module buzz.tests.test_checkin` +- `get_checkin_stats`: fixture with 3 tickets / 1 checked-in → totals + per-ticket-type breakdown + exact; cross-team caller raises. +- `checkin_ticket` team guard: frontdesk member of team A checking team B's ticket raises; + same-team succeeds; duplicate check-in returns the already-checked-in response (existing + behavior preserved). + +### Browser checks (agent-browser, dev server :8080) +1. Check-in tab → stats strip shows 0/N; paste valid ticket ID in manual input → success state, + stats tick to 1/N, feed shows the entry. +2. Same ticket again → "already checked in" warning state (no double record). +3. Attendees tab → row "Check in" action → works, stats update. +4. Frontdesk-role member logs in → sidebar shows only Events; event row deep-links to the + check-in tab; Tickets tab route → blocked/hidden. +5. Standalone `/dashboard/check-in/` still functions (regression for mid-event usage). + +## Dependencies & risks + +- Camera permissions on mobile — scanner panel must degrade to manual input (already does today). +- Refactor of `CheckInScanner.vue` must not regress the standalone route mid-event; feature-flag + the redirect. diff --git a/specs/v2/01-dashboard/09-venues-hosts-library.md b/specs/v2/01-dashboard/09-venues-hosts-library.md new file mode 100644 index 00000000..d61f1431 --- /dev/null +++ b/specs/v2/01-dashboard/09-venues-hosts-library.md @@ -0,0 +1,78 @@ +# 09 — Venues & hosts library + +| Phase | Depends on | Status | +|---|---|---| +| D | 01-dashboard/01 | Draft | + +## Goal + +Team-level reusable resources get a sidebar home: `/manage/venues` and `/manage/hosts` — the +"Library" section. Unblocks fully-Desk-free event creation (Overview's venue/host Links need +somewhere to create entries). + +## Demo criteria (definition of done) + +Manager creates a venue and a host from the library; both are selectable in the event Overview +form; host logo renders on the public page. + +## Scope + +### In +- Venues page: CRUD on `Event Venue` (team-scoped) +- Hosts page: CRUD on `Event Host` incl. `social_media_links` child rows +### Out (deferred to) +- `Event Category` management — global taxonomy, stays Desk/System Manager +- Public host profile pages → future "public event frontend" pillar + +## Backend changes + +None (team field + hooks from 00-teams cover both doctypes). + +## Frontend changes + +- Sidebar: enable "Venues" (`lucide-map-pin`) and "Hosts" (`lucide-users`) under a "Library" + `SidebarGroup`. +- `src/pages/manage/VenuesList.vue` — `List`: name, `type` Badge (per Select options), `address` + excerpt. `VenueDialog`: name (doc name/title), `type` Select, `address` Textarea, + `google_maps_embed_code` Code textarea (collapsed "Advanced" section), `latitude`/`longitude`. +- `src/pages/manage/HostsList.vue` — card grid (logo `Avatar`, name, `by_line`). `HostDialog`: + name, `logo` FileUploader, `by_line`, `about` (`Editor`), `country`, `address`, + `social_media_links` child editor (platform Select + URL rows). +- `data/venues.ts`, `data/hosts.ts`. + +## API contract + +No new endpoints. + +## Permissions notes + +Manager+ CRUD; Viewer read-only. Link fields in Overview (step 03) automatically list only the +team's venues/hosts thanks to query conditions applying to link searches. + +## Demo script + +1. `/manage/venues` → create "Community Hall", type + address → appears. +2. `/manage/hosts` → create host with logo + 2 social links. +3. Event Overview → venue/host selectable; save → public page shows host block. +4. Other team's manager sees neither entry in their library or link searches. + +## Acceptance criteria (merge gate) + +### Automated tests +None new — team-direct doctypes covered by `test_team_permissions` parametrized cases +(`Event Venue`, `Event Host` list isolation + role matrix). + +### Browser checks (agent-browser, dev server :8080) +1. `/dashboard/manage/venues` → create "Community Hall" (type, address) → row appears. +2. `/dashboard/manage/hosts` → create host with logo + 2 social links → card renders logo. +3. Event Overview → venue Link search returns "Community Hall"; host Link returns the new host; + save → public page shows host block. +4. Team-B manager session → venues/hosts lists empty of team A's entries; Overview Link search + does not surface them (link-query scoping check). +5. Viewer-role member → library pages read-only (no New button). + +## Dependencies & risks + +- `Event Venue` uses the document name as its title (no separate name field) — dialog maps the + name input accordingly (rename on edit uses `frappe.rename_doc` semantics; keep name immutable + in UI, edit only other fields, to avoid rename complexity). diff --git a/specs/v2/01-dashboard/10-schedule-tracks-and-talks.md b/specs/v2/01-dashboard/10-schedule-tracks-and-talks.md new file mode 100644 index 00000000..534d0135 --- /dev/null +++ b/specs/v2/01-dashboard/10-schedule-tracks-and-talks.md @@ -0,0 +1,101 @@ +# 10 — Schedule, tracks & talks + +| Phase | Depends on | Status | +|---|---|---| +| D | 01-dashboard/03 | Draft | + +## Goal + +Desk parity for the agenda: Schedule tab managing `Event Track`, `Schedule Item` (child table on +Buzz Event), `Event Talk`, plus a read-and-accept inbox for `Talk Proposal` submissions. +(CRUD parity only — the drag-drop visual builder remains a future pillar.) + +## Demo criteria (definition of done) + +Manager builds a 2-track half-day schedule; the public schedule rendering shows it. Accepting a +talk proposal creates an `Event Talk` that can be slotted. + +## Scope + +### In +- Tracks chips CRUD; schedule items list grouped by day → time; talk CRUD with speakers +- Proposals inbox: list + detail + Accept/Reject +### Out (deferred to) +- Gantt/timeline drag-drop builder → future "schedule builder" pillar +- Speaker Profile management (attendee-owned) → Phase E + +## Backend changes + +`buzz/api/talks.py` (new): + +```python +@frappe.whitelist() +def accept_talk_proposal(proposal: str) -> dict: + """Sets Talk Proposal status Approved; creates Event Talk (+ Talk Speaker child rows + from Proposal Speaker). Returns the new talk name. Manager+ on the event's team. + Reuses existing controller logic if present — audit talk_proposal.py first.""" +``` + +(If the accept transition already exists on the doctype controller, the endpoint is a thin wrapper.) + +## Frontend changes + +- `src/pages/manage/event/ScheduleTab.vue` — three stacked sections: + 1. **Tracks** — chip row (`Badge`-styled) + add/rename/delete (`dialog.prompt`). + 2. **Schedule** — `Schedule Item` rows grouped by date, sorted by start time: time range + (`tabular-nums`), title, track chip, linked talk. Row edit dialog: date, start/end time, + title, track Select, talk Link. Saved through the parent `Buzz Event` doc + (`useDoc` child-table update). + 3. **Talks** — `List` of `Event Talk`: title, speakers (Avatars), track, status. `TalkDialog`: + title, description (`Editor`), track, speakers child editor (Link → Speaker Profile). +- **Proposals inbox** — collapsible section: pending `Talk Proposal` rows (title, speaker, + submitted); detail slide-over with full submission; `Button` Accept (→ `accept_talk_proposal`, + toast + talk appears) / Reject (`dialog.confirm`, sets status). +- `data/schedule.ts`, `data/talks.ts`. + +## API contract + +| Endpoint | Method | Params | Returns | Auth | +|---|---|---|---|---| +| `buzz.api.talks.accept_talk_proposal` | POST | `proposal` | `{talk}` | team Manager+ | + +## Permissions notes + +`Talk Proposal` is attendee-submitted (owner = speaker) — team members read via team scoping +(cascade stamps team from event); speakers keep editing their own (existing +`allow_editing_talks_after_acceptance` behavior untouched). + +## Demo script + +1. Add tracks "Main Hall", "Workshop". +2. Create 4 schedule items across two dates → grouped rendering correct. +3. Submit a talk proposal via the public flow → appears in inbox. +4. Accept → `Event Talk` created with speakers; slot it into a schedule item. +5. Public schedule (where rendered) shows the agenda. + +## Acceptance criteria (merge gate) + +### Automated tests — `buzz/tests/test_talks.py` (new) +`bench --site buzz.localhost run-tests --module buzz.tests.test_talks` +- `accept_talk_proposal`: proposal with 2 `Proposal Speaker` rows → status Approved, `Event Talk` + created with matching speakers; second call raises (already accepted); Viewer/cross-team caller + raises. +- Reject path sets status without creating a talk. +- Speaker-side edit-after-acceptance behavior unchanged + (`allow_editing_talks_after_acceptance` on/off). + +### Browser checks (agent-browser, dev server :8080) +1. Schedule tab → add tracks "Main Hall", "Workshop" → chips render. +2. Create 4 schedule items across 2 dates → grouped by date, sorted by time + (assert order in DOM). +3. Fresh session → submit talk proposal via public flow → inbox shows it pending. +4. Accept from slide-over → toast, talk appears in Talks section with speakers; slot it into a + schedule item. +5. Public schedule rendering shows both tracks and the slotted talk. + +## Dependencies & risks + +- `Schedule Item` is a child table — mutations go through the parent event doc save; concurrent + edits overwrite (single-editor assumption fine for now, note it). +- Audit existing `talk_proposal.py` controller before writing the accept endpoint — logic may + already exist Desk-side. diff --git a/specs/v2/01-dashboard/11-event-templates.md b/specs/v2/01-dashboard/11-event-templates.md new file mode 100644 index 00000000..a11c7b7a --- /dev/null +++ b/specs/v2/01-dashboard/11-event-templates.md @@ -0,0 +1,92 @@ +# 11 — Event templates + +| Phase | Depends on | Status | +|---|---|---| +| D | 01-dashboard/02 | Draft | + +## Goal + +`Event Template` manageable from the dashboard and "New event from template" — repeat organizers +spin up configured events in one step. + +## Demo criteria (definition of done) + +Manager creates a template with 2 ticket types and 1 add-on; "New Event" dialog with that template +produces an event with those ticket types/add-ons pre-created. + +## Scope + +### In +- `/manage/templates` list + template editor (children: `template_ticket_types`, + `template_add_ons`, `template_custom_fields`) +- Template picker in the New Event dialog + instantiation endpoint +### Out (deferred to) +- Template sharing across teams — templates are team-scoped like everything else + +## Backend changes + +`buzz/api/templates.py` (new): + +```python +@frappe.whitelist() +def create_event_from_template(template: str, title: str, start_date: str, end_date: str | None = None) -> dict: + """Creates Buzz Event copying template fields (category, medium, banner_image, host, venue, + guest-booking config, tax config, email/deck config) + Event Ticket Type and Ticket Add-on + records from the template child tables. Team from active team; Manager+. + Audit event_template.py first — reuse existing instantiation logic if present.""" +``` + +## Frontend changes + +- Sidebar: enable "Templates" (`lucide-layout-template`). +- `src/pages/manage/TemplatesList.vue` — `List`: `template_name`, category, medium, counts of + child rows. +- `src/pages/manage/TemplateEditor.vue` (route `/manage/templates/:id`) — sectioned form mirroring + the doctype: basics (`template_name`, `category`, `medium`, `banner_image`, `host`, `venue`), + booking config (`allow_guest_booking`, `guest_verification_method`), content + (`short_description`, `about` Editor), tax (`apply_tax`, `tax_label`, `tax_percentage`), + emails (`send_ticket_email`, `ticket_email_template`, sponsor deck fields), children as + sectioned lists: ticket types (title/price/qty rows), add-ons, custom fields. +- `NewEventDialog` (step 01): optional "Start from template" `Select`; when set → calls + `create_event_from_template` instead of plain insert. +- `data/templates.ts`. + +## API contract + +| Endpoint | Method | Params | Returns | Auth | +|---|---|---|---|---| +| `buzz.api.templates.create_event_from_template` | POST | `template, title, start_date, end_date?` | `{event}` | team Manager+ | + +## Permissions notes + +Templates team-scoped (00-teams/02 team-direct doctype). Manager+ CRUD. + +## Demo script + +1. Create template "Monthly Meetup" with 2 ticket types + 1 add-on. +2. New Event → pick template, title "July Meetup", date → created. +3. Workspace Tickets tab shows both ticket types; Add-ons shows the add-on. +4. Public page bookable immediately after publish. + +## Acceptance criteria (merge gate) + +### Automated tests — `buzz/tests/test_event_templates.py` (new) +`bench --site buzz.localhost run-tests --module buzz.tests.test_event_templates` +- `create_event_from_template`: template with 2 ticket types + 1 add-on + 1 custom field → + event created copying scalar config (category, medium, tax, email settings) and children as + real `Event Ticket Type`/`Ticket Add-on`/`Buzz Custom Field` rows with `event` + `team` stamped. +- Cross-team template use raises; Viewer caller raises. +- Template with empty children → event still created cleanly. + +### Browser checks (agent-browser, dev server :8080) +1. `/dashboard/manage/templates` → create "Monthly Meetup" with 2 ticket types + 1 add-on in the + editor → counts show in list. +2. Events list → "New Event" → pick the template, title "July Meetup", date → created; workspace + Tickets tab shows both ticket types, Add-ons shows the add-on. +3. Publish → public page bookable with the template's ticket types. +4. Team-B manager → templates list does not contain team A's template. + +## Dependencies & risks + +- Instantiation copies must stamp `event` + let the team cascade stamp `team` — verify no + `ignore_permissions` needed (creator is Manager+). diff --git a/specs/v2/01-dashboard/12-event-settings-payments-and-forms.md b/specs/v2/01-dashboard/12-event-settings-payments-and-forms.md new file mode 100644 index 00000000..f6add761 --- /dev/null +++ b/specs/v2/01-dashboard/12-event-settings-payments-and-forms.md @@ -0,0 +1,99 @@ +# 12 — Event settings: payments, registration & forms + +| Phase | Depends on | Status | +|---|---|---| +| D | 01-dashboard/03 | Draft | + +## Goal + +The event Settings tab — everything else on the `Buzz Event` form: payment gateways, tax, +registration/guest-booking config, ticket email config, custom fields, custom forms, extra pages. +Completes single-event Desk parity. + +## Demo criteria (definition of done) + +Manager adds a payment gateway row and it appears at public checkout; sets +`registrations_close_at` in the past and public booking closes; adds a custom field and it shows +in the booking form. + +## Scope + +### In (grouped settings sections, all on `useDoc("Buzz Event")` unless noted) +- **Payments**: `payment_gateways` child rows (`Event Payment Gateway`: gateway Link + currency); + `Offline Payment Method` list for the event (separate doctype CRUD); `free_webinar` toggle +- **Tax**: `apply_tax`, `tax_label`, `tax_percentage`, `tax_inclusive` +- **Registration**: `registrations_close_at`, `allow_guest_booking`, + `guest_verification_method`, `external_registration_page` + `registration_url`, + `default_ticket_type`, `attach_calendar_invite` +- **Ticket email**: `send_ticket_email`, `ticket_email_template` (Link → Email Template), + `attach_email_ticket`, `ticket_print_format` +- **Sponsor deck**: `auto_send_pitch_deck`, `sponsor_deck_email_template`, + `sponsor_deck_reply_to`, `sponsor_deck_cc`, `sponsor_deck_attachments`, + `show_sponsorship_section` +- **Custom fields**: `Buzz Custom Field` CRUD for this event (label, type, options, required) +- **Forms & pages**: `custom_forms` child rows (`Buzz Event Form`) linking custom forms; + `Additional Event Page` CRUD + +### Out (deferred to) +- Team-level defaults (transfer/cancel/add-on windows, default email templates, sponsor-deck + defaults) — live in `Buzz Team Settings` (`00-teams/05`), surfaced in the Team Settings dialog + (step 13), not here. `Buzz Settings` (site-wide Single) stays Desk/System Manager for global + admin config (login banner, proposal funnel) +- Email Template authoring UI — Link picker only; authoring stays Desk (Phase E) + +## Backend changes + +None expected — all doc/child CRUD under hooks. If offline-method or custom-field creation needs +validation beyond doctype controllers, add to existing controllers, not new endpoints. + +## Frontend changes + +- `src/pages/manage/event/SettingsTab.vue` — settings-page archetype: left anchor nav + (sections above), right content cards; each card = `FormControl` group bound to `useDoc`, + sticky save. Child-table editors reuse the row-editor pattern from steps 05/11. +- `data/eventSettings.ts` (offline methods, custom fields `useList`s). + +## API contract + +No new endpoints. + +## Permissions notes + +Manager+ writes; Viewer read-only. Gateway credentials themselves live in the gateway apps' +settings (System Manager) — this tab only *selects* gateways, never edits credentials. + +## Demo script + +1. Add Razorpay gateway row (INR) → public checkout offers it. +2. Enable tax 18% "GST" → checkout total shows tax line. +3. Set `registrations_close_at` past → public page shows "registrations closed"; clear it → open. +4. Add custom field "Company" (required) → booking form renders it; value lands in + `Additional Field` rows. +5. Toggle `allow_guest_booking` off → guest checkout demands login. + +## Acceptance criteria (merge gate) + +### Automated tests — extend booking-flow tests (`buzz/tests/test_booking_flow.py` or existing module) +- `registrations_close_at` in the past → `get_event_booking_data`/`process_booking` reject with + the closed-registrations error; cleared → accepted. +- `allow_guest_booking=0` → guest `process_booking` raises; logged-in booking succeeds. +- Tax config: `apply_tax` 18% → booking totals include tax line (inclusive + exclusive branches). +- Required `Buzz Custom Field` missing in submission → validation error; provided → stored in + `Additional Field` rows. + +### Browser checks (agent-browser, dev server :8080) +1. Settings tab → add Razorpay gateway row (INR) → fresh session checkout offers Razorpay. +2. Enable tax 18% "GST" → checkout shows tax line, math correct on screen. +3. Set `registrations_close_at` past → public page shows registrations-closed state; clear → + bookable again. +4. Add required custom field "Company" → booking form renders it; submitting without it blocks + with field error; with it → booking completes. +5. Toggle `allow_guest_booking` off → guest checkout demands login. +6. Anchor nav: each settings section scrolls/activates correctly; sticky save reflects dirty + state per section. + +## Dependencies & risks + +- This tab touches many existing public-flow behaviors — regression pass over the booking flow is + part of the demo, not optional. +- `sponsor_deck_attachments` child (files) needs `FileUploader` multi-attach handling. diff --git a/specs/v2/01-dashboard/13-team-settings-members-and-invites.md b/specs/v2/01-dashboard/13-team-settings-members-and-invites.md new file mode 100644 index 00000000..a1ccfa52 --- /dev/null +++ b/specs/v2/01-dashboard/13-team-settings-members-and-invites.md @@ -0,0 +1,117 @@ +# 13 — Team settings, members & invites + +| Phase | Depends on | Status | +|---|---|---| +| D | 00-teams/04, 00-teams/05, 01-dashboard/01 | Draft | + +## Goal + +Team administration UI: edit team profile, manage members and roles, invite by email, see/revoke +pending invitations. + +## Demo criteria (definition of done) + +Owner invites a colleague as Frontdesk from the UI; colleague accepts, appears in Members, logs in +and sees only the check-in surface. Owner changes a member's role and removes another. + +## Scope + +### In +- Team settings dialog (settings-dialog archetype): General, Members, Invites sections +- Role change / remove member / leave team +### Out (deferred to) +- Ownership transfer flow — explicit future step (Owner role not assignable via invite/role change) +- Billing/plans — future pillar + +## Backend changes + +`buzz/api/teams.py` additions: + +```python +@frappe.whitelist() +def get_team_members(team) -> list # Active memberships + user full_name, avatar; member-visible + +@frappe.whitelist() +def update_member_role(team, user, team_role) # Owner/Admin; cannot touch Owner rows; not to Owner + +@frappe.whitelist() +def remove_member(team, user) # Owner/Admin; sets membership Disabled; cannot remove last Owner + +@frappe.whitelist() +def update_team(team, team_name=None, logo=None) # Owner only + +@frappe.whitelist() +def leave_team(team) # any member except last Owner +``` + +Role revocation on remove/demote goes through `buzz.teams.sync_frappe_roles(user)` (recompute +across remaining memberships). + +## Frontend changes + +- Sidebar: enable "Team Settings" (`lucide-settings`), opens `TeamSettingsDialog.vue` + (settings-dialog archetype: left nav list, right content pane, `Dialog` size xl): + - **General** — team name `FormControl`, logo `FileUploader`, slug (read-only display), + danger zone: Leave team (`dialog.confirm`). + - **Defaults** — `Buzz Team Settings` (00-teams/05) bound via + `useDoc("Buzz Team Settings", team)`: transfer/add-on/cancellation window Ints, support + email, `default_ticket_email_template` + sponsor-deck defaults (Email Template Links, + reply-to, cc, `auto_send_pitch_deck` Switch). Owner/Admin editable; others read-only. + - **Members** — `List`: `Avatar`, full name, email, `team_role` — inline `Select` editable by + Owner/Admin (disabled on Owner rows), remove action (`dialog.confirm`). + - **Invites** — "Invite member" row: email `FormControl` + role `Select` + (Admin/Manager/Frontdesk/Viewer) + `Button` "Send invite" → `invite_member`; pending list + (email, role, sent time) with Revoke. +- `data/teams.ts` additions: members/invites `useList`-style calls + mutations. + +## API contract + +| Endpoint | Method | Params | Returns | Auth | +|---|---|---|---|---| +| `buzz.api.teams.get_team_members` | GET | `team` | `[{user, full_name, user_image, team_role, status}]` | team member | +| `buzz.api.teams.update_member_role` | POST | `team, user, team_role` | `{ok}` | Owner/Admin | +| `buzz.api.teams.remove_member` | POST | `team, user` | `{ok}` | Owner/Admin | +| `buzz.api.teams.update_team` | POST | `team, team_name?, logo?` | `{ok}` | Owner | +| `buzz.api.teams.leave_team` | POST | `team` | `{ok}` | member | +| (from 00-teams/04) `invite_member`, `get_pending_invitations`, `revoke_invitation` | | | | Owner/Admin | + +## Permissions notes + +All mutations double-guarded: UI hides controls by `team_role`, server validates via membership +lookups. Last-Owner protection lives in the membership controller (00-teams/01). + +## Demo script + +1. As Owner: Team Settings → rename team + upload logo → sidebar switcher updates. +2. Invites → invite `frank@x.com` as Frontdesk → pending row appears. +3. Frank accepts → Members shows Frank (Frontdesk); Frank's session: check-in-only nav (step 08). +4. Change Frank → Viewer → his next load shows read-only workspace. +5. Remove Frank → memberships Disabled; Frank's `/manage` → onboarding screen. +6. As Manager-role member: settings dialog shows Members read-only, no invite form. + +## Acceptance criteria (merge gate) + +### Automated tests — extend `buzz/tests/test_teams.py` +- `update_member_role`: Owner/Admin succeeds; Manager raises; targeting an Owner row raises; + promoting to Owner raises. +- `remove_member`: sets Disabled; last-Owner removal raises; role recompute — user in two teams + keeps `Event Manager` when removed from one, loses it when removed from both. +- `leave_team`: member succeeds; last Owner raises. +- `update_team`: Owner succeeds; Admin raises. + +### Browser checks (agent-browser, dev server :8080) +1. Owner → Team Settings dialog → General: rename + logo upload → sidebar switcher label/avatar + update. +2. Defaults section → change transfer window to 7 → saved; attendee-facing effect covered by + `00-teams/05` check 1 (re-run it here). +3. Invites → invite `frank@x.com` as Frontdesk → pending row appears; accept in fresh session → + Members shows Frank (Frontdesk). +4. Frank's session → sidebar trimmed to check-in surface (cross-check with step 08 criteria). +5. Change Frank → Viewer → his next page load shows read-only workspace; remove Frank → his + `/dashboard/manage` shows onboarding. +6. Manager-role member → Members list read-only, no invite form, no Defaults editing. + +## Dependencies & risks + +- Role sync recompute on remove/demote is the fiddly bit — test a user in two teams keeping + `Event Manager` when removed from one. diff --git a/specs/v2/01-dashboard/14-pulse-analytics.md b/specs/v2/01-dashboard/14-pulse-analytics.md new file mode 100644 index 00000000..9a3fa2b4 --- /dev/null +++ b/specs/v2/01-dashboard/14-pulse-analytics.md @@ -0,0 +1,115 @@ +# 14 — Product analytics (Pulse) + +| Phase | Depends on | Status | +|---|---|---| +| Cross-cutting (init after 01, instrument as steps land) | 01-dashboard/01 | Draft | + +## Goal + +Product analytics via **Frappe Pulse**, exactly the way Frappe CRM does it on current develop: +the `telemetryPlugin` that ships inside frappe-ui v1 beta + capture call sites. Near-zero backend +code — boot config, client loading, and ingest all live in frappe / frappe-ui / the Pulse service. + +## How it works (verified against frappe/crm develop + frappe-ui main) + +- frontend init: `app.use(telemetryPlugin, { app_name: 'buzz' })` — plugin from + `frappe-ui/frappe` (`frappe/telemetry/index.ts`). +- The plugin fetches boot config from the whitelisted framework method + `frappe.utils.telemetry.pulse.client.boot_config`, which returns enabled/host/key/site only when + site config has `pulse_api_key` **and** System Settings `enable_telemetry` is on + (or `pulse_force_enabled`). Without config it returns `{enabled: false}` → the plugin no-ops. +- The Pulse client JS is dynamically imported at runtime from the pulse host + (default `https://pulse.m.frappe.cloud/assets/pulse/js/pulse_client.js`); origin-validated. +- Components: `const { capture } = useTelemetry()` then `capture('event_created', {...})` — + **bare snake_case event names**, no app prefix (`app_name` is passed separately on every event). +- Server-side captures: `from frappe.utils.telemetry import capture; capture("active_site", "buzz")`. + +## Demo criteria (definition of done) + +On a staging site with `pulse_api_key` configured: creating an event produces an `event_created` +event visible in the Pulse dashboard attributed to app `buzz`. On an unconfigured site: zero +console errors, zero network noise beyond the one boot_config call. + +## Scope + +### In +- Plugin init + `useTelemetry` capture call sites across manager + attendee flows +- Optional daily server-side `active_site` heartbeat +### Out (deferred to) +- Publisher-facing event analytics (page views/funnels) — separate future pillar +- Self-hosted Pulse — supported via `pulse_host` site config, no code change; not set up here + +## Backend changes + +Optional, tiny: `buzz/tasks.py` daily job → `capture("active_site", "buzz")` (mirrors CRM's +`crm/www/crm.py`). Nothing else — no doctypes, no endpoints. + +## Frontend changes + +- `dashboard/src/main.ts`: `import { telemetryPlugin } from 'frappe-ui/frappe'` + + `app.use(telemetryPlugin, { app_name: 'buzz' })` (pass `router` for pageview tracking on new + sites, mirroring CRM). +- Capture call sites — the **event registry** (single source of truth; add rows as steps land): + +| Event name | Where (step) | Properties | +|---|---|---| +| `team_created` | onboarding + switcher create (01) | — | +| `event_created` | NewEventDialog (01) | `{from_template: bool}` | +| `event_published` / `event_unpublished` | Overview (03) | — | +| `ticket_type_created` | TicketTypeDialog (02) | — | +| `coupon_created` | CouponDialog (04) | `{coupon_type}` | +| `add_on_created` | AddOnDialog (05) | — | +| `sponsor_added` / `sponsorship_tier_created` | Sponsors tab (06) | — | +| `offline_payment_confirmed` | Bookings (07) | — | +| `attendees_exported` | Attendees (07) | — | +| `attendee_checked_in` | Check-in (08) | `{manual: bool}` | +| `member_invited` | Invites (13) | `{team_role}` | +| `member_role_changed` / `member_removed` | Members (13) | — | +| `template_created` / `event_created_from_template` | Templates (11) | — | +| `booking_initiated` / `booking_completed` | attendee BookingForm flow | `{is_guest, has_coupon}` | +| `ticket_transferred` / `cancellation_requested` | attendee TicketDetails | — | + +- Convention: capture **after** the mutation succeeds (in the `await`ed success path), never on + click. No PII in properties (no emails/names); team/user identity comes from Pulse's own + anonymized boot identity. + +## API contract + +No new endpoints (uses framework `boot_config`). + +## Permissions notes + +Telemetry is opt-in at the site level (`enable_telemetry` + `pulse_api_key`). Nothing captured for +sites that don't opt in. Anonymous mode handled by the SDK (cookieless default). + +## Demo script + +1. Unconfigured dev site: boot → one `boot_config` call returning `{enabled: false}`; no errors. +2. Staging with `pulse_api_key`: create team + event + ticket type → three events visible in the + Pulse dashboard under app `buzz`. +3. Grep check: every `capture(` call site uses a name from the registry table above. + +## Acceptance criteria (merge gate) + +### Automated tests +- Registry lint (CI grep or small pytest): every `capture('...')` call site in `dashboard/src` + uses an event name present in this file's registry table; fails on unknown names. +- Backend heartbeat (if shipped): scheduler job calls `frappe.utils.telemetry.capture` — smoke + test that it no-ops without config (no exception). + +### Browser checks (agent-browser, dev server :8080) +1. Unconfigured dev site: load `/dashboard/manage/events` → read console messages → zero + telemetry errors; network log shows exactly one `boot_config` call returning + `{enabled: false}` and no pulse-host requests. +2. Configured staging site (`pulse_api_key` + `enable_telemetry`): create team → event → ticket + type in the UI → network log shows capture requests to the pulse host for `team_created`, + `event_created`, `ticket_type_created`; events visible in the Pulse dashboard under app + `buzz`. +3. Capture timing: cancel the New Event dialog → no `event_created` fired (success-path-only + convention holds). + +## Dependencies & risks + +- Requires frappe version with `frappe.utils.telemetry.pulse` (present on this bench's frappe + develop) and frappe-ui v1 beta (Step-0 gate). +- Registry drift: PR review rule — new capture call sites must update the table in this file. diff --git a/specs/v2/02-speaker-remodel.md b/specs/v2/02-speaker-remodel.md new file mode 100644 index 00000000..43b6739d --- /dev/null +++ b/specs/v2/02-speaker-remodel.md @@ -0,0 +1,116 @@ +# 02 — Speaker model remodel (exploration) + +> **STATUS: OPEN — NO DECISION TAKEN.** This spec captures the problem, the options, and a leaning. +> The model must be finalized before `01-dashboard/10-schedule-tracks-and-talks.md` is implemented +> (it CRUDs talks/speakers) and before the teams permission inventory treats speaker doctypes as +> settled. + +## 1. Current model + +| Doctype | Shape | Notes | +|---|---|---| +| `Speaker Profile` | standalone, autoincrement | `user` (Link User), `display_name` (synced from User via `hooks.py:38` `on_update`), `designation`, `company`, `display_image`, `social_media_links` child | +| `Talk Speaker` | child of `Event Talk` | `speaker` (Link Speaker Profile) **+ its own `social_media_links` child** | +| `Proposal Speaker` | child of `Talk Proposal` | raw `first_name`, `last_name`, `email` — **no link to Speaker Profile** | +| `Event Featured Speaker` | child of `Buzz Event` | `speaker` (Link Speaker Profile) | + +## 2. Problems + +1. **Two disjoint speaker representations.** CFP submissions carry raw contact rows + (`Proposal Speaker`); accepted talks carry profile links (`Talk Speaker`). The accept flow has + to reconcile/dedupe them, and today doesn't — profiles and proposal speakers never meet. +2. **Profile requires a User.** Co-speakers without accounts have no clean home; `Proposal Speaker` + only has an email. +3. **Global profile vs multi-tenant v2.** One global record: Team A edits a bio → Team B's public + event page changes. Ownership is unanswered (`00-teams/00-plan.md` parks Speaker Profile as + "attendee-owned, not team-scoped" — a dodge, not a decision). +4. **Per-event customization already leaking in.** `Talk Speaker.social_media_links` duplicates the + profile's socials — an ad-hoc per-talk override. Real speakers tailor bio/headshot per event. +5. **`Event Featured Speaker` is a separate child table** for what is essentially a flag + ordering + on the event's speakers. + +## 3. Prior art (researched 2026-07) + +Both major CFP/speaker platforms converged on **global person identity + per-event speaker +profile**: + +- **pretalx** — `SpeakerProfile` exists per (user, event): per-event biography, per-event profile + picture; bios from past events offered as suggestions on new submissions. + ([data models](https://docs.pretalx.org/developer/interfaces/models/), + [concepts](https://docs.pretalx.org/developer/architecture/concepts/)) +- **Sessionize** — default profile auto-copied into each event submission, then edited *per event*; + editing the default never touches existing events. + ([speaker dashboard](https://sessionize.com/playbook/speaker-dashboard), + [submit a session](https://sessionize.com/playbook/submit-your-session-for-an-event)) + +## 4. Options + +### Option A — Per-event speaker record (pretalx/Sessionize model) + +New **`Event Speaker`** doctype (standalone, team via cascade): + +| fieldname | type | notes | +|---|---|---| +| `event` | Link Buzz Event, reqd | | +| `team` | Link Buzz Team, read-only | stamped via `inherit_team_from_event` | +| `full_name` | Data, reqd | | +| `email` | Data | identity key for find-or-create | +| `user` | Link User | optional — enables self-serve editing (CFP flow, `allow_editing_talks_after_acceptance`) | +| `designation`, `company` | Data | | +| `bio` | Text Editor | per-event | +| `photo` | Attach Image | per-event | +| `social_media_links` | Table Social Media Link | | +| `is_featured` | Check | replaces `Event Featured Speaker` | +| `featured_order` | Int | | + +Consequences: +- `Talk Speaker` child → Link `Event Speaker`; drop its `social_media_links` child. +- `Event Featured Speaker` child table **deleted** (flag + order above). +- Proposal accept → find-or-create `Event Speaker` by `(event, email)` per `Proposal Speaker` row. +- "Copy from previous event" prefill action (the reuse UX pretalx/Sessionize ship). +- Global `Speaker Profile`: either **dropped**, or shrunk to a thin identity record used only for + prefill suggestions. +- Migration patch: one `Event Speaker` per distinct (event, `Talk Speaker.speaker`) + featured + rows; repoint children. + +**Pros:** per-event bio/photo (industry standard); account-less speakers fine; clean team-scoped +permissions; dashboard Schedule tab CRUD gets simpler. +**Cons:** migration; mild duplication of a person across events (the accepted trade-off everywhere). + +### Option B — Global profile + per-event override junction + +Keep `Speaker Profile` canonical; junction holds override fields (bio, photo, socials). More +normalized, but every renderer resolves two levels, and non-overridden fields still leak across +teams. More moving parts than A for less benefit. + +### Option C — Minimal patch, no remodel + +Make `user` optional; key identity on email; auto-create profile from `Proposal Speaker` on accept; +drop `Talk Speaker` socials duplication. Cheap; solves neither multi-tenant ownership nor per-event +bios. Only defensible if speakers are out of v2 scope. + +### Option D — Team-scoped speaker directory (library model) + +`Speaker` belongs to a team (like Venues/Hosts in `01-dashboard/09`), reused across that team's +events. Great for repeat meetups; simple permissions. No per-event bio; a person speaking for two +teams = two records (Luma/Eventbrite behave this way). + +**Hybrid D+A** (team directory record + per-event rows referencing it with bio override) is +possible but reintroduces B's two-level resolution complexity. + +## 5. Current leaning (not a decision) + +**Option A**, keeping the optional `user` link so CFP self-editing still works, and treating +"repeat speaker" reuse as a copy-from-previous-event action rather than a shared mutable record. +Revisit D if usage turns out meetup-heavy (same roster every month). + +## 6. Open questions to finalize + +1. Drop `Speaker Profile` entirely, or keep as thin prefill identity? +2. Does the speaker get a public self-view/edit page (pretalx-style) in v2, or is editing + manager-only + CFP-flow-only? +3. `Proposal Speaker` — replace with early `Event Speaker` creation at submission time, or keep raw + rows until accept? (Early creation pollutes the speaker list with rejected proposals.) +4. Featured speakers ordering UX — flag on speaker vs drag-ordered list on the event. +5. Where does speaker management live in the dashboard — inside the Schedule tab (step 10) or its + own workspace tab? diff --git a/specs/v2/README.md b/specs/v2/README.md new file mode 100644 index 00000000..5263a247 --- /dev/null +++ b/specs/v2/README.md @@ -0,0 +1,100 @@ +# Buzz v2 Specs + +Buzz v2 turns Buzz from a single-tenant, Desk-administered event app into a **multi-tenant, +self-serve event platform**. Two pillars are specced here as tracer-bullet step sequences: + +| Folder | Pillar | +|---|---| +| [`00-teams/`](00-teams/00-plan.md) | Teams & multi-tenancy — the foundation. `Buzz Team` + `Buzz Team Membership`, tenant field cascade, permission hooks, invitations. | +| [`01-dashboard/`](01-dashboard/00-plan.md) | Unified manager + attendee dashboard — Frappe UI app shell, event workspace, Desk parity for organizers, Pulse product analytics. | +| [`02-speaker-remodel.md`](02-speaker-remodel.md) | **OPEN — decision pending.** Speaker model exploration: per-event speaker vs global profile. Must be finalized before `01-dashboard/10`. | + +## Conventions + +- **One step file = one PR-sized, independently demoable vertical slice.** Filename number = merge order within its folder. +- Each folder has a `00-plan.md` index (step order, dependencies, scope map). +- Every step file starts with a front-matter table: `Phase | Depends on | Status`. +- Status values: `Draft` → `Ready` → `In Progress` → `Done`. + +## Cross-folder build sequence + +``` +00-teams/01 ─► 00-teams/02 ─► 00-teams/03 ─┐ + ├─► 01-dashboard/01 ─► 01-dashboard/02 (tracer bullet demo) + │ +00-teams/04 (invitations) ─────────────────┴─► 01-dashboard/03 … 14 (parity phases B–D) +``` + +Teams steps 01–03 land first (doctypes → tenant field → permissions), then the dashboard tracer +bullet (shell + events list + ticket type CRUD) proves the whole architecture end to end. Team +invitations (00-teams/04) are deliberately deferred until after the tracer bullet — not needed for +the first demo. Dashboard phases B–D then march to Desk parity. + +## Step file template + +```markdown +# NN — + +| Phase | Depends on | Status | +|---|---|---| +| A/B/C/D | | Draft | + +## Goal +1–2 sentences: the thin vertical slice and why it's next. + +## Demo criteria (definition of done) +What a reviewer sees working end to end, phrased as the demo. + +## Scope +### In +### Out (deferred to) + +## Backend changes +Doctypes (field tables), hooks.py keys, API signatures, patches, fixtures. + +## Frontend changes +Routes added to router.ts, pages/components (frappe-ui component names), src/data/*.ts modules. + +## API contract +| Endpoint (dotted path) | Method | Params | Returns | Auth | + +## Permissions notes +Who can do what; guest/attendee flows that must not regress. + +## Demo script +Numbered manual QA steps (exact URLs, expected outcomes). + +## Acceptance criteria (merge gate) +### Automated tests +`bench --site buzz.localhost run-tests --module ...` targets + the cases they must cover. +### Browser checks (agent-browser) +Scripted agent-browser flows against the local site (dev server :8080 for frontend work). + +## Dependencies & risks +``` + +### Acceptance-criteria conventions + +- **Every step is merge-gated by its Acceptance criteria section** — Demo script is the human + walkthrough; acceptance criteria are the strict, repeatable checks. +- **Unit/integration tests**: live in `buzz/tests/` (or doctype `test_*.py`), run via + `bench --site buzz.localhost run-tests --module `. Backend steps must ship tests in the + same PR; a step whose tests are listed here but missing from the PR is incomplete. +- **Browser checks**: executed with **agent-browser** (`agent-browser --help`) against the local + site — frontend steps target the vite dev server on `:8080`, public/attendee flows target the + site directly. Each check is written as a concrete flow (URL → actions → asserted outcome) so it + can be replayed verbatim. Run headed locally; headless is fine for CI. +- **Cross-tenant checks always use two seeded teams** (team A / team B with one event each) — + seed via a shared test fixture/factory so browser and unit checks agree on data. + +## Global assumptions + +1. **frappe-ui v1 beta migration is a prerequisite** (`DesktopShell`, `frappe-ui/list`, + `useCall`/`useList`/`useDoc`, semantic tokens, `telemetryPlugin`). `dashboard/package.json` + currently pins `frappe-ui ^0.1.257` — the Step-0 checklist in `01-dashboard/00-plan.md` gates + Phase A on the upgrade being merged. +2. **Single SPA.** Manager area grafts onto the existing dashboard app under `/manage/*`; + attendee (`/account/*`) and public booking routes stay intact. +3. **Teams are the tenant boundary.** Every organizer-facing record belongs to a `Buzz Team`; + permissions derive from `Buzz Team Membership`, not global roles alone. +4. **No Desk for organizers** at the end of Phase D; Desk remains for System Managers. diff --git a/worktrees/custom-forms b/worktrees/custom-forms deleted file mode 160000 index 334469be..00000000 --- a/worktrees/custom-forms +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 334469be82bbad8970bdfb546531d5720739d30f