mirror of
https://github.com/frappe/erpnext.git
synced 2026-06-01 19:29:10 +00:00
Merge branch 'version-14-hotfix' of https://github.com/frappe/erpnext into default_dates_in_reports
This commit is contained in:
@@ -1,26 +1,26 @@
|
||||
// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Appointment', {
|
||||
refresh: function(frm) {
|
||||
if(frm.doc.lead){
|
||||
frm.add_custom_button(frm.doc.lead,()=>{
|
||||
frappe.ui.form.on("Appointment", {
|
||||
refresh: function (frm) {
|
||||
if (frm.doc.lead) {
|
||||
frm.add_custom_button(frm.doc.lead, () => {
|
||||
frappe.set_route("Form", "Lead", frm.doc.lead);
|
||||
});
|
||||
}
|
||||
if(frm.doc.calendar_event){
|
||||
frm.add_custom_button(__(frm.doc.calendar_event),()=>{
|
||||
if (frm.doc.calendar_event) {
|
||||
frm.add_custom_button(__(frm.doc.calendar_event), () => {
|
||||
frappe.set_route("Form", "Event", frm.doc.calendar_event);
|
||||
});
|
||||
}
|
||||
},
|
||||
onload: function(frm){
|
||||
frm.set_query("appointment_with", function(){
|
||||
onload: function (frm) {
|
||||
frm.set_query("appointment_with", function () {
|
||||
return {
|
||||
filters : {
|
||||
"name": ["in", ["Customer", "Lead"]]
|
||||
}
|
||||
filters: {
|
||||
name: ["in", ["Customer", "Lead"]],
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -178,7 +178,9 @@ class Appointment(Document):
|
||||
"starts_on": self.scheduled_time,
|
||||
"status": "Open",
|
||||
"type": "Public",
|
||||
"send_reminder": frappe.db.get_single_value("Appointment Booking Settings", "email_reminders"),
|
||||
"send_reminder": frappe.db.get_single_value(
|
||||
"Appointment Booking Settings", "email_reminders"
|
||||
),
|
||||
"event_participants": [
|
||||
dict(reference_doctype=self.appointment_with, reference_docname=self.party)
|
||||
],
|
||||
@@ -231,9 +233,7 @@ def _get_agent_list_as_strings():
|
||||
|
||||
|
||||
def _check_agent_availability(agent_email, scheduled_time):
|
||||
appointemnts_at_scheduled_time = frappe.get_all(
|
||||
"Appointment", filters={"scheduled_time": scheduled_time}
|
||||
)
|
||||
appointemnts_at_scheduled_time = frappe.get_all("Appointment", filters={"scheduled_time": scheduled_time})
|
||||
for appointment in appointemnts_at_scheduled_time:
|
||||
if appointment._assign == agent_email:
|
||||
return False
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
frappe.ui.form.on('Appointment Booking Settings', 'validate',check_times);
|
||||
frappe.ui.form.on("Appointment Booking Settings", "validate", check_times);
|
||||
function check_times(frm) {
|
||||
$.each(frm.doc.availability_of_slots || [], function (i, d) {
|
||||
let from_time = Date.parse('01/01/2019 ' + d.from_time);
|
||||
let to_time = Date.parse('01/01/2019 ' + d.to_time);
|
||||
let from_time = Date.parse("01/01/2019 " + d.from_time);
|
||||
let to_time = Date.parse("01/01/2019 " + d.to_time);
|
||||
if (from_time > to_time) {
|
||||
frappe.throw(__('In row {0} of Appointment Booking Slots: "To Time" must be later than "From Time".', [i + 1]));
|
||||
frappe.throw(
|
||||
__('In row {0} of Appointment Booking Slots: "To Time" must be later than "From Time".', [
|
||||
i + 1,
|
||||
])
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
|
||||
import datetime
|
||||
import typing
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
@@ -10,7 +11,7 @@ from frappe.model.document import Document
|
||||
|
||||
|
||||
class AppointmentBookingSettings(Document):
|
||||
agent_list = [] # Hack
|
||||
agent_list: typing.ClassVar[list] = [] # Hack
|
||||
min_date = "01/01/1970 "
|
||||
format_string = "%d/%m/%Y %H:%M:%S"
|
||||
|
||||
@@ -19,13 +20,13 @@ class AppointmentBookingSettings(Document):
|
||||
|
||||
def save(self):
|
||||
self.number_of_agents = len(self.agent_list)
|
||||
super(AppointmentBookingSettings, self).save()
|
||||
super().save()
|
||||
|
||||
def validate_availability_of_slots(self):
|
||||
for record in self.availability_of_slots:
|
||||
from_time = datetime.datetime.strptime(self.min_date + record.from_time, self.format_string)
|
||||
to_time = datetime.datetime.strptime(self.min_date + record.to_time, self.format_string)
|
||||
timedelta = to_time - from_time
|
||||
to_time - from_time
|
||||
self.validate_from_and_to_time(from_time, to_time, record)
|
||||
self.duration_is_divisible(from_time, to_time)
|
||||
|
||||
@@ -39,6 +40,4 @@ class AppointmentBookingSettings(Document):
|
||||
def duration_is_divisible(self, from_time, to_time):
|
||||
timedelta = to_time - from_time
|
||||
if timedelta.total_seconds() % (self.appointment_duration * 60):
|
||||
frappe.throw(
|
||||
_("The difference between from time and To Time must be a multiple of Appointment")
|
||||
)
|
||||
frappe.throw(_("The difference between from time and To Time must be a multiple of Appointment"))
|
||||
|
||||
@@ -1,17 +1,25 @@
|
||||
// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Campaign', {
|
||||
refresh: function(frm) {
|
||||
frappe.ui.form.on("Campaign", {
|
||||
refresh: function (frm) {
|
||||
erpnext.toggle_naming_series();
|
||||
|
||||
if (frm.is_new()) {
|
||||
frm.toggle_display("naming_series", frappe.boot.sysdefaults.campaign_naming_by=="Naming Series");
|
||||
frm.toggle_display(
|
||||
"naming_series",
|
||||
frappe.boot.sysdefaults.campaign_naming_by == "Naming Series"
|
||||
);
|
||||
} else {
|
||||
cur_frm.add_custom_button(__("View Leads"), function() {
|
||||
frappe.route_options = {"source": "Campaign", "campaign_name": frm.doc.name};
|
||||
frappe.set_route("List", "Lead");
|
||||
}, "fa fa-list", true);
|
||||
cur_frm.add_custom_button(
|
||||
__("View Leads"),
|
||||
function () {
|
||||
frappe.route_options = { source: "Campaign", campaign_name: frm.doc.name };
|
||||
frappe.set_route("List", "Lead");
|
||||
},
|
||||
"fa fa-list",
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Competitor', {
|
||||
frappe.ui.form.on("Competitor", {
|
||||
// refresh: function(frm) {
|
||||
|
||||
// }
|
||||
});
|
||||
|
||||
@@ -5,12 +5,12 @@ frappe.ui.form.on("Contract", {
|
||||
contract_template: function (frm) {
|
||||
if (frm.doc.contract_template) {
|
||||
frappe.call({
|
||||
method: 'erpnext.crm.doctype.contract_template.contract_template.get_contract_template',
|
||||
method: "erpnext.crm.doctype.contract_template.contract_template.get_contract_template",
|
||||
args: {
|
||||
template_name: frm.doc.contract_template,
|
||||
doc: frm.doc
|
||||
doc: frm.doc,
|
||||
},
|
||||
callback: function(r) {
|
||||
callback: function (r) {
|
||||
if (r && r.message) {
|
||||
let contract_template = r.message.contract_template;
|
||||
frm.set_value("contract_terms", r.message.contract_terms);
|
||||
@@ -18,15 +18,15 @@ frappe.ui.form.on("Contract", {
|
||||
|
||||
if (frm.doc.requires_fulfilment) {
|
||||
// Populate the fulfilment terms table from a contract template, if any
|
||||
r.message.contract_template.fulfilment_terms.forEach(element => {
|
||||
r.message.contract_template.fulfilment_terms.forEach((element) => {
|
||||
let d = frm.add_child("fulfilment_terms");
|
||||
d.requirement = element.requirement;
|
||||
});
|
||||
frm.refresh_field("fulfilment_terms");
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -13,12 +13,12 @@ class Contract(Document):
|
||||
name = self.party_name
|
||||
|
||||
if self.contract_template:
|
||||
name += " - {} Agreement".format(self.contract_template)
|
||||
name += f" - {self.contract_template} Agreement"
|
||||
|
||||
# If identical, append contract name with the next number in the iteration
|
||||
if frappe.db.exists("Contract", name):
|
||||
count = len(frappe.get_all("Contract", filters={"name": ["like", "%{}%".format(name)]}))
|
||||
name = "{} - {}".format(name, count)
|
||||
count = len(frappe.get_all("Contract", filters={"name": ["like", f"%{name}%"]}))
|
||||
name = f"{name} - {count}"
|
||||
|
||||
self.name = _(name)
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
frappe.listview_settings['Contract'] = {
|
||||
frappe.listview_settings["Contract"] = {
|
||||
add_fields: ["status"],
|
||||
get_indicator: function (doc) {
|
||||
if (doc.status == "Unsigned") {
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Contract Fulfilment Checklist', {
|
||||
refresh: function(frm) {
|
||||
|
||||
}
|
||||
frappe.ui.form.on("Contract Fulfilment Checklist", {
|
||||
refresh: function (frm) {},
|
||||
});
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Contract Template', {
|
||||
refresh: function(frm) {
|
||||
|
||||
}
|
||||
frappe.ui.form.on("Contract Template", {
|
||||
refresh: function (frm) {},
|
||||
});
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('CRM Settings', {
|
||||
frappe.ui.form.on("CRM Settings", {
|
||||
// refresh: function(frm) {
|
||||
|
||||
// }
|
||||
});
|
||||
|
||||
@@ -1,8 +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', '');
|
||||
}
|
||||
frappe.ui.form.on("Email Campaign", {
|
||||
email_campaign_for: function (frm) {
|
||||
frm.set_value("recipient", "");
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
frappe.listview_settings['Email Campaign'] = {
|
||||
get_indicator: function(doc) {
|
||||
frappe.listview_settings["Email Campaign"] = {
|
||||
get_indicator: function (doc) {
|
||||
var colors = {
|
||||
"Unsubscribed": "red",
|
||||
"Scheduled": "blue",
|
||||
Unsubscribed: "red",
|
||||
Scheduled: "blue",
|
||||
"In Progress": "orange",
|
||||
"Completed": "green"
|
||||
Completed: "green",
|
||||
};
|
||||
return [__(doc.status), colors[doc.status], "status,=," + doc.status];
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -5,46 +5,50 @@ frappe.provide("erpnext");
|
||||
cur_frm.email_field = "email_id";
|
||||
|
||||
erpnext.LeadController = class LeadController extends frappe.ui.form.Controller {
|
||||
setup () {
|
||||
setup() {
|
||||
this.frm.make_methods = {
|
||||
'Customer': this.make_customer,
|
||||
'Quotation': this.make_quotation,
|
||||
'Opportunity': this.make_opportunity
|
||||
Customer: this.make_customer,
|
||||
Quotation: this.make_quotation,
|
||||
Opportunity: this.make_opportunity,
|
||||
};
|
||||
|
||||
// For avoiding integration issues.
|
||||
this.frm.set_df_property('first_name', 'reqd', true);
|
||||
this.frm.set_df_property("first_name", "reqd", true);
|
||||
}
|
||||
|
||||
onload () {
|
||||
onload() {
|
||||
this.frm.set_query("customer", function (doc, cdt, cdn) {
|
||||
return { query: "erpnext.controllers.queries.customer_query" }
|
||||
return { query: "erpnext.controllers.queries.customer_query" };
|
||||
});
|
||||
|
||||
this.frm.set_query("lead_owner", function (doc, cdt, cdn) {
|
||||
return { query: "frappe.core.doctype.user.user.user_query" }
|
||||
return { query: "frappe.core.doctype.user.user.user_query" };
|
||||
});
|
||||
}
|
||||
|
||||
refresh () {
|
||||
refresh() {
|
||||
var me = this;
|
||||
let doc = this.frm.doc;
|
||||
erpnext.toggle_naming_series();
|
||||
frappe.dynamic_link = {
|
||||
doc: doc,
|
||||
fieldname: 'name',
|
||||
doctype: 'Lead'
|
||||
fieldname: "name",
|
||||
doctype: "Lead",
|
||||
};
|
||||
|
||||
if (!this.frm.is_new() && doc.__onload && !doc.__onload.is_customer) {
|
||||
this.frm.add_custom_button(__("Customer"), this.make_customer, __("Create"));
|
||||
this.frm.add_custom_button(__("Opportunity"), function() {
|
||||
me.frm.trigger("make_opportunity");
|
||||
}, __("Create"));
|
||||
this.frm.add_custom_button(
|
||||
__("Opportunity"),
|
||||
function () {
|
||||
me.frm.trigger("make_opportunity");
|
||||
},
|
||||
__("Create")
|
||||
);
|
||||
this.frm.add_custom_button(__("Quotation"), this.make_quotation, __("Create"));
|
||||
if (!doc.__onload.linked_prospects.length) {
|
||||
this.frm.add_custom_button(__("Prospect"), this.make_prospect, __("Create"));
|
||||
this.frm.add_custom_button(__('Add to Prospect'), this.add_lead_to_prospect, __('Action'));
|
||||
this.frm.add_custom_button(__("Add to Prospect"), this.add_lead_to_prospect, __("Action"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,50 +62,54 @@ erpnext.LeadController = class LeadController extends frappe.ui.form.Controller
|
||||
this.show_activities();
|
||||
}
|
||||
|
||||
add_lead_to_prospect () {
|
||||
frappe.prompt([
|
||||
{
|
||||
fieldname: 'prospect',
|
||||
label: __('Prospect'),
|
||||
fieldtype: 'Link',
|
||||
options: 'Prospect',
|
||||
reqd: 1
|
||||
}
|
||||
],
|
||||
function(data) {
|
||||
frappe.call({
|
||||
method: 'erpnext.crm.doctype.lead.lead.add_lead_to_prospect',
|
||||
args: {
|
||||
'lead': cur_frm.doc.name,
|
||||
'prospect': data.prospect
|
||||
add_lead_to_prospect() {
|
||||
frappe.prompt(
|
||||
[
|
||||
{
|
||||
fieldname: "prospect",
|
||||
label: __("Prospect"),
|
||||
fieldtype: "Link",
|
||||
options: "Prospect",
|
||||
reqd: 1,
|
||||
},
|
||||
callback: function(r) {
|
||||
if (!r.exc) {
|
||||
frm.reload_doc();
|
||||
}
|
||||
},
|
||||
freeze: true,
|
||||
freeze_message: __('Adding Lead to Prospect...')
|
||||
});
|
||||
}, __('Add Lead to Prospect'), __('Add'));
|
||||
],
|
||||
function (data) {
|
||||
frappe.call({
|
||||
method: "erpnext.crm.doctype.lead.lead.add_lead_to_prospect",
|
||||
args: {
|
||||
lead: cur_frm.doc.name,
|
||||
prospect: data.prospect,
|
||||
},
|
||||
callback: function (r) {
|
||||
if (!r.exc) {
|
||||
frm.reload_doc();
|
||||
}
|
||||
},
|
||||
freeze: true,
|
||||
freeze_message: __("Adding Lead to Prospect..."),
|
||||
});
|
||||
},
|
||||
__("Add Lead to Prospect"),
|
||||
__("Add")
|
||||
);
|
||||
}
|
||||
|
||||
make_customer () {
|
||||
make_customer() {
|
||||
frappe.model.open_mapped_doc({
|
||||
method: "erpnext.crm.doctype.lead.lead.make_customer",
|
||||
frm: cur_frm
|
||||
})
|
||||
frm: cur_frm,
|
||||
});
|
||||
}
|
||||
|
||||
make_quotation () {
|
||||
make_quotation() {
|
||||
frappe.model.open_mapped_doc({
|
||||
method: "erpnext.crm.doctype.lead.lead.make_quotation",
|
||||
frm: cur_frm
|
||||
})
|
||||
frm: cur_frm,
|
||||
});
|
||||
}
|
||||
|
||||
make_prospect () {
|
||||
frappe.model.with_doctype("Prospect", function() {
|
||||
make_prospect() {
|
||||
frappe.model.with_doctype("Prospect", function () {
|
||||
let prospect = frappe.model.get_new_doc("Prospect");
|
||||
prospect.company_name = cur_frm.doc.company_name;
|
||||
prospect.no_of_employees = cur_frm.doc.no_of_employees;
|
||||
@@ -113,14 +121,14 @@ erpnext.LeadController = class LeadController extends frappe.ui.form.Controller
|
||||
prospect.prospect_owner = cur_frm.doc.lead_owner;
|
||||
prospect.notes = cur_frm.doc.notes;
|
||||
|
||||
let leads_row = frappe.model.add_child(prospect, 'leads');
|
||||
let leads_row = frappe.model.add_child(prospect, "leads");
|
||||
leads_row.lead = cur_frm.doc.name;
|
||||
|
||||
frappe.set_route("Form", "Prospect", prospect.name);
|
||||
});
|
||||
}
|
||||
|
||||
company_name () {
|
||||
company_name() {
|
||||
if (!this.frm.doc.lead_name) {
|
||||
this.frm.set_value("lead_name", this.frm.doc.company_name);
|
||||
}
|
||||
@@ -149,86 +157,91 @@ erpnext.LeadController = class LeadController extends frappe.ui.form.Controller
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
extend_cscript(cur_frm.cscript, new erpnext.LeadController({ frm: cur_frm }));
|
||||
|
||||
frappe.ui.form.on("Lead", {
|
||||
make_opportunity: async function(frm) {
|
||||
let existing_prospect = (await frappe.db.get_value("Prospect Lead",
|
||||
{
|
||||
"lead": frm.doc.name
|
||||
},
|
||||
"name", null, "Prospect"
|
||||
)).message.name;
|
||||
make_opportunity: async function (frm) {
|
||||
let existing_prospect = (
|
||||
await frappe.db.get_value(
|
||||
"Prospect Lead",
|
||||
{
|
||||
lead: frm.doc.name,
|
||||
},
|
||||
"name",
|
||||
null,
|
||||
"Prospect"
|
||||
)
|
||||
).message.name;
|
||||
|
||||
if (!existing_prospect) {
|
||||
var fields = [
|
||||
{
|
||||
"label": "Create Prospect",
|
||||
"fieldname": "create_prospect",
|
||||
"fieldtype": "Check",
|
||||
"default": 1
|
||||
label: "Create Prospect",
|
||||
fieldname: "create_prospect",
|
||||
fieldtype: "Check",
|
||||
default: 1,
|
||||
},
|
||||
{
|
||||
"label": "Prospect Name",
|
||||
"fieldname": "prospect_name",
|
||||
"fieldtype": "Data",
|
||||
"default": frm.doc.company_name,
|
||||
"depends_on": "create_prospect"
|
||||
}
|
||||
label: "Prospect Name",
|
||||
fieldname: "prospect_name",
|
||||
fieldtype: "Data",
|
||||
default: frm.doc.company_name,
|
||||
depends_on: "create_prospect",
|
||||
},
|
||||
];
|
||||
}
|
||||
let existing_contact = (await frappe.db.get_value("Contact",
|
||||
{
|
||||
"first_name": frm.doc.first_name || frm.doc.lead_name,
|
||||
"last_name": frm.doc.last_name
|
||||
},
|
||||
"name"
|
||||
)).message.name;
|
||||
let existing_contact = (
|
||||
await frappe.db.get_value(
|
||||
"Contact",
|
||||
{
|
||||
first_name: frm.doc.first_name || frm.doc.lead_name,
|
||||
last_name: frm.doc.last_name,
|
||||
},
|
||||
"name"
|
||||
)
|
||||
).message.name;
|
||||
|
||||
if (!existing_contact) {
|
||||
fields.push(
|
||||
{
|
||||
"label": "Create Contact",
|
||||
"fieldname": "create_contact",
|
||||
"fieldtype": "Check",
|
||||
"default": "1"
|
||||
}
|
||||
);
|
||||
fields.push({
|
||||
label: "Create Contact",
|
||||
fieldname: "create_contact",
|
||||
fieldtype: "Check",
|
||||
default: "1",
|
||||
});
|
||||
}
|
||||
|
||||
if (fields) {
|
||||
var d = new frappe.ui.Dialog({
|
||||
title: __('Create Opportunity'),
|
||||
title: __("Create Opportunity"),
|
||||
fields: fields,
|
||||
primary_action: function() {
|
||||
primary_action: function () {
|
||||
var data = d.get_values();
|
||||
frappe.call({
|
||||
method: 'create_prospect_and_contact',
|
||||
method: "create_prospect_and_contact",
|
||||
doc: frm.doc,
|
||||
args: {
|
||||
data: data,
|
||||
},
|
||||
freeze: true,
|
||||
callback: function(r) {
|
||||
callback: function (r) {
|
||||
if (!r.exc) {
|
||||
frappe.model.open_mapped_doc({
|
||||
method: "erpnext.crm.doctype.lead.lead.make_opportunity",
|
||||
frm: frm
|
||||
frm: frm,
|
||||
});
|
||||
}
|
||||
d.hide();
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
primary_action_label: __('Create')
|
||||
primary_action_label: __("Create"),
|
||||
});
|
||||
d.show();
|
||||
} else {
|
||||
frappe.model.open_mapped_doc({
|
||||
method: "erpnext.crm.doctype.lead.lead.make_opportunity",
|
||||
frm: frm
|
||||
frm: frm,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -19,7 +19,7 @@ from erpnext.selling.doctype.customer.customer import parse_full_name
|
||||
|
||||
class Lead(SellingController, CRMNote):
|
||||
def get_feed(self):
|
||||
return "{0}: {1}".format(_(self.status), self.lead_name)
|
||||
return f"{_(self.status)}: {self.lead_name}"
|
||||
|
||||
def onload(self):
|
||||
customer = frappe.db.get_value("Customer", {"lead_name": self.name})
|
||||
@@ -122,9 +122,7 @@ class Lead(SellingController, CRMNote):
|
||||
self.contact_doc.save()
|
||||
|
||||
def update_prospect(self):
|
||||
lead_row_name = frappe.db.get_value(
|
||||
"Prospect Lead", filters={"lead": self.name}, fieldname="name"
|
||||
)
|
||||
lead_row_name = frappe.db.get_value("Prospect Lead", filters={"lead": self.name}, fieldname="name")
|
||||
if lead_row_name:
|
||||
lead_row = frappe.get_doc("Prospect Lead", lead_row_name)
|
||||
lead_row.update(
|
||||
@@ -174,9 +172,7 @@ class Lead(SellingController, CRMNote):
|
||||
)
|
||||
|
||||
def has_lost_quotation(self):
|
||||
return frappe.db.get_value(
|
||||
"Quotation", {"party_name": self.name, "docstatus": 1, "status": "Lost"}
|
||||
)
|
||||
return frappe.db.get_value("Quotation", {"party_name": self.name, "docstatus": 1, "status": "Lost"})
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_prospect_and_contact(self, data):
|
||||
@@ -448,8 +444,8 @@ def get_lead_with_phone_number(number):
|
||||
leads = frappe.get_all(
|
||||
"Lead",
|
||||
or_filters={
|
||||
"phone": ["like", "%{}".format(number)],
|
||||
"mobile_no": ["like", "%{}".format(number)],
|
||||
"phone": ["like", f"%{number}"],
|
||||
"mobile_no": ["like", f"%{number}"],
|
||||
},
|
||||
limit=1,
|
||||
order_by="creation DESC",
|
||||
@@ -476,9 +472,7 @@ def add_lead_to_prospect(lead, prospect):
|
||||
link_open_events("Lead", lead, prospect)
|
||||
|
||||
frappe.msgprint(
|
||||
_("Lead {0} has been added to prospect {1}.").format(
|
||||
frappe.bold(lead), frappe.bold(prospect.name)
|
||||
),
|
||||
_("Lead {0} has been added to prospect {1}.").format(frappe.bold(lead), frappe.bold(prospect.name)),
|
||||
title=_("Lead -> Prospect"),
|
||||
indicator="green",
|
||||
)
|
||||
|
||||
@@ -1,28 +1,42 @@
|
||||
frappe.listview_settings['Lead'] = {
|
||||
onload: function(listview) {
|
||||
frappe.listview_settings["Lead"] = {
|
||||
onload: function (listview) {
|
||||
if (frappe.boot.user.can_create.includes("Prospect")) {
|
||||
listview.page.add_action_item(__("Create Prospect"), function() {
|
||||
frappe.model.with_doctype("Prospect", function() {
|
||||
listview.page.add_action_item(__("Create Prospect"), function () {
|
||||
frappe.model.with_doctype("Prospect", function () {
|
||||
let prospect = frappe.model.get_new_doc("Prospect");
|
||||
let leads = listview.get_checked_items();
|
||||
frappe.db.get_value("Lead", leads[0].name, ["company_name", "no_of_employees", "industry", "market_segment", "territory", "fax", "website", "lead_owner"], (r) => {
|
||||
prospect.company_name = r.company_name;
|
||||
prospect.no_of_employees = r.no_of_employees;
|
||||
prospect.industry = r.industry;
|
||||
prospect.market_segment = r.market_segment;
|
||||
prospect.territory = r.territory;
|
||||
prospect.fax = r.fax;
|
||||
prospect.website = r.website;
|
||||
prospect.prospect_owner = r.lead_owner;
|
||||
frappe.db.get_value(
|
||||
"Lead",
|
||||
leads[0].name,
|
||||
[
|
||||
"company_name",
|
||||
"no_of_employees",
|
||||
"industry",
|
||||
"market_segment",
|
||||
"territory",
|
||||
"fax",
|
||||
"website",
|
||||
"lead_owner",
|
||||
],
|
||||
(r) => {
|
||||
prospect.company_name = r.company_name;
|
||||
prospect.no_of_employees = r.no_of_employees;
|
||||
prospect.industry = r.industry;
|
||||
prospect.market_segment = r.market_segment;
|
||||
prospect.territory = r.territory;
|
||||
prospect.fax = r.fax;
|
||||
prospect.website = r.website;
|
||||
prospect.prospect_owner = r.lead_owner;
|
||||
|
||||
leads.forEach(function(lead) {
|
||||
let lead_prospect_row = frappe.model.add_child(prospect, 'leads');
|
||||
lead_prospect_row.lead = lead.name;
|
||||
});
|
||||
frappe.set_route("Form", "Prospect", prospect.name);
|
||||
});
|
||||
leads.forEach(function (lead) {
|
||||
let lead_prospect_row = frappe.model.add_child(prospect, "leads");
|
||||
lead_prospect_row.lead = lead.name;
|
||||
});
|
||||
frappe.set_route("Form", "Prospect", prospect.name);
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -134,9 +134,7 @@ class TestLead(unittest.TestCase):
|
||||
self.assertEqual(event.event_participants[1].reference_docname, opportunity.name)
|
||||
|
||||
self.assertTrue(
|
||||
frappe.db.get_value(
|
||||
"ToDo", {"reference_type": "Opportunity", "reference_name": opportunity.name}
|
||||
)
|
||||
frappe.db.get_value("ToDo", {"reference_type": "Opportunity", "reference_name": opportunity.name})
|
||||
)
|
||||
|
||||
def test_copy_events_from_lead_to_prospect(self):
|
||||
@@ -194,7 +192,7 @@ def make_lead(**args):
|
||||
"doctype": "Lead",
|
||||
"first_name": args.first_name or "_Test",
|
||||
"last_name": args.last_name or "Lead",
|
||||
"email_id": args.email_id or "new_lead_{}@example.com".format(random_string(5)),
|
||||
"email_id": args.email_id or f"new_lead_{random_string(5)}@example.com",
|
||||
"company_name": args.company_name or "_Test Company",
|
||||
}
|
||||
).insert()
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Lead Source', {
|
||||
frappe.ui.form.on("Lead Source", {
|
||||
// refresh: function(frm) {
|
||||
|
||||
// }
|
||||
});
|
||||
|
||||
@@ -1,23 +1,29 @@
|
||||
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('LinkedIn Settings', {
|
||||
onload: function(frm) {
|
||||
if (frm.doc.session_status == 'Expired' && frm.doc.consumer_key && frm.doc.consumer_secret) {
|
||||
frappe.ui.form.on("LinkedIn Settings", {
|
||||
onload: function (frm) {
|
||||
if (frm.doc.session_status == "Expired" && frm.doc.consumer_key && frm.doc.consumer_secret) {
|
||||
frappe.confirm(
|
||||
__('Session not valid. Do you want to login?'),
|
||||
function(){
|
||||
__("Session not valid. Do you want to login?"),
|
||||
function () {
|
||||
frm.trigger("login");
|
||||
},
|
||||
function(){
|
||||
function () {
|
||||
window.close();
|
||||
}
|
||||
);
|
||||
}
|
||||
frm.dashboard.set_headline(__("For more information, {0}.", [`<a target='_blank' href='https://docs.erpnext.com/docs/user/manual/en/CRM/linkedin-settings'>${__('click here')}</a>`]));
|
||||
frm.dashboard.set_headline(
|
||||
__("For more information, {0}.", [
|
||||
`<a target='_blank' href='https://docs.erpnext.com/docs/user/manual/en/CRM/linkedin-settings'>${__(
|
||||
"click here"
|
||||
)}</a>`,
|
||||
])
|
||||
);
|
||||
},
|
||||
refresh: function(frm) {
|
||||
if (frm.doc.session_status=="Expired"){
|
||||
refresh: function (frm) {
|
||||
if (frm.doc.session_status == "Expired") {
|
||||
let msg = __("Session not active. Save document to login.");
|
||||
frm.dashboard.set_headline_alert(
|
||||
`<div class="row">
|
||||
@@ -28,19 +34,18 @@ frappe.ui.form.on('LinkedIn Settings', {
|
||||
);
|
||||
}
|
||||
|
||||
if (frm.doc.session_status=="Active"){
|
||||
if (frm.doc.session_status == "Active") {
|
||||
let d = new Date(frm.doc.modified);
|
||||
d.setDate(d.getDate()+60);
|
||||
d.setDate(d.getDate() + 60);
|
||||
let dn = new Date();
|
||||
let days = d.getTime() - dn.getTime();
|
||||
days = Math.floor(days/(1000 * 3600 * 24));
|
||||
let msg,color;
|
||||
days = Math.floor(days / (1000 * 3600 * 24));
|
||||
let msg, color;
|
||||
|
||||
if (days>0){
|
||||
if (days > 0) {
|
||||
msg = __("Your session will be expire in {0} days.", [days]);
|
||||
color = "green";
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
msg = __("Session is expired. Save doc to login.");
|
||||
color = "red";
|
||||
}
|
||||
@@ -54,21 +59,23 @@ frappe.ui.form.on('LinkedIn Settings', {
|
||||
);
|
||||
}
|
||||
},
|
||||
login: function(frm) {
|
||||
if (frm.doc.consumer_key && frm.doc.consumer_secret){
|
||||
login: function (frm) {
|
||||
if (frm.doc.consumer_key && frm.doc.consumer_secret) {
|
||||
frappe.dom.freeze();
|
||||
frappe.call({
|
||||
doc: frm.doc,
|
||||
method: "get_authorization_url",
|
||||
callback : function(r) {
|
||||
window.location.href = r.message;
|
||||
}
|
||||
}).fail(function() {
|
||||
frappe.dom.unfreeze();
|
||||
});
|
||||
frappe
|
||||
.call({
|
||||
doc: frm.doc,
|
||||
method: "get_authorization_url",
|
||||
callback: function (r) {
|
||||
window.location.href = r.message;
|
||||
},
|
||||
})
|
||||
.fail(function () {
|
||||
frappe.dom.unfreeze();
|
||||
});
|
||||
}
|
||||
},
|
||||
after_save: function(frm) {
|
||||
after_save: function (frm) {
|
||||
frm.trigger("login");
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -19,14 +19,14 @@ class LinkedInSettings(Document):
|
||||
{
|
||||
"response_type": "code",
|
||||
"client_id": self.consumer_key,
|
||||
"redirect_uri": "{0}/api/method/erpnext.crm.doctype.linkedin_settings.linkedin_settings.callback?".format(
|
||||
"redirect_uri": "{}/api/method/erpnext.crm.doctype.linkedin_settings.linkedin_settings.callback?".format(
|
||||
frappe.utils.get_url()
|
||||
),
|
||||
"scope": "r_emailaddress w_organization_social r_basicprofile r_liteprofile r_organization_social rw_organization_admin w_member_social",
|
||||
}
|
||||
)
|
||||
|
||||
url = "https://www.linkedin.com/oauth/v2/authorization?{}".format(params)
|
||||
url = f"https://www.linkedin.com/oauth/v2/authorization?{params}"
|
||||
|
||||
return url
|
||||
|
||||
@@ -37,7 +37,7 @@ class LinkedInSettings(Document):
|
||||
"code": code,
|
||||
"client_id": self.consumer_key,
|
||||
"client_secret": self.get_password(fieldname="consumer_secret"),
|
||||
"redirect_uri": "{0}/api/method/erpnext.crm.doctype.linkedin_settings.linkedin_settings.callback?".format(
|
||||
"redirect_uri": "{}/api/method/erpnext.crm.doctype.linkedin_settings.linkedin_settings.callback?".format(
|
||||
frappe.utils.get_url()
|
||||
),
|
||||
}
|
||||
@@ -80,7 +80,7 @@ class LinkedInSettings(Document):
|
||||
body = {
|
||||
"registerUploadRequest": {
|
||||
"recipes": ["urn:li:digitalmediaRecipe:feedshare-image"],
|
||||
"owner": "urn:li:organization:{0}".format(self.company_id),
|
||||
"owner": f"urn:li:organization:{self.company_id}",
|
||||
"serviceRelationships": [
|
||||
{"relationshipType": "OWNER", "identifier": "urn:li:userGeneratedContent"}
|
||||
],
|
||||
@@ -100,7 +100,7 @@ class LinkedInSettings(Document):
|
||||
if response.status_code < 200 and response.status_code > 299:
|
||||
frappe.throw(
|
||||
_("Error While Uploading Image"),
|
||||
title="{0} {1}".format(response.status_code, response.reason),
|
||||
title=f"{response.status_code} {response.reason}",
|
||||
)
|
||||
return None
|
||||
return asset
|
||||
@@ -115,7 +115,7 @@ class LinkedInSettings(Document):
|
||||
|
||||
body = {
|
||||
"distribution": {"linkedInDistributionTarget": {}},
|
||||
"owner": "urn:li:organization:{0}".format(self.company_id),
|
||||
"owner": f"urn:li:organization:{self.company_id}",
|
||||
"subject": title,
|
||||
"text": {"text": text},
|
||||
}
|
||||
@@ -136,13 +136,13 @@ class LinkedInSettings(Document):
|
||||
if response.status_code not in [201, 200]:
|
||||
raise
|
||||
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
self.api_error(response)
|
||||
|
||||
return response
|
||||
|
||||
def get_headers(self):
|
||||
return {"Authorization": "Bearer {}".format(self.access_token)}
|
||||
return {"Authorization": f"Bearer {self.access_token}"}
|
||||
|
||||
def get_reference_url(self, text):
|
||||
import re
|
||||
@@ -155,7 +155,7 @@ class LinkedInSettings(Document):
|
||||
def delete_post(self, post_id):
|
||||
try:
|
||||
response = requests.delete(
|
||||
url="https://api.linkedin.com/v2/shares/urn:li:share:{0}".format(post_id),
|
||||
url=f"https://api.linkedin.com/v2/shares/urn:li:share:{post_id}",
|
||||
headers=self.get_headers(),
|
||||
)
|
||||
if response.status_code != 200:
|
||||
@@ -164,7 +164,7 @@ class LinkedInSettings(Document):
|
||||
self.api_error(response)
|
||||
|
||||
def get_post(self, post_id):
|
||||
url = "https://api.linkedin.com/v2/organizationalEntityShareStatistics?q=organizationalEntity&organizationalEntity=urn:li:organization:{0}&shares[0]=urn:li:share:{1}".format(
|
||||
url = "https://api.linkedin.com/v2/organizationalEntityShareStatistics?q=organizationalEntity&organizationalEntity=urn:li:organization:{}&shares[0]=urn:li:share:{}".format(
|
||||
self.company_id, post_id
|
||||
)
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Lost Reason Detail', {
|
||||
refresh: function() {
|
||||
|
||||
}
|
||||
frappe.ui.form.on("Lost Reason Detail", {
|
||||
refresh: function () {},
|
||||
});
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Market Segment', {
|
||||
refresh: function(frm) {
|
||||
|
||||
}
|
||||
frappe.ui.form.on("Market Segment", {
|
||||
refresh: function (frm) {},
|
||||
});
|
||||
|
||||
@@ -304,9 +304,7 @@ def make_quotation(source_name, target_doc=None):
|
||||
quotation.conversion_rate = exchange_rate
|
||||
|
||||
# get default taxes
|
||||
taxes = get_default_taxes_and_charges(
|
||||
"Sales Taxes and Charges Template", company=quotation.company
|
||||
)
|
||||
taxes = get_default_taxes_and_charges("Sales Taxes and Charges Template", company=quotation.company)
|
||||
if taxes.get("taxes"):
|
||||
quotation.update(taxes)
|
||||
|
||||
@@ -412,9 +410,7 @@ def set_multiple_status(names, status):
|
||||
|
||||
def auto_close_opportunity():
|
||||
"""auto close the `Replied` Opportunities after 7 days"""
|
||||
auto_close_after_days = (
|
||||
frappe.db.get_single_value("CRM Settings", "close_opportunity_after_days") or 15
|
||||
)
|
||||
auto_close_after_days = frappe.db.get_single_value("CRM Settings", "close_opportunity_after_days") or 15
|
||||
|
||||
table = frappe.qb.DocType("Opportunity")
|
||||
opportunities = (
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
frappe.listview_settings['Opportunity'] = {
|
||||
frappe.listview_settings["Opportunity"] = {
|
||||
add_fields: ["customer_name", "opportunity_type", "opportunity_from", "status"],
|
||||
get_indicator: function(doc) {
|
||||
get_indicator: function (doc) {
|
||||
var indicator = [__(doc.status), frappe.utils.guess_colour(doc.status), "status,=," + doc.status];
|
||||
if(doc.status=="Quotation") {
|
||||
if (doc.status == "Quotation") {
|
||||
indicator[1] = "green";
|
||||
}
|
||||
return indicator;
|
||||
},
|
||||
onload: function(listview) {
|
||||
onload: function (listview) {
|
||||
var method = "erpnext.crm.doctype.opportunity.opportunity.set_multiple_status";
|
||||
|
||||
listview.page.add_menu_item(__("Set as Open"), function() {
|
||||
listview.call_for_selected_items(method, {"status": "Open"});
|
||||
listview.page.add_menu_item(__("Set as Open"), function () {
|
||||
listview.call_for_selected_items(method, { status: "Open" });
|
||||
});
|
||||
|
||||
listview.page.add_menu_item(__("Set as Closed"), function() {
|
||||
listview.call_for_selected_items(method, {"status": "Closed"});
|
||||
listview.page.add_menu_item(__("Set as Closed"), function () {
|
||||
listview.call_for_selected_items(method, { status: "Closed" });
|
||||
});
|
||||
|
||||
if(listview.page.fields_dict.opportunity_from) {
|
||||
listview.page.fields_dict.opportunity_from.get_query = function() {
|
||||
if (listview.page.fields_dict.opportunity_from) {
|
||||
listview.page.fields_dict.opportunity_from.get_query = function () {
|
||||
return {
|
||||
"filters": {
|
||||
"name": ["in", ["Customer", "Lead"]],
|
||||
}
|
||||
filters: {
|
||||
name: ["in", ["Customer", "Lead"]],
|
||||
},
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe.utils import add_days, now_datetime, random_string, today
|
||||
from frappe.utils import now_datetime, random_string, today
|
||||
|
||||
from erpnext.crm.doctype.lead.lead import make_customer
|
||||
from erpnext.crm.doctype.lead.test_lead import make_lead
|
||||
@@ -32,9 +32,7 @@ class TestOpportunity(unittest.TestCase):
|
||||
|
||||
self.assertTrue(opp_doc.party_name)
|
||||
self.assertEqual(opp_doc.opportunity_from, "Lead")
|
||||
self.assertEqual(
|
||||
frappe.db.get_value("Lead", opp_doc.party_name, "email_id"), opp_doc.contact_email
|
||||
)
|
||||
self.assertEqual(frappe.db.get_value("Lead", opp_doc.party_name, "email_id"), opp_doc.contact_email)
|
||||
|
||||
# create new customer and create new contact against 'new.opportunity@example.com'
|
||||
customer = make_customer(opp_doc.party_name).insert(ignore_permissions=True)
|
||||
@@ -53,9 +51,7 @@ class TestOpportunity(unittest.TestCase):
|
||||
self.assertEqual(opportunity_doc.total, 2200)
|
||||
|
||||
def test_carry_forward_of_email_and_comments(self):
|
||||
frappe.db.set_value(
|
||||
"CRM Settings", "CRM Settings", "carry_forward_communication_and_comments", 1
|
||||
)
|
||||
frappe.db.set_value("CRM Settings", "CRM Settings", "carry_forward_communication_and_comments", 1)
|
||||
lead_doc = make_lead()
|
||||
lead_doc.add_comment("Comment", text="Test Comment 1")
|
||||
lead_doc.add_comment("Comment", text="Test Comment 2")
|
||||
@@ -66,9 +62,7 @@ class TestOpportunity(unittest.TestCase):
|
||||
opportunity_comment_count = frappe.db.count(
|
||||
"Comment", {"reference_doctype": opp_doc.doctype, "reference_name": opp_doc.name}
|
||||
)
|
||||
opportunity_communication_count = len(
|
||||
get_linked_communication_list(opp_doc.doctype, opp_doc.name)
|
||||
)
|
||||
opportunity_communication_count = len(get_linked_communication_list(opp_doc.doctype, opp_doc.name))
|
||||
self.assertEqual(opportunity_comment_count, 2)
|
||||
self.assertEqual(opportunity_communication_count, 2)
|
||||
|
||||
@@ -79,7 +73,7 @@ class TestOpportunity(unittest.TestCase):
|
||||
|
||||
|
||||
def make_opportunity_from_lead():
|
||||
new_lead_email_id = "new{}@example.com".format(random_string(5))
|
||||
new_lead_email_id = f"new{random_string(5)}@example.com"
|
||||
args = {
|
||||
"doctype": "Opportunity",
|
||||
"contact_email": new_lead_email_id,
|
||||
@@ -128,9 +122,7 @@ def make_opportunity(**args):
|
||||
return opp_doc
|
||||
|
||||
|
||||
def create_communication(
|
||||
reference_doctype, reference_name, sender, sent_or_received=None, creation=None
|
||||
):
|
||||
def create_communication(reference_doctype, reference_name, sender, sent_or_received=None, creation=None):
|
||||
communication = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Communication",
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Opportunity Lost Reason', {
|
||||
refresh: function() {
|
||||
|
||||
}
|
||||
frappe.ui.form.on("Opportunity Lost Reason", {
|
||||
refresh: function () {},
|
||||
});
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Opportunity Type', {
|
||||
refresh: function(frm) {
|
||||
|
||||
}
|
||||
frappe.ui.form.on("Opportunity Type", {
|
||||
refresh: function (frm) {},
|
||||
});
|
||||
|
||||
@@ -1,25 +1,33 @@
|
||||
// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Prospect', {
|
||||
refresh (frm) {
|
||||
frappe.ui.form.on("Prospect", {
|
||||
refresh(frm) {
|
||||
frappe.dynamic_link = { doc: frm.doc, fieldname: "name", doctype: frm.doctype };
|
||||
|
||||
if (!frm.is_new() && frappe.boot.user.can_create.includes("Customer")) {
|
||||
frm.add_custom_button(__("Customer"), function() {
|
||||
frappe.model.open_mapped_doc({
|
||||
method: "erpnext.crm.doctype.prospect.prospect.make_customer",
|
||||
frm: frm
|
||||
});
|
||||
}, __("Create"));
|
||||
frm.add_custom_button(
|
||||
__("Customer"),
|
||||
function () {
|
||||
frappe.model.open_mapped_doc({
|
||||
method: "erpnext.crm.doctype.prospect.prospect.make_customer",
|
||||
frm: frm,
|
||||
});
|
||||
},
|
||||
__("Create")
|
||||
);
|
||||
}
|
||||
if (!frm.is_new() && frappe.boot.user.can_create.includes("Opportunity")) {
|
||||
frm.add_custom_button(__("Opportunity"), function() {
|
||||
frappe.model.open_mapped_doc({
|
||||
method: "erpnext.crm.doctype.prospect.prospect.make_opportunity",
|
||||
frm: frm
|
||||
});
|
||||
}, __("Create"));
|
||||
frm.add_custom_button(
|
||||
__("Opportunity"),
|
||||
function () {
|
||||
frappe.model.open_mapped_doc({
|
||||
method: "erpnext.crm.doctype.prospect.prospect.make_opportunity",
|
||||
frm: frm,
|
||||
});
|
||||
},
|
||||
__("Create")
|
||||
);
|
||||
}
|
||||
|
||||
if (!frm.is_new()) {
|
||||
@@ -31,7 +39,7 @@ frappe.ui.form.on('Prospect', {
|
||||
frm.trigger("show_activities");
|
||||
},
|
||||
|
||||
show_notes (frm) {
|
||||
show_notes(frm) {
|
||||
const crm_notes = new erpnext.utils.CRMNotes({
|
||||
frm: frm,
|
||||
notes_wrapper: $(frm.fields_dict.notes_html.wrapper),
|
||||
@@ -39,7 +47,7 @@ frappe.ui.form.on('Prospect', {
|
||||
crm_notes.refresh();
|
||||
},
|
||||
|
||||
show_activities (frm) {
|
||||
show_activities(frm) {
|
||||
const crm_activities = new erpnext.utils.CRMActivities({
|
||||
frm: frm,
|
||||
open_activities_wrapper: $(frm.fields_dict.open_activities_html.wrapper),
|
||||
@@ -47,6 +55,5 @@ frappe.ui.form.on('Prospect', {
|
||||
form_wrapper: $(frm.wrapper),
|
||||
});
|
||||
crm_activities.refresh();
|
||||
}
|
||||
|
||||
},
|
||||
});
|
||||
|
||||
@@ -34,7 +34,7 @@ def make_prospect(**args):
|
||||
prospect_doc = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Prospect",
|
||||
"company_name": args.company_name or "_Test Company {}".format(random_string(3)),
|
||||
"company_name": args.company_name or f"_Test Company {random_string(3)}",
|
||||
}
|
||||
).insert()
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Sales Stage', {
|
||||
refresh: function(frm) {
|
||||
|
||||
}
|
||||
frappe.ui.form.on("Sales Stage", {
|
||||
refresh: function (frm) {},
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
frappe.ui.form.on('Social Media Post', {
|
||||
validate: function(frm) {
|
||||
frappe.ui.form.on("Social Media Post", {
|
||||
validate: function (frm) {
|
||||
if (frm.doc.twitter === 0 && frm.doc.linkedin === 0) {
|
||||
frappe.throw(__("Select atleast one Social Media Platform to Share on."));
|
||||
}
|
||||
@@ -12,111 +12,116 @@ frappe.ui.form.on('Social Media Post', {
|
||||
frappe.throw(__("Scheduled Time must be a future time."));
|
||||
}
|
||||
}
|
||||
frm.trigger('validate_tweet_length');
|
||||
frm.trigger("validate_tweet_length");
|
||||
},
|
||||
|
||||
text: function(frm) {
|
||||
text: function (frm) {
|
||||
if (frm.doc.text) {
|
||||
frm.set_df_property('text', 'description', `${frm.doc.text.length}/280`);
|
||||
frm.refresh_field('text');
|
||||
frm.trigger('validate_tweet_length');
|
||||
frm.set_df_property("text", "description", `${frm.doc.text.length}/280`);
|
||||
frm.refresh_field("text");
|
||||
frm.trigger("validate_tweet_length");
|
||||
}
|
||||
},
|
||||
|
||||
validate_tweet_length: function(frm) {
|
||||
validate_tweet_length: function (frm) {
|
||||
if (frm.doc.text && frm.doc.text.length > 280) {
|
||||
frappe.throw(__("Tweet length Must be less than 280."));
|
||||
}
|
||||
},
|
||||
|
||||
onload: function(frm) {
|
||||
frm.trigger('make_dashboard');
|
||||
onload: function (frm) {
|
||||
frm.trigger("make_dashboard");
|
||||
},
|
||||
|
||||
make_dashboard: function(frm) {
|
||||
make_dashboard: function (frm) {
|
||||
if (frm.doc.post_status == "Posted") {
|
||||
frappe.call({
|
||||
doc: frm.doc,
|
||||
method: 'get_post',
|
||||
method: "get_post",
|
||||
freeze: true,
|
||||
callback: (r) => {
|
||||
if (!r.message) {
|
||||
return;
|
||||
}
|
||||
|
||||
let datasets = [], colors = [];
|
||||
let datasets = [],
|
||||
colors = [];
|
||||
if (r.message && r.message.twitter) {
|
||||
colors.push('#1DA1F2');
|
||||
colors.push("#1DA1F2");
|
||||
datasets.push({
|
||||
name: 'Twitter',
|
||||
values: [r.message.twitter.favorite_count, r.message.twitter.retweet_count]
|
||||
name: "Twitter",
|
||||
values: [r.message.twitter.favorite_count, r.message.twitter.retweet_count],
|
||||
});
|
||||
}
|
||||
if (r.message && r.message.linkedin) {
|
||||
colors.push('#0077b5');
|
||||
colors.push("#0077b5");
|
||||
datasets.push({
|
||||
name: 'LinkedIn',
|
||||
values: [r.message.linkedin.totalShareStatistics.likeCount, r.message.linkedin.totalShareStatistics.shareCount]
|
||||
name: "LinkedIn",
|
||||
values: [
|
||||
r.message.linkedin.totalShareStatistics.likeCount,
|
||||
r.message.linkedin.totalShareStatistics.shareCount,
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
if (datasets.length) {
|
||||
frm.dashboard.render_graph({
|
||||
data: {
|
||||
labels: ['Likes', 'Retweets/Shares'],
|
||||
datasets: datasets
|
||||
labels: ["Likes", "Retweets/Shares"],
|
||||
datasets: datasets,
|
||||
},
|
||||
|
||||
title: __("Post Metrics"),
|
||||
type: 'bar',
|
||||
type: "bar",
|
||||
height: 300,
|
||||
colors: colors
|
||||
colors: colors,
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
refresh: function(frm) {
|
||||
frm.trigger('text');
|
||||
refresh: function (frm) {
|
||||
frm.trigger("text");
|
||||
|
||||
if (frm.doc.docstatus === 1) {
|
||||
if (!['Posted', 'Deleted'].includes(frm.doc.post_status)) {
|
||||
frm.trigger('add_post_btn');
|
||||
if (!["Posted", "Deleted"].includes(frm.doc.post_status)) {
|
||||
frm.trigger("add_post_btn");
|
||||
}
|
||||
if (frm.doc.post_status !='Deleted') {
|
||||
frm.add_custom_button(__('Delete Post'), function() {
|
||||
frappe.confirm(__('Are you sure want to delete the Post from Social Media platforms?'),
|
||||
function() {
|
||||
if (frm.doc.post_status != "Deleted") {
|
||||
frm.add_custom_button(__("Delete Post"), function () {
|
||||
frappe.confirm(
|
||||
__("Are you sure want to delete the Post from Social Media platforms?"),
|
||||
function () {
|
||||
frappe.call({
|
||||
doc: frm.doc,
|
||||
method: 'delete_post',
|
||||
method: "delete_post",
|
||||
freeze: true,
|
||||
callback: () => {
|
||||
frm.reload_doc();
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
if (frm.doc.post_status !='Deleted') {
|
||||
let html='';
|
||||
if (frm.doc.post_status != "Deleted") {
|
||||
let html = "";
|
||||
if (frm.doc.twitter) {
|
||||
let color = frm.doc.twitter_post_id ? "green" : "red";
|
||||
let status = frm.doc.twitter_post_id ? "Posted" : "Not Posted";
|
||||
html += `<div class="col-xs-6">
|
||||
<span class="indicator whitespace-nowrap ${color}"><span>Twitter : ${status} </span></span>
|
||||
</div>` ;
|
||||
</div>`;
|
||||
}
|
||||
if (frm.doc.linkedin) {
|
||||
let color = frm.doc.linkedin_post_id ? "green" : "red";
|
||||
let status = frm.doc.linkedin_post_id ? "Posted" : "Not Posted";
|
||||
html += `<div class="col-xs-6">
|
||||
<span class="indicator whitespace-nowrap ${color}"><span>LinkedIn : ${status} </span></span>
|
||||
</div>` ;
|
||||
</div>`;
|
||||
}
|
||||
html = `<div class="row">${html}</div>`;
|
||||
frm.dashboard.set_headline_alert(html);
|
||||
@@ -124,16 +129,16 @@ frappe.ui.form.on('Social Media Post', {
|
||||
}
|
||||
},
|
||||
|
||||
add_post_btn: function(frm) {
|
||||
frm.add_custom_button(__('Post Now'), function() {
|
||||
add_post_btn: function (frm) {
|
||||
frm.add_custom_button(__("Post Now"), function () {
|
||||
frappe.call({
|
||||
doc: frm.doc,
|
||||
method: 'post',
|
||||
method: "post",
|
||||
freeze: true,
|
||||
callback: function() {
|
||||
callback: function () {
|
||||
frm.reload_doc();
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -26,7 +26,7 @@ class SocialMediaPost(Document):
|
||||
def submit(self):
|
||||
if self.scheduled_time:
|
||||
self.post_status = "Scheduled"
|
||||
super(SocialMediaPost, self).submit()
|
||||
super().submit()
|
||||
|
||||
def on_cancel(self):
|
||||
self.db_set("post_status", "Cancelled")
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
frappe.listview_settings['Social Media Post'] = {
|
||||
frappe.listview_settings["Social Media Post"] = {
|
||||
add_fields: ["status", "post_status"],
|
||||
get_indicator: function(doc) {
|
||||
return [__(doc.post_status), {
|
||||
"Scheduled": "orange",
|
||||
"Posted": "green",
|
||||
"Error": "red",
|
||||
"Deleted": "red"
|
||||
}[doc.post_status]];
|
||||
}
|
||||
}
|
||||
get_indicator: function (doc) {
|
||||
return [
|
||||
__(doc.post_status),
|
||||
{
|
||||
Scheduled: "orange",
|
||||
Posted: "green",
|
||||
Error: "red",
|
||||
Deleted: "red",
|
||||
}[doc.post_status],
|
||||
];
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,31 +1,38 @@
|
||||
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Twitter Settings', {
|
||||
onload: function(frm) {
|
||||
if (frm.doc.session_status == 'Expired' && frm.doc.consumer_key && frm.doc.consumer_secret){
|
||||
frappe.ui.form.on("Twitter Settings", {
|
||||
onload: function (frm) {
|
||||
if (frm.doc.session_status == "Expired" && frm.doc.consumer_key && frm.doc.consumer_secret) {
|
||||
frappe.confirm(
|
||||
__('Session not valid, Do you want to login?'),
|
||||
function(){
|
||||
__("Session not valid, Do you want to login?"),
|
||||
function () {
|
||||
frm.trigger("login");
|
||||
},
|
||||
function(){
|
||||
function () {
|
||||
window.close();
|
||||
}
|
||||
);
|
||||
}
|
||||
frm.dashboard.set_headline(__("For more information, {0}.", [`<a target='_blank' href='https://docs.erpnext.com/docs/user/manual/en/CRM/twitter-settings'>${__('click here')}</a>`]));
|
||||
frm.dashboard.set_headline(
|
||||
__("For more information, {0}.", [
|
||||
`<a target='_blank' href='https://docs.erpnext.com/docs/user/manual/en/CRM/twitter-settings'>${__(
|
||||
"click here"
|
||||
)}</a>`,
|
||||
])
|
||||
);
|
||||
},
|
||||
refresh: function(frm) {
|
||||
let msg, color, flag=false;
|
||||
refresh: function (frm) {
|
||||
let msg,
|
||||
color,
|
||||
flag = false;
|
||||
if (frm.doc.session_status == "Active") {
|
||||
msg = __("Session Active");
|
||||
color = 'green';
|
||||
color = "green";
|
||||
flag = true;
|
||||
}
|
||||
else if(frm.doc.consumer_key && frm.doc.consumer_secret) {
|
||||
} else if (frm.doc.consumer_key && frm.doc.consumer_secret) {
|
||||
msg = __("Session Not Active. Save doc to login.");
|
||||
color = 'red';
|
||||
color = "red";
|
||||
flag = true;
|
||||
}
|
||||
|
||||
@@ -39,21 +46,23 @@ frappe.ui.form.on('Twitter Settings', {
|
||||
);
|
||||
}
|
||||
},
|
||||
login: function(frm) {
|
||||
if (frm.doc.consumer_key && frm.doc.consumer_secret){
|
||||
login: function (frm) {
|
||||
if (frm.doc.consumer_key && frm.doc.consumer_secret) {
|
||||
frappe.dom.freeze();
|
||||
frappe.call({
|
||||
doc: frm.doc,
|
||||
method: "get_authorize_url",
|
||||
callback : function(r) {
|
||||
window.location.href = r.message;
|
||||
}
|
||||
}).fail(function() {
|
||||
frappe.dom.unfreeze();
|
||||
});
|
||||
frappe
|
||||
.call({
|
||||
doc: frm.doc,
|
||||
method: "get_authorize_url",
|
||||
callback: function (r) {
|
||||
window.location.href = r.message;
|
||||
},
|
||||
})
|
||||
.fail(function () {
|
||||
frappe.dom.unfreeze();
|
||||
});
|
||||
}
|
||||
},
|
||||
after_save: function(frm) {
|
||||
after_save: function (frm) {
|
||||
frm.trigger("login");
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -16,10 +16,8 @@ from tweepy.error import TweepError
|
||||
class TwitterSettings(Document):
|
||||
@frappe.whitelist()
|
||||
def get_authorize_url(self):
|
||||
callback_url = (
|
||||
"{0}/api/method/erpnext.crm.doctype.twitter_settings.twitter_settings.callback?".format(
|
||||
frappe.utils.get_url()
|
||||
)
|
||||
callback_url = "{}/api/method/erpnext.crm.doctype.twitter_settings.twitter_settings.callback?".format(
|
||||
frappe.utils.get_url()
|
||||
)
|
||||
auth = tweepy.OAuthHandler(
|
||||
self.consumer_key, self.get_password(fieldname="consumer_secret"), callback_url
|
||||
@@ -27,10 +25,12 @@ class TwitterSettings(Document):
|
||||
try:
|
||||
redirect_url = auth.get_authorization_url()
|
||||
return redirect_url
|
||||
except tweepy.TweepError as e:
|
||||
except tweepy.TweepError:
|
||||
frappe.msgprint(_("Error! Failed to get request token."))
|
||||
frappe.throw(
|
||||
_("Invalid {0} or {1}").format(frappe.bold("Consumer Key"), frappe.bold("Consumer Secret Key"))
|
||||
_("Invalid {0} or {1}").format(
|
||||
frappe.bold("Consumer Key"), frappe.bold("Consumer Secret Key")
|
||||
)
|
||||
)
|
||||
|
||||
def get_access_token(self, oauth_token, oauth_verifier):
|
||||
@@ -59,7 +59,7 @@ class TwitterSettings(Document):
|
||||
|
||||
frappe.local.response["type"] = "redirect"
|
||||
frappe.local.response["location"] = get_url_to_form("Twitter Settings", "Twitter Settings")
|
||||
except TweepError as e:
|
||||
except TweepError:
|
||||
frappe.msgprint(_("Error! Failed to get access token."))
|
||||
frappe.throw(_("Invalid Consumer Key or Consumer Secret Key"))
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ import frappe
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_last_interaction(contact=None, lead=None):
|
||||
|
||||
if not contact and not lead:
|
||||
return
|
||||
|
||||
@@ -23,16 +22,14 @@ def get_last_interaction(contact=None, lead=None):
|
||||
# remove extra appended 'OR'
|
||||
query_condition = query_condition[:-2]
|
||||
last_communication = frappe.db.sql(
|
||||
"""
|
||||
f"""
|
||||
SELECT `name`, `content`
|
||||
FROM `tabCommunication`
|
||||
WHERE `sent_or_received`='Received'
|
||||
AND ({})
|
||||
AND ({query_condition})
|
||||
ORDER BY `modified`
|
||||
LIMIT 1
|
||||
""".format(
|
||||
query_condition
|
||||
),
|
||||
""",
|
||||
values,
|
||||
as_dict=1,
|
||||
) # nosec
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
frappe.query_reports["Campaign Efficiency"] = {
|
||||
"filters": [
|
||||
filters: [
|
||||
{
|
||||
"fieldname": "from_date",
|
||||
"label": __("From Date"),
|
||||
"fieldtype": "Date",
|
||||
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
|
||||
fieldname: "from_date",
|
||||
label: __("From Date"),
|
||||
fieldtype: "Date",
|
||||
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
|
||||
},
|
||||
{
|
||||
"fieldname": "to_date",
|
||||
"label": __("To Date"),
|
||||
"fieldtype": "Date",
|
||||
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
|
||||
}
|
||||
]
|
||||
fieldname: "to_date",
|
||||
label: __("To Date"),
|
||||
fieldtype: "Date",
|
||||
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -33,13 +33,11 @@ def get_lead_data(filters, based_on):
|
||||
conditions = get_filter_conditions(filters)
|
||||
|
||||
lead_details = frappe.db.sql(
|
||||
"""
|
||||
f"""
|
||||
select {based_on_field}, name
|
||||
from `tabLead`
|
||||
where {based_on_field} is not null and {based_on_field} != '' {conditions}
|
||||
""".format(
|
||||
based_on_field=based_on_field, conditions=conditions
|
||||
),
|
||||
""",
|
||||
filters,
|
||||
as_dict=1,
|
||||
)
|
||||
@@ -82,9 +80,7 @@ def get_lead_quotation_count(leads):
|
||||
where quotation_to = 'Lead' and party_name in (%s)"""
|
||||
% ", ".join(["%s"] * len(leads)),
|
||||
tuple(leads),
|
||||
)[0][
|
||||
0
|
||||
] # nosec
|
||||
)[0][0] # nosec
|
||||
|
||||
|
||||
def get_lead_opp_count(leads):
|
||||
|
||||
@@ -3,41 +3,43 @@
|
||||
/* eslint-disable */
|
||||
|
||||
frappe.query_reports["First Response Time for Opportunity"] = {
|
||||
"filters": [
|
||||
filters: [
|
||||
{
|
||||
"fieldname": "from_date",
|
||||
"label": __("From Date"),
|
||||
"fieldtype": "Date",
|
||||
"reqd": 1,
|
||||
"default": frappe.datetime.add_days(frappe.datetime.nowdate(), -30)
|
||||
fieldname: "from_date",
|
||||
label: __("From Date"),
|
||||
fieldtype: "Date",
|
||||
reqd: 1,
|
||||
default: frappe.datetime.add_days(frappe.datetime.nowdate(), -30),
|
||||
},
|
||||
{
|
||||
"fieldname": "to_date",
|
||||
"label": __("To Date"),
|
||||
"fieldtype": "Date",
|
||||
"reqd": 1,
|
||||
"default": frappe.datetime.nowdate()
|
||||
fieldname: "to_date",
|
||||
label: __("To Date"),
|
||||
fieldtype: "Date",
|
||||
reqd: 1,
|
||||
default: frappe.datetime.nowdate(),
|
||||
},
|
||||
],
|
||||
get_chart_data: function (_columns, result) {
|
||||
return {
|
||||
data: {
|
||||
labels: result.map(d => d.creation_date),
|
||||
datasets: [{
|
||||
name: "First Response Time",
|
||||
values: result.map(d => d.first_response_time)
|
||||
}]
|
||||
labels: result.map((d) => d.creation_date),
|
||||
datasets: [
|
||||
{
|
||||
name: "First Response Time",
|
||||
values: result.map((d) => d.first_response_time),
|
||||
},
|
||||
],
|
||||
},
|
||||
type: "line",
|
||||
tooltipOptions: {
|
||||
formatTooltipY: d => {
|
||||
formatTooltipY: (d) => {
|
||||
let duration_options = {
|
||||
hide_days: 0,
|
||||
hide_seconds: 0
|
||||
hide_seconds: 0,
|
||||
};
|
||||
return frappe.utils.get_formatted_duration(d, duration_options);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
@@ -3,20 +3,20 @@
|
||||
/* eslint-disable */
|
||||
|
||||
frappe.query_reports["Lead Conversion Time"] = {
|
||||
"filters": [
|
||||
filters: [
|
||||
{
|
||||
"fieldname":"from_date",
|
||||
"label": __("From Date"),
|
||||
"fieldtype": "Date",
|
||||
'reqd': 1,
|
||||
"default": frappe.datetime.add_days(frappe.datetime.nowdate(), -30)
|
||||
fieldname: "from_date",
|
||||
label: __("From Date"),
|
||||
fieldtype: "Date",
|
||||
reqd: 1,
|
||||
default: frappe.datetime.add_days(frappe.datetime.nowdate(), -30),
|
||||
},
|
||||
{
|
||||
"fieldname":"to_date",
|
||||
"label": __("To Date"),
|
||||
"fieldtype": "Date",
|
||||
'reqd': 1,
|
||||
"default":frappe.datetime.nowdate()
|
||||
fieldname: "to_date",
|
||||
label: __("To Date"),
|
||||
fieldtype: "Date",
|
||||
reqd: 1,
|
||||
default: frappe.datetime.nowdate(),
|
||||
},
|
||||
]
|
||||
],
|
||||
};
|
||||
|
||||
@@ -3,50 +3,50 @@
|
||||
/* eslint-disable */
|
||||
|
||||
frappe.query_reports["Lead Details"] = {
|
||||
"filters": [
|
||||
filters: [
|
||||
{
|
||||
"fieldname":"company",
|
||||
"label": __("Company"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Company",
|
||||
"default": frappe.defaults.get_user_default("Company"),
|
||||
"reqd": 1
|
||||
fieldname: "company",
|
||||
label: __("Company"),
|
||||
fieldtype: "Link",
|
||||
options: "Company",
|
||||
default: frappe.defaults.get_user_default("Company"),
|
||||
reqd: 1,
|
||||
},
|
||||
{
|
||||
"fieldname":"from_date",
|
||||
"label": __("From Date"),
|
||||
"fieldtype": "Date",
|
||||
"default": frappe.datetime.add_months(frappe.datetime.get_today(), -12),
|
||||
"reqd": 1
|
||||
fieldname: "from_date",
|
||||
label: __("From Date"),
|
||||
fieldtype: "Date",
|
||||
default: frappe.datetime.add_months(frappe.datetime.get_today(), -12),
|
||||
reqd: 1,
|
||||
},
|
||||
{
|
||||
"fieldname":"to_date",
|
||||
"label": __("To Date"),
|
||||
"fieldtype": "Date",
|
||||
"default": frappe.datetime.get_today(),
|
||||
"reqd": 1
|
||||
fieldname: "to_date",
|
||||
label: __("To Date"),
|
||||
fieldtype: "Date",
|
||||
default: frappe.datetime.get_today(),
|
||||
reqd: 1,
|
||||
},
|
||||
{
|
||||
"fieldname":"status",
|
||||
"label": __("Status"),
|
||||
"fieldtype": "Select",
|
||||
fieldname: "status",
|
||||
label: __("Status"),
|
||||
fieldtype: "Select",
|
||||
options: [
|
||||
{ "value": "Lead", "label": __("Lead") },
|
||||
{ "value": "Open", "label": __("Open") },
|
||||
{ "value": "Replied", "label": __("Replied") },
|
||||
{ "value": "Opportunity", "label": __("Opportunity") },
|
||||
{ "value": "Quotation", "label": __("Quotation") },
|
||||
{ "value": "Lost Quotation", "label": __("Lost Quotation") },
|
||||
{ "value": "Interested", "label": __("Interested") },
|
||||
{ "value": "Converted", "label": __("Converted") },
|
||||
{ "value": "Do Not Contact", "label": __("Do Not Contact") },
|
||||
{ value: "Lead", label: __("Lead") },
|
||||
{ value: "Open", label: __("Open") },
|
||||
{ value: "Replied", label: __("Replied") },
|
||||
{ value: "Opportunity", label: __("Opportunity") },
|
||||
{ value: "Quotation", label: __("Quotation") },
|
||||
{ value: "Lost Quotation", label: __("Lost Quotation") },
|
||||
{ value: "Interested", label: __("Interested") },
|
||||
{ value: "Converted", label: __("Converted") },
|
||||
{ value: "Do Not Contact", label: __("Do Not Contact") },
|
||||
],
|
||||
},
|
||||
{
|
||||
"fieldname":"territory",
|
||||
"label": __("Territory"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Territory",
|
||||
}
|
||||
]
|
||||
fieldname: "territory",
|
||||
label: __("Territory"),
|
||||
fieldtype: "Link",
|
||||
options: "Territory",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
frappe.query_reports["Lead Owner Efficiency"] = {
|
||||
"filters": [
|
||||
{
|
||||
"fieldname": "from_date",
|
||||
"label": __("From Date"),
|
||||
"fieldtype": "Date",
|
||||
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
|
||||
},
|
||||
{
|
||||
"fieldname": "to_date",
|
||||
"label": __("To Date"),
|
||||
"fieldtype": "Date",
|
||||
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
|
||||
}
|
||||
]};
|
||||
frappe.query_reports["Lead Owner Efficiency"] = {
|
||||
filters: [
|
||||
{
|
||||
fieldname: "from_date",
|
||||
label: __("From Date"),
|
||||
fieldtype: "Date",
|
||||
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
|
||||
},
|
||||
{
|
||||
fieldname: "to_date",
|
||||
label: __("To Date"),
|
||||
fieldtype: "Date",
|
||||
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -3,59 +3,59 @@
|
||||
/* eslint-disable */
|
||||
|
||||
frappe.query_reports["Lost Opportunity"] = {
|
||||
"filters": [
|
||||
filters: [
|
||||
{
|
||||
"fieldname":"company",
|
||||
"label": __("Company"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Company",
|
||||
"default": frappe.defaults.get_user_default("Company"),
|
||||
"reqd": 1
|
||||
fieldname: "company",
|
||||
label: __("Company"),
|
||||
fieldtype: "Link",
|
||||
options: "Company",
|
||||
default: frappe.defaults.get_user_default("Company"),
|
||||
reqd: 1,
|
||||
},
|
||||
{
|
||||
"fieldname":"from_date",
|
||||
"label": __("From Date"),
|
||||
"fieldtype": "Date",
|
||||
"default": frappe.datetime.add_months(frappe.datetime.get_today(), -12),
|
||||
"reqd": 1
|
||||
fieldname: "from_date",
|
||||
label: __("From Date"),
|
||||
fieldtype: "Date",
|
||||
default: frappe.datetime.add_months(frappe.datetime.get_today(), -12),
|
||||
reqd: 1,
|
||||
},
|
||||
{
|
||||
"fieldname":"to_date",
|
||||
"label": __("To Date"),
|
||||
"fieldtype": "Date",
|
||||
"default": frappe.datetime.get_today(),
|
||||
"reqd": 1
|
||||
fieldname: "to_date",
|
||||
label: __("To Date"),
|
||||
fieldtype: "Date",
|
||||
default: frappe.datetime.get_today(),
|
||||
reqd: 1,
|
||||
},
|
||||
{
|
||||
"fieldname":"lost_reason",
|
||||
"label": __("Lost Reason"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Opportunity Lost Reason"
|
||||
fieldname: "lost_reason",
|
||||
label: __("Lost Reason"),
|
||||
fieldtype: "Link",
|
||||
options: "Opportunity Lost Reason",
|
||||
},
|
||||
{
|
||||
"fieldname":"territory",
|
||||
"label": __("Territory"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Territory"
|
||||
fieldname: "territory",
|
||||
label: __("Territory"),
|
||||
fieldtype: "Link",
|
||||
options: "Territory",
|
||||
},
|
||||
{
|
||||
"fieldname":"opportunity_from",
|
||||
"label": __("Opportunity From"),
|
||||
"fieldtype": "Link",
|
||||
"options": "DocType",
|
||||
"get_query": function() {
|
||||
fieldname: "opportunity_from",
|
||||
label: __("Opportunity From"),
|
||||
fieldtype: "Link",
|
||||
options: "DocType",
|
||||
get_query: function () {
|
||||
return {
|
||||
"filters": {
|
||||
"name": ["in", ["Customer", "Lead"]],
|
||||
}
|
||||
}
|
||||
}
|
||||
filters: {
|
||||
name: ["in", ["Customer", "Lead"]],
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
"fieldname":"party_name",
|
||||
"label": __("Party"),
|
||||
"fieldtype": "Dynamic Link",
|
||||
"options": "opportunity_from"
|
||||
fieldname: "party_name",
|
||||
label: __("Party"),
|
||||
fieldtype: "Dynamic Link",
|
||||
options: "opportunity_from",
|
||||
},
|
||||
]
|
||||
],
|
||||
};
|
||||
|
||||
@@ -67,7 +67,7 @@ def get_columns():
|
||||
|
||||
def get_data(filters):
|
||||
return frappe.db.sql(
|
||||
"""
|
||||
f"""
|
||||
SELECT
|
||||
`tabOpportunity`.name,
|
||||
`tabOpportunity`.opportunity_from,
|
||||
@@ -79,17 +79,15 @@ def get_data(filters):
|
||||
`tabOpportunity`.territory
|
||||
FROM
|
||||
`tabOpportunity`
|
||||
{join}
|
||||
{get_join(filters)}
|
||||
WHERE
|
||||
`tabOpportunity`.status = 'Lost' and `tabOpportunity`.company = %(company)s
|
||||
AND DATE(`tabOpportunity`.modified) BETWEEN %(from_date)s AND %(to_date)s
|
||||
{conditions}
|
||||
{get_conditions(filters)}
|
||||
GROUP BY
|
||||
`tabOpportunity`.name
|
||||
ORDER BY
|
||||
`tabOpportunity`.creation asc """.format(
|
||||
conditions=get_conditions(filters), join=get_join(filters)
|
||||
),
|
||||
`tabOpportunity`.creation asc """,
|
||||
filters,
|
||||
as_dict=1,
|
||||
)
|
||||
@@ -119,9 +117,7 @@ def get_join(filters):
|
||||
join = """JOIN `tabOpportunity Lost Reason Detail`
|
||||
ON `tabOpportunity Lost Reason Detail`.parenttype = 'Opportunity' and
|
||||
`tabOpportunity Lost Reason Detail`.parent = `tabOpportunity`.name and
|
||||
`tabOpportunity Lost Reason Detail`.lost_reason = '{0}'
|
||||
""".format(
|
||||
filters.get("lost_reason")
|
||||
)
|
||||
`tabOpportunity Lost Reason Detail`.lost_reason = '{}'
|
||||
""".format(filters.get("lost_reason"))
|
||||
|
||||
return join
|
||||
|
||||
@@ -3,26 +3,25 @@
|
||||
/* eslint-disable */
|
||||
|
||||
frappe.query_reports["Opportunity Summary by Sales Stage"] = {
|
||||
"filters": [
|
||||
filters: [
|
||||
{
|
||||
fieldname: "based_on",
|
||||
label: __("Based On"),
|
||||
fieldtype: "Select",
|
||||
options: "Opportunity Owner\nSource\nOpportunity Type",
|
||||
default: "Opportunity Owner"
|
||||
default: "Opportunity Owner",
|
||||
},
|
||||
{
|
||||
fieldname: "data_based_on",
|
||||
label: __("Data Based On"),
|
||||
fieldtype: "Select",
|
||||
options: "Number\nAmount",
|
||||
default: "Number"
|
||||
default: "Number",
|
||||
},
|
||||
{
|
||||
fieldname: "from_date",
|
||||
label: __("From Date"),
|
||||
fieldtype: "Date",
|
||||
|
||||
},
|
||||
{
|
||||
fieldname: "to_date",
|
||||
@@ -33,14 +32,14 @@ frappe.query_reports["Opportunity Summary by Sales Stage"] = {
|
||||
fieldname: "status",
|
||||
label: __("Status"),
|
||||
fieldtype: "MultiSelectList",
|
||||
get_data: function() {
|
||||
get_data: function () {
|
||||
return [
|
||||
{value: "Open", description: "Status"},
|
||||
{value: "Converted", description: "Status"},
|
||||
{value: "Quotation", description: "Status"},
|
||||
{value: "Replied", description: "Status"}
|
||||
]
|
||||
}
|
||||
{ value: "Open", description: "Status" },
|
||||
{ value: "Converted", description: "Status" },
|
||||
{ value: "Quotation", description: "Status" },
|
||||
{ value: "Replied", description: "Status" },
|
||||
];
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldname: "opportunity_source",
|
||||
@@ -59,7 +58,7 @@ frappe.query_reports["Opportunity Summary by Sales Stage"] = {
|
||||
label: __("Company"),
|
||||
fieldtype: "Link",
|
||||
options: "Company",
|
||||
default: frappe.defaults.get_user_default("Company")
|
||||
}
|
||||
]
|
||||
default: frappe.defaults.get_user_default("Company"),
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -14,7 +14,7 @@ def execute(filters=None):
|
||||
return OpportunitySummaryBySalesStage(filters).run()
|
||||
|
||||
|
||||
class OpportunitySummaryBySalesStage(object):
|
||||
class OpportunitySummaryBySalesStage:
|
||||
def __init__(self, filters=None):
|
||||
self.filters = frappe._dict(filters or {})
|
||||
|
||||
@@ -199,7 +199,6 @@ class OpportunitySummaryBySalesStage(object):
|
||||
return filters
|
||||
|
||||
def get_chart_data(self):
|
||||
labels = []
|
||||
datasets = []
|
||||
values = [0] * len(self.sales_stage_list)
|
||||
|
||||
|
||||
@@ -2,24 +2,24 @@
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.query_reports["Prospects Engaged But Not Converted"] = {
|
||||
"filters": [
|
||||
filters: [
|
||||
{
|
||||
"fieldname": "lead",
|
||||
"label": __("Lead"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Lead"
|
||||
fieldname: "lead",
|
||||
label: __("Lead"),
|
||||
fieldtype: "Link",
|
||||
options: "Lead",
|
||||
},
|
||||
{
|
||||
"fieldname": "no_of_interaction",
|
||||
"label": __("Number of Interaction"),
|
||||
"fieldtype": "Int",
|
||||
"default": 1
|
||||
fieldname: "no_of_interaction",
|
||||
label: __("Number of Interaction"),
|
||||
fieldtype: "Int",
|
||||
default: 1,
|
||||
},
|
||||
{
|
||||
"fieldname": "lead_age",
|
||||
"label": __("Minimum Lead Age (Days)"),
|
||||
"fieldtype": "Int",
|
||||
"default": 60
|
||||
fieldname: "lead_age",
|
||||
label: __("Minimum Lead Age (Days)"),
|
||||
fieldtype: "Int",
|
||||
default: 60,
|
||||
},
|
||||
]
|
||||
}
|
||||
],
|
||||
};
|
||||
|
||||
@@ -62,9 +62,7 @@ def get_data(filters):
|
||||
lead_details = []
|
||||
lead_filters = get_lead_filters(filters)
|
||||
|
||||
for lead in frappe.get_all(
|
||||
"Lead", fields=["name", "lead_name", "company_name"], filters=lead_filters
|
||||
):
|
||||
for lead in frappe.get_all("Lead", fields=["name", "lead_name", "company_name"], filters=lead_filters):
|
||||
data = frappe.db.sql(
|
||||
"""
|
||||
select
|
||||
@@ -90,7 +88,7 @@ def get_data(filters):
|
||||
)
|
||||
|
||||
for lead_info in data:
|
||||
lead_data = [lead.name, lead.lead_name, lead.company_name] + list(lead_info)
|
||||
lead_data = [lead.name, lead.lead_name, lead.company_name, *list(lead_info)]
|
||||
lead_details.append(lead_data)
|
||||
|
||||
return lead_details
|
||||
|
||||
@@ -3,68 +3,68 @@
|
||||
/* eslint-disable */
|
||||
|
||||
frappe.query_reports["Sales Pipeline Analytics"] = {
|
||||
"filters": [
|
||||
filters: [
|
||||
{
|
||||
fieldname: "pipeline_by",
|
||||
label: __("Pipeline By"),
|
||||
fieldtype: "Select",
|
||||
options: "Owner\nSales Stage",
|
||||
default: "Owner"
|
||||
default: "Owner",
|
||||
},
|
||||
{
|
||||
fieldname: "from_date",
|
||||
label: __("From Date"),
|
||||
fieldtype: "Date"
|
||||
fieldtype: "Date",
|
||||
},
|
||||
{
|
||||
fieldname: "to_date",
|
||||
label: __("To Date"),
|
||||
fieldtype: "Date"
|
||||
fieldtype: "Date",
|
||||
},
|
||||
{
|
||||
fieldname: "range",
|
||||
label: __("Range"),
|
||||
fieldtype: "Select",
|
||||
options: "Monthly\nQuarterly",
|
||||
default: "Monthly"
|
||||
default: "Monthly",
|
||||
},
|
||||
{
|
||||
fieldname: "assigned_to",
|
||||
label: __("Assigned To"),
|
||||
fieldtype: "Link",
|
||||
options: "User"
|
||||
options: "User",
|
||||
},
|
||||
{
|
||||
fieldname: "status",
|
||||
label: __("Status"),
|
||||
fieldtype: "Select",
|
||||
options: "Open\nQuotation\nConverted\nReplied"
|
||||
options: "Open\nQuotation\nConverted\nReplied",
|
||||
},
|
||||
{
|
||||
fieldname: "based_on",
|
||||
label: __("Based On"),
|
||||
fieldtype: "Select",
|
||||
options: "Number\nAmount",
|
||||
default: "Number"
|
||||
default: "Number",
|
||||
},
|
||||
{
|
||||
fieldname: "company",
|
||||
label: __("Company"),
|
||||
fieldtype: "Link",
|
||||
options: "Company",
|
||||
default: frappe.defaults.get_user_default("Company")
|
||||
default: frappe.defaults.get_user_default("Company"),
|
||||
},
|
||||
{
|
||||
fieldname: "opportunity_source",
|
||||
label: __("Opportunity Source"),
|
||||
fieldtype: "Link",
|
||||
options: "Lead Source"
|
||||
options: "Lead Source",
|
||||
},
|
||||
{
|
||||
fieldname: "opportunity_type",
|
||||
label: __("Opportunity Type"),
|
||||
fieldtype: "Link",
|
||||
options: "Opportunity Type"
|
||||
options: "Opportunity Type",
|
||||
},
|
||||
]
|
||||
],
|
||||
};
|
||||
|
||||
@@ -17,7 +17,7 @@ def execute(filters=None):
|
||||
return SalesPipelineAnalytics(filters).run()
|
||||
|
||||
|
||||
class SalesPipelineAnalytics(object):
|
||||
class SalesPipelineAnalytics:
|
||||
def __init__(self, filters=None):
|
||||
self.filters = frappe._dict(filters or {})
|
||||
|
||||
@@ -41,7 +41,9 @@ class SalesPipelineAnalytics(object):
|
||||
month_list = self.get_month_list()
|
||||
|
||||
for month in month_list:
|
||||
self.columns.append({"fieldname": month, "fieldtype": based_on, "label": month, "width": 200})
|
||||
self.columns.append(
|
||||
{"fieldname": month, "fieldtype": based_on, "label": _(month), "width": 200}
|
||||
)
|
||||
|
||||
elif self.filters.get("range") == "Quarterly":
|
||||
for quarter in range(1, 5):
|
||||
@@ -96,7 +98,7 @@ class SalesPipelineAnalytics(object):
|
||||
"Opportunity",
|
||||
filters=self.get_conditions(),
|
||||
fields=[self.based_on, self.data_based_on, self.duration],
|
||||
group_by="{},{}".format(self.group_by_based_on, self.group_by_period),
|
||||
group_by=f"{self.group_by_based_on},{self.group_by_period}",
|
||||
order_by=self.group_by_period,
|
||||
)
|
||||
|
||||
@@ -156,7 +158,7 @@ class SalesPipelineAnalytics(object):
|
||||
|
||||
for column in self.columns:
|
||||
if column["fieldname"] != "opportunity_owner" and column["fieldname"] != "sales_stage":
|
||||
labels.append(column["fieldname"])
|
||||
labels.append(_(column["fieldname"]))
|
||||
|
||||
self.chart = {"data": {"labels": labels, "datasets": datasets}, "type": "line"}
|
||||
|
||||
@@ -228,7 +230,7 @@ class SalesPipelineAnalytics(object):
|
||||
current_date = date.today()
|
||||
month_number = date.today().month
|
||||
|
||||
for month in range(month_number, 13):
|
||||
for _month in range(month_number, 13):
|
||||
month_list.append(current_date.strftime("%B"))
|
||||
current_date = current_date + relativedelta(months=1)
|
||||
|
||||
|
||||
@@ -94,9 +94,7 @@ def get_linked_prospect(reference_doctype, reference_name):
|
||||
"Opportunity", reference_name, ["opportunity_from", "party_name"]
|
||||
)
|
||||
if opportunity_from == "Lead":
|
||||
prospect = frappe.db.get_value(
|
||||
"Prospect Opportunity", {"opportunity": reference_name}, "parent"
|
||||
)
|
||||
prospect = frappe.db.get_value("Prospect Opportunity", {"opportunity": reference_name}, "parent")
|
||||
if opportunity_from == "Prospect":
|
||||
prospect = party_name
|
||||
|
||||
|
||||
Reference in New Issue
Block a user