Skip to content
Merged
15 changes: 12 additions & 3 deletions buzz/events/doctype/buzz_event/buzz_event.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
"sponsor_deck_attachments",
"customisations_tab",
"ticket_email_template",
"attach_calendar_invite",
"column_break_ukql",
"ticket_print_format",
"talks_section",
Expand Down Expand Up @@ -136,12 +137,14 @@
{
"fieldname": "start_time",
"fieldtype": "Time",
"label": "Start Time"
"label": "Start Time",
"reqd": 1
},
{
"fieldname": "end_time",
"fieldtype": "Time",
"label": "End Time"
"label": "End Time",
"reqd": 1
},
{
"fieldname": "host",
Expand Down Expand Up @@ -391,6 +394,12 @@
"fieldname": "card_image",
"fieldtype": "Attach Image",
"label": "Card Image"
},
{
"default": "1",
"fieldname": "attach_calendar_invite",
"fieldtype": "Check",
"label": "Attach Calendar Invite"
}
Comment on lines +398 to 403

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be in the customisations tab below the ticket email template field. Should be checked by default.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

],
"grid_page_length": 50,
Expand Down Expand Up @@ -463,7 +472,7 @@
"link_fieldname": "event"
}
],
"modified": "2026-01-14 11:13:04.386770",
"modified": "2026-01-27 12:09:30.031738",
"modified_by": "Administrator",
"module": "Events",
"name": "Buzz Event",
Expand Down
7 changes: 5 additions & 2 deletions buzz/events/doctype/buzz_event/buzz_event.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,16 @@ class BuzzEvent(Document):
from buzz.proposals.doctype.sponsorship_deck_item.sponsorship_deck_item import SponsorshipDeckItem

about: DF.TextEditor | None
allow_editing_talks_after_acceptance: DF.Check
apply_tax: DF.Check
attach_calendar_invite: DF.Check
auto_send_pitch_deck: DF.Check
banner_image: DF.AttachImage | None
card_image: DF.AttachImage | None
category: DF.Link
default_ticket_type: DF.Link | None
end_date: DF.Date | None
end_time: DF.Time | None
end_time: DF.Time
external_registration_page: DF.Check
featured_speakers: DF.Table[EventFeaturedSpeaker]
free_webinar: DF.Check
Expand All @@ -45,12 +47,13 @@ class BuzzEvent(Document):
route: DF.Data | None
schedule: DF.Table[ScheduleItem]
short_description: DF.SmallText | None
show_sponsorship_section: DF.Check
sponsor_deck_attachments: DF.Table[SponsorshipDeckItem]
sponsor_deck_cc: DF.SmallText | None
sponsor_deck_email_template: DF.Link | None
sponsor_deck_reply_to: DF.Data | None
start_date: DF.Date
start_time: DF.Time | None
start_time: DF.Time
tax_label: DF.Data | None
tax_percentage: DF.Percent
ticket_email_template: DF.Link | None
Expand Down
2 changes: 2 additions & 0 deletions buzz/install.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ def setup_test_records():
"title": "Test Event",
"route": "test-route",
"start_date": frappe.utils.today(),
"start_time": "10:00:00",
"end_time": "18:00:00",
}
).insert(ignore_if_duplicate=True)

Expand Down
16 changes: 16 additions & 0 deletions buzz/templates/ics/ics.jinja2
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Buzz Events//EN
CALSCALE:GREGORIAN
METHOD:PUBLISH
BEGIN:VEVENT
UID:{{uid}}@buzz
DTSTAMP:{{now}}
DTSTART;TZID={{timezone}}:{{start}}
DTEND;TZID={{timezone}}:{{end}}
SUMMARY:{{title}}
LOCATION:{{location}}
ATTENDEE;CN=Attendee;RSVP=TRUE:mailto:{{attendee_email}}
DESCRIPTION:{{description}}
END:VEVENT
END:VCALENDAR
Original file line number Diff line number Diff line change
Expand Up @@ -690,6 +690,8 @@ def test_coupon_event_scope_validation(self):
"title": "Other Event",
"route": "other-event-test",
"start_date": "2025-12-31",
"start_time": "10:00:00",
"end_time": "18:00:00",
"category": category,
"host": host,
}
Expand Down Expand Up @@ -784,6 +786,8 @@ def test_coupon_category_scope_validation(self):
"title": "Other Category Event",
"route": "other-category-event-test",
"start_date": "2025-12-31",
"start_time": "10:00:00",
"end_time": "18:00:00",
"category": other_category.name,
"host": self.test_event.host,
}
Expand Down Expand Up @@ -856,6 +860,8 @@ def test_coupon_global_scope(self):
"title": "Another Event",
"route": "another-event-global-test",
"start_date": "2025-12-31",
"start_time": "10:00:00",
"end_time": "18:00:00",
"category": self.test_event.category,
"host": self.test_event.host,
}
Expand Down
15 changes: 12 additions & 3 deletions buzz/ticketing/doctype/event_ticket/event_ticket.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from frappe.core.api.user_invitation import invite_by_email
from frappe.model.document import Document

