# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. 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. --- ## This defines the best practices to write backend code in the Frappe Framework * 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. ## Backend Development ### JSON & Request Handling * Always use built-in functions for parsing JSON: * `frappe.parse_json` (handles dicts, lists, and JSON strings safely) * Never use `json.loads` directly on request data. * For outbound HTTP requests (calling external APIs), use: * `frappe.integration.utils.make_get_request` * `frappe.integration.utils.make_post_request` * `frappe.integration.utils.make_put_request` * `frappe.integration.utils.make_patch_request` ### Datatype Conversion & Utilities * For converting datatypes (e.g. str → int, str → float, etc.) use built-in helpers: * `frappe.utils.data.cint` * `frappe.utils.data.cstr` * `frappe.utils.data.flt` * `frappe.utils.data.getdate` * `frappe.utils.data.get_datetime` * `frappe.utils.data` contains most conversion and formatting helpers you will ever need: * 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 event_manager/public/dashboard + event_manager/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 event_manager/ ruff format event_manager/ # install app to site bench --site [site-name] install-app event_manager ``` 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 event_manager.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**: `Pohodex Event Manager Event` DocType drives everything (tickets, sponsors, schedule, payments). **Main modules** (inside `event_manager/`): - `events/` - Event, Venue, Category, Talks, Sponsors, Check-ins - `ticketing/` - Bookings, Tickets, Add-ons, Cancellations, Coupons - `proposals/` - Talk Proposals, Sponsorship Enquiries - `event_manager/` - 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 `event_manager/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 Pohodex Event Manager 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**: `event_manager/api.py`, `event_manager/ticketing/doctype/event_booking/`, `dashboard/src/components/BookingForm.vue` **Ticket lifecycle**: `event_manager/ticketing/doctype/event_ticket/`, `dashboard/src/pages/TicketDetails.vue` **Sponsorships**: `event_manager/proposals/doctype/sponsorship_enquiry/`, `dashboard/src/pages/SponsorshipDetails.vue` **Check-in**: `event_manager/api.py` (validate_ticket_for_checkin, checkin_ticket), `dashboard/src/pages/CheckInScanner.vue` **Event config**: `event_manager/events/doctype/buzz_event/` **Reports**: `event_manager/events/report/` and `event_manager/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