[Enhancement] Improvement to the sales pipeline (#15524)
* Additions to sales pipeline * Codacy corrections * Codacy corrections * Codacy corrections * Replace _ with dummy for unused variable * Performance + dates corrections * Itertuples modification * Removing trailing whitespaces * Sales stage doctype * Divide sales stages fixtures in separate functions * Remove duplicate fixtures * Add newline after method * Missing requirement
This commit is contained in:
parent
efd776da46
commit
75fa6b3ee8
@ -107,6 +107,8 @@ erpnext.crm.Opportunity = frappe.ui.form.Controller.extend({
|
||||
if(!this.frm.doc.company && frappe.defaults.get_user_default("Company"))
|
||||
set_multiple(this.frm.doc.doctype, this.frm.doc.name,
|
||||
{ company:frappe.defaults.get_user_default("Company") });
|
||||
if(!this.frm.doc.currency)
|
||||
set_multiple(this.frm.doc.doctype, this.frm.doc.name, { currency:frappe.defaults.get_user_default("Currency") });
|
||||
|
||||
this.setup_queries();
|
||||
},
|
||||
|
File diff suppressed because it is too large
Load Diff
0
erpnext/crm/doctype/sales_stage/__init__.py
Normal file
0
erpnext/crm/doctype/sales_stage/__init__.py
Normal file
8
erpnext/crm/doctype/sales_stage/sales_stage.js
Normal file
8
erpnext/crm/doctype/sales_stage/sales_stage.js
Normal file
@ -0,0 +1,8 @@
|
||||
// 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) {
|
||||
|
||||
}
|
||||
});
|
96
erpnext/crm/doctype/sales_stage/sales_stage.json
Normal file
96
erpnext/crm/doctype/sales_stage/sales_stage.json
Normal file
@ -0,0 +1,96 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_events_in_timeline": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"autoname": "field:stage_name",
|
||||
"beta": 0,
|
||||
"creation": "2018-10-01 09:28:16.399518",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "stage_name",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Stage Name",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 1
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2018-10-01 09:29:43.230378",
|
||||
"modified_by": "Administrator",
|
||||
"module": "CRM",
|
||||
"name": "Sales Stage",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Sales Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0,
|
||||
"track_views": 0
|
||||
}
|
10
erpnext/crm/doctype/sales_stage/sales_stage.py
Normal file
10
erpnext/crm/doctype/sales_stage/sales_stage.py
Normal file
@ -0,0 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2018, 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 SalesStage(Document):
|
||||
pass
|
23
erpnext/crm/doctype/sales_stage/test_sales_stage.js
Normal file
23
erpnext/crm/doctype/sales_stage/test_sales_stage.js
Normal file
@ -0,0 +1,23 @@
|
||||
/* eslint-disable */
|
||||
// rename this file from _test_[name] to test_[name] to activate
|
||||
// and remove above this line
|
||||
|
||||
QUnit.test("test: Sales Stage", function (assert) {
|
||||
let done = assert.async();
|
||||
|
||||
// number of asserts
|
||||
assert.expect(1);
|
||||
|
||||
frappe.run_serially([
|
||||
// insert a new Sales Stage
|
||||
() => frappe.tests.make('Sales Stage', [
|
||||
// values to be set
|
||||
{key: 'value'}
|
||||
]),
|
||||
() => {
|
||||
assert.equal(cur_frm.doc.key, 'value');
|
||||
},
|
||||
() => done()
|
||||
]);
|
||||
|
||||
});
|
10
erpnext/crm/doctype/sales_stage/test_sales_stage.py
Normal file
10
erpnext/crm/doctype/sales_stage/test_sales_stage.py
Normal file
@ -0,0 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
import unittest
|
||||
|
||||
class TestSalesStage(unittest.TestCase):
|
||||
pass
|
@ -567,4 +567,5 @@ erpnext.patches.v10_0.delete_hub_documents # 12-08-2018
|
||||
erpnext.patches.v11_0.rename_healthcare_fields
|
||||
erpnext.patches.v11_0.remove_land_unit_icon
|
||||
erpnext.patches.v11_0.add_default_dispatch_notification_template
|
||||
erpnext.patches.v11_0.add_market_segments
|
||||
erpnext.patches.v11_0.add_market_segments
|
||||
erpnext.patches.v11_0.add_sales_stages
|
||||
|
10
erpnext/patches/v11_0/add_sales_stages.py
Normal file
10
erpnext/patches/v11_0/add_sales_stages.py
Normal file
@ -0,0 +1,10 @@
|
||||
import frappe
|
||||
from frappe import _
|
||||
from erpnext.setup.setup_wizard.operations.install_fixtures import add_sale_stages
|
||||
|
||||
def execute():
|
||||
frappe.reload_doc('crm', 'doctype', 'sales_stage')
|
||||
|
||||
frappe.local.lang = frappe.db.get_default("lang") or 'en'
|
||||
|
||||
add_sale_stages()
|
@ -13,23 +13,34 @@ frappe.pages['sales-funnel'].on_page_load = function(wrapper) {
|
||||
frappe.breadcrumbs.add("Selling");
|
||||
}
|
||||
|
||||
erpnext.SalesFunnel = Class.extend({
|
||||
init: function(wrapper) {
|
||||
erpnext.SalesFunnel = class SalesFunnel {
|
||||
constructor(wrapper) {
|
||||
var me = this;
|
||||
// 0 setTimeout hack - this gives time for canvas to get width and height
|
||||
setTimeout(function() {
|
||||
me.setup(wrapper);
|
||||
me.get_data();
|
||||
}, 0);
|
||||
},
|
||||
}
|
||||
|
||||
setup: function(wrapper) {
|
||||
setup(wrapper) {
|
||||
var me = this;
|
||||
|
||||
this.company_field = wrapper.page.add_field({"fieldtype": "Link", "fieldname": "company", "options": "Company",
|
||||
"label": __("Company"), "reqd": 1, "default": frappe.defaults.get_user_default('company'),
|
||||
change: function() {
|
||||
me.company = this.value || frappe.defaults.get_user_default('company');
|
||||
me.get_data();
|
||||
}
|
||||
}),
|
||||
|
||||
this.elements = {
|
||||
layout: $(wrapper).find(".layout-main"),
|
||||
from_date: wrapper.page.add_date(__("From Date")),
|
||||
to_date: wrapper.page.add_date(__("To Date")),
|
||||
chart: wrapper.page.add_select(__("Chart"), [{value: 'sales_funnel', label:__("Sales Funnel")},
|
||||
{value: 'sales_pipeline', label:__("Sales Pipeline")},
|
||||
{value: 'opp_by_lead_source', label:__("Opportunities by lead source")}]),
|
||||
refresh_btn: wrapper.page.set_primary_action(__("Refresh"),
|
||||
function() { me.get_data(); }, "fa fa-refresh"),
|
||||
};
|
||||
@ -41,16 +52,27 @@ erpnext.SalesFunnel = Class.extend({
|
||||
this.elements.funnel_wrapper = $('<div class="funnel-wrapper text-center"></div>')
|
||||
.appendTo(this.elements.layout);
|
||||
|
||||
this.company = frappe.defaults.get_user_default('company');
|
||||
this.options = {
|
||||
from_date: frappe.datetime.add_months(frappe.datetime.get_today(), -1),
|
||||
to_date: frappe.datetime.get_today()
|
||||
to_date: frappe.datetime.get_today(),
|
||||
chart: 'sales_funnel'
|
||||
};
|
||||
|
||||
// set defaults and bind on change
|
||||
$.each(this.options, function(k, v) {
|
||||
me.elements[k].val(frappe.datetime.str_to_user(v));
|
||||
if (['from_date', 'to_date'].includes(k)) {
|
||||
me.elements[k].val(frappe.datetime.str_to_user(v));
|
||||
} else {
|
||||
me.elements[k].val(v);
|
||||
}
|
||||
|
||||
me.elements[k].on("change", function() {
|
||||
me.options[k] = frappe.datetime.user_to_str($(this).val());
|
||||
if (['from_date', 'to_date'].includes(k)) {
|
||||
me.options[k] = frappe.datetime.user_to_str($(this).val()) != 'Invalid date' ? frappe.datetime.user_to_str($(this).val()) : frappe.datetime.get_today();
|
||||
} else {
|
||||
me.options.chart = $(this).val();
|
||||
}
|
||||
me.get_data();
|
||||
});
|
||||
});
|
||||
@ -64,29 +86,90 @@ erpnext.SalesFunnel = Class.extend({
|
||||
$(window).resize(function() {
|
||||
me.render();
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
get_data: function(btn) {
|
||||
get_data(btn) {
|
||||
var me = this;
|
||||
frappe.call({
|
||||
method: "erpnext.selling.page.sales_funnel.sales_funnel.get_funnel_data",
|
||||
args: {
|
||||
from_date: this.options.from_date,
|
||||
to_date: this.options.to_date
|
||||
},
|
||||
btn: btn,
|
||||
callback: function(r) {
|
||||
if(!r.exc) {
|
||||
me.options.data = r.message;
|
||||
me.render();
|
||||
if (me.options.chart == 'sales_funnel'){
|
||||
frappe.call({
|
||||
method: "erpnext.selling.page.sales_funnel.sales_funnel.get_funnel_data",
|
||||
args: {
|
||||
from_date: this.options.from_date,
|
||||
to_date: this.options.to_date,
|
||||
company: this.company
|
||||
},
|
||||
btn: btn,
|
||||
callback: function(r) {
|
||||
if(!r.exc) {
|
||||
me.options.data = r.message;
|
||||
if (me.options.data=='empty') {
|
||||
const $parent = me.elements.funnel_wrapper;
|
||||
$parent.html(__('No data for this period'));
|
||||
} else {
|
||||
me.render_funnel();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
} else if (me.options.chart == 'opp_by_lead_source'){
|
||||
frappe.call({
|
||||
method: "erpnext.selling.page.sales_funnel.sales_funnel.get_opp_by_lead_source",
|
||||
args: {
|
||||
from_date: this.options.from_date,
|
||||
to_date: this.options.to_date,
|
||||
company: this.company
|
||||
},
|
||||
btn: btn,
|
||||
callback: function(r) {
|
||||
if(!r.exc) {
|
||||
me.options.data = r.message;
|
||||
if (me.options.data=='empty') {
|
||||
const $parent = me.elements.funnel_wrapper;
|
||||
$parent.html(__('No data for this period'));
|
||||
} else {
|
||||
me.render_opp_by_lead_source();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (me.options.chart == 'sales_pipeline'){
|
||||
frappe.call({
|
||||
method: "erpnext.selling.page.sales_funnel.sales_funnel.get_pipeline_data",
|
||||
args: {
|
||||
from_date: this.options.from_date,
|
||||
to_date: this.options.to_date,
|
||||
company: this.company
|
||||
},
|
||||
btn: btn,
|
||||
callback: function(r) {
|
||||
if(!r.exc) {
|
||||
me.options.data = r.message;
|
||||
if (me.options.data=='empty') {
|
||||
const $parent = me.elements.funnel_wrapper;
|
||||
$parent.html(__('No data for this period'));
|
||||
} else {
|
||||
me.render_pipeline();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
render: function() {
|
||||
render() {
|
||||
let me = this;
|
||||
if (me.options.chart == 'sales_funnel'){
|
||||
me.render_funnel();
|
||||
} else if (me.options.chart == 'opp_by_lead_source'){
|
||||
me.render_opp_by_lead_source();
|
||||
} else if (me.options.chart == 'sales_pipeline'){
|
||||
me.render_pipeline();
|
||||
}
|
||||
}
|
||||
|
||||
render_funnel() {
|
||||
var me = this;
|
||||
this.prepare();
|
||||
this.prepare_funnel();
|
||||
|
||||
var context = this.elements.context,
|
||||
x_start = 0.0,
|
||||
@ -119,9 +202,9 @@ erpnext.SalesFunnel = Class.extend({
|
||||
|
||||
me.draw_legend(x_mid, y_mid, me.options.width, me.options.height, d.value + " - " + d.title);
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
prepare: function() {
|
||||
prepare_funnel() {
|
||||
var me = this;
|
||||
|
||||
this.elements.no_data.toggle(false);
|
||||
@ -147,9 +230,9 @@ erpnext.SalesFunnel = Class.extend({
|
||||
.attr("height", this.options.height);
|
||||
|
||||
this.elements.context = this.elements.canvas.get(0).getContext("2d");
|
||||
},
|
||||
}
|
||||
|
||||
draw_triangle: function(x_start, x_mid, x_end, y, height) {
|
||||
draw_triangle(x_start, x_mid, x_end, y, height) {
|
||||
var context = this.elements.context;
|
||||
context.beginPath();
|
||||
context.moveTo(x_start, y);
|
||||
@ -158,9 +241,9 @@ erpnext.SalesFunnel = Class.extend({
|
||||
context.lineTo(x_start, y);
|
||||
context.closePath();
|
||||
context.fill();
|
||||
},
|
||||
}
|
||||
|
||||
draw_legend: function(x_mid, y_mid, width, height, title) {
|
||||
draw_legend(x_mid, y_mid, width, height, title) {
|
||||
var context = this.elements.context;
|
||||
|
||||
if(y_mid == 0) {
|
||||
@ -186,4 +269,44 @@ erpnext.SalesFunnel = Class.extend({
|
||||
context.font = "1.1em sans-serif";
|
||||
context.fillText(__(title), width + 20, y_mid);
|
||||
}
|
||||
});
|
||||
|
||||
render_opp_by_lead_source() {
|
||||
let me = this;
|
||||
let currency = frappe.defaults.get_default("currency");
|
||||
|
||||
let chart_data = me.options.data ? me.options.data : null;
|
||||
|
||||
const parent = me.elements.funnel_wrapper[0];
|
||||
this.chart = new Chart(parent, {
|
||||
title: __("Sales Opportunities by Source"),
|
||||
height: 400,
|
||||
data: chart_data,
|
||||
type: 'bar',
|
||||
barOptions: {
|
||||
stacked: 1
|
||||
},
|
||||
tooltipOptions: {
|
||||
formatTooltipY: d => format_currency(d, currency),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
render_pipeline() {
|
||||
let me = this;
|
||||
let currency = frappe.defaults.get_default("currency");
|
||||
|
||||
let chart_data = me.options.data ? me.options.data : null;
|
||||
|
||||
const parent = me.elements.funnel_wrapper[0];
|
||||
this.chart = new Chart(parent, {
|
||||
title: __("Sales Pipeline by Stage"),
|
||||
height: 400,
|
||||
data: chart_data,
|
||||
type: 'bar',
|
||||
tooltipOptions: {
|
||||
formatTooltipY: d => format_currency(d, currency),
|
||||
},
|
||||
colors: ['light-green', 'green']
|
||||
});
|
||||
}
|
||||
};
|
||||
|
@ -1,16 +1,18 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
|
||||
from frappe import _
|
||||
from erpnext.accounts.report.utils import convert
|
||||
import pandas as pd
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_funnel_data(from_date, to_date):
|
||||
def get_funnel_data(from_date, to_date, company):
|
||||
active_leads = frappe.db.sql("""select count(*) from `tabLead`
|
||||
where (date(`modified`) between %s and %s)
|
||||
and status != "Do Not Contact" """, (from_date, to_date))[0][0]
|
||||
and status != "Do Not Contact" and company=%s""", (from_date, to_date, company))[0][0]
|
||||
|
||||
active_leads += frappe.db.sql("""select count(distinct contact.name) from `tabContact` contact
|
||||
left join `tabDynamic Link` dl on (dl.parent=contact.name) where dl.link_doctype='Customer'
|
||||
@ -18,14 +20,14 @@ def get_funnel_data(from_date, to_date):
|
||||
|
||||
opportunities = frappe.db.sql("""select count(*) from `tabOpportunity`
|
||||
where (date(`creation`) between %s and %s)
|
||||
and status != "Lost" """, (from_date, to_date))[0][0]
|
||||
and status != "Lost" and company=%s""", (from_date, to_date, company))[0][0]
|
||||
|
||||
quotations = frappe.db.sql("""select count(*) from `tabQuotation`
|
||||
where docstatus = 1 and (date(`creation`) between %s and %s)
|
||||
and status != "Lost" """, (from_date, to_date))[0][0]
|
||||
and status != "Lost" and company=%s""", (from_date, to_date, company))[0][0]
|
||||
|
||||
sales_orders = frappe.db.sql("""select count(*) from `tabSales Order`
|
||||
where docstatus = 1 and (date(`creation`) between %s and %s)""", (from_date, to_date))[0][0]
|
||||
where docstatus = 1 and (date(`creation`) between %s and %s) and company=%s""", (from_date, to_date, company))[0][0]
|
||||
|
||||
return [
|
||||
{ "title": _("Active Leads / Customers"), "value": active_leads, "color": "#B03B46" },
|
||||
@ -33,3 +35,54 @@ def get_funnel_data(from_date, to_date):
|
||||
{ "title": _("Quotations"), "value": quotations, "color": "#006685" },
|
||||
{ "title": _("Sales Orders"), "value": sales_orders, "color": "#00AD65" }
|
||||
]
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_opp_by_lead_source(from_date, to_date, company):
|
||||
opportunities = frappe.get_all("Opportunity", filters=[['status', 'in', ['Open', 'Quotation', 'Replied']], ['company', '=', company], ['transaction_date', 'Between', [from_date, to_date]]], fields=['currency', 'sales_stage', 'opportunity_amount', 'probability', 'source'])
|
||||
|
||||
if opportunities:
|
||||
default_currency = frappe.get_cached_value('Global Defaults', 'None', 'default_currency')
|
||||
|
||||
cp_opportunities = [dict(x, **{'compound_amount': (convert(x['opportunity_amount'], x['currency'], default_currency, to_date) * x['probability']/100)}) for x in opportunities]
|
||||
|
||||
df = pd.DataFrame(cp_opportunities).groupby(['source', 'sales_stage'], as_index=False).agg({'compound_amount': 'sum'})
|
||||
|
||||
result = {}
|
||||
result['labels'] = list(set(df.source.values))
|
||||
result['datasets'] = []
|
||||
|
||||
for s in set(df.sales_stage.values):
|
||||
result['datasets'].append({'name': s, 'values': [0]*len(result['labels']), 'chartType': 'bar'})
|
||||
|
||||
for row in df.itertuples():
|
||||
source_index = result['labels'].index(row.source)
|
||||
|
||||
for dataset in result['datasets']:
|
||||
if dataset['name'] == row.sales_stage:
|
||||
dataset['values'][source_index] = row.compound_amount
|
||||
|
||||
return result
|
||||
|
||||
else:
|
||||
return 'empty'
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_pipeline_data(from_date, to_date, company):
|
||||
opportunities = frappe.get_all("Opportunity", filters=[['status', 'in', ['Open', 'Quotation', 'Replied']], ['company', '=', company], ['transaction_date', 'Between', [from_date, to_date]]], fields=['currency', 'sales_stage', 'opportunity_amount', 'probability'])
|
||||
|
||||
if opportunities:
|
||||
default_currency = frappe.get_cached_value('Global Defaults', 'None', 'default_currency')
|
||||
|
||||
cp_opportunities = [dict(x, **{'compound_amount': (convert(x['opportunity_amount'], x['currency'], default_currency, to_date) * x['probability']/100)}) for x in opportunities]
|
||||
|
||||
df = pd.DataFrame(cp_opportunities).groupby(['sales_stage'], as_index=True).agg({'compound_amount': 'sum'}).to_dict()
|
||||
|
||||
result = {}
|
||||
result['labels'] = df['compound_amount'].keys()
|
||||
result['datasets'] = []
|
||||
result['datasets'].append({'name': _("Total Amount"), 'values': df['compound_amount'].values(), 'chartType': 'bar'})
|
||||
|
||||
return result
|
||||
|
||||
else:
|
||||
return 'empty'
|
@ -308,6 +308,20 @@ def add_market_segments():
|
||||
|
||||
make_fixture_records(records)
|
||||
|
||||
def add_sale_stages():
|
||||
# Sale Stages
|
||||
records = [
|
||||
{"doctype": "Sales Stage", "stage_name": _("Prospecting")},
|
||||
{"doctype": "Sales Stage", "stage_name": _("Qualification")},
|
||||
{"doctype": "Sales Stage", "stage_name": _("Needs Analysis")},
|
||||
{"doctype": "Sales Stage", "stage_name": _("Value Proposition")},
|
||||
{"doctype": "Sales Stage", "stage_name": _("Identifying Decision Makers")},
|
||||
{"doctype": "Sales Stage", "stage_name": _("Perception Analysis")},
|
||||
{"doctype": "Sales Stage", "stage_name": _("Proposal/Price Quote")},
|
||||
{"doctype": "Sales Stage", "stage_name": _("Negotiation/Review")}
|
||||
]
|
||||
make_fixture_records(records)
|
||||
|
||||
def make_fixture_records(records):
|
||||
from frappe.modules import scrub
|
||||
for r in records:
|
||||
|
@ -105,6 +105,7 @@ def setup_complete(args=None):
|
||||
def stage_fixtures(args):
|
||||
install_fixtures.install(args.get("country"))
|
||||
install_fixtures.add_market_segments()
|
||||
install_fixtures.add_sale_stages()
|
||||
|
||||
def setup_company(args):
|
||||
defaults_setup.create_price_lists(args)
|
||||
|
@ -6,3 +6,4 @@ python-stdnum
|
||||
braintree
|
||||
gocardless_pro
|
||||
woocommerce
|
||||
pandas
|
Loading…
x
Reference in New Issue
Block a user