Initialize fork and rebrand app to event_manager
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
// Copyright (c) 2025, BWH Studios and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on("Event Proposal", {
|
||||
refresh(frm) {
|
||||
if (!frm.is_new() && frm.doc.docstatus == 0) {
|
||||
frm.set_intro("Pohodex Event Manager Event will be created on submission of this document", "yellow");
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,215 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"autoname": "naming_series:",
|
||||
"creation": "2025-12-10 17:17:09.702412",
|
||||
"doctype": "DocType",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"title",
|
||||
"category",
|
||||
"free_webinar",
|
||||
"medium",
|
||||
"column_break_bixo",
|
||||
"status",
|
||||
"naming_series",
|
||||
"section_break_psfj",
|
||||
"start_date",
|
||||
"start_time",
|
||||
"column_break_wfaw",
|
||||
"end_date",
|
||||
"end_time",
|
||||
"section_break_zajc",
|
||||
"short_description",
|
||||
"column_break_yxbj",
|
||||
"about",
|
||||
"host_tab",
|
||||
"host",
|
||||
"section_break_xzvg",
|
||||
"host_company",
|
||||
"additional_notes",
|
||||
"host_company_logo",
|
||||
"section_break_kray",
|
||||
"amended_from"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"default": "Received",
|
||||
"fieldname": "status",
|
||||
"fieldtype": "Select",
|
||||
"label": "Status",
|
||||
"no_copy": 1,
|
||||
"options": "Received\nIn Review\nApproved\nEvent Created\nRejected"
|
||||
},
|
||||
{
|
||||
"fieldname": "naming_series",
|
||||
"fieldtype": "Select",
|
||||
"label": "Naming Series",
|
||||
"options": "EPR-.###"
|
||||
},
|
||||
{
|
||||
"fieldname": "title",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Title",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_bixo",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_zajc",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "host_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Host"
|
||||
},
|
||||
{
|
||||
"fieldname": "host_company",
|
||||
"fieldtype": "Data",
|
||||
"label": "Host Company"
|
||||
},
|
||||
{
|
||||
"fieldname": "additional_notes",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Additional Notes"
|
||||
},
|
||||
{
|
||||
"description": "Required for creating an event",
|
||||
"fieldname": "host",
|
||||
"fieldtype": "Link",
|
||||
"label": "Host",
|
||||
"options": "Event Host"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_kray",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "host_company_logo",
|
||||
"fieldtype": "Attach Image",
|
||||
"label": "Host Company Logo"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_psfj",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "start_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Start Date",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "end_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "End Date"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_wfaw",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "start_time",
|
||||
"fieldtype": "Time",
|
||||
"label": "Start Time"
|
||||
},
|
||||
{
|
||||
"fieldname": "end_time",
|
||||
"fieldtype": "Time",
|
||||
"label": "End Time"
|
||||
},
|
||||
{
|
||||
"fieldname": "about",
|
||||
"fieldtype": "Text Editor",
|
||||
"label": "About the event",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_yxbj",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "short_description",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Short Description"
|
||||
},
|
||||
{
|
||||
"default": "Online",
|
||||
"fieldname": "medium",
|
||||
"fieldtype": "Select",
|
||||
"label": "Medium",
|
||||
"options": "Online\nIn Person",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.event_category==\"Webinars\"",
|
||||
"fieldname": "free_webinar",
|
||||
"fieldtype": "Check",
|
||||
"label": "Free Webinar?"
|
||||
},
|
||||
{
|
||||
"fieldname": "amended_from",
|
||||
"fieldtype": "Link",
|
||||
"label": "Amended From",
|
||||
"no_copy": 1,
|
||||
"options": "Event Proposal",
|
||||
"print_hide": 1,
|
||||
"read_only": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "category",
|
||||
"fieldtype": "Link",
|
||||
"label": "Event Category",
|
||||
"options": "Event Category",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_xzvg",
|
||||
"fieldtype": "Section Break"
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2025-12-11 11:36:40.618179",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Proposals",
|
||||
"name": "Event Proposal",
|
||||
"naming_rule": "By \"Naming Series\" field",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"row_format": "Dynamic",
|
||||
"rows_threshold_for_grid_search": 20,
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": [
|
||||
{
|
||||
"color": "Green",
|
||||
"title": "Event Created"
|
||||
},
|
||||
{
|
||||
"color": "Orange",
|
||||
"title": "In Review"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
# Copyright (c) 2025, BWH Studios and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
from frappe.utils.data import get_url_to_form, getdate, today
|
||||
|
||||
|
||||
class EventProposal(Document):
|
||||
# begin: auto-generated types
|
||||
# This code is auto-generated. Do not modify anything in this block.
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from frappe.types import DF
|
||||
|
||||
about: DF.TextEditor
|
||||
additional_notes: DF.SmallText | None
|
||||
amended_from: DF.Link | None
|
||||
category: DF.Link
|
||||
end_date: DF.Date | None
|
||||
end_time: DF.Time | None
|
||||
free_webinar: DF.Check
|
||||
host: DF.Link | None
|
||||
host_company: DF.Data | None
|
||||
host_company_logo: DF.AttachImage | None
|
||||
medium: DF.Literal["Online", "In Person"]
|
||||
naming_series: DF.Literal["EPR-.###"]
|
||||
short_description: DF.SmallText | None
|
||||
start_date: DF.Date
|
||||
start_time: DF.Time | None
|
||||
status: DF.Literal["Received", "In Review", "Approved", "Event Created", "Rejected"]
|
||||
title: DF.Data
|
||||
# end: auto-generated types
|
||||
|
||||
def validate(self):
|
||||
self.validate_dates()
|
||||
self.validate_times()
|
||||
|
||||
def validate_dates(self):
|
||||
if getdate(self.start_date) < getdate(today()):
|
||||
frappe.throw(_("Start Date cannot be in the past."))
|
||||
|
||||
if self.end_date and getdate(self.end_date) < getdate(self.start_date):
|
||||
frappe.throw(_("End Date cannot be before Start Date."))
|
||||
|
||||
def validate_times(self):
|
||||
if not self.start_time or not self.end_time:
|
||||
return
|
||||
|
||||
same_day = not self.end_date or getdate(self.end_date) == getdate(self.start_date)
|
||||
if same_day and self.end_time <= self.start_time:
|
||||
frappe.throw(_("End Time must be after Start Time for same-day events."))
|
||||
|
||||
def before_submit(self):
|
||||
if self.status not in ("Approved", "Rejected"):
|
||||
frappe.throw(_("Only Approved or Rejected proposals can be submitted."))
|
||||
|
||||
self.create_event()
|
||||
|
||||
def create_event(self):
|
||||
if self.status == "Rejected":
|
||||
return
|
||||
|
||||
if not self.host:
|
||||
frappe.throw(_("Please create or set a Host before submitting the proposal."))
|
||||
|
||||
buzz_event = get_mapped_doc(
|
||||
"Event Proposal", self.name, {"Event Proposal": {"doctype": "Pohodex Event Manager Event"}}
|
||||
)
|
||||
buzz_event.proposal = self.name
|
||||
buzz_event.insert()
|
||||
|
||||
self.status = "Event Created"
|
||||
|
||||
frappe.msgprint(
|
||||
_("Pohodex Event Manager Event {0} created successfully.").format(
|
||||
f'<a href="{get_url_to_form("Pohodex Event Manager Event", buzz_event.name)}">{buzz_event.title}</a>'
|
||||
)
|
||||
)
|
||||
@@ -0,0 +1,20 @@
|
||||
# Copyright (c) 2025, BWH Studios and Contributors
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
# On IntegrationTestCase, the doctype test records and all
|
||||
# link-field test record dependencies are recursively loaded
|
||||
# Use these module variables to add/remove to/from that list
|
||||
EXTRA_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"]
|
||||
IGNORE_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"]
|
||||
|
||||
|
||||
class IntegrationTestEventProposal(IntegrationTestCase):
|
||||
"""
|
||||
Integration tests for EventProposal.
|
||||
Use this class for testing interactions between multiple components.
|
||||
"""
|
||||
|
||||
pass
|
||||
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"creation": "2025-07-26 12:07:26.497133",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"first_name",
|
||||
"last_name",
|
||||
"column_break_ouof",
|
||||
"email"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "first_name",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "First Name",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "last_name",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Last Name"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_ouof",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "email",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Email",
|
||||
"options": "Email",
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2025-07-26 12:12:56.836403",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Proposals",
|
||||
"name": "Proposal Speaker",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"row_format": "Dynamic",
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
# Copyright (c) 2025, BWH Studios and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class ProposalSpeaker(Document):
|
||||
# begin: auto-generated types
|
||||
# This code is auto-generated. Do not modify anything in this block.
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from frappe.types import DF
|
||||
|
||||
email: DF.Data
|
||||
first_name: DF.Data
|
||||
last_name: DF.Data | None
|
||||
parent: DF.Data
|
||||
parentfield: DF.Data
|
||||
parenttype: DF.Data
|
||||
# end: auto-generated types
|
||||
|
||||
pass
|
||||
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"creation": "2025-10-29 17:07:45.086186",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"file"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "file",
|
||||
"fieldtype": "Attach",
|
||||
"in_list_view": 1,
|
||||
"label": "File",
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2025-10-29 17:07:59.277650",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Proposals",
|
||||
"name": "Sponsorship Deck Item",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"row_format": "Dynamic",
|
||||
"rows_threshold_for_grid_search": 20,
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
# Copyright (c) 2025, BWH Studios and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class SponsorshipDeckItem(Document):
|
||||
# begin: auto-generated types
|
||||
# This code is auto-generated. Do not modify anything in this block.
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from frappe.types import DF
|
||||
|
||||
file: DF.Attach
|
||||
parent: DF.Data
|
||||
parentfield: DF.Data
|
||||
parenttype: DF.Data
|
||||
# end: auto-generated types
|
||||
|
||||
pass
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright (c) 2025, BWH Studios and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on("Sponsorship Enquiry", {
|
||||
refresh(frm) {
|
||||
if (!frm.doc.__islocal) {
|
||||
if (frm.doc.status === "Approval Pending") {
|
||||
frm.add_custom_button(__("Approve"), () => {
|
||||
frm.set_value("status", "Payment Pending");
|
||||
frm.save();
|
||||
});
|
||||
}
|
||||
|
||||
frm.add_custom_button(__("Create Sponsor"), () => {
|
||||
frm.call("create_sponsor").then(() => {
|
||||
frappe.show_alert(__("Sponsor Created!"));
|
||||
frm.refresh();
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,137 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"creation": "2025-07-26 12:13:43.344063",
|
||||
"doctype": "DocType",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"event",
|
||||
"company_name",
|
||||
"company_logo",
|
||||
"website",
|
||||
"column_break_fhgg",
|
||||
"status",
|
||||
"tier",
|
||||
"country",
|
||||
"phone",
|
||||
"section_break_additional",
|
||||
"additional_fields"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"default": "Approval Pending",
|
||||
"fieldname": "status",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"in_preview": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Status",
|
||||
"options": "Approval Pending\nPayment Pending\nPaid\nWithdrawn"
|
||||
},
|
||||
{
|
||||
"fieldname": "company_name",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Company Name",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "company_logo",
|
||||
"fieldtype": "Attach Image",
|
||||
"label": "Company Logo",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_fhgg",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "tier",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Tier",
|
||||
"options": "Sponsorship Tier"
|
||||
},
|
||||
{
|
||||
"fieldname": "event",
|
||||
"fieldtype": "Link",
|
||||
"label": "Event",
|
||||
"options": "Pohodex Event Manager Event",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "website",
|
||||
"fieldtype": "Data",
|
||||
"label": "Website",
|
||||
"options": "URL"
|
||||
},
|
||||
{
|
||||
"fieldname": "country",
|
||||
"fieldtype": "Link",
|
||||
"label": "Country",
|
||||
"options": "Country"
|
||||
},
|
||||
{
|
||||
"fieldname": "phone",
|
||||
"fieldtype": "Phone",
|
||||
"label": "Phone"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_additional",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "additional_fields",
|
||||
"fieldtype": "Table",
|
||||
"label": "Additional Fields",
|
||||
"options": "Additional Field"
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"image_field": "company_logo",
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [
|
||||
{
|
||||
"link_doctype": "Event Sponsor",
|
||||
"link_fieldname": "enquiry"
|
||||
},
|
||||
{
|
||||
"link_doctype": "Event Payment",
|
||||
"link_fieldname": "reference_docname"
|
||||
}
|
||||
],
|
||||
"modified": "2025-10-28 17:00:34.475703",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Proposals",
|
||||
"name": "Sponsorship Enquiry",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"if_owner": 1,
|
||||
"read": 1,
|
||||
"role": "Pohodex Event Manager User"
|
||||
}
|
||||
],
|
||||
"row_format": "Dynamic",
|
||||
"show_title_field_in_link": 1,
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"title_field": "company_name",
|
||||
"track_seen": 1,
|
||||
"track_views": 1
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
# Copyright (c) 2025, BWH Studios and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
import frappe
|
||||
from frappe.email.doctype.email_template.email_template import get_email_template
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import get_url
|
||||
|
||||
from event_manager.payments import mark_payment_as_received
|
||||
|
||||
|
||||
class SponsorshipEnquiry(Document):
|
||||
# begin: auto-generated types
|
||||
# This code is auto-generated. Do not modify anything in this block.
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from frappe.types import DF
|
||||
|
||||
company_logo: DF.AttachImage
|
||||
company_name: DF.Data
|
||||
country: DF.Link | None
|
||||
event: DF.Link
|
||||
phone: DF.Phone | None
|
||||
status: DF.Literal["Approval Pending", "Payment Pending", "Paid", "Withdrawn"]
|
||||
tier: DF.Link | None
|
||||
website: DF.Data | None
|
||||
# end: auto-generated types
|
||||
|
||||
def on_update(self):
|
||||
if self.has_value_changed("status") and self.status == "Payment Pending":
|
||||
try:
|
||||
self.send_approval_notification()
|
||||
except Exception:
|
||||
frappe.log_error("Error sending Sponsorship Approval Notification")
|
||||
|
||||
def on_payment_authorized(self, payment_status: str):
|
||||
if payment_status in ("Authorized", "Completed"):
|
||||
mark_payment_as_received(self.doctype, self.name)
|
||||
frappe.get_doc(
|
||||
{
|
||||
"doctype": "Event Sponsor",
|
||||
"company_name": self.company_name,
|
||||
"company_logo": self.company_logo,
|
||||
"event": self.event,
|
||||
"tier": self.tier,
|
||||
"enquiry": self.name,
|
||||
"website": self.website,
|
||||
}
|
||||
).insert(ignore_permissions=True)
|
||||
self.db_set("status", "Paid")
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_sponsor(self):
|
||||
frappe.only_for("Event Manager")
|
||||
|
||||
if not self.tier:
|
||||
frappe.throw(frappe._("Please select a sponsorship tier!"))
|
||||
|
||||
frappe.get_doc(
|
||||
{
|
||||
"doctype": "Event Sponsor",
|
||||
"company_name": self.company_name,
|
||||
"company_logo": self.company_logo,
|
||||
"event": self.event,
|
||||
"tier": self.tier,
|
||||
"enquiry": self.name,
|
||||
"website": self.website,
|
||||
"country": self.country,
|
||||
}
|
||||
).insert(ignore_permissions=True)
|
||||
|
||||
def after_insert(self):
|
||||
try:
|
||||
self.send_pitch_deck()
|
||||
except Exception:
|
||||
frappe.log_error("Error sending Sponsor Pitch Deck")
|
||||
|
||||
def send_pitch_deck(self, now=False):
|
||||
event = frappe.get_cached_doc("Pohodex Event Manager Event", self.event)
|
||||
settings = frappe.get_cached_doc("Pohodex Event Manager Settings")
|
||||
|
||||
# Check event-level toggle first, then fall back to global
|
||||
if not event.auto_send_pitch_deck and not settings.auto_send_pitch_deck:
|
||||
return
|
||||
|
||||
# Get template: event-level takes precedence, fall back to global
|
||||
template_name = event.sponsor_deck_email_template or settings.default_sponsor_deck_email_template
|
||||
if not template_name:
|
||||
frappe.log_error("No sponsor deck email template configured", "Sponsorship Enquiry")
|
||||
return
|
||||
|
||||
email_template = get_email_template(template_name, {"doc": self, "event": event})
|
||||
|
||||
subject = email_template.get("subject")
|
||||
content = email_template.get("message")
|
||||
|
||||
# Get CC and Reply-To: event-level takes precedence
|
||||
cc = event.sponsor_deck_cc or settings.default_sponsor_deck_cc
|
||||
reply_to = event.sponsor_deck_reply_to or settings.default_sponsor_deck_reply_to
|
||||
|
||||
frappe.sendmail(
|
||||
recipients=[self.owner],
|
||||
subject=subject,
|
||||
cc=cc,
|
||||
reply_to=reply_to,
|
||||
content=content,
|
||||
reference_doctype=self.doctype,
|
||||
reference_name=self.name,
|
||||
now=now,
|
||||
attachments=[{"file_url": attachment.file} for attachment in event.sponsor_deck_attachments],
|
||||
)
|
||||
|
||||
def send_approval_notification(self):
|
||||
event = frappe.get_cached_doc("Pohodex Event Manager Event", self.event)
|
||||
host_name = event.host or "The Event Team"
|
||||
dashboard_link = get_url(f"/dashboard/account/sponsorships/{self.name}")
|
||||
|
||||
subject = f"[Payment Pending] Your Sponsorship for {event.title} has been Approved!"
|
||||
message = f"""
|
||||
<p>Dear {self.company_name},</p>
|
||||
|
||||
<p>We are pleased to inform you that your sponsorship enquiry for <strong>{event.title}</strong> has been approved.</p>
|
||||
|
||||
<p>You can now proceed to select a sponsorship tier and complete the payment by visiting your dashboard <a href="{dashboard_link}">here</a>.</p>
|
||||
|
||||
<br>{host_name}</p>
|
||||
"""
|
||||
|
||||
frappe.sendmail(
|
||||
recipients=[self.owner],
|
||||
subject=subject,
|
||||
message=message,
|
||||
reference_doctype=self.doctype,
|
||||
reference_name=self.name,
|
||||
)
|
||||
@@ -0,0 +1,171 @@
|
||||
# Copyright (c) 2025, BWH Studios and Contributors
|
||||
# See license.txt
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
import frappe
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
EXTRA_TEST_RECORD_DEPENDENCIES = []
|
||||
IGNORE_TEST_RECORD_DEPENDENCIES = []
|
||||
|
||||
|
||||
class TestSponsorshipEnquiryEmail(IntegrationTestCase):
|
||||
"""Tests for Sponsorship Enquiry pitch deck email with template fallback logic."""
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.test_event = frappe.get_doc("Pohodex Event Manager Event", {"route": "test-route"})
|
||||
cls.test_event.auto_send_pitch_deck = False
|
||||
cls.test_event.sponsor_deck_email_template = None
|
||||
cls.test_event.sponsor_deck_cc = None
|
||||
cls.test_event.sponsor_deck_reply_to = None
|
||||
cls.test_event.save()
|
||||
|
||||
settings = frappe.get_doc("Pohodex Event Manager Settings")
|
||||
settings.auto_send_pitch_deck = False
|
||||
settings.default_sponsor_deck_email_template = None
|
||||
settings.default_sponsor_deck_cc = None
|
||||
settings.default_sponsor_deck_reply_to = None
|
||||
settings.save()
|
||||
|
||||
def _create_enquiry(self):
|
||||
return frappe.get_doc(
|
||||
{
|
||||
"doctype": "Sponsorship Enquiry",
|
||||
"event": self.test_event.name,
|
||||
"company_name": "Test Sponsor Co",
|
||||
"company_logo": "/files/test-logo.png",
|
||||
}
|
||||
).insert()
|
||||
|
||||
def _create_template(self, name, subject_prefix):
|
||||
if frappe.db.exists("Email Template", name):
|
||||
frappe.delete_doc("Email Template", name, force=True)
|
||||
return frappe.get_doc(
|
||||
{
|
||||
"doctype": "Email Template",
|
||||
"name": name,
|
||||
"subject": f"{subject_prefix} - {{{{ event.title }}}}",
|
||||
"response": f"<p>{subject_prefix} content</p>",
|
||||
}
|
||||
).insert()
|
||||
|
||||
def tearDown(self):
|
||||
for e in frappe.get_all("Sponsorship Enquiry", {"company_name": "Test Sponsor Co"}, pluck="name"):
|
||||
frappe.delete_doc("Sponsorship Enquiry", e, force=True)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
cls.test_event.auto_send_pitch_deck = False
|
||||
cls.test_event.sponsor_deck_email_template = None
|
||||
cls.test_event.sponsor_deck_cc = None
|
||||
cls.test_event.sponsor_deck_reply_to = None
|
||||
cls.test_event.save()
|
||||
|
||||
settings = frappe.get_doc("Pohodex Event Manager Settings")
|
||||
settings.auto_send_pitch_deck = False
|
||||
settings.default_sponsor_deck_email_template = None
|
||||
settings.default_sponsor_deck_cc = None
|
||||
settings.default_sponsor_deck_reply_to = None
|
||||
settings.save()
|
||||
super().tearDownClass()
|
||||
|
||||
@patch("frappe.sendmail")
|
||||
def test_no_email_when_disabled(self, mock_sendmail):
|
||||
self.test_event.auto_send_pitch_deck = False
|
||||
self.test_event.save()
|
||||
|
||||
enquiry = self._create_enquiry()
|
||||
enquiry.send_pitch_deck()
|
||||
|
||||
mock_sendmail.assert_not_called()
|
||||
|
||||
@patch("frappe.sendmail")
|
||||
def test_uses_event_settings(self, mock_sendmail):
|
||||
template = self._create_template("Event Sponsor Template", "EVENT")
|
||||
try:
|
||||
self.test_event.auto_send_pitch_deck = True
|
||||
self.test_event.sponsor_deck_email_template = template.name
|
||||
self.test_event.sponsor_deck_reply_to = "event@test.com"
|
||||
self.test_event.sponsor_deck_cc = "event-cc@test.com"
|
||||
self.test_event.save()
|
||||
|
||||
# after_insert hook triggers send_pitch_deck automatically
|
||||
self._create_enquiry()
|
||||
|
||||
mock_sendmail.assert_called_once()
|
||||
args = mock_sendmail.call_args[1]
|
||||
self.assertIn("EVENT", args["subject"])
|
||||
self.assertEqual(args["reply_to"], "event@test.com")
|
||||
self.assertEqual(args["cc"], "event-cc@test.com")
|
||||
finally:
|
||||
self.test_event.auto_send_pitch_deck = False
|
||||
self.test_event.sponsor_deck_email_template = None
|
||||
self.test_event.sponsor_deck_reply_to = None
|
||||
self.test_event.sponsor_deck_cc = None
|
||||
self.test_event.save()
|
||||
frappe.delete_doc("Email Template", template.name, force=True)
|
||||
|
||||
@patch("frappe.sendmail")
|
||||
def test_falls_back_to_global_settings(self, mock_sendmail):
|
||||
template = self._create_template("Global Sponsor Template", "GLOBAL")
|
||||
try:
|
||||
settings = frappe.get_doc("Pohodex Event Manager Settings")
|
||||
settings.auto_send_pitch_deck = True
|
||||
settings.default_sponsor_deck_email_template = template.name
|
||||
settings.default_sponsor_deck_reply_to = "global@test.com"
|
||||
settings.default_sponsor_deck_cc = "global-cc@test.com"
|
||||
settings.save()
|
||||
|
||||
# after_insert hook triggers send_pitch_deck automatically
|
||||
self._create_enquiry()
|
||||
|
||||
mock_sendmail.assert_called_once()
|
||||
args = mock_sendmail.call_args[1]
|
||||
self.assertIn("GLOBAL", args["subject"])
|
||||
self.assertEqual(args["reply_to"], "global@test.com")
|
||||
self.assertEqual(args["cc"], "global-cc@test.com")
|
||||
finally:
|
||||
settings.auto_send_pitch_deck = False
|
||||
settings.default_sponsor_deck_email_template = None
|
||||
settings.default_sponsor_deck_reply_to = None
|
||||
settings.default_sponsor_deck_cc = None
|
||||
settings.save()
|
||||
frappe.delete_doc("Email Template", template.name, force=True)
|
||||
|
||||
@patch("frappe.sendmail")
|
||||
def test_event_settings_take_precedence(self, mock_sendmail):
|
||||
event_template = self._create_template("Event Template", "EVENT")
|
||||
global_template = self._create_template("Global Template", "GLOBAL")
|
||||
try:
|
||||
self.test_event.auto_send_pitch_deck = True
|
||||
self.test_event.sponsor_deck_email_template = event_template.name
|
||||
self.test_event.sponsor_deck_reply_to = "event@test.com"
|
||||
self.test_event.save()
|
||||
|
||||
settings = frappe.get_doc("Pohodex Event Manager Settings")
|
||||
settings.auto_send_pitch_deck = True
|
||||
settings.default_sponsor_deck_email_template = global_template.name
|
||||
settings.default_sponsor_deck_reply_to = "global@test.com"
|
||||
settings.save()
|
||||
|
||||
# after_insert hook triggers send_pitch_deck automatically
|
||||
self._create_enquiry()
|
||||
|
||||
mock_sendmail.assert_called_once()
|
||||
args = mock_sendmail.call_args[1]
|
||||
self.assertIn("EVENT", args["subject"])
|
||||
self.assertEqual(args["reply_to"], "event@test.com")
|
||||
finally:
|
||||
self.test_event.auto_send_pitch_deck = False
|
||||
self.test_event.sponsor_deck_email_template = None
|
||||
self.test_event.sponsor_deck_reply_to = None
|
||||
self.test_event.save()
|
||||
settings.auto_send_pitch_deck = False
|
||||
settings.default_sponsor_deck_email_template = None
|
||||
settings.default_sponsor_deck_reply_to = None
|
||||
settings.save()
|
||||
frappe.delete_doc("Email Template", event_template.name, force=True)
|
||||
frappe.delete_doc("Email Template", global_template.name, force=True)
|
||||
@@ -0,0 +1,21 @@
|
||||
// Copyright (c) 2025, BWH Studios and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on("Talk Proposal", {
|
||||
refresh(frm) {
|
||||
if (frm.doc.status != "Accepted") {
|
||||
const btn = frm.add_custom_button(__("Accept and Create Talk"), () => {
|
||||
frm.call({
|
||||
method: "create_talk",
|
||||
doc: frm.doc,
|
||||
btn,
|
||||
}).then(({ message: talk }) => {
|
||||
frm.set_value("status", "Accepted");
|
||||
frm.save();
|
||||
frm.refresh();
|
||||
frappe.set_route("Form", "Event Talk", talk.name);
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,160 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"creation": "2025-07-26 11:59:34.416282",
|
||||
"doctype": "DocType",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"title",
|
||||
"submitted_by",
|
||||
"column_break_esac",
|
||||
"status",
|
||||
"event",
|
||||
"section_break_yqfb",
|
||||
"description",
|
||||
"column_break_kemm",
|
||||
"speakers",
|
||||
"section_break_ouiw",
|
||||
"phone",
|
||||
"section_break_additional",
|
||||
"additional_fields"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "title",
|
||||
"fieldtype": "Data",
|
||||
"label": "Title",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "submitted_by",
|
||||
"fieldtype": "Link",
|
||||
"label": "Submitted By",
|
||||
"options": "User"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_esac",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "Review Pending",
|
||||
"fieldname": "status",
|
||||
"fieldtype": "Select",
|
||||
"label": "Status",
|
||||
"options": "Review Pending\nShortlisted\nAccepted\nRejected"
|
||||
},
|
||||
{
|
||||
"fieldname": "event",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Event",
|
||||
"options": "Pohodex Event Manager Event",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_yqfb",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Text Editor",
|
||||
"label": "Description"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_kemm",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "speakers",
|
||||
"fieldtype": "Table",
|
||||
"label": "Speakers",
|
||||
"options": "Proposal Speaker",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_ouiw",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "phone",
|
||||
"fieldtype": "Phone",
|
||||
"label": "Phone"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_additional",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "additional_fields",
|
||||
"fieldtype": "Table",
|
||||
"label": "Additional Fields",
|
||||
"options": "Additional Field"
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [
|
||||
{
|
||||
"link_doctype": "Event Talk",
|
||||
"link_fieldname": "proposal"
|
||||
}
|
||||
],
|
||||
"modified": "2026-03-23 17:28:23.254661",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Proposals",
|
||||
"name": "Talk Proposal",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Event Manager",
|
||||
"select": 1,
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Pohodex Event Manager User",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"row_format": "Dynamic",
|
||||
"show_title_field_in_link": 1,
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": [
|
||||
{
|
||||
"color": "Green",
|
||||
"title": "Accepted"
|
||||
}
|
||||
],
|
||||
"title_field": "title",
|
||||
"track_changes": 1,
|
||||
"track_seen": 1,
|
||||
"track_views": 1
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
# Copyright (c) 2025, BWH Studios and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
|
||||
|
||||
class TalkProposal(Document):
|
||||
# begin: auto-generated types
|
||||
# This code is auto-generated. Do not modify anything in this block.
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from frappe.types import DF
|
||||
|
||||
from event_manager.proposals.doctype.proposal_speaker.proposal_speaker import ProposalSpeaker
|
||||
from event_manager.ticketing.doctype.additional_field.additional_field import AdditionalField
|
||||
|
||||
additional_fields: DF.Table[AdditionalField]
|
||||
description: DF.TextEditor | None
|
||||
event: DF.Link
|
||||
phone: DF.Phone | None
|
||||
speakers: DF.Table[ProposalSpeaker]
|
||||
status: DF.Literal["Review Pending", "Shortlisted", "Accepted", "Rejected"]
|
||||
submitted_by: DF.Link | None
|
||||
title: DF.Data
|
||||
# end: auto-generated types
|
||||
|
||||
def validate(self):
|
||||
if not self.submitted_by:
|
||||
self.submitted_by = frappe.session.user
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_talk(self):
|
||||
talk = get_mapped_doc("Talk Proposal", self.name, {"Talk Proposal": {"doctype": "Event Talk"}})
|
||||
|
||||
for speaker in self.speakers:
|
||||
user = frappe.db.exists("User", speaker.email)
|
||||
if not user:
|
||||
user = (
|
||||
frappe.get_doc(
|
||||
{
|
||||
"doctype": "User",
|
||||
"first_name": speaker.first_name,
|
||||
"last_name": speaker.last_name,
|
||||
"email": speaker.email,
|
||||
}
|
||||
)
|
||||
.insert()
|
||||
.name
|
||||
)
|
||||
|
||||
speaker_profile = frappe.db.exists("Speaker Profile", {"user": user})
|
||||
if not speaker_profile:
|
||||
speaker_profile = frappe.get_doc({"doctype": "Speaker Profile", "user": user}).insert().name
|
||||
|
||||
talk.append("speakers", {"speaker": speaker_profile})
|
||||
return talk.save()
|
||||
@@ -0,0 +1,20 @@
|
||||
# Copyright (c) 2025, BWH Studios and Contributors
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
# On IntegrationTestCase, the doctype test records and all
|
||||
# link-field test record dependencies are recursively loaded
|
||||
# Use these module variables to add/remove to/from that list
|
||||
EXTRA_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"]
|
||||
IGNORE_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"]
|
||||
|
||||
|
||||
class IntegrationTestTalkProposal(IntegrationTestCase):
|
||||
"""
|
||||
Integration tests for TalkProposal.
|
||||
Use this class for testing interactions between multiple components.
|
||||
"""
|
||||
|
||||
pass
|
||||
Reference in New Issue
Block a user