Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions buzz/api/booking/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -242,8 +242,21 @@ def offline_booking_response(self, booking: "EventBooking") -> OfflineBookingRes
booking.flags.ignore_permissions = True
booking.save()
self.attach_payment_proof(booking)
self.acknowledge_offline_booking(booking)
return OfflineBookingResponse(booking_name=booking.name, offline_payment=True)

def acknowledge_offline_booking(self, booking: "EventBooking") -> None:
"""The booking is only a draft until an approval verifies the payment, so this
acknowledgement is the booker's only receipt until then."""
try:
booking.send_offline_acknowledgement_email()
except Exception:
frappe.log_error(
title="Offline booking acknowledgement email failed",
reference_doctype=booking.doctype,
reference_name=booking.name,
)

def offline_method(self) -> dict:
filters = {"event": self.request.event, "enabled": 1}
if self.request.offline_payment_method:
Expand Down
49 changes: 49 additions & 0 deletions buzz/api/booking/test_booking.py
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,55 @@ def test_offline_booking_awaits_approval(self):
self.assertEqual(booking.status, "Approval Pending")
self.assertEqual(booking.payment_status, "Verification Pending")

def test_gateway_booking_is_not_acknowledged(self):
"""The acknowledgement belongs to the offline path only: a gateway booking is
still unpaid at this point and gets its confirmation after the payment lands."""
request = self.make_paid_request()

with (
patch("buzz.api.booking.services.get_payment_link_for_booking", return_value="/pay"),
patch("frappe.sendmail") as sendmail,
):
process_booking(request)

sendmail.assert_not_called()

def test_offline_booking_acknowledges_then_confirms(self):
"""Offline is a two-stage conversation: an acknowledgement while the payment is
unverified, the existing confirmation only once an approval submits the booking."""
self.set_event({"send_ticket_email": 0})
if not frappe.db.exists("User", BOOKER):
frappe.get_doc(
{"doctype": "User", "email": BOOKER, "first_name": "Booking", "send_welcome_email": 0}
).insert(ignore_permissions=True)
frappe.get_doc(
{
"doctype": "Offline Payment Method",
"event": self.event.name,
"title": f"Bank Transfer {frappe.generate_hash(length=6)}",
"enabled": 1,
}
).insert(ignore_permissions=True)
request = self.make_paid_request(is_offline=True)

frappe.set_user(BOOKER)
self.addCleanup(frappe.set_user, "Administrator")

with patch("frappe.sendmail") as sendmail:
booking_name = process_booking(request).booking_name

sendmail.assert_called_once()
self.assertEqual(sendmail.call_args[1]["template"], "offline_booking_acknowledgement")
self.assertIn(BOOKER, sendmail.call_args[1]["recipients"])
self.assertFalse(frappe.db.exists("Event Ticket", {"booking": booking_name}))

frappe.set_user("Administrator")
frappe.get_doc("Event Booking", booking_name).approve_booking()

self.assertEqual(sendmail.call_args[1]["template"], "booking_confirmation")

self.assertTrue(frappe.db.exists("Event Ticket", {"booking": booking_name}))


class TestBookingAddOnPricing(BookingTestCase):
"""The add-on price is server-authoritative: it comes from the Ticket Add-on catalog,
Expand Down
13 changes: 11 additions & 2 deletions buzz/events/doctype/buzz_event/buzz_event.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
"booking_confirmation_email_section",
"send_booking_confirmation_email",
"booking_confirmation_email_template",
"offline_acknowledgement_email_template",
"talks_section",
"allow_editing_talks_after_acceptance",
"custom_forms_tab",
Expand Down Expand Up @@ -288,7 +289,7 @@
},
{
"default": "1",
"description": "Send a confirmation email with a booking summary to the person who made the booking.",
"description": "Send booking emails to the person who made the booking: an acknowledgement while an offline payment awaits verification, and a confirmation once the booking is confirmed.",
"fieldname": "send_booking_confirmation_email",
"fieldtype": "Check",
"label": "Send Booking Confirmation Email"
Expand All @@ -300,6 +301,14 @@
"label": "Booking Confirmation Email Template",
"options": "Email Template"
},
{
"depends_on": "eval:doc.send_booking_confirmation_email;",
"description": "Sent when a booking is made with an offline payment method, before the payment is verified.",
"fieldname": "offline_acknowledgement_email_template",
"fieldtype": "Link",
"label": "Offline Payment Acknowledgement Email Template",
"options": "Email Template"
},
{
"fieldname": "customisations_tab",
"fieldtype": "Tab Break",
Expand Down Expand Up @@ -602,7 +611,7 @@
"link_fieldname": "event"
}
],
"modified": "2026-07-23 12:00:00.000000",
"modified": "2026-08-27 12:00:00.000000",
"modified_by": "Administrator",
"module": "Events",
"name": "Buzz Event",
Expand Down
3 changes: 3 additions & 0 deletions buzz/events/doctype/buzz_event/buzz_event.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ class BuzzEvent(Document):
attach_email_ticket: DF.Check
auto_send_pitch_deck: DF.Check
banner_image: DF.AttachImage | None
booking_confirmation_email_template: DF.Link | None
card_image: DF.AttachImage | None
category: DF.Link
custom_forms: DF.Table[BuzzEventForm]
Expand All @@ -63,12 +64,14 @@ class BuzzEvent(Document):
medium: DF.Literal["In Person", "Online"]
meta_image: DF.AttachImage | None
name: DF.Int | None
offline_acknowledgement_email_template: DF.Link | None
payment_gateways: DF.Table[EventPaymentGateway]
proposal: DF.Link | None
registration_url: DF.Data | None
registrations_close_at: DF.Datetime | None
route: DF.Data | None
schedule: DF.Table[ScheduleItem]
send_booking_confirmation_email: DF.Check
send_ticket_email: DF.Check
short_description: DF.SmallText | None
show_sponsorship_section: DF.Check
Expand Down
Loading
Loading