Initialize fork and rebrand app to event_manager
This commit is contained in:
@@ -0,0 +1,368 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user