from buzz.utils import generate_qr_code_file, only_if_app_installed
from buzz.utils import generate_ics_file, generate_qr_code_file, only_if_app_installed


class EventTicket(Document):
Expand Down Expand Up @@ -88,9 +88,10 @@ def send_ticket_email(self, now: bool = False):
ticket_template = frappe.db.get_single_value("Buzz Settings", "default_ticket_email_template")

subject = frappe._("Your ticket to {0} 🎟️").format(event_title)
event_doc = frappe.get_cached_doc("Buzz Event", self.event)
args = {
"doc": self,
"event_doc": frappe.get_cached_doc("Buzz Event", self.event),
"event_doc": event_doc,
"event_title": event_title,
"venue": venue,
}
Expand All @@ -102,6 +103,13 @@ def send_ticket_email(self, now: bool = False):
subject = email_template.get("subject")
content = email_template.get("message")

if event_doc.attach_calendar_invite:
ics_content = generate_ics_file(event_doc, self.attendee_email)
attachments = {
"fname": f"{event_doc.title}.ics",
"fcontent": ics_content,
}

frappe.sendmail(
recipients=[self.attendee_email],
subject=subject,
Expand All @@ -118,7 +126,8 @@ def send_ticket_email(self, now: bool = False):
"name": self.name,
"print_format": ticket_print_format or "Standard Ticket",
}
],
]
+ ([attachments] if event_doc.attach_calendar_invite else []),
)

def validate_coupon_usage(self):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ def _create_test_event(cls):
"category": "Test Category",
"host": "Test Host",
"start_date": frappe.utils.today(),
"start_time": "10:00:00",
"end_time": "18:00:00",
"medium": "Online",
"apply_tax": False,
}
Expand Down Expand Up @@ -763,6 +765,8 @@ def test_report_with_no_tickets(self):
"category": "Test Category",
"host": "Test Host",
"start_date": frappe.utils.today(),
"start_time": "10:00:00",
"end_time": "18:00:00",
"medium": "Online",
}
).insert()
Expand Down
47 changes: 47 additions & 0 deletions buzz/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,3 +135,50 @@ def generate_qr_code_file(doc, data: str, field_name: str = "qr_code", file_pref
}
).save(ignore_permissions=True)
return qr_code_file.file_url


def build_event_datetimes(event_doc):
from datetime import datetime, timedelta

from frappe.utils import get_time, getdate

start_date = getdate(event_doc.start_date)
start_time = get_time(event_doc.start_time)

start_datetime = datetime.combine(start_date, start_time)

end_date = getdate(event_doc.end_date) if event_doc.end_date else start_date

if event_doc.end_time:
end_time = get_time(event_doc.end_time)
end_datetime = datetime.combine(end_date, end_time)
else:
end_datetime = start_datetime + timedelta(hours=1)

return start_datetime, end_datetime


def generate_ics_file(event_doc, attendee_email: str):
from uuid import uuid4

from frappe.utils import now_datetime

start_dt, end_dt = build_event_datetimes(event_doc)

venue_address = ""
if event_doc.venue:
venue_address = frappe.db.get_value("Event Venue", event_doc.venue, "address") or ""

context = {
"uid": uuid4(),
"now": now_datetime().strftime("%Y%m%dT%H%M%S"),
"timezone": event_doc.time_zone,
"start": start_dt.strftime("%Y%m%dT%H%M%S"),
"end": end_dt.strftime("%Y%m%dT%H%M%S"),
"title": event_doc.title,
"location": venue_address,
"attendee_email": attendee_email,
"description": f"Your ticket for {event_doc.title}",
}

return frappe.render_template("templates/ics/ics.jinja2", context, is_path=True)
2 changes: 1 addition & 1 deletion e2e/tests/auth.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ test.describe("Authentication - Fresh state", () => {
test("should login via UI", async ({ page }) => {
const loginPage = new LoginPage(page);

await loginPage.login("Administrator", "admin");
await loginPage.login(process.env.FRAPPE_USER || "Administrator", process.env.FRAPPE_PASSWORD || "admin");

// Should be redirected away from login
await expect(page).not.toHaveURL(/.*login.*/);
Expand Down
2 changes: 2 additions & 0 deletions e2e/tests/event.setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ setup("create test event for booking", async ({ request }) => {
start_date: startDate,
route: testEventRoute,
is_published: 1,
start_time: "09:00:00",
end_time: "17:00:00",
medium: "In Person",
});
console.log(`Created Buzz Event: ${event.name} (route: ${testEventRoute})`);
Expand Down
Loading