[minor] crm funnel first cut
diff --git a/selling/page/crm_funnel/__init__.py b/selling/page/crm_funnel/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/selling/page/crm_funnel/__init__.py
diff --git a/selling/page/crm_funnel/crm_funnel.css b/selling/page/crm_funnel/crm_funnel.css
new file mode 100644
index 0000000..89e904f
--- /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 0000000..a2d2b93
--- /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 = $('<div class="alert alert-warning">No Data</div>')
+ .toggle(false)
+ .appendTo(this.elements.layout);
+
+ this.elements.funnel_wrapper = $('<div class="funnel-wrapper text-center"></div>')
+ .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 = $('<canvas></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 0000000..be0aebe
--- /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 0000000..29cf566
--- /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