diff --git a/buzz/proposals/doctype/event_proposal/event_proposal.js b/buzz/proposals/doctype/event_proposal/event_proposal.js index aa70e91..2ecbd08 100644 --- a/buzz/proposals/doctype/event_proposal/event_proposal.js +++ b/buzz/proposals/doctype/event_proposal/event_proposal.js @@ -6,5 +6,14 @@ frappe.ui.form.on("Event Proposal", { if (!frm.is_new() && frm.doc.docstatus == 0) { frm.set_intro("Buzz Event will be created on submission of this document", "yellow"); } + + if (!frm.is_new() && frm.doc.docstatus == 0 && !frm.doc.host) { + frm.add_custom_button(__("Create Host"), () => { + frm.call("create_host").then(() => { + frappe.show_alert(__("Host Created!")); + frm.refresh(); + }); + }); + } }, }); diff --git a/buzz/proposals/doctype/event_proposal/event_proposal.py b/buzz/proposals/doctype/event_proposal/event_proposal.py index 425a3bc..adb9f26 100644 --- a/buzz/proposals/doctype/event_proposal/event_proposal.py +++ b/buzz/proposals/doctype/event_proposal/event_proposal.py @@ -63,17 +63,57 @@ def before_submit(self): self.create_event() + @frappe.whitelist() + def create_host(self): + self.check_permission("write") + + if self.host: + frappe.throw(_("A Host is already linked to this proposal.")) + + self._create_host() + self.save() + return self.host + + def _create_host(self): + if not self.host_company: + frappe.throw(_("Please enter the Company Name before creating a Host.")) + + if frappe.db.exists("Event Host", self.host_company): + host = frappe.get_doc("Event Host", self.host_company) + updated = False + if self.host_company_logo and not host.logo: + host.logo = self.host_company_logo + updated = True + if self.about_the_company and not host.about: + host.about = self.about_the_company + updated = True + if updated: + host.save(ignore_permissions=True) + else: + host = frappe.new_doc("Event Host") + host.name = self.host_company + host.logo = self.host_company_logo + host.about = self.about_the_company + host.insert(ignore_permissions=True) + + self.host = host.name + 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.")) + if not self.host_company: + frappe.throw(_("Please set a Host (or enter a Company Name) before submitting.")) + self._create_host() buzz_event = get_mapped_doc( "Event Proposal", self.name, {"Event Proposal": {"doctype": "Buzz Event"}} ) buzz_event.proposal = self.name + # host may have just been auto-created in-memory and is not yet persisted, + # so the mapped doc (read from DB) would miss it. + buzz_event.host = self.host buzz_event.insert() self.status = "Event Created" diff --git a/buzz/proposals/doctype/event_proposal/test_event_proposal.py b/buzz/proposals/doctype/event_proposal/test_event_proposal.py index 6eecb99..09362ca 100644 --- a/buzz/proposals/doctype/event_proposal/test_event_proposal.py +++ b/buzz/proposals/doctype/event_proposal/test_event_proposal.py @@ -1,7 +1,7 @@ # Copyright (c) 2025, BWH Studios and Contributors # See license.txt -# import frappe +import frappe from frappe.tests import IntegrationTestCase # On IntegrationTestCase, the doctype test records and all @@ -17,4 +17,116 @@ class IntegrationTestEventProposal(IntegrationTestCase): Use this class for testing interactions between multiple components. """ - pass + @classmethod + def setUpClass(cls): + super().setUpClass() + if not frappe.db.exists("Event Category", "Test Proposal Category"): + category = frappe.new_doc("Event Category") + category.name = "Test Proposal Category" + category.insert(ignore_permissions=True) + cls.category = "Test Proposal Category" + + def make_proposal(self, **kwargs): + proposal = frappe.new_doc("Event Proposal") + proposal.update( + { + "title": f"Proposal {frappe.generate_hash(length=6)}", + "category": self.category, + "medium": "Online", + "start_date": "2030-01-01", + "start_time": "10:00:00", + "end_time": "18:00:00", + "about": "

About the event

", + } + ) + proposal.update(kwargs) + proposal.insert(ignore_permissions=True) + return proposal + + def test_create_host_creates_and_links_event_host(self): + company = f"Acme {frappe.generate_hash(length=6)}" + proposal = self.make_proposal(host_company=company, about_the_company="We host events.") + + host_name = proposal.create_host() + + self.assertEqual(host_name, company) + self.assertEqual(proposal.host, company) + self.assertTrue(frappe.db.exists("Event Host", company)) + self.assertEqual(frappe.db.get_value("Event Host", company, "about"), "We host events.") + + def test_create_host_reuses_existing_host(self): + company = f"Existing {frappe.generate_hash(length=6)}" + existing = frappe.new_doc("Event Host") + existing.name = company + existing.insert(ignore_permissions=True) + + proposal = self.make_proposal(host_company=company) + proposal.create_host() + + self.assertEqual(proposal.host, company) + + def test_reuse_fills_only_empty_host_fields(self): + # Existing host with empty logo/about -> proposal values fill them in. + company = f"Empty {frappe.generate_hash(length=6)}" + frappe.get_doc({"doctype": "Event Host", "__newname": company}).insert(ignore_permissions=True) + + proposal = self.make_proposal( + host_company=company, + host_company_logo="/files/proposal-logo.png", + about_the_company="Proposal about.", + ) + proposal.create_host() + + host = frappe.get_doc("Event Host", company) + self.assertEqual(host.logo, "/files/proposal-logo.png") + self.assertEqual(host.about, "Proposal about.") + + def test_reuse_does_not_overwrite_populated_host_fields(self): + # Existing host already has logo/about -> proposal must not clobber them. + company = f"Populated {frappe.generate_hash(length=6)}" + frappe.get_doc( + { + "doctype": "Event Host", + "__newname": company, + "logo": "/files/original-logo.png", + "about": "Original about.", + } + ).insert(ignore_permissions=True) + + proposal = self.make_proposal( + host_company=company, + host_company_logo="/files/proposal-logo.png", + about_the_company="Proposal about.", + ) + proposal.create_host() + + host = frappe.get_doc("Event Host", company) + self.assertEqual(host.logo, "/files/original-logo.png") + self.assertEqual(host.about, "Original about.") + + def test_create_host_requires_company_name(self): + proposal = self.make_proposal() + with self.assertRaises(frappe.ValidationError): + proposal.create_host() + + def test_create_host_throws_if_already_linked(self): + company = f"Acme {frappe.generate_hash(length=6)}" + proposal = self.make_proposal(host_company=company) + proposal.create_host() + with self.assertRaises(frappe.ValidationError): + proposal.create_host() + + def test_submit_auto_creates_host_from_company(self): + company = f"Auto {frappe.generate_hash(length=6)}" + proposal = self.make_proposal(host_company=company, status="Approved") + + proposal.submit() + + self.assertEqual(proposal.host, company) + self.assertTrue(frappe.db.exists("Event Host", company)) + self.assertEqual(proposal.status, "Event Created") + + def test_submit_without_host_or_company_throws(self): + proposal = self.make_proposal(status="Approved") + with self.assertRaises(frappe.ValidationError): + proposal.submit()