Merge branch 'develop' of https://github.com/frappe/erpnext into develop
This commit is contained in:
commit
7b76cdbb29
@ -141,6 +141,11 @@ def get_data():
|
|||||||
"name": "Campaign",
|
"name": "Campaign",
|
||||||
"description": _("Sales campaigns."),
|
"description": _("Sales campaigns."),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "doctype",
|
||||||
|
"name": "Email Campaign",
|
||||||
|
"description": _("Sends Mails to lead or contact based on a Campaign schedule"),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "doctype",
|
"type": "doctype",
|
||||||
"name": "SMS Center",
|
"name": "SMS Center",
|
||||||
|
|||||||
@ -40,7 +40,6 @@ status_map = {
|
|||||||
["To Bill", "eval:self.per_delivered == 100 and self.per_billed < 100 and self.docstatus == 1"],
|
["To Bill", "eval:self.per_delivered == 100 and self.per_billed < 100 and self.docstatus == 1"],
|
||||||
["To Deliver", "eval:self.per_delivered < 100 and self.per_billed == 100 and self.docstatus == 1"],
|
["To Deliver", "eval:self.per_delivered < 100 and self.per_billed == 100 and self.docstatus == 1"],
|
||||||
["Completed", "eval:self.per_delivered == 100 and self.per_billed == 100 and self.docstatus == 1"],
|
["Completed", "eval:self.per_delivered == 100 and self.per_billed == 100 and self.docstatus == 1"],
|
||||||
["Completed", "eval:self.order_type == 'Maintenance' and self.per_billed == 100 and self.docstatus == 1"],
|
|
||||||
["Cancelled", "eval:self.docstatus==2"],
|
["Cancelled", "eval:self.docstatus==2"],
|
||||||
["Closed", "eval:self.status=='Closed'"],
|
["Closed", "eval:self.status=='Closed'"],
|
||||||
["On Hold", "eval:self.status=='On Hold'"],
|
["On Hold", "eval:self.status=='On Hold'"],
|
||||||
|
|||||||
@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"creation": "2019-06-30 15:56:20.306901",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"email_template",
|
||||||
|
"send_after_days"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "send_after_days",
|
||||||
|
"fieldtype": "Int",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Send After (days)",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "email_template",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Email Template",
|
||||||
|
"options": "Email Template",
|
||||||
|
"reqd": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"istable": 1,
|
||||||
|
"modified": "2019-07-12 11:46:43.184123",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "CRM",
|
||||||
|
"name": "Campaign Email Schedule",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [],
|
||||||
|
"quick_entry": 1,
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"track_changes": 1
|
||||||
|
}
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
# import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
class CampaignEmailSchedule(Document):
|
||||||
|
pass
|
||||||
0
erpnext/crm/doctype/email_campaign/__init__.py
Normal file
0
erpnext/crm/doctype/email_campaign/__init__.py
Normal file
8
erpnext/crm/doctype/email_campaign/email_campaign.js
Normal file
8
erpnext/crm/doctype/email_campaign/email_campaign.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
frappe.ui.form.on('Email Campaign', {
|
||||||
|
email_campaign_for: function(frm) {
|
||||||
|
frm.set_value('recipient', '');
|
||||||
|
}
|
||||||
|
});
|
||||||
95
erpnext/crm/doctype/email_campaign/email_campaign.json
Normal file
95
erpnext/crm/doctype/email_campaign/email_campaign.json
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
{
|
||||||
|
"autoname": "format:MAIL-CAMP-{YYYY}-{#####}",
|
||||||
|
"creation": "2019-06-30 16:05:30.015615",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"campaign_name",
|
||||||
|
"email_campaign_for",
|
||||||
|
"recipient",
|
||||||
|
"sender",
|
||||||
|
"column_break_4",
|
||||||
|
"start_date",
|
||||||
|
"end_date",
|
||||||
|
"status"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "campaign_name",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Campaign",
|
||||||
|
"options": "Campaign",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "status",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"label": "Status",
|
||||||
|
"options": "\nScheduled\nIn Progress\nCompleted\nUnsubscribed",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_4",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "start_date",
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"label": "Start Date",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "end_date",
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"label": "End Date",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "Lead",
|
||||||
|
"fieldname": "email_campaign_for",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Email Campaign For ",
|
||||||
|
"options": "\nLead\nContact"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "recipient",
|
||||||
|
"fieldtype": "Dynamic Link",
|
||||||
|
"label": "Recipient",
|
||||||
|
"options": "email_campaign_for",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "__user",
|
||||||
|
"fieldname": "sender",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Sender",
|
||||||
|
"options": "User"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"modified": "2019-07-12 13:47:37.261213",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "CRM",
|
||||||
|
"name": "Email Campaign",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "System Manager",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"quick_entry": 1,
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"track_changes": 1
|
||||||
|
}
|
||||||
102
erpnext/crm/doctype/email_campaign/email_campaign.py
Normal file
102
erpnext/crm/doctype/email_campaign/email_campaign.py
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
import frappe
|
||||||
|
from frappe import _
|
||||||
|
from frappe.utils import getdate, add_days, today, nowdate, cstr
|
||||||
|
from frappe.model.document import Document
|
||||||
|
from frappe.core.doctype.communication.email import make
|
||||||
|
|
||||||
|
class EmailCampaign(Document):
|
||||||
|
def validate(self):
|
||||||
|
self.set_date()
|
||||||
|
#checking if email is set for lead. Not checking for contact as email is a mandatory field for contact.
|
||||||
|
if self.email_campaign_for == "Lead":
|
||||||
|
self.validate_lead()
|
||||||
|
self.validate_email_campaign_already_exists()
|
||||||
|
self.update_status()
|
||||||
|
|
||||||
|
def set_date(self):
|
||||||
|
if getdate(self.start_date) < getdate(today()):
|
||||||
|
frappe.throw(_("Start Date cannot be before the current date"))
|
||||||
|
#set the end date as start date + max(send after days) in campaign schedule
|
||||||
|
send_after_days = []
|
||||||
|
campaign = frappe.get_doc("Campaign", self.campaign_name)
|
||||||
|
for entry in campaign.get("campaign_schedules"):
|
||||||
|
send_after_days.append(entry.send_after_days)
|
||||||
|
try:
|
||||||
|
end_date = add_days(getdate(self.start_date), max(send_after_days))
|
||||||
|
except ValueError:
|
||||||
|
frappe.throw(_("Please set up the Campaign Schedule in the Campaign {0}").format(self.campaign_name))
|
||||||
|
|
||||||
|
def validate_lead(self):
|
||||||
|
lead_email_id = frappe.db.get_value("Lead", self.recipient, 'email_id')
|
||||||
|
if not lead_email_id:
|
||||||
|
lead_name = frappe.db.get_value("Lead", self.recipient, 'lead_name')
|
||||||
|
frappe.throw(_("Please set an email id for the Lead {0}").format(lead_name))
|
||||||
|
|
||||||
|
def validate_email_campaign_already_exists(self):
|
||||||
|
email_campaign_exists = frappe.db.exists("Email Campaign", {
|
||||||
|
"campaign_name": self.campaign_name,
|
||||||
|
"recipient": self.recipient,
|
||||||
|
"status": ("in", ["In Progress", "Scheduled"])
|
||||||
|
})
|
||||||
|
if email_campaign_exists:
|
||||||
|
frappe.throw(_("The Campaign '{0}' already exists for the {1} '{2}'").format(self.campaign_name, self.email_campaign_for, self.recipient))
|
||||||
|
|
||||||
|
def update_status(self):
|
||||||
|
start_date = getdate(self.start_date)
|
||||||
|
end_date = getdate(self.end_date)
|
||||||
|
today_date = getdate(today())
|
||||||
|
if start_date > today_date:
|
||||||
|
self.status = "Scheduled"
|
||||||
|
elif end_date >= today_date:
|
||||||
|
self.status = "In Progress"
|
||||||
|
elif end_date < today_date:
|
||||||
|
self.status = "Completed"
|
||||||
|
|
||||||
|
#called through hooks to send campaign mails to leads
|
||||||
|
def send_email_to_leads_or_contacts():
|
||||||
|
email_campaigns = frappe.get_all("Email Campaign", filters = { 'status': ('not in', ['Unsubscribed', 'Completed', 'Scheduled']) })
|
||||||
|
for camp in email_campaigns:
|
||||||
|
email_campaign = frappe.get_doc("Email Campaign", camp.name)
|
||||||
|
campaign = frappe.get_cached_doc("Campaign", email_campaign.campaign_name)
|
||||||
|
for entry in campaign.get("campaign_schedules"):
|
||||||
|
scheduled_date = add_days(email_campaign.get('start_date'), entry.get('send_after_days'))
|
||||||
|
if scheduled_date == getdate(today()):
|
||||||
|
send_mail(entry, email_campaign)
|
||||||
|
|
||||||
|
def send_mail(entry, email_campaign):
|
||||||
|
recipient = frappe.db.get_value(email_campaign.email_campaign_for, email_campaign.get("recipient"), 'email_id')
|
||||||
|
|
||||||
|
email_template = frappe.get_doc("Email Template", entry.get("email_template"))
|
||||||
|
sender = frappe.db.get_value("User", email_campaign.get("sender"), 'email')
|
||||||
|
|
||||||
|
# send mail and link communication to document
|
||||||
|
comm = make(
|
||||||
|
doctype = "Email Campaign",
|
||||||
|
name = email_campaign.name,
|
||||||
|
subject = email_template.get("subject"),
|
||||||
|
content = email_template.get("response"),
|
||||||
|
sender = sender,
|
||||||
|
recipients = recipient,
|
||||||
|
communication_medium = "Email",
|
||||||
|
sent_or_received = "Sent",
|
||||||
|
send_email = True,
|
||||||
|
email_template = email_template.name
|
||||||
|
)
|
||||||
|
return comm
|
||||||
|
|
||||||
|
#called from hooks on doc_event Email Unsubscribe
|
||||||
|
def unsubscribe_recipient(unsubscribe, method):
|
||||||
|
if unsubscribe.reference_doctype == 'Email Campaign':
|
||||||
|
frappe.db.set_value("Email Campaign", unsubscribe.reference_name, "status", "Unsubscribed")
|
||||||
|
|
||||||
|
#called through hooks to update email campaign status daily
|
||||||
|
def set_email_campaign_status():
|
||||||
|
email_campaigns = frappe.get_all("Email Campaign", filters = { 'status': ('!=', 'Unsubscribed')})
|
||||||
|
for entry in email_campaigns:
|
||||||
|
email_campaign = frappe.get_doc("Email Campaign", entry.name)
|
||||||
|
email_campaign.update_status()
|
||||||
11
erpnext/crm/doctype/email_campaign/email_campaign_list.js
Normal file
11
erpnext/crm/doctype/email_campaign/email_campaign_list.js
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
frappe.listview_settings['Email Campaign'] = {
|
||||||
|
get_indicator: function(doc) {
|
||||||
|
var colors = {
|
||||||
|
"Unsubscribed": "red",
|
||||||
|
"Scheduled": "blue",
|
||||||
|
"In Progress": "orange",
|
||||||
|
"Completed": "green"
|
||||||
|
};
|
||||||
|
return [__(doc.status), colors[doc.status], "status,=," + doc.status];
|
||||||
|
}
|
||||||
|
};
|
||||||
10
erpnext/crm/doctype/email_campaign/test_email_campaign.py
Normal file
10
erpnext/crm/doctype/email_campaign/test_email_campaign.py
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
|
# See license.txt
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
class TestEmailCampaign(unittest.TestCase):
|
||||||
|
pass
|
||||||
@ -233,6 +233,9 @@ doc_events = {
|
|||||||
},
|
},
|
||||||
"Contact":{
|
"Contact":{
|
||||||
"on_trash": "erpnext.support.doctype.issue.issue.update_issue"
|
"on_trash": "erpnext.support.doctype.issue.issue.update_issue"
|
||||||
|
},
|
||||||
|
"Email Unsubscribe": {
|
||||||
|
"after_insert": "erpnext.crm.doctype.email_campaign.email_campaign.unsubscribe_recipient"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -272,6 +275,8 @@ scheduler_events = {
|
|||||||
"erpnext.projects.doctype.project.project.send_project_status_email_to_users",
|
"erpnext.projects.doctype.project.project.send_project_status_email_to_users",
|
||||||
"erpnext.quality_management.doctype.quality_review.quality_review.review",
|
"erpnext.quality_management.doctype.quality_review.quality_review.review",
|
||||||
"erpnext.support.doctype.service_level_agreement.service_level_agreement.check_agreement_status",
|
"erpnext.support.doctype.service_level_agreement.service_level_agreement.check_agreement_status",
|
||||||
|
"erpnext.crm.doctype.email_campaign.email_campaign.send_email_to_leads_or_contacts",
|
||||||
|
"erpnext.crm.doctype.email_campaign.email_campaign.set_email_campaign_status"
|
||||||
],
|
],
|
||||||
"daily_long": [
|
"daily_long": [
|
||||||
"erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool.update_latest_price_in_all_boms"
|
"erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool.update_latest_price_in_all_boms"
|
||||||
|
|||||||
@ -6,18 +6,13 @@
|
|||||||
"description": "Keep Track of Sales Campaigns. Keep track of Leads, Quotations, Sales Order etc from Campaigns to gauge Return on Investment. ",
|
"description": "Keep Track of Sales Campaigns. Keep track of Leads, Quotations, Sales Order etc from Campaigns to gauge Return on Investment. ",
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"document_type": "Setup",
|
"document_type": "Setup",
|
||||||
|
"engine": "InnoDB",
|
||||||
"field_order": [
|
"field_order": [
|
||||||
"campaign",
|
"campaign",
|
||||||
"campaign_name",
|
"campaign_name",
|
||||||
"naming_series",
|
"naming_series",
|
||||||
"from_date",
|
"campaign_schedules_section",
|
||||||
"column_break1",
|
"campaign_schedules",
|
||||||
"status",
|
|
||||||
"to_date",
|
|
||||||
"budget_section",
|
|
||||||
"currency",
|
|
||||||
"column_break2",
|
|
||||||
"budget",
|
|
||||||
"description_section",
|
"description_section",
|
||||||
"description"
|
"description"
|
||||||
],
|
],
|
||||||
@ -52,57 +47,25 @@
|
|||||||
"oldfieldtype": "Text",
|
"oldfieldtype": "Text",
|
||||||
"width": "300px"
|
"width": "300px"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"fieldname": "status",
|
|
||||||
"fieldtype": "Select",
|
|
||||||
"in_list_view": 1,
|
|
||||||
"label": "Status",
|
|
||||||
"options": "\nPlanned\nIn Progress\nCompleted\nCancelled",
|
|
||||||
"reqd": 1,
|
|
||||||
"default": "Planned"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "from_date",
|
|
||||||
"fieldtype": "Date",
|
|
||||||
"label": "From Date"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "to_date",
|
|
||||||
"fieldtype": "Date",
|
|
||||||
"label": "To Date"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "column_break1",
|
|
||||||
"fieldtype": "Column Break"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "budget",
|
|
||||||
"fieldtype": "Currency",
|
|
||||||
"label": "Budget"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "description_section",
|
"fieldname": "description_section",
|
||||||
"fieldtype": "Section Break"
|
"fieldtype": "Section Break"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "currency",
|
"fieldname": "campaign_schedules",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Table",
|
||||||
"label": "Currency",
|
"label": "Campaign Schedules",
|
||||||
"options": "Currency"
|
"options": "Campaign Email Schedule"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "column_break2",
|
"fieldname": "campaign_schedules_section",
|
||||||
"fieldtype": "Column Break"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "budget_section",
|
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"label": "BUDGET"
|
"label": "Campaign Schedules"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-bullhorn",
|
"icon": "fa fa-bullhorn",
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"modified": "2019-04-29 22:09:39.251884",
|
"modified": "2019-07-22 12:03:39.832342",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Selling",
|
"module": "Selling",
|
||||||
"name": "Campaign",
|
"name": "Campaign",
|
||||||
@ -140,5 +103,7 @@
|
|||||||
"write": 1
|
"write": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"quick_entry": 1
|
"quick_entry": 1,
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC"
|
||||||
}
|
}
|
||||||
13
erpnext/selling/doctype/campaign/campaign_dashboard.py
Normal file
13
erpnext/selling/doctype/campaign/campaign_dashboard.py
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
from frappe import _
|
||||||
|
|
||||||
|
def get_data():
|
||||||
|
return {
|
||||||
|
'fieldname': 'campaign_name',
|
||||||
|
'transactions': [
|
||||||
|
{
|
||||||
|
'label': _('Email Campaigns'),
|
||||||
|
'items': ['Email Campaign']
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user