From 2dfac129fa08842a143a9ab07ef9a8208dbfdc49 Mon Sep 17 00:00:00 2001 From: Jeremy Rangel Date: Wed, 4 Jun 2025 18:09:56 -0700 Subject: [PATCH] Added override for email campaign sending to send emails at 08:15AM instead of midnight --- .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 199 bytes .../__pycache__/hooks.cpython-310.pyc | Bin 0 -> 1506 bytes rangeldigital/hooks.py | 23 +++++++ rangeldigital/install.py | 26 +++++++ rangeldigital/public/js/lead.js | 65 ++++++++++++++++++ .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 193 bytes rangeldigital/utilities/__init__.py | 1 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 209 bytes .../__pycache__/install.cpython-310.pyc | Bin 0 -> 933 bytes .../__pycache__/uninstall.cpython-310.pyc | Bin 0 -> 939 bytes rangeldigital/utilities/install.py | 26 +++++++ rangeldigital/utilities/lead/__init__.py | 1 + .../lead/__pycache__/__init__.cpython-310.pyc | Bin 0 -> 214 bytes .../lead/__pycache__/lead_api.cpython-310.pyc | Bin 0 -> 981 bytes rangeldigital/utilities/lead/lead_api.py | 31 +++++++++ rangeldigital/utilities/scheduler/__init__.py | 1 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 219 bytes .../__pycache__/scheduler.cpython-310.pyc | Bin 0 -> 425 bytes .../utilities/scheduler/scheduler.py | 4 ++ rangeldigital/utilities/uninstall.py | 26 +++++++ 20 files changed, 204 insertions(+) create mode 100644 rangeldigital/__pycache__/__init__.cpython-310.pyc create mode 100644 rangeldigital/__pycache__/hooks.cpython-310.pyc create mode 100644 rangeldigital/install.py create mode 100644 rangeldigital/public/js/lead.js create mode 100644 rangeldigital/rangel_digital/__pycache__/__init__.cpython-310.pyc create mode 100644 rangeldigital/utilities/__init__.py create mode 100644 rangeldigital/utilities/__pycache__/__init__.cpython-310.pyc create mode 100644 rangeldigital/utilities/__pycache__/install.cpython-310.pyc create mode 100644 rangeldigital/utilities/__pycache__/uninstall.cpython-310.pyc create mode 100644 rangeldigital/utilities/install.py create mode 100644 rangeldigital/utilities/lead/__init__.py create mode 100644 rangeldigital/utilities/lead/__pycache__/__init__.cpython-310.pyc create mode 100644 rangeldigital/utilities/lead/__pycache__/lead_api.cpython-310.pyc create mode 100644 rangeldigital/utilities/lead/lead_api.py create mode 100644 rangeldigital/utilities/scheduler/__init__.py create mode 100644 rangeldigital/utilities/scheduler/__pycache__/__init__.cpython-310.pyc create mode 100644 rangeldigital/utilities/scheduler/__pycache__/scheduler.cpython-310.pyc create mode 100644 rangeldigital/utilities/scheduler/scheduler.py create mode 100644 rangeldigital/utilities/uninstall.py diff --git a/rangeldigital/__pycache__/__init__.cpython-310.pyc b/rangeldigital/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8f40bbc2eb3e1e868a03e3948f2acb515600531f GIT binary patch literal 199 zcmd1j<>g`kf|J+GGsJ-OV-N=!FakLaKwQiLBvKfn7*ZI688n%ySPk?H^bGwp8E#mBE?C}IMt0~5bO^+St+68c$1iFxU%Ir_nwC8@>wX+?PQmhYAm6DmBS(2E8#*2^7%*!l^kJl@xyv1RYo1apelWGTYS~18v4h8^+o;4`| literal 0 HcmV?d00001 diff --git a/rangeldigital/__pycache__/hooks.cpython-310.pyc b/rangeldigital/__pycache__/hooks.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4438fd2edec7b9c2656385655753dd7af5a453ac GIT binary patch literal 1506 zcma)6OOMkq5Khu;w~x&}KtkN8ct~5Ii9kX^2%%kx3lAZ&3m0FaG@fnUP3)}gcGXPLgSh0%dH(Jpc3kPTX=o3!!SA$8iMEoPHV+NN978`umRY0z!bq&uWFu;}g= zo3wG$LlV(^a*dNl+2v;TB)HWoV4 zOmP4GV%BwXuk_WKJ0s1jHR8U7h~|6QxSy~%7^xsZ?2lC8#!~RS_2^0GY3IIa`J2@c z#+oHeGZS_^1UgPYF3*6|5YmVxp%$TWszM>dSa2Q1TIIbeXN)Vnk;IQqG)O#)66KGp z=wrUv&7j_KxiE2#tZht;cODLk$u(UJ`Y zcn))?cPi=U;(S56T}g_^N{iG)SDna8W=QRB<`4evFji~l;G?A~Z$nmkiUKTI6^WEA z)Xiy0stEFm)YC&qp`_lc4v}Hi2$Zi0?jN*>voz8(Z~N8WB&ZB4mrY9MyRKI>qAUw} zltR%oGtG1Yg=eN^4X7c=V#h!%$XK$>)DkJFrJ70GtYOZ;6%;oXwHs0q>uCmIoS@{2 z)&gQ7?Ov5f2ovC1;n$Y6UF@zamu#OyAEh5BV+!F&k)36X2lLgEZTvTu-H)T-qXMad z(M$@#G1H)e6V!GF^P$%V9uEVwtVv+4For^$DnFYRjpwN_75&0C|KW$&xt41$np1P^ c`Y*TPUOIKhM)cFUbT8eu>ozv5Pg`fd0ZJPh82|tP literal 0 HcmV?d00001 diff --git a/rangeldigital/hooks.py b/rangeldigital/hooks.py index 830f2b6..5525498 100644 --- a/rangeldigital/hooks.py +++ b/rangeldigital/hooks.py @@ -11,8 +11,31 @@ override_doctype_class = { } +# Lead: Add javascript to add "Add to Email Campaign to Action Dropdown" +doctype_js = { + "Lead": "public/js/lead.js" +} +# Add cron scheduler for email campaign sending + # Send email at 08:15 AM every day +scheduler_events = { + "cron": { + "15 8 * * *": [ + "rangeldigital.utilities.scheduler.scheduler.send_email_to_leads_or_contacts" + ] + } +} + +# Disable default send_email_to_leads_or_contacts +after_install = "rangeldigital.utilities.install.after_install" + + +# Enable default send_email_to_leads_or_contacts +before_uninstall = "rangeldigital.utilities.uninstall.before_uninstall" + + +#app_include_js = "/assets/rangeldigital/js/lead.js" web_include_js = [ diff --git a/rangeldigital/install.py b/rangeldigital/install.py new file mode 100644 index 0000000..ba4203b --- /dev/null +++ b/rangeldigital/install.py @@ -0,0 +1,26 @@ +import frappe + +def after_install(): + stop_erpnext_scheduled_send_email_job() + +def stop_erpnext_scheduled_send_email_job(): + # Get list of Scheduled Job Type docs with that method + job_docs = frappe.get_all( + "Scheduled Job Type", + filters={"method": "erpnext.crm.doctype.email_campaign.email_campaign.send_email_to_leads_or_contacts"}, + limit_page_length=1 + ) + + if not job_docs: + frappe.msgprint("Scheduled Job Type not found.") + return + + job_name = job_docs[0].name + job_doc = frappe.get_doc("Scheduled Job Type", job_name) + + # Set stopped = 1 + job_doc.stopped = 1 + job_doc.save(ignore_permissions=True) + frappe.db.commit() + + frappe.msgprint(f"Scheduled Job Type '{job_doc.method}' stopped successfully.") diff --git a/rangeldigital/public/js/lead.js b/rangeldigital/public/js/lead.js new file mode 100644 index 0000000..ec65adb --- /dev/null +++ b/rangeldigital/public/js/lead.js @@ -0,0 +1,65 @@ +frappe.ui.form.on('Lead', { + refresh(frm) { + // Only show the button if the Lead has been saved and has an email + if (!frm.doc.__islocal && frm.doc.email_id) { + frm.add_custom_button(__('Add to Email Campaign'), () => { + open_email_campaign_dialog(frm); + }, __('Action')); + } else if (!frm.doc.email_id) { + // Optional: give user visual feedback + frm.dashboard.set_headline(__('This Lead has no email address — cannot add to Email Campaign.')); + } + } +}); + +function open_email_campaign_dialog(frm) { + frappe.prompt([ + { + fieldname: 'campaign_name', + label: 'Campaign', + fieldtype: 'Link', + options: 'Campaign', + reqd: 1 + }, + { + fieldname: 'start_date', + label: 'Start Date', + fieldtype: 'Date', + default: frappe.datetime.get_today(), + reqd: 1 + } + ], + (values) => { + frappe.call({ + method: 'rangeldigital.utilities.lead.lead_api.add_lead_to_campaign', + args: { + lead_name: frm.doc.name, + campaign_name: values.campaign_name, + start_date: values.start_date + }, + freeze: true, + freeze_message: __('Creating Email Campaign...'), + callback: (r) => { + if (r.message && r.message.status === 'success') { + frappe.show_alert({ + message: __('Email Campaign created successfully'), + indicator: 'green' + }); + } else { + frappe.msgprint(__('Something went wrong. Please try again.')); + } + }, + error: (err) => { + if (err && err.message) { + frappe.msgprint(err.message); + } else { + frappe.msgprint(__('Unexpected error. Check the console.')); + console.error(err); + } + } + }); + }, + __('New Email Campaign'), + __('Create') + ); +} diff --git a/rangeldigital/rangel_digital/__pycache__/__init__.cpython-310.pyc b/rangeldigital/rangel_digital/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6df4fd18d6dca38e5c7c1fddde71c3f954ad98f1 GIT binary patch literal 193 zcmd1j<>g`kf|J+GGeGoX5P=LBfgA@QE@lA|DGb33nv8xc8Hzx{2;x_YerR!OQL%nj zQDR-iWKWZRHbC5XO<-9VCBWbB;(^V^D;}~ ag`kf^0^I3^5@67{oyaj6jY95EpX*i4=w?h7`tN22G|aRs%f)JwrcD##`L+ z@nxw+#hLke@$oAeikN`vz{Ib3{m^2dgnm|0VqSV`j(%`vNouivT2W#_K`NBeO-jv6 z&d>*n6zfA&rDUdOmL%q&@k&cFb23XZf!gEaGxIV_;^XxSDsOSvzF2FWcV$axX;{-S7{p+GpukxztT9mLb~0rfAynTAW-X!+4` zDCSZuD=9cgEx4?6Rq=(Hej^s|wpBmhkKTc_Fk0ALm$Dj-m6I@<*}Q4wZhJYEx|om9 z1EVcUs8YGSx@Rq&s+3cht~6k{8a69`l+T>BeCG-0LLecdwu9SW&kMoeJVSv7l_Lu( zXrM>6=u#q@1X7Ao&4=D)q)85=yz`Vm51Ms@xJ?vjtR~SdRX%zqH zs4ZulEhJ~ua>XdG3nA%TPrf1p#cj49?+5QJH+C>Lg{rt5jD+KMFf~Qf@a^$(!c{pN zfW;0rC?+c53VBCcI3a}-5WY}CK`m1Do29=$;Ztom{cQ}F+dxH*%^nUD;zqtPvO-8XvDJzF;1CpC97%~o%>^!7_r_n9so zIQtuJ%c5=yQK`G?4*gD3$qQzwHnh~r6{WK`p{YIM)2WaSdf^{PQ42?#qT+y7m7A@e z|65U4Y1f_Vg<^dZGMvE#9Kr2@>xP?JSSz%$t4IF{JzhON?pmjz$*gM^Wy!6bE~H%c zd$~_G-S=t59fcjQ{`u literal 0 HcmV?d00001 diff --git a/rangeldigital/utilities/install.py b/rangeldigital/utilities/install.py new file mode 100644 index 0000000..ba4203b --- /dev/null +++ b/rangeldigital/utilities/install.py @@ -0,0 +1,26 @@ +import frappe + +def after_install(): + stop_erpnext_scheduled_send_email_job() + +def stop_erpnext_scheduled_send_email_job(): + # Get list of Scheduled Job Type docs with that method + job_docs = frappe.get_all( + "Scheduled Job Type", + filters={"method": "erpnext.crm.doctype.email_campaign.email_campaign.send_email_to_leads_or_contacts"}, + limit_page_length=1 + ) + + if not job_docs: + frappe.msgprint("Scheduled Job Type not found.") + return + + job_name = job_docs[0].name + job_doc = frappe.get_doc("Scheduled Job Type", job_name) + + # Set stopped = 1 + job_doc.stopped = 1 + job_doc.save(ignore_permissions=True) + frappe.db.commit() + + frappe.msgprint(f"Scheduled Job Type '{job_doc.method}' stopped successfully.") diff --git a/rangeldigital/utilities/lead/__init__.py b/rangeldigital/utilities/lead/__init__.py new file mode 100644 index 0000000..f102a9c --- /dev/null +++ b/rangeldigital/utilities/lead/__init__.py @@ -0,0 +1 @@ +__version__ = "0.0.1" diff --git a/rangeldigital/utilities/lead/__pycache__/__init__.cpython-310.pyc b/rangeldigital/utilities/lead/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3c6f40862a8450cacc2e225a244d158f7766bbcc GIT binary patch literal 214 zcmd1j<>g`kf=ot-3^5@67{oyaj6jY95EpX*i4=w?h7`tN22G|aRs%f)JwrcD##`L+ z@nxw+#hLke@$oAeikN`vz{IZ<{m^2dgnm|0VqSV`j(%`vNouivT2W#_K`NBeO-jv6 z&d>*n6zfA&rDUdOmL%q&@k&cFb23XZf!cFY6I1l#<1_OzOXB183My}L*yQG?l;)(` Mfm~V)vYmqg03~lZ+yDRo literal 0 HcmV?d00001 diff --git a/rangeldigital/utilities/lead/__pycache__/lead_api.cpython-310.pyc b/rangeldigital/utilities/lead/__pycache__/lead_api.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4afff3c2026a14bebebac874089822b1df876df4 GIT binary patch literal 981 zcmZ8g&2AGh5VpO)*(L!M#08L6;RK2mTj&Nyk3WW0Hlx(4QP-)CQ&jex7^oe7yqHJ=}-5^*0oT z%;=f{U6BpbeS;x^Z7995dn>Y{K*7f}VZjOp(Urep7v9QKFFNmd>%I2D+t3St<*x&W zKpbmdoAA;_ie@qNt^((|@@G9rVCU;ij$}h9fj$gic*T(ZgCpyI5FD0|oJ^q+I;|U% ziqBG;bOtCpey6ETB(U^ub{?qSl>N#REdW0hyx|;f1qI)j2-ZrPVm|_Vf-nGuTtMZO` Y=3w!gs&V&ipE?R|9x>_DkUgaT0N^YY`v3p{ literal 0 HcmV?d00001 diff --git a/rangeldigital/utilities/lead/lead_api.py b/rangeldigital/utilities/lead/lead_api.py new file mode 100644 index 0000000..b4ff908 --- /dev/null +++ b/rangeldigital/utilities/lead/lead_api.py @@ -0,0 +1,31 @@ +import frappe +from frappe.utils import today + +@frappe.whitelist() +def add_lead_to_campaign(lead_name, campaign_name, start_date=None): + if not frappe.db.exists("Lead", lead_name): + frappe.throw("Lead does not exist") + + # Check if there's already a scheduled/in-progress campaign for this Lead with same name + existing = frappe.db.exists("Email Campaign", { + "recipient": lead_name, + "campaign_name": campaign_name, + "email_campaign_for": "Lead", + "status": ["in", ["Scheduled", "In Progress"]] + }) + + if existing: + frappe.throw("This Lead is already part of an Email Campaign with this name.") + + doc = frappe.new_doc("Email Campaign") + doc.update({ + "campaign_name": campaign_name, + "email_campaign_for": "Lead", + "recipient": lead_name, + "start_date": start_date or today(), + "sender": frappe.session.user # or replace with a specific default sender + }) + doc.insert(ignore_permissions=True) + frappe.db.commit() + + return {"status": "success", "message": "Email Campaign created"} diff --git a/rangeldigital/utilities/scheduler/__init__.py b/rangeldigital/utilities/scheduler/__init__.py new file mode 100644 index 0000000..f102a9c --- /dev/null +++ b/rangeldigital/utilities/scheduler/__init__.py @@ -0,0 +1 @@ +__version__ = "0.0.1" diff --git a/rangeldigital/utilities/scheduler/__pycache__/__init__.cpython-310.pyc b/rangeldigital/utilities/scheduler/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c2557f9bfe9ea62c5906bc71a275b56acb01174e GIT binary patch literal 219 zcmd1j<>g`kf{RBSGQ@!NV-N=!FakLaKwQiLBvKfn7*ZI688n%ySPk?H^bGwp8E#mBE?C}IMt0~5b8^+St+68c$1iFxU%Ir_nwC8@>wX+?PQmhYAm6DmBS(2E8#w#t!%*iat1Zpo%&PYuu%}FiNkB`sH%PfhH*DI*J#bJ}1 P4-&NlIky<(1P%rOV9Px< literal 0 HcmV?d00001 diff --git a/rangeldigital/utilities/scheduler/__pycache__/scheduler.cpython-310.pyc b/rangeldigital/utilities/scheduler/__pycache__/scheduler.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b97b32cecbdf8060be06b8f34c62623992a25164 GIT binary patch literal 425 zcmZ8dy-ve05Vn(6LRGsku`y>L7CSr z@Rd~ba2vrY4!zKZC&vv46}bysI-7({QRcdHp$MK(V;&=sH-gLr8R-hyt|52}uZfSW zA0$k$axi~e_8cKZwhh9zVAxtGh-?#t_i%D|FJNWIFcaCaO6gW7VJ^@vX`_?I_9#10 zI|E^iYQGQp5M>@T2Qxb0RaG|krx|M*`dT@e_8#iv0i@`?(5=0ILu<%f<0{8!in+tp RL+|qckNGq)FFPmm;vd