diff --git a/selling/page/crm_funnel/__init__.py b/selling/page/crm_funnel/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/selling/page/crm_funnel/crm_funnel.css b/selling/page/crm_funnel/crm_funnel.css new file mode 100644 index 0000000000..89e904fcfc --- /dev/null +++ b/selling/page/crm_funnel/crm_funnel.css @@ -0,0 +1,3 @@ +.funnel-wrapper { + margin: 15px; +} \ No newline at end of file diff --git a/selling/page/crm_funnel/crm_funnel.js b/selling/page/crm_funnel/crm_funnel.js new file mode 100644 index 0000000000..a2d2b93479 --- /dev/null +++ b/selling/page/crm_funnel/crm_funnel.js @@ -0,0 +1,176 @@ +// Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. +// License: GNU General Public License v3. See license.txt + +wn.pages['crm-funnel'].onload = function(wrapper) { + wn.ui.make_app_page({ + parent: wrapper, + title: 'CRM Funnel', + single_column: true + }); + + wrapper.crm_funnel = new erpnext.CRMFunnel(wrapper); +} + +erpnext.CRMFunnel = Class.extend({ + init: function(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) { + var me = this; + + this.elements = { + layout: $(wrapper).find(".layout-main"), + from_date: wrapper.appframe.add_date("From Date"), + to_date: wrapper.appframe.add_date("To Date"), + refresh_btn: wrapper.appframe.add_button("Refresh", + function() { me.get_data(); }, "icon-refresh"), + }; + + this.elements.no_data = $('
No Data
') + .toggle(false) + .appendTo(this.elements.layout); + + this.elements.funnel_wrapper = $('
') + .appendTo(this.elements.layout); + + this.options = { + from_date: wn.datetime.get_today(), + to_date: wn.datetime.add_months(wn.datetime.get_today(), -1) + }; + + // set defaults and bind on change + $.each(this.options, function(k, v) { + me.elements[k].val(wn.datetime.str_to_user(v)); + me.elements[k].on("change", function() { + me.options[k] = wn.datetime.user_to_str($(this).val()); + me.get_data(); + }); + }); + + // bind refresh + this.elements.refresh_btn.on("click", function() { + me.get_data(this); + }); + + // bind resize + $(window).resize(function() { + me.render(); + }); + }, + + get_data: function(btn) { + var me = this; + wn.call({ + module: "selling", + page: "crm_funnel", + method: "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(); + } + } + }); + }, + + render: function() { + var me = this; + this.prepare(); + + var context = this.elements.context, + x_start = 0.0, + x_end = this.options.width, + x_mid = (x_end - x_start) / 2.0, + y = 0, + y_old = 0.0; + + if(this.options.total_value === 0) { + this.elements.no_data.toggle(true); + return; + } + + this.options.data.forEach(function(d) { + context.fillStyle = d.color; + context.strokeStyle = d.color; + me.draw_triangle(x_start, x_mid, x_end, y, me.options.height); + + y_old = y; + + // new y + y = y + (me.options.height * d.value / me.options.total_value); + + // new x + var half_side = (me.options.height - y) / Math.sqrt(3); + x_start = x_mid - half_side; + x_end = x_mid + half_side; + + var y_mid = y_old + (y - y_old) / 2.0; + + me.draw_legend(x_mid, y_mid, me.options.width, me.options.height, d.value + " - " + d.title); + }); + }, + + prepare: function() { + this.elements.no_data.toggle(false); + + // calculate width and height options + this.options.width = $(this.elements.funnel_wrapper).width() * 2.0 / 3.0; + this.options.height = (Math.sqrt(3) * this.options.width) / 2.0; + + // calculate total value + this.options.total_value = this.options.data.reduce( + function(prev, curr) { return prev + curr.value; }, 0.0); + + this.elements.canvas = $('') + .appendTo(this.elements.funnel_wrapper.empty()) + .attr("width", $(this.elements.funnel_wrapper).width()) + .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) { + var context = this.elements.context; + context.beginPath(); + context.moveTo(x_start, y); + context.lineTo(x_end, y); + context.lineTo(x_mid, height); + context.lineTo(x_start, y); + context.closePath(); + context.fill(); + }, + + draw_legend: function(x_mid, y_mid, width, height, title) { + var context = this.elements.context; + + // draw line + context.beginPath(); + context.moveTo(x_mid, y_mid); + context.lineTo(width, y_mid); + context.closePath(); + context.stroke(); + + // draw circle + context.beginPath(); + context.arc(width, y_mid, 5, 0, Math.PI * 2, false); + context.closePath(); + context.fill(); + + // draw text + context.fillStyle = "black"; + context.textBaseline = "middle"; + context.font = "1.1em sans-serif"; + context.fillText(title, width + 20, y_mid); + } +}); \ No newline at end of file diff --git a/selling/page/crm_funnel/crm_funnel.py b/selling/page/crm_funnel/crm_funnel.py new file mode 100644 index 0000000000..be0aebe7bb --- /dev/null +++ b/selling/page/crm_funnel/crm_funnel.py @@ -0,0 +1,33 @@ +# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals +import webnotes + +@webnotes.whitelist() +def get_funnel_data(from_date, to_date): + active_leads = webnotes.conn.sql("""select count(*) from `tabLead` + where (`modified` between %s and %s) + and status != "Do Not Contact" """, (from_date, to_date))[0][0] + + active_leads += webnotes.conn.sql("""select count(distinct customer) from `tabContact` + where (`modified` between %s and %s) + and status != "Passive" """, (from_date, to_date))[0][0] + + opportunities = webnotes.conn.sql("""select count(*) from `tabOpportunity` + where docstatus = 1 and (`modified` between %s and %s) + and status != "Lost" """, (from_date, to_date))[0][0] + + quotations = webnotes.conn.sql("""select count(*) from `tabQuotation` + where docstatus = 1 and (`modified` between %s and %s) + and status != "Lost" """, (from_date, to_date))[0][0] + + sales_orders = webnotes.conn.sql("""select count(*) from `tabQuotation` + where docstatus = 1 and (`modified` between %s and %s)""", (from_date, to_date))[0][0] + + return [ + { "title": "Active Leads / Customers", "value": active_leads, "color": "#B03B46" }, + { "title": "Opportunities", "value": opportunities, "color": "#F09C00" }, + { "title": "Quotations", "value": quotations, "color": "#006685" }, + { "title": "Sales Orders", "value": sales_orders, "color": "#00AD65" } + ] diff --git a/selling/page/crm_funnel/crm_funnel.txt b/selling/page/crm_funnel/crm_funnel.txt new file mode 100644 index 0000000000..29cf566053 --- /dev/null +++ b/selling/page/crm_funnel/crm_funnel.txt @@ -0,0 +1,33 @@ +[ + { + "creation": "2013-10-04 13:17:18", + "docstatus": 0, + "modified": "2013-10-04 13:17:18", + "modified_by": "Administrator", + "owner": "Administrator" + }, + { + "doctype": "Page", + "icon": "icon-filter", + "module": "Selling", + "name": "__common__", + "page_name": "crm-funnel", + "standard": "Yes", + "title": "CRM Funnel" + }, + { + "doctype": "Page Role", + "name": "__common__", + "parent": "crm-funnel", + "parentfield": "roles", + "parenttype": "Page", + "role": "Sales Manager" + }, + { + "doctype": "Page", + "name": "crm-funnel" + }, + { + "doctype": "Page Role" + } +] \ No newline at end of file