From 922bb1af468d8578d7279bf58bb1bdb5f4fd27aa Mon Sep 17 00:00:00 2001 From: Anand Doshi Date: Wed, 26 Jun 2013 17:14:40 +0530 Subject: [PATCH 01/12] [shipping rule] validations and test cases --- .../doctype/shipping_rule/shipping_rule.py | 77 ++++++++++++++++--- .../shipping_rule/test_shipping_rule.py | 66 ++++++++++++++++ 2 files changed, 133 insertions(+), 10 deletions(-) create mode 100644 accounts/doctype/shipping_rule/test_shipping_rule.py diff --git a/accounts/doctype/shipping_rule/shipping_rule.py b/accounts/doctype/shipping_rule/shipping_rule.py index 5ed4ed38e0..87148656df 100644 --- a/accounts/doctype/shipping_rule/shipping_rule.py +++ b/accounts/doctype/shipping_rule/shipping_rule.py @@ -3,23 +3,80 @@ from __future__ import unicode_literals import webnotes from webnotes import _, msgprint +from webnotes.utils import flt, fmt_money +from webnotes.model.controller import DocListController +from setup.utils import get_company_currency -class DocType: +class OverlappingConditionError(webnotes.ValidationError): pass +class FromGreaterThanToError(webnotes.ValidationError): pass +class ManyBlankToValuesError(webnotes.ValidationError): pass + +class DocType(DocListController): def __init__(self, d, dl): self.doc, self.doclist = d, dl def validate(self): - self.validate_to_value_of_shipping_rule_conditions() + self.shipping_rule_conditions = self.doclist.get({"parentfield": "shipping_rule_conditions"}) + self.validate_from_to_values() + self.sort_shipping_rule_conditions() self.validate_overlapping_shipping_rule_conditions() + def validate_from_to_values(self): + zero_to_values = [] - def validate_to_value_of_shipping_rule_conditions(self): - """check if more than two or more rows has To Value = 0""" - shipping_rule_conditions_with_0_to_value = self.doclist.get({ - "parentfield": "shipping_rule_conditions", "to_value": ["in", [0, None]]}) - if len(shipping_rule_conditions_with_0_to_value) >= 2: - msgprint(_('''There can only be one shipping rule with 0 or blank value for "To Value"'''), - raise_exception=True) + for d in self.shipping_rule_conditions: + self.round_floats_in(d) + + # values cannot be negative + self.validate_value("from_value", ">=", 0.0, d) + self.validate_value("to_value", ">=", 0.0, d) + + if d.to_value == 0: + zero_to_values.append(d) + elif d.from_value >= d.to_value: + msgprint(_("Error") + ": " + _("Row") + " # %d: " % d.idx + + _("From Value should be less than To Value"), + raise_exception=FromGreaterThanToError) + + # check if more than two or more rows has To Value = 0 + if len(zero_to_values) >= 2: + msgprint(_('''There can only be one Shipping Rule Condition with 0 or blank value for "To Value"'''), + raise_exception=ManyBlankToValuesError) + def sort_shipping_rule_conditions(self): + """Sort Shipping Rule Conditions based on increasing From Value""" + self.shipping_rules_conditions = sorted(self.shipping_rule_conditions, key=lambda d: flt(d.from_value)) + for i, d in enumerate(self.shipping_rule_conditions): + d.idx = i + 1 + def validate_overlapping_shipping_rule_conditions(self): - pass \ No newline at end of file + def overlap_exists_between((x1, x2), (y1, y2)): + """ + (x1, x2) and (y1, y2) are two ranges + if condition x = 100 to 300 + then condition y can only be like 50 to 99 or 301 to 400 + hence, non-overlapping condition = (x1 <= x2 < y1 <= y2) or (y1 <= y2 < x1 <= x2) + """ + separate = (x1 <= x2 < y1 <= y2) or (y1 <= y2 < x1 <= x2) + return (not separate) + + overlaps = [] + for i in xrange(0, len(self.shipping_rule_conditions)): + for j in xrange(i+1, len(self.shipping_rule_conditions)): + d1, d2 = self.shipping_rule_conditions[i], self.shipping_rule_conditions[j] + if d1.fields != d2.fields: + # in our case, to_value can be zero, hence pass the from_value if so + range_a = (d1.from_value, d1.to_value or d1.from_value) + range_b = (d2.from_value, d2.to_value or d2.from_value) + if overlap_exists_between(range_a, range_b): + overlaps.append([d1, d2]) + + if overlaps: + company_currency = get_company_currency(self.doc.company) + msgprint(_("Error") + ": " + _("Overlapping Conditions found between") + ":") + messages = [] + for d1, d2 in overlaps: + messages.append("%s-%s = %s " % (d1.from_value, d1.to_value, fmt_money(d1.shipping_amount, currency=company_currency)) + + _("and") + " %s-%s = %s" % (d2.from_value, d2.to_value, fmt_money(d2.shipping_amount, currency=company_currency))) + + msgprint("\n".join(messages), raise_exception=OverlappingConditionError) \ No newline at end of file diff --git a/accounts/doctype/shipping_rule/test_shipping_rule.py b/accounts/doctype/shipping_rule/test_shipping_rule.py new file mode 100644 index 0000000000..ff217bc5a7 --- /dev/null +++ b/accounts/doctype/shipping_rule/test_shipping_rule.py @@ -0,0 +1,66 @@ +import webnotes +import unittest +from accounts.doctype.shipping_rule.shipping_rule import FromGreaterThanToError, ManyBlankToValuesError, OverlappingConditionError + +class TestShippingRule(unittest.TestCase): + def test_from_greater_than_to(self): + shipping_rule = webnotes.bean(copy=test_records[0]) + shipping_rule.doclist[1].from_value = 101 + self.assertRaises(FromGreaterThanToError, shipping_rule.insert) + + def test_many_zero_to_values(self): + shipping_rule = webnotes.bean(copy=test_records[0]) + shipping_rule.doclist[1].to_value = 0 + self.assertRaises(ManyBlankToValuesError, shipping_rule.insert) + + def test_overlapping_conditions(self): + for range_a, range_b in [ + ((50, 150), (0, 100)), + ((50, 150), (100, 200)), + ((50, 150), (75, 125)), + ((50, 150), (25, 175)), + ((50, 150), (50, 150)), + ]: + shipping_rule = webnotes.bean(copy=test_records[0]) + shipping_rule.doclist[1].from_value = range_a[0] + shipping_rule.doclist[1].to_value = range_a[1] + shipping_rule.doclist[2].from_value = range_b[0] + shipping_rule.doclist[2].to_value = range_b[1] + self.assertRaises(OverlappingConditionError, shipping_rule.insert) + +test_records = [ + [ + { + "doctype": "Shipping Rule", + "calculate_based_on": "Amount", + "company": "_Test Company", + "account": "_Test Account Shipping Charges - _TC", + "cost_center": "_Test Cost Center - _TC" + }, + { + "doctype": "Shipping Rule Condition", + "parentfield": "shipping_rule_conditions", + "from_value": 0, + "to_value": 100, + "shipping_amount": 50.0 + }, + { + "doctype": "Shipping Rule Condition", + "parentfield": "shipping_rule_conditions", + "from_value": 101, + "to_value": 200, + "shipping_amount": 100.0 + }, + { + "doctype": "Shipping Rule Condition", + "parentfield": "shipping_rule_conditions", + "from_value": 201, + "shipping_amount": 0.0 + }, + { + "doctype": "For Territory", + "parentfield": "valid_for_territories", + "territory": "_Test Territory" + } + ] +] \ No newline at end of file From 2f7e1abd0f372a0882047b322809bdbf7467a907 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Wed, 26 Jun 2013 17:20:12 +0530 Subject: [PATCH 02/12] [setup] new page + minor fixes --- public/js/startup.js | 13 +- setup/doctype/setup_control/setup_control.py | 165 ++++---- setup/page/setup/setup.css | 6 - setup/page/setup/setup.js | 400 ++++++++----------- setup/page/setup/setup.py | 244 +++++++++++ startup/boot.py | 8 +- startup/install.py | 8 +- 7 files changed, 505 insertions(+), 339 deletions(-) delete mode 100644 setup/page/setup/setup.css create mode 100644 setup/page/setup/setup.py diff --git a/public/js/startup.js b/public/js/startup.js index 96953ad1d0..26e521981d 100644 --- a/public/js/startup.js +++ b/public/js/startup.js @@ -36,11 +36,18 @@ erpnext.startup.start = function() { erpnext.toolbar.setup(); // complete registration - if(in_list(user_roles,'System Manager') && (wn.boot.setup_complete=='No')) { + if(in_list(user_roles,'System Manager') && (wn.boot.setup_complete==='No')) { wn.require("app/js/complete_setup.js"); erpnext.complete_setup.show(); - } - if(wn.boot.expires_on && in_list(user_roles, 'System Manager')) { + } else if(!wn.boot.customer_count) { + if(wn.get_route()[0]!=="Setup") { + msgprint("" + + wn._("Proceed to Setup") + "\ +

"+ + wn._("This message goes away after you create your first customer.")+ + "

", wn._("Welcome")); + } + } else if(wn.boot.expires_on && in_list(user_roles, 'System Manager')) { var today = dateutil.str_to_obj(wn.boot.server_date); var expires_on = dateutil.str_to_obj(wn.boot.expires_on); var diff = dateutil.get_diff(expires_on, today); diff --git a/setup/doctype/setup_control/setup_control.py b/setup/doctype/setup_control/setup_control.py index 0f4f6a1e70..9cb0c482db 100644 --- a/setup/doctype/setup_control/setup_control.py +++ b/setup/doctype/setup_control/setup_control.py @@ -17,7 +17,7 @@ from __future__ import unicode_literals import webnotes -from webnotes.utils import cint, cstr, getdate, now, nowdate +from webnotes.utils import cint, cstr, getdate, now, nowdate, get_defaults from webnotes.model.doc import Document, addchild from webnotes.model.code import get_obj from webnotes import session, form, msgprint @@ -26,16 +26,31 @@ class DocType: def __init__(self, d, dl): self.doc, self.doclist = d, dl - # Account Setup - # --------------- def setup_account(self, args): import webnotes, json args = json.loads(args) webnotes.conn.begin() - curr_fiscal_year, fy_start_date, fy_abbr = self.get_fy_details(args.get('fy_start')) - #webnotes.msgprint(self.get_fy_details(args.get('fy_start'))) + self.update_profile_name(args) + add_all_roles_to(webnotes.session.user) + self.create_fiscal_year_and_company(args) + self.set_defaults(args) + create_territories() + self.create_price_lists(args) + self.create_feed_and_todo() + self.create_email_digest() + webnotes.clear_cache() + msgprint("Company setup is complete. This page will be refreshed in a moment.") + webnotes.conn.commit() + + return { + 'sys_defaults': get_defaults(), + 'user_fullname': (args.get('first_name') or '') + (args.get('last_name') + and (" " + args.get('last_name')) or '') + } + + def update_profile_name(self, args): args['name'] = webnotes.session.get('user') # Update Profile @@ -44,39 +59,54 @@ class DocType: UPDATE `tabProfile` SET first_name=%(first_name)s, last_name=%(last_name)s WHERE name=%(name)s AND docstatus<2""", args) - - + + def create_fiscal_year_and_company(self, args): + curr_fiscal_year, fy_start_date, fy_abbr = self.get_fy_details(args.get('fy_start')) # Fiscal Year - master_dict = {'Fiscal Year':{ + webnotes.bean([{ + "doctype":"Fiscal Year", 'year': curr_fiscal_year, 'year_start_date': fy_start_date, - }} - self.create_records(master_dict) - + }]).insert() # Company - master_dict = {'Company': { + webnotes.bean([{ + "doctype":"Company", 'company_name':args.get('company_name'), 'abbr':args.get('company_abbr'), 'default_currency':args.get('currency') - }} - self.create_records(master_dict) - + }]).insert() + + self.curr_fiscal_year = curr_fiscal_year + + def create_price_lists(self, args): + webnotes.bean({ + 'doctype': 'Price List', + 'price_list_name': 'Standard Selling', + "buying_or_selling": "Selling", + "currency": args["currency"] + }).insert(), + webnotes.bean({ + 'doctype': 'Price List', + 'price_list_name': 'Standard Buying', + "buying_or_selling": "Buying", + "currency": args["currency"] + }).insert(), + + def set_defaults(self, args): # enable default currency webnotes.conn.set_value("Currency", args.get("currency"), "enabled", 1) - def_args = { - 'current_fiscal_year':curr_fiscal_year, + global_defaults = webnotes.bean("Global Defaults", "Global Defaults") + global_defaults.doc.fields.update({ + 'current_fiscal_year': self.curr_fiscal_year, 'default_currency': args.get('currency'), 'default_company':args.get('company_name'), - 'date_format': webnotes.conn.get_value("Country", - args.get("country"), "date_format"), + 'date_format': webnotes.conn.get_value("Country", args.get("country"), "date_format"), 'emp_created_by':'Naming Series', "float_precision": 4 - } - - # Set - self.set_defaults(def_args) + }) + global_defaults.save() webnotes.conn.set_value("Accounts Settings", None, "auto_inventory_accounting", 1) webnotes.conn.set_default("auto_inventory_accounting", 1) @@ -100,29 +130,20 @@ class DocType: buying_settings.doc.pr_required = "No" buying_settings.doc.maintain_same_rate = 1 buying_settings.save() - - cp_args = {} + + notification_control = webnotes.bean("Notification Control") + notification_control.doc.quotation = 1 + notification_control.doc.sales_invoice = 1 + notification_control.doc.purchase_order = 1 + notification_control.save() + + # control panel + cp = webnotes.doc("Control Panel", "Control Panel") for k in ['industry', 'country', 'timezone', 'company_name']: - cp_args[k] = args[k] - - self.set_cp_defaults(**cp_args) - - create_territories() - - self.create_feed_and_todo() - - self.create_email_digest() - - webnotes.clear_cache() - msgprint("Company setup is complete. This page will be refreshed in a moment.") - - import webnotes.utils - user_fullname = (args.get('first_name') or '') + (args.get('last_name') - and (" " + args.get('last_name')) or '') - - webnotes.conn.commit() - return {'sys_defaults': webnotes.utils.get_defaults(), 'user_fullname': user_fullname} - + cp.fields[k] = args[k] + + cp.save() + def create_feed_and_todo(self): """update activty feed and create todo for creation of item, customer, vendor""" import home @@ -131,24 +152,9 @@ class DocType: To Do List' + '"', '#6B24B3') d = Document('ToDo') - d.description = 'Create your first Customer' + d.description = 'Complete ERPNext Setup' d.priority = 'High' d.date = nowdate() - d.reference_type = 'Customer' - d.save(1) - - d = Document('ToDo') - d.description = 'Create your first Item' - d.priority = 'High' - d.date = nowdate() - d.reference_type = 'Item' - d.save(1) - - d = Document('ToDo') - d.description = 'Create your first Supplier' - d.priority = 'High' - d.date = nowdate() - d.reference_type = 'Supplier' d.save(1) def create_email_digest(self): @@ -206,42 +212,7 @@ class DocType: fy = cstr(curr_year) + '-' + cstr(curr_year+1) abbr = cstr(curr_year)[-2:] + '-' + cstr(curr_year+1)[-2:] return fy, stdt, abbr - - - def create_records(self, master_dict): - for d in master_dict.keys(): - rec = Document(d) - for fn in master_dict[d].keys(): - rec.fields[fn] = master_dict[d][fn] - - rec_obj = get_obj(doc=rec) - rec_obj.doc.save(1) - if hasattr(rec_obj, 'on_update'): - rec_obj.on_update() - - - # Set System Defaults - # -------------------- - def set_defaults(self, def_args): - ma_obj = get_obj('Global Defaults','Global Defaults') - for d in def_args.keys(): - ma_obj.doc.fields[d] = def_args[d] - ma_obj.doc.save() - ma_obj.on_update() - - - # Set Control Panel Defaults - # -------------------------- - def set_cp_defaults(self, industry, country, timezone, company_name): - cp = Document('Control Panel','Control Panel') - cp.company_name = company_name - cp.industry = industry - cp.time_zone = timezone - cp.country = country - cp.save() - # Create Profile - # -------------- def create_profile(self, user_email, user_fname, user_lname, pwd=None): pr = Document('Profile') pr.first_name = user_fname @@ -261,7 +232,7 @@ def add_all_roles_to(name): profile = webnotes.doc("Profile", name) for role in webnotes.conn.sql("""select name from tabRole"""): if role[0] not in ["Administrator", "Guest", "All", "Customer", "Supplier", "Partner"]: - d = profile.addchild("userroles", "UserRole") + d = profile.addchild("user_roles", "UserRole") d.role = role[0] d.insert() diff --git a/setup/page/setup/setup.css b/setup/page/setup/setup.css deleted file mode 100644 index bff44db14c..0000000000 --- a/setup/page/setup/setup.css +++ /dev/null @@ -1,6 +0,0 @@ -.setup-column { - float: left; - width: 45%; - margin-right: 5%; - margin-bottom: 15px; -} \ No newline at end of file diff --git a/setup/page/setup/setup.js b/setup/page/setup/setup.js index 788021a42a..04a8d48517 100644 --- a/setup/page/setup/setup.js +++ b/setup/page/setup/setup.js @@ -1,228 +1,180 @@ -// ERPNext: Copyright 2013 Web Notes Technologies Pvt Ltd -// GNU General Public License. See "license.txt" +wn.pages['Setup'].onload = function(wrapper) { + if(msg_dialog && msg_dialog.display) msg_dialog.hide(); + wn.ui.make_app_page({ + parent: wrapper, + title: 'Setup', + single_column: true + }); -wn.module_page["Setup"] = [ - { - title: wn._("Organization"), - icon: "icon-building", - items: [ - { - "label":wn._("Company"), - "doctype":"Company", - "description":wn._("List of companies (not customers / suppliers)") - }, - { - "doctype":"Fiscal Year", - "label": wn._("Fiscal Year"), - "description":wn._("Financial Years for books of accounts") - }, - { - "doctype":"Currency", - "label": wn._("Currency"), - "description": wn._("Enable / disable currencies.") - }, - ] - }, - { - title: wn._("Users"), - icon: "icon-group", - right: true, - items: [ - { - "doctype":"Profile", - "label": wn._("Profile"), - "description": wn._("Add/remove users, set roles, passwords etc") - }, - { - "page":"permission-manager", - label: wn._("Permission Manager"), - "description": wn._("Set permissions on transactions / masters") - }, - { - "page":"user-properties", - label: wn._("User Properties"), - "description":wn._("Set default values for users (also used for permissions).") - }, - { - "doctype":"Workflow", - label:wn._("Workflow"), - "description":wn._("Set workflow rules.") - }, - { - "doctype":"Authorization Rule", - label:wn._("Authorization Rule"), - "description":wn._("Restrict submission rights based on amount") - }, - ] - }, - { - title: wn._("Data"), - icon: "icon-table", - items: [ - { - "page":"data-import-tool", - label: wn._("Data Import"), - "description":wn._("Import data from spreadsheet (csv) files") - }, - { - "route":"Form/Global Defaults", - doctype: "Global Defaults", - label: wn._("Global Defaults"), - "description":wn._("Set default values for entry"), - }, - { - "route":"Form/Naming Series/Naming Series", - doctype: "Naming Series", - label: wn._("Manage Numbering Series"), - "description":wn._("Set multiple numbering series for transactions") - }, - { - "route":"Form/Rename Tool", - doctype: "Rename Tool", - label: wn._("Rename Tool"), - "description":wn._("Rename multiple items in one go") - }, - { - "route":"List/File Data", - doctype: "File Data", - label: wn._("File Manager"), - "description":wn._("List, delete uploaded files.") - }, - ] - }, - { - title: wn._("Branding and Printing"), - icon: "icon-print", - right: true, - items: [ - { - "doctype":"Letter Head", - label:wn._("Letter Head"), - "description":wn._("Letter heads for print") - }, - { - "doctype":"Print Format", - label:wn._("Print Format"), - "description":wn._("HTML print formats for quotes, invoices etc") - }, - { - "doctype":"Print Heading", - label:wn._("Print Heading"), - "description":wn._("Add headers for standard print formats") - }, - ] - }, - { - title: wn._("Email Settings"), - icon: "icon-envelope", - items: [ - { - "route":"Form/Email Settings/Email Settings", - doctype:"Email Settings", - label: wn._("Email Settings"), - "description":wn._("Out going mail server and support ticket mailbox") - }, - { - "route":"Form/Sales Email Settings", - doctype:"Sales Email Settings", - label: wn._("Sales Email Settings"), - "description":wn._("Extract Leads from sales email id e.g. sales@example.com") - }, - { - "route":"Form/Jobs Email Settings", - doctype:"Jobs Email Settings", - label: wn._("Jobs Email Settings"), - "description":wn._("Extract Job Applicant from jobs email id e.g. jobs@example.com") - }, - { - "route":"Form/Notification Control/Notification Control", - doctype:"Notification Control", - label: wn._("Notification Control"), - "description":wn._("Prompt email sending to customers and suppliers"), - }, - { - "doctype":"Email Digest", - label: wn._("Email Digest"), - "description":wn._("Daily, weekly, monthly email Digests") - }, - { - "route":"Form/SMS Settings/SMS Settings", - doctype:"SMS Settings", - label: wn._("SMS Settings"), - "description":wn._("Setup outgoing SMS via your bulk SMS provider") - }, - { - "route":"Form/SMS Center/SMS Center", - doctype:"SMS Center", - label: wn._("SMS Center"), - "description":wn._("Send bulk SMS to leads, customers, contacts") - }, - ] - }, - { - title: wn._("Customize"), - icon: "icon-wrench", - items: [ - { - "route":"Form/Customize Form/Customize Form", - doctype:"Customize Form", - label: wn._("Customize Form"), - "description":wn._("Change entry properties (hide fields, make mandatory etc)") - }, - { - "doctype":"Custom Field", - label: wn._("Custom Field"), - "description":wn._("Add fields to forms") - }, - { - "doctype":"Custom Script", - label: wn._("Custom Script"), - "description":wn._("Add custom code to forms") - }, - { - "route":"Form/Features Setup/Features Setup", - "description":wn._("Simplify entry forms by disabling features"), - doctype:"Features Setup", - label: wn._("Features Setup"), - }, - { - "page":"modules_setup", - label: wn._("Show / Hide Modules"), - "description":wn._("Show, hide modules") - }, - ] - }, - { - title: wn._("Administration"), - icon: "icon-rocket", - right: true, - items: [ - { - "page":"update-manager", - label: wn._("Update This Application"), - "description":wn._("Apply latest updates and patches to this app") - }, - { - "route":"Form/Backup Manager", - doctype:"Backup Manager", - label: wn._("Backup Manager"), - "description":wn._("Sync backups with remote tools like Dropbox etc.") - }, - { - "route":"List/Scheduler Log", - doctype:"Scheduler Log", - label: wn._("Scheduler Error Log"), - "description":wn._("Get a list of errors encountered by the Scheduler") - }, - ] - }, -] + wrapper.appframe.add_module_icon("Setup"); + + var body = $(wrapper).find(".layout-main"), + total = 0, + completed = 0; -pscript['onload_Setup'] = function(wrapper) { - wn.views.moduleview.make(wrapper, "Setup"); - if(wn.boot.expires_on) { - $(wrapper).find(".main-section") - .prepend("
Your ERPNext account will expire on " - + wn.datetime.global_date_format(wn.boot.expires_on) + "
"); + body.html('
\ +
') + + wn.call({ + method: "setup.page.setup.setup.get", + callback: function(r) { + if(r.message) { + body.empty(); + if(wn.boot.expires_on) { + $(body).prepend("
Account expires on " + + wn.datetime.global_date_format(wn.boot.expires_on) + "
"); + } + + $completed = $('

Setup Completed

\ +
') + .appendTo(body); + + $.each(r.message, function(i, item) { + render_item(item) + }); + + var completed_percent = cint(flt(completed) / total * 100) + "%"; + $completed + .find(".progress-bar") + .css({"width": completed_percent}); + $(body) + .find(".completed-percent") + .html("(" + completed_percent + ")"); + } + } + }); + + var render_item = function(item, dependency) { + if(item.type==="Section") { + $("

") + .css({"margin": "20px 0px 15px 0px"}) + .html(' ' + item.title).appendTo(body); + return; + } + var row = $('
') + .css({ + "margin-bottom": "7px", + "padding-bottom": "7px", + "border-bottom": "1px solid #eee" + }) + .appendTo(body); + + $('
').appendTo(row); + + if(item.type==="Link") { + var col = $('').appendTo(row); + + } else { + var col = $('
\ + '+ item.count +''+ + ' ' + (item.title || item.doctype) + '' + +'
') + .appendTo(row); + + if(dependency) + col.addClass("col-offset-1"); + else + $('
').appendTo(row); + + col.find(".badge") + .css({ + "background-color": (item.count ? "green" : "orange"), + "display": "inline-block", + "min-width": "40px" + }); + + total += 1; + if(item.count) + completed += 1; + } + + if(item.doctype) { + col.find(".badge") + .attr("data-doctype", item.doctype) + .css({"cursor": "pointer"}) + .click(function() { + wn.set_route("List", $(this).attr("data-doctype")) + }) + } + + // tree + $links = $('
').appendTo(row); + + if(item.tree) { + $(' Browse\ + | \ + Import') + .appendTo($links) + + $links.find(".view-link") + .attr("data-doctype", item.doctype) + .click(function() { + wn.set_route(item.tree, item.doctype); + }) + } else if(item.single) { + $(' Edit') + .appendTo($links) + + $links.find(".view-link") + .attr("data-doctype", item.doctype) + .click(function() { + wn.set_route("Form", $(this).attr("data-doctype")); + }) + } else if(item.type !== "Link"){ + $(' New \ + | \ + View \ + | \ + Import') + .appendTo($links) + + $links.find(".view-link") + .attr("data-doctype", item.doctype) + .click(function() { + if($(this).attr("data-filter")) { + wn.route_options = JSON.parse($(this).attr("data-filter")); + } + wn.set_route("List", $(this).attr("data-doctype")); + }) + + if(item.filter) + $links.find(".view-link").attr("data-filter", JSON.stringify(item.filter)) + + if(wn.model.can_create(item.doctype)) { + $links.find(".new-link") + .attr("data-doctype", item.doctype) + .click(function() { + new_doc($(this).attr("data-doctype")) + }) + } else { + $links.find(".new-link").remove(); + $links.find(".text-muted:first").remove(); + } + + } + + $links.find(".import-link") + .attr("data-doctype", item.doctype) + .click(function() { + wn.route_options = {doctype:$(this).attr("data-doctype")} + wn.set_route("data-import-tool"); + }) + + if(item.links) { + $.each(item.links, function(i, link) { + var newlinks = $(' | \ + '+link.title+'') + .appendTo($links) + }) + } + + if(item.dependencies) { + $.each(item.dependencies, function(i, d) { + render_item(d, true); + }) + } } } \ No newline at end of file diff --git a/setup/page/setup/setup.py b/setup/page/setup/setup.py new file mode 100644 index 0000000000..ab2cdb7384 --- /dev/null +++ b/setup/page/setup/setup.py @@ -0,0 +1,244 @@ +from __future__ import unicode_literals +import webnotes + +items = [ + { + "type": "Section", + "title": "Organization", + "icon": "icon-building" + }, + {"doctype":"Company"}, + {"doctype":"Fiscal Year"}, + {"doctype":"Currency"}, + { + "type": "Section", + "title": "Users and Permissions", + "icon": "icon-user" + }, + { + "doctype":"Profile", + }, + { + "doctype":"Role", + }, + { "title": "Permission Manager", + "route": "permission-manager", "type": "Link", "icon": "icon-shield" }, + { "title": "User Properties", + "route": "user-properties", "type": "Link", "icon": "icon-user" }, + { + "type": "Section", + "title": "Master Data", + "icon": "icon-star" + }, + { + "doctype": "Item", + "dependencies": [ + {"doctype":"Item Group", "tree": "Sales Browser"}, + {"doctype":"Warehouse"}, + {"doctype":"UOM"}, + {"doctype":"Brand"}, + {"doctype":"Price List"}, + ], + }, + { + "doctype": "Customer", + "dependencies": [ + {"doctype":"Customer Group", "tree": "Sales Browser"}, + {"doctype":"Territory", "tree": "Sales Browser"}, + {"doctype":"Sales Person", "tree": "Sales Browser"}, + {"doctype":"Contact"}, + {"doctype":"Address"}, + ] + }, + { + "doctype": "Supplier", + "dependencies": [ + {"doctype":"Supplier Type"}, + {"doctype":"Contact"}, + {"doctype":"Address"}, + ] + }, + { + "type": "Section", + "title": "Accounts", + "icon": "icon-money" + }, + { + "doctype": "Account", + "tree": "Accounts Browser", + "dependencies": [ + { + "title": "Bank Accounts", + "doctype":"Account", + "filter": {"account_type": "Bank or Cash"} + }, + { + "title": "Tax Accounts", + "doctype":"Account", + "filter": {"account_type": "Tax"} + }, + ] + }, + { + "doctype": "Cost Center", + "tree": "Accounts Browser", + }, + { "doctype": "Sales Taxes and Charges Master" }, + { "doctype": "Purchase Taxes and Charges Master" }, + { + "type": "Section", + "title": "Human Resource", + "icon": "icon-group" + }, + { + "doctype": "Employee", + "dependencies": [ + { "doctype": "Employment Type" }, + { "doctype": "Branch" }, + { "doctype": "Department" }, + { "doctype": "Designation" }, + { "doctype": "Holiday List" }, + { "doctype": "Grade" }, + ] + }, + { "doctype": "Salary Structure" }, + { "doctype": "Leave Allocation" }, + { "doctype": "Appraisal Template" }, + { + "type": "Section", + "title": "Printing", + "icon": "icon-print" + }, + { "doctype": "Letter Head" }, + { "doctype": "Print Heading" }, + { "doctype": "Print Format", "filter": {"standard": "No"} }, + { + "type": "Section", + "title": "Email", + "icon": "icon-envelope-alt" + }, + { + "title": "Outgoing Email Settings", + "doctype": "Email Settings", + "single": 1, + "query": "select count(*) from tabSingles where doctype='Email Settings' and field='outgoing_mail_server'" + }, + { + "doctype": "Support Email Settings", + "single": 1, + "query": "select count(*) from tabSingles where doctype='Email Settings' and field='support_host'" + }, + { + "doctype": "Sales Email Settings", + "single": 1, + "query": "select count(*) from tabSingles where doctype='Sales Email Settings' and field='host'" + }, + { + "doctype": "Jobs Email Settings", + "single": 1, + "query": "select count(*) from tabSingles where doctype='Jobs Email Settings' and field='host'" + }, + { + "doctype": "Email Digest", + }, + { + "doctype": "SMS Settings", + "single": 1, + "query": "select count(*) from tabSingles where doctype='SMS Settings' and field='sms_gateway_url'" + }, + { + "type": "Section", + "title": "Opening Accounts and Stock", + "icon": "icon-eye-open" + }, + { "doctype": "Stock Reconciliation" }, + { + "doctype": "Journal Voucher", + "title": "Opening Accounting Entries", + "filter": { + "is_opening": "Yes" + } + }, + { + "type": "Section", + "title": "Customization", + "icon": "icon-glass" + }, + { + "doctype": "Customize Form", + "single": 1, + "query": "select count(distinct doc_type) from `tabProperty Setter`" + }, + { "doctype": "Workflow" }, + { "doctype": "Authorization Rule" }, + { "doctype": "Custom Field" }, + { "doctype": "Custom Script" }, + { + "type": "Section", + "title": "Tools", + "icon": "icon-wrench" + }, + { "title": "Global Settings / Default Values", + "doctype": "Global Defaults", "single": 1, + "query": """select count(*) from tabSingles where doctype='Global Defaults' + and field not in ('owner', 'creation', 'modified', 'modified_by')"""}, + + { "title": "Show / Hide Features", + "doctype": "Features Setup", "single": 1, + "query": """select count(*) from tabSingles where doctype='Features Setup' + and field not in ('owner', 'creation', 'modified', 'modified_by')"""}, + + { "title": "Enable / Disable Email Notifications", + "doctype": "Notification Control", "single": 1, + "query": """select count(*) from tabSingles where doctype='Notification Control' + and field in ('quotation', 'sales_order', 'sales_invoice', 'purchase_order', 'purchase_receipt', 'expense_claim', 'delivery_note')"""}, + + { "doctype": "File Data", "title": "Uploaded File Attachments" }, + + { "title": "Data Import", + "route": "data-import-tool", "type": "Link", "icon": "icon-upload" }, + { "title": "Bulk Rename", + "route": "Form/Rename Tool", "type": "Link", "icon": "icon-upload" }, + { "title": "Update Numbering Series", + "route": "Form/Naming Series", "type": "Link", "icon": "icon-sort-by-order" }, + { "title": "Show / Hide Modules", + "route": "modules_setup", "type": "Link", "icon": "icon-th" }, + { "title": "Send Bulk SMS to Leads / Contacts", + "route": "Form/SMS Center", "type": "Link", "icon": "icon-mobile-phone" }, + { + "type": "Section", + "title": "System Administration", + "icon": "icon-cog" + }, + { "title": "Update ERPNext", + "route": "update-manager", "type": "Link", "icon": "icon-rss" }, + { "title": "Manage 3rd Party Backups", + "route": "Form/Backup Manager", "type": "Link", "icon": "icon-cloud" }, + { "title": "System Scheduler Errors", + "route": "Report/Scheduler Log", "type": "Link", "icon": "icon-exclamation-sign" }, +] + +@webnotes.whitelist(allow_roles=["System Manager"]) +def get(): + for item in items: + if item.get("type")=="Section": + continue + set_count(item) + + if item.get("dependencies"): + for d in item["dependencies"]: + set_count(d) + + return items + +def set_count(item): + if "query" in item: + item["count"] = webnotes.conn.sql(item["query"])[0][0] + elif "filter" in item: + key = item["filter"].keys()[0] + item["count"] = webnotes.conn.sql("""select count(*) from `tab%s` where + %s = %s""" % (item["doctype"], key, "%s"), + item["filter"][key])[0][0] + elif "doctype" in item: + item["count"] = webnotes.conn.sql("select count(*) from `tab%s`" \ + % item["doctype"])[0][0] diff --git a/startup/boot.py b/startup/boot.py index 7d5eb4e6ef..a75c78ad5a 100644 --- a/startup/boot.py +++ b/startup/boot.py @@ -22,8 +22,12 @@ def boot_session(bootinfo): "Notification Control").get_values() # if no company, show a dialog box to create a new company - bootinfo['setup_complete'] = webnotes.conn.sql("""select name from - tabCompany limit 1""") and 'Yes' or 'No' + bootinfo["customer_count"] = webnotes.conn.sql("""select count(*) from tabCustomer""")[0][0] + + if not bootinfo["customer_count"]: + bootinfo['setup_complete'] = webnotes.conn.sql("""select name from + tabCompany limit 1""") and 'Yes' or 'No' + # load subscription info import conf diff --git a/startup/install.py b/startup/install.py index 205bee46e7..89ab290d82 100644 --- a/startup/install.py +++ b/startup/install.py @@ -135,13 +135,7 @@ def import_defaults(): # supplier type {'doctype': 'Supplier Type', 'name': 'Default Supplier Type', 'supplier_type': 'Default Supplier Type'}, - - # Price List - {'doctype': 'Price List', 'name': 'Default Price List', 'price_list_name': 'Default Price List', - "buying_or_selling": "Selling"}, - {'doctype': 'Price List', 'name': 'Standard', 'price_list_name': 'Standard', - "buying_or_selling": "Selling"}, - + # warehouse type {'doctype': 'Warehouse Type', 'name': 'Default Warehouse Type', 'warehouse_type': 'Default Warehouse Type'}, {'doctype': 'Warehouse Type', 'name': 'Fixed Asset', 'warehouse_type': 'Fixed Asset'}, From f7c4d1e807e43dfff3a3cdab40bd3ed1344dbf94 Mon Sep 17 00:00:00 2001 From: Anand Doshi Date: Wed, 26 Jun 2013 17:33:59 +0530 Subject: [PATCH 03/12] [setup home] moved SMS Settings link to Tools section --- setup/page/setup/setup.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/setup/page/setup/setup.py b/setup/page/setup/setup.py index ab2cdb7384..1a467c3d8e 100644 --- a/setup/page/setup/setup.py +++ b/setup/page/setup/setup.py @@ -141,11 +141,6 @@ items = [ { "doctype": "Email Digest", }, - { - "doctype": "SMS Settings", - "single": 1, - "query": "select count(*) from tabSingles where doctype='SMS Settings' and field='sms_gateway_url'" - }, { "type": "Section", "title": "Opening Accounts and Stock", @@ -194,6 +189,11 @@ items = [ and field in ('quotation', 'sales_order', 'sales_invoice', 'purchase_order', 'purchase_receipt', 'expense_claim', 'delivery_note')"""}, { "doctype": "File Data", "title": "Uploaded File Attachments" }, + { + "doctype": "SMS Settings", + "single": 1, + "query": "select count(*) from tabSingles where doctype='SMS Settings' and field='sms_gateway_url'" + }, { "title": "Data Import", "route": "data-import-tool", "type": "Link", "icon": "icon-upload" }, From 65d12926fdb2f4056ba21af89274a0693a4ed63d Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Wed, 26 Jun 2013 22:31:22 +0530 Subject: [PATCH 04/12] [setup] bug fixes --- buying/doctype/buying_settings/buying_settings.py | 2 +- controllers/js/contact_address_common.js | 6 +++++- setup/doctype/setup_control/setup_control.py | 2 +- setup/page/setup/setup.js | 12 ++++++------ setup/page/setup/setup.py | 6 ++++++ stock/doctype/item/item.txt | 3 ++- 6 files changed, 21 insertions(+), 10 deletions(-) diff --git a/buying/doctype/buying_settings/buying_settings.py b/buying/doctype/buying_settings/buying_settings.py index 7a97349f22..b00bcef42f 100644 --- a/buying/doctype/buying_settings/buying_settings.py +++ b/buying/doctype/buying_settings/buying_settings.py @@ -8,6 +8,6 @@ class DocType: self.doc, self.doclist = d, dl def validate(self): - for key in ["supplier_type", "maintain_same_rate"]: + for key in ["supplier_type", "supp_master_name", "maintain_same_rate"]: webnotes.conn.set_default(key, self.doc.fields.get(key, "")) \ No newline at end of file diff --git a/controllers/js/contact_address_common.js b/controllers/js/contact_address_common.js index ca9e084f5c..89b1d43ac5 100644 --- a/controllers/js/contact_address_common.js +++ b/controllers/js/contact_address_common.js @@ -15,12 +15,16 @@ cur_frm.cscript.onload = function(doc, cdt, cdn) { var refdoc = wn.model.get_doc(last_route[1], last_route[2]); cur_frm.set_value("customer", refdoc.customer || refdoc.name); cur_frm.set_value("customer_name", refdoc.customer_name); + if(cur_frm.doc.doctype==="Address") + cur_frm.set_value("address_title", cur_frm.doc.customer) } if(["Supplier", "Supplier Quotation", "Purchase Order", "Purchase Invoice", "Purchase Receipt"] .indexOf(last_route[1])!==-1) { - var customer = wn.model.get_doc(last_route[1], last_route[2]); + var refdoc = wn.model.get_doc(last_route[1], last_route[2]); cur_frm.set_value("supplier", refdoc.supplier || refdoc.name); cur_frm.set_value("supplier_name", refdoc.supplier_name); + if(cur_frm.doc.doctype==="Address") + cur_frm.set_value("address_title", cur_frm.doc.supplier) } } } diff --git a/setup/doctype/setup_control/setup_control.py b/setup/doctype/setup_control/setup_control.py index 9cb0c482db..eb668a7556 100644 --- a/setup/doctype/setup_control/setup_control.py +++ b/setup/doctype/setup_control/setup_control.py @@ -119,7 +119,7 @@ class DocType: stock_settings.save() selling_settings = webnotes.bean("Selling Settings") - selling_settings.cust_master_name = "Customer Name" + selling_settings.doc.cust_master_name = "Customer Name" selling_settings.doc.so_required = "No" selling_settings.doc.dn_required = "No" selling_settings.save() diff --git a/setup/page/setup/setup.js b/setup/page/setup/setup.js index 04a8d48517..908718815c 100644 --- a/setup/page/setup/setup.js +++ b/setup/page/setup/setup.js @@ -73,11 +73,6 @@ wn.pages['Setup'].onload = function(wrapper) { +'
') .appendTo(row); - if(dependency) - col.addClass("col-offset-1"); - else - $('
').appendTo(row); - col.find(".badge") .css({ "background-color": (item.count ? "green" : "orange"), @@ -89,13 +84,18 @@ wn.pages['Setup'].onload = function(wrapper) { if(item.count) completed += 1; } + + if(dependency) + col.addClass("col-offset-1"); + else + $('
').appendTo(row); if(item.doctype) { col.find(".badge") .attr("data-doctype", item.doctype) .css({"cursor": "pointer"}) .click(function() { - wn.set_route("List", $(this).attr("data-doctype")) + wn.set_route(item.tree || "List", $(this).attr("data-doctype")) }) } diff --git a/setup/page/setup/setup.py b/setup/page/setup/setup.py index ab2cdb7384..96cd6e4bdb 100644 --- a/setup/page/setup/setup.py +++ b/setup/page/setup/setup.py @@ -38,6 +38,8 @@ items = [ {"doctype":"UOM"}, {"doctype":"Brand"}, {"doctype":"Price List"}, + { "title": "Stock Settings", + "route": "Form/Stock Settings", "type": "Link", "icon": "icon-cog" }, ], }, { @@ -48,6 +50,8 @@ items = [ {"doctype":"Sales Person", "tree": "Sales Browser"}, {"doctype":"Contact"}, {"doctype":"Address"}, + { "title": "Selling Settings", + "route": "Form/Selling Settings", "type": "Link", "icon": "icon-cog" }, ] }, { @@ -56,6 +60,8 @@ items = [ {"doctype":"Supplier Type"}, {"doctype":"Contact"}, {"doctype":"Address"}, + { "title": "Buying Settings", + "route": "Form/Buying Settings", "type": "Link", "icon": "icon-cog" }, ] }, { diff --git a/stock/doctype/item/item.txt b/stock/doctype/item/item.txt index e23bbe053e..1273b12420 100644 --- a/stock/doctype/item/item.txt +++ b/stock/doctype/item/item.txt @@ -2,7 +2,7 @@ { "creation": "2013-05-03 10:45:46", "docstatus": 0, - "modified": "2013-06-13 16:17:42", + "modified": "2013-06-26 21:39:46", "modified_by": "Administrator", "owner": "Administrator" }, @@ -838,6 +838,7 @@ "read_only": 0 }, { + "depends_on": "show_in_website", "doctype": "DocField", "fieldname": "copy_from_item_group", "fieldtype": "Button", From e6d986adea480743f2b3a4571fdffd930135b60e Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Thu, 27 Jun 2013 10:54:49 +0530 Subject: [PATCH 05/12] [import] fixes --- setup/page/setup/setup.py | 1 + stock/doctype/item/item.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/setup/page/setup/setup.py b/setup/page/setup/setup.py index 444c5c3bbc..10fc2a4913 100644 --- a/setup/page/setup/setup.py +++ b/setup/page/setup/setup.py @@ -115,6 +115,7 @@ items = [ "title": "Printing", "icon": "icon-print" }, + { "doctype": "Terms and Conditions" }, { "doctype": "Letter Head" }, { "doctype": "Print Heading" }, { "doctype": "Print Format", "filter": {"standard": "No"} }, diff --git a/stock/doctype/item/item.py b/stock/doctype/item/item.py index d743a98005..09a5ce6526 100644 --- a/stock/doctype/item/item.py +++ b/stock/doctype/item/item.py @@ -32,7 +32,7 @@ class DocType(DocListController): from webnotes.model.doc import make_autoname self.doc.item_code = make_autoname(self.doc.naming_series+'.#####') elif not self.doc.item_code: - msgprint(_("Item Code is mandatory"), raise_exception=1) + msgprint(_("Item Code (item_code) is mandatory because Item naming is not sequential."), raise_exception=1) self.doc.name = self.doc.item_code From e5dfc6c440eb6bd91b5899638ba4c300938a9a0f Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Thu, 27 Jun 2013 11:45:47 +0530 Subject: [PATCH 06/12] [grid] [bom] fixes erpnext #514, #515, wnframework #187 --- manufacturing/doctype/bom/bom.js | 6 +++--- manufacturing/doctype/bom/bom.txt | 3 ++- manufacturing/doctype/bom_item/bom_item.txt | 3 ++- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/manufacturing/doctype/bom/bom.js b/manufacturing/doctype/bom/bom.js index b1f43f7620..d14590cb08 100644 --- a/manufacturing/doctype/bom/bom.js +++ b/manufacturing/doctype/bom/bom.js @@ -44,9 +44,9 @@ var set_operation_no = function(doc) { var op = op_table[i].operation_no; if (op && !inList(operations, op)) operations.push(op); } - - cur_frm.fields_dict["bom_materials"].grid.get_field("operation_no") - .df.options = operations.join("\n"); + + wn.meta.get_docfield("BOM Item", "operation_no", + cur_frm.docname).options = operations.join("\n"); $.each(getchildren("BOM Item", doc.name, "bom_materials"), function(i, v) { if(!inList(operations, cstr(v.operation_no))) v.operation_no = null; diff --git a/manufacturing/doctype/bom/bom.txt b/manufacturing/doctype/bom/bom.txt index 922fd80363..d539ad0338 100644 --- a/manufacturing/doctype/bom/bom.txt +++ b/manufacturing/doctype/bom/bom.txt @@ -2,7 +2,7 @@ { "creation": "2013-01-22 15:11:38", "docstatus": 0, - "modified": "2013-01-29 17:32:53", + "modified": "2013-06-27 11:08:28", "modified_by": "Administrator", "owner": "Administrator" }, @@ -112,6 +112,7 @@ "options": "Specify the operations, operating cost and give a unique Operation no to your operations." }, { + "depends_on": "with_operations", "doctype": "DocField", "fieldname": "bom_operations", "fieldtype": "Table", diff --git a/manufacturing/doctype/bom_item/bom_item.txt b/manufacturing/doctype/bom_item/bom_item.txt index 14be95a336..2554adf21f 100644 --- a/manufacturing/doctype/bom_item/bom_item.txt +++ b/manufacturing/doctype/bom_item/bom_item.txt @@ -2,7 +2,7 @@ { "creation": "2013-02-22 01:27:49", "docstatus": 0, - "modified": "2013-03-07 07:03:18", + "modified": "2013-06-27 11:30:07", "modified_by": "Administrator", "owner": "Administrator" }, @@ -92,6 +92,7 @@ "reqd": 1 }, { + "description": "See \"Rate Of Materials Based On\" in Costing Section", "doctype": "DocField", "fieldname": "rate", "fieldtype": "Float", From 77cd10b10a2c0a55b959e9f0d3ead64d2741e640 Mon Sep 17 00:00:00 2001 From: Anand Doshi Date: Thu, 27 Jun 2013 12:17:30 +0530 Subject: [PATCH 07/12] [selling] added shipping rule link besides taxes master link field --- accounts/doctype/sales_invoice/sales_invoice.txt | 11 ++++++----- selling/doctype/quotation/quotation.txt | 11 ++++++----- selling/doctype/sales_common/sales_common.js | 2 +- selling/doctype/sales_order/sales_order.txt | 14 ++++++++------ stock/doctype/delivery_note/delivery_note.txt | 11 ++++++----- 5 files changed, 27 insertions(+), 22 deletions(-) diff --git a/accounts/doctype/sales_invoice/sales_invoice.txt b/accounts/doctype/sales_invoice/sales_invoice.txt index 27a0340f90..81b078528e 100644 --- a/accounts/doctype/sales_invoice/sales_invoice.txt +++ b/accounts/doctype/sales_invoice/sales_invoice.txt @@ -2,7 +2,7 @@ { "creation": "2013-05-24 19:29:05", "docstatus": 0, - "modified": "2013-06-12 15:14:00", + "modified": "2013-06-27 11:35:29", "modified_by": "Administrator", "owner": "Administrator" }, @@ -432,7 +432,7 @@ "doctype": "DocField", "fieldname": "charge", "fieldtype": "Link", - "label": "Taxes and Charges", + "label": "Apply Taxes and Charges Master", "oldfieldname": "charge", "oldfieldtype": "Link", "options": "Sales Taxes and Charges Master", @@ -446,10 +446,11 @@ }, { "doctype": "DocField", - "fieldname": "get_charges", - "fieldtype": "Button", - "label": "Get Taxes and Charges", + "fieldname": "shipping_rule", + "fieldtype": "Link", + "label": "Apply Shipping Rule", "oldfieldtype": "Button", + "options": "Shipping Rule", "print_hide": 1, "read_only": 0 }, diff --git a/selling/doctype/quotation/quotation.txt b/selling/doctype/quotation/quotation.txt index 74a4396aef..1a01e1645e 100644 --- a/selling/doctype/quotation/quotation.txt +++ b/selling/doctype/quotation/quotation.txt @@ -2,7 +2,7 @@ { "creation": "2013-05-24 19:29:08", "docstatus": 0, - "modified": "2013-06-19 15:55:15", + "modified": "2013-06-27 11:31:49", "modified_by": "Administrator", "owner": "Administrator" }, @@ -420,7 +420,7 @@ "fieldname": "charge", "fieldtype": "Link", "hidden": 0, - "label": "Sales Taxes and Charges", + "label": "Apply Taxes and Charges Master", "oldfieldname": "charge", "oldfieldtype": "Link", "options": "Sales Taxes and Charges Master", @@ -434,11 +434,12 @@ }, { "doctype": "DocField", - "fieldname": "get_charges", - "fieldtype": "Button", + "fieldname": "shipping_rule", + "fieldtype": "Link", "hidden": 0, - "label": "Get Taxes and Charges", + "label": "Apply Shipping Rule", "oldfieldtype": "Button", + "options": "Shipping Rule", "print_hide": 1, "read_only": 0 }, diff --git a/selling/doctype/sales_common/sales_common.js b/selling/doctype/sales_common/sales_common.js index 2d30601f26..a027e9c4c1 100644 --- a/selling/doctype/sales_common/sales_common.js +++ b/selling/doctype/sales_common/sales_common.js @@ -368,7 +368,7 @@ erpnext.selling.SellingController = erpnext.TransactionController.extend({ } }, - get_charges: function() { + charge: function() { var me = this; if(this.frm.doc.charge) { this.frm.call({ diff --git a/selling/doctype/sales_order/sales_order.txt b/selling/doctype/sales_order/sales_order.txt index dcb0306beb..6bb1d265f6 100644 --- a/selling/doctype/sales_order/sales_order.txt +++ b/selling/doctype/sales_order/sales_order.txt @@ -2,7 +2,7 @@ { "creation": "2013-06-18 12:39:59", "docstatus": 0, - "modified": "2013-06-18 17:49:11", + "modified": "2013-06-27 11:31:02", "modified_by": "Administrator", "owner": "Administrator" }, @@ -450,7 +450,7 @@ "doctype": "DocField", "fieldname": "charge", "fieldtype": "Link", - "label": "Sales Taxes and Charges", + "label": "Apply Taxes and Charges Master", "oldfieldname": "charge", "oldfieldtype": "Link", "options": "Sales Taxes and Charges Master", @@ -463,10 +463,12 @@ }, { "doctype": "DocField", - "fieldname": "get_charges", - "fieldtype": "Button", - "label": "Get Taxes and Charges", - "oldfieldtype": "Button" + "fieldname": "shipping_rule", + "fieldtype": "Link", + "label": "Apply Shipping Rule", + "oldfieldtype": "Button", + "options": "Shipping Rule", + "print_hide": 1 }, { "doctype": "DocField", diff --git a/stock/doctype/delivery_note/delivery_note.txt b/stock/doctype/delivery_note/delivery_note.txt index 74a21b3a14..20e2196cb0 100644 --- a/stock/doctype/delivery_note/delivery_note.txt +++ b/stock/doctype/delivery_note/delivery_note.txt @@ -2,7 +2,7 @@ { "creation": "2013-05-24 19:29:09", "docstatus": 0, - "modified": "2013-06-05 19:22:52", + "modified": "2013-06-27 11:33:53", "modified_by": "Administrator", "owner": "Administrator" }, @@ -449,7 +449,7 @@ "doctype": "DocField", "fieldname": "charge", "fieldtype": "Link", - "label": "Taxes and Charges", + "label": "Apply Taxes and Charges Master", "oldfieldname": "charge", "oldfieldtype": "Link", "options": "Sales Taxes and Charges Master", @@ -463,10 +463,11 @@ }, { "doctype": "DocField", - "fieldname": "get_charges", - "fieldtype": "Button", - "label": "Get Taxes and Charges", + "fieldname": "shipping_rule", + "fieldtype": "Link", + "label": "Apply Shipping Rule", "oldfieldtype": "Button", + "options": "Shipping Rule", "print_hide": 1, "read_only": 0 }, From 03e9a4ea11ab48f0496f88887a0df0918b999a7c Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Fri, 28 Jun 2013 12:49:28 +0530 Subject: [PATCH 08/12] [company] [setup] added on_doctype_update to add indexes, renamed make_obj to make_controller --- patches/february_2013/p09_timesheets.py | 2 +- setup/doctype/company/company.py | 41 +++++++------------ .../stock_ledger_entry/stock_ledger_entry.py | 11 ++++- .../stock_ledger_entry/stock_ledger_entry.txt | 2 +- .../stock_reconciliation.py | 7 +++- stock/doctype/warehouse/warehouse.py | 2 +- 6 files changed, 33 insertions(+), 32 deletions(-) diff --git a/patches/february_2013/p09_timesheets.py b/patches/february_2013/p09_timesheets.py index 2242d6b629..609dba33bc 100644 --- a/patches/february_2013/p09_timesheets.py +++ b/patches/february_2013/p09_timesheets.py @@ -49,7 +49,7 @@ def execute(): for key in custom_map["Timesheet Detail"]: tl.doc.fields[key] = tsd.fields.get(key) - tl.make_obj() + tl.make_controller() tl.controller.set_status() tl.controller.calculate_total_hours() tl.doc.insert() diff --git a/setup/doctype/company/company.py b/setup/doctype/company/company.py index 64783a0c60..54a293f6ea 100644 --- a/setup/doctype/company/company.py +++ b/setup/doctype/company/company.py @@ -190,33 +190,23 @@ class DocType: self.doc.letter_head = header def set_default_accounts(self): - if not self.doc.receivables_group and webnotes.conn.exists('Account', - 'Accounts Receivable - ' + self.doc.abbr): - webnotes.conn.set(self.doc, 'receivables_group', 'Accounts Receivable - ' + - self.doc.abbr) - - if not self.doc.payables_group and webnotes.conn.exists('Account', - 'Accounts Payable - ' + self.doc.abbr): - webnotes.conn.set(self.doc, 'payables_group', 'Accounts Payable - ' + self.doc.abbr) - - if not self.doc.stock_received_but_not_billed and webnotes.conn.exists("Account", - "Stock Received But Not Billed - " + self.doc.abbr): - webnotes.conn.set(self.doc, "stock_received_but_not_billed", - "Stock Received But Not Billed - " + self.doc.abbr) - - if not self.doc.stock_adjustment_account and webnotes.conn.exists("Account", - "Stock Adjustment - " + self.doc.abbr): - webnotes.conn.set(self.doc, "stock_adjustment_account", "Stock Adjustment - " + - self.doc.abbr) - - if not self.doc.expenses_included_in_valuation and webnotes.conn.exists("Account", - "Expenses Included In Valuation - " + self.doc.abbr): - webnotes.conn.set(self.doc, "expenses_included_in_valuation", - "Expenses Included In Valuation - " + self.doc.abbr) + accounts = { + "receivables_group": "Accounts Receivable", + "payables_group": "Accounts Payable", + "stock_received_but_not_billed": "Stock Received But Not Billed", + "stock_in_hand_account": "Stock In Hand", + "stock_adjustment_account": "Stock Adjustment", + "expenses_included_in_valuation": "Expenses Included In Valuation" + } + for a in accounts: + account_name = accounts[a] + " - " + self.doc.abbr + if not self.doc.fields[a] and webnotes.conn.exists("Account", account_name): + webnotes.conn.set(self.doc, account_name) + if not self.doc.stock_adjustment_cost_center: webnotes.conn.set(self.doc, "stock_adjustment_cost_center", self.doc.cost_center) - + # Create default cost center # --------------------------------------------------- def create_default_cost_center(self): @@ -249,8 +239,7 @@ class DocType: where company=%s and docstatus<2 limit 1""", self.doc.name): self.create_default_accounts() - if not webnotes.conn.sql("""select name from `tabCost Center` - where cost_center_name = 'All Units' and company_name = %s""", self.doc.name): + if not self.doc.cost_center: self.create_default_cost_center() self.set_default_accounts() diff --git a/stock/doctype/stock_ledger_entry/stock_ledger_entry.py b/stock/doctype/stock_ledger_entry/stock_ledger_entry.py index 3cde2e20c9..3492931451 100644 --- a/stock/doctype/stock_ledger_entry/stock_ledger_entry.py +++ b/stock/doctype/stock_ledger_entry/stock_ledger_entry.py @@ -119,4 +119,13 @@ class DocType(DocListController): def scrub_posting_time(self): if not self.doc.posting_time or self.doc.posting_time == '00:0': self.doc.posting_time = '00:00' - \ No newline at end of file + + def on_doctype_update(self): + webnotes.msgprint(webnotes.conn.sql("""show index from `tabStock Ledger Entry` + where Key_name="posting_sort_index" """)) + if not webnotes.conn.sql("""show index from `tabStock Ledger Entry` + where Key_name="posting_sort_index" """): + webnotes.conn.commit() + webnotes.conn.sql("""alter table `tabStock Ledger Entry` + add index posting_sort_index(posting_date, posting_time, name)""") + webnotes.conn.begin() \ No newline at end of file diff --git a/stock/doctype/stock_ledger_entry/stock_ledger_entry.txt b/stock/doctype/stock_ledger_entry/stock_ledger_entry.txt index 6bcd758733..674104dfeb 100644 --- a/stock/doctype/stock_ledger_entry/stock_ledger_entry.txt +++ b/stock/doctype/stock_ledger_entry/stock_ledger_entry.txt @@ -2,7 +2,7 @@ { "creation": "2013-01-29 19:25:42", "docstatus": 0, - "modified": "2013-03-25 16:04:59", + "modified": "2013-06-28 12:39:07", "modified_by": "Administrator", "owner": "Administrator" }, diff --git a/stock/doctype/stock_reconciliation/stock_reconciliation.py b/stock/doctype/stock_reconciliation/stock_reconciliation.py index 49e8b15547..8e5698c4e2 100644 --- a/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -45,9 +45,12 @@ class DocType(StockController): return data = json.loads(self.doc.reconciliation_json) + + # strip out extra columns (if any) + data = [row[:4] for row in data] + if self.head_row not in data: - msgprint(_("""Hey! You seem to be using the wrong template. \ - Click on 'Download Template' button to get the correct template."""), + msgprint(_("""Wrong Template: Unable to find head row."""), raise_exception=1) # remove the help part and save the json diff --git a/stock/doctype/warehouse/warehouse.py b/stock/doctype/warehouse/warehouse.py index 264e459e56..758bd31eb0 100644 --- a/stock/doctype/warehouse/warehouse.py +++ b/stock/doctype/warehouse/warehouse.py @@ -42,7 +42,7 @@ class DocType: bin_wrapper.ignore_permissions = 1 bin_wrapper.insert() - bin_obj = bin_wrapper.make_obj() + bin_obj = bin_wrapper.make_controller() else: bin_obj = get_obj('Bin', bin) return bin_obj From b78a4e0f80145a038793b66943fc288456364ce4 Mon Sep 17 00:00:00 2001 From: Anand Doshi Date: Fri, 28 Jun 2013 19:17:55 +0530 Subject: [PATCH 09/12] [Newsletter] [fix] in create missing leads --- support/doctype/newsletter/newsletter.py | 27 +++++++++++++----------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/support/doctype/newsletter/newsletter.py b/support/doctype/newsletter/newsletter.py index 78921f176f..e654470998 100644 --- a/support/doctype/newsletter/newsletter.py +++ b/support/doctype/newsletter/newsletter.py @@ -19,7 +19,6 @@ from __future__ import unicode_literals import webnotes import webnotes.utils from webnotes.utils import cstr -from webnotes.model.doc import Document from webnotes import _ class DocType(): @@ -73,8 +72,7 @@ class DocType(): elif self.doc.email_list: email_list = [cstr(email).strip() for email in self.doc.email_list.split(",")] for email in email_list: - if not webnotes.conn.exists({"doctype": "Lead", "email_id": email}): - create_lead(email) + create_lead(email) self.send_to_doctype = "Lead" return email_list @@ -116,15 +114,20 @@ def create_lead(email_id): """create a lead if it does not exist""" from email.utils import parseaddr real_name, email_id = parseaddr(email_id) - lead = Document("Lead") - lead.fields["__islocal"] = 1 - lead.lead_name = real_name or email_id - lead.email_id = email_id - lead.status = "Contacted" - lead.naming_series = lead_naming_series or get_lead_naming_series() - lead.company = webnotes.conn.get_default("company") - lead.source = "Email" - lead.save() + + if webnotes.conn.get_value("Lead", {"email_id": email_id}): + return + + lead = webnotes.bean({ + "doctype": "Lead", + "email_id": email_id, + "lead_name": real_name or email_id, + "status": "Contacted", + "naming_series": lead_naming_series or get_lead_naming_series(), + "company": webnotes.conn.get_default("company"), + "source": "Email" + }) + lead.insert() def get_lead_naming_series(): """gets lead's default naming series""" From 8f9f8a43c71443a6db46b4e849f58fca3ed73fca Mon Sep 17 00:00:00 2001 From: Anand Doshi Date: Fri, 28 Jun 2013 19:18:33 +0530 Subject: [PATCH 10/12] [lead] [address] store lead address in Address doctype, with link field=lead --- controllers/js/contact_address_common.js | 29 +++- .../p07_taxes_price_list_for_territory.py | 3 +- patches/june_2013/p10_lead_address.py | 50 ++++++ patches/patch_list.py | 3 + selling/doctype/lead/lead.js | 142 +++++++++--------- selling/doctype/lead/lead.py | 3 + selling/doctype/lead/lead.txt | 92 ++++-------- selling/doctype/quotation/quotation.txt | 20 ++- selling/doctype/sales_common/sales_common.js | 24 +++ selling/doctype/sales_order/sales_order.js | 23 --- .../contact_control/contact_control.js | 4 +- utilities/doctype/address/address.py | 46 +++--- utilities/doctype/address/address.txt | 31 +++- utilities/transaction_base.py | 33 ++-- 14 files changed, 287 insertions(+), 216 deletions(-) create mode 100644 patches/june_2013/p10_lead_address.py diff --git a/controllers/js/contact_address_common.js b/controllers/js/contact_address_common.js index 89b1d43ac5..de1ab3478f 100644 --- a/controllers/js/contact_address_common.js +++ b/controllers/js/contact_address_common.js @@ -5,6 +5,11 @@ cur_frm.cscript.onload = function(doc, cdt, cdn) { cur_frm.fields_dict.customer.get_query = erpnext.utils.customer_query; cur_frm.fields_dict.supplier.get_query = erpnext.utils.supplier_query; + if(cur_frm.fields_dict.lead) { + cur_frm.fields_dict.lead.get_query = erpnext.utils.lead_query; + cur_frm.add_fetch('lead', 'lead_name', 'lead_name'); + } + if(doc.__islocal) { var last_route = wn.route_history.slice(-2, -1)[0]; if(last_route && last_route[0]==="Form") { @@ -13,10 +18,13 @@ cur_frm.cscript.onload = function(doc, cdt, cdn) { "Maintenance Schedule"] .indexOf(last_route[1])!==-1) { var refdoc = wn.model.get_doc(last_route[1], last_route[2]); - cur_frm.set_value("customer", refdoc.customer || refdoc.name); - cur_frm.set_value("customer_name", refdoc.customer_name); - if(cur_frm.doc.doctype==="Address") - cur_frm.set_value("address_title", cur_frm.doc.customer) + + if(refdoc.doctype == "Quotation" ? refdoc.quotation_to=="Customer" : true) { + cur_frm.set_value("customer", refdoc.customer || refdoc.name); + cur_frm.set_value("customer_name", refdoc.customer_name); + if(cur_frm.doc.doctype==="Address") + cur_frm.set_value("address_title", cur_frm.doc.customer_name); + } } if(["Supplier", "Supplier Quotation", "Purchase Order", "Purchase Invoice", "Purchase Receipt"] .indexOf(last_route[1])!==-1) { @@ -24,7 +32,18 @@ cur_frm.cscript.onload = function(doc, cdt, cdn) { cur_frm.set_value("supplier", refdoc.supplier || refdoc.name); cur_frm.set_value("supplier_name", refdoc.supplier_name); if(cur_frm.doc.doctype==="Address") - cur_frm.set_value("address_title", cur_frm.doc.supplier) + cur_frm.set_value("address_title", cur_frm.doc.supplier_name); + } + if(["Lead", "Quotation"] + .indexOf(last_route[1])!==-1) { + var refdoc = wn.model.get_doc(last_route[1], last_route[2]); + + if(refdoc.doctype == "Quotation" ? refdoc.quotation_to=="Lead" : true) { + cur_frm.set_value("lead", refdoc.lead || refdoc.name); + cur_frm.set_value("lead_name", refdoc.customer_name || refdoc.company_name || refdoc.lead_name); + if(cur_frm.doc.doctype==="Address") + cur_frm.set_value("address_title", cur_frm.doc.lead_name); + } } } } diff --git a/patches/june_2013/p07_taxes_price_list_for_territory.py b/patches/june_2013/p07_taxes_price_list_for_territory.py index fbce11557a..9204866718 100644 --- a/patches/june_2013/p07_taxes_price_list_for_territory.py +++ b/patches/june_2013/p07_taxes_price_list_for_territory.py @@ -4,11 +4,12 @@ def execute(): webnotes.reload_doc("setup", "doctype", "for_territory") webnotes.reload_doc("setup", "doctype", "price_list") webnotes.reload_doc("accounts", "doctype", "sales_taxes_and_charges_master") + webnotes.reload_doc("accounts", "doctype", "shipping_rule") from setup.utils import get_root_of root_territory = get_root_of("Territory") - for parenttype in ["Sales Taxes and Charges Master", "Price List"]: + for parenttype in ["Sales Taxes and Charges Master", "Price List", "Shipping Rule"]: for name in webnotes.conn.sql_list("""select name from `tab%s` main where not exists (select parent from `tabFor Territory` territory where territory.parenttype=%s and territory.parent=main.name)""" % \ diff --git a/patches/june_2013/p10_lead_address.py b/patches/june_2013/p10_lead_address.py new file mode 100644 index 0000000000..516e2a68e1 --- /dev/null +++ b/patches/june_2013/p10_lead_address.py @@ -0,0 +1,50 @@ +import webnotes + +def execute(): + webnotes.reload_doc("utilities", "doctype", "address") + + webnotes.conn.auto_commit_on_many_writes = True + + for lead in webnotes.conn.sql("""select name as lead, lead_name, address_line1, address_line2, city, country, + state, pincode, status, company_name from `tabLead` where not exists + (select name from `tabAddress` where `tabAddress`.lead=`tabLead`.name) and + (ifnull(address_line1, '')!='' or ifnull(city, '')!='' or ifnull(country, '')!='' or ifnull(pincode, '')!='')""", as_dict=True): + if set_in_customer(lead): + continue + + create_address_for(lead) + + webnotes.conn.auto_commit_on_many_writes = False + +def set_in_customer(lead): + customer = webnotes.conn.get_value("Customer", {"lead_name": lead.lead}) + if customer: + customer_address = webnotes.conn.sql("""select name from `tabAddress` + where customer=%s and (address_line1=%s or address_line2=%s or pincode=%s)""", + (customer, lead.address_line1, lead.address_line2, lead.pincode)) + if customer_address: + webnotes.conn.sql("""update `tabAddress` set lead=%s, lead_name=%s + where name=%s""", (lead.lead, lead.company_name or lead.lead_name, customer_address[0][0])) + return True + + return False + +def create_address_for(lead): + address_title = lead.company_name or lead.lead_name or lead.lead + + for c in ['%', "'", '"', '#', '*', '?', '`']: + address_title = address_title.replace(c, "") + + if webnotes.conn.get_value("Address", address_title.strip() + "-" + "Billing"): + address_title += " " + lead.lead + + lead.update({ + "doctype": "Address", + "address_type": "Billing", + "address_title": address_title + }) + + del lead["company_name"] + del lead["status"] + + webnotes.bean(lead).insert() \ No newline at end of file diff --git a/patches/patch_list.py b/patches/patch_list.py index 8979f43ea2..aaf5102f64 100644 --- a/patches/patch_list.py +++ b/patches/patch_list.py @@ -247,5 +247,8 @@ patch_list = [ "patches.june_2013.p06_drop_unused_tables", "patches.june_2013.p08_shopping_cart_settings", "patches.june_2013.p05_remove_search_criteria_reports", + "patches.june_2013.p07_taxes_price_list_for_territory", + "patches.june_2013.p08_shopping_cart_settings", "patches.june_2013.p09_update_global_defaults", + "patches.june_2013.p10_lead_address", ] \ No newline at end of file diff --git a/selling/doctype/lead/lead.js b/selling/doctype/lead/lead.js index c94aafa08a..8fb26c5a89 100644 --- a/selling/doctype/lead/lead.js +++ b/selling/doctype/lead/lead.js @@ -14,78 +14,79 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -// Module CRM - wn.require('app/utilities/doctype/sms_control/sms_control.js'); +wn.require('app/setup/doctype/contact_control/contact_control.js'); -cur_frm.cscript.onload = function(doc, cdt, cdn) { - if(user =='Guest'){ - hide_field(['status', 'naming_series', 'order_lost_reason', - 'customer', 'rating', 'fax', 'website', 'territory', - 'address_line1', 'address_line2', 'city', 'state', - 'country', 'pincode', 'address', 'lead_owner', 'market_segment', - 'industry', 'campaign_name', 'interested_in', 'company', - 'fiscal_year', 'contact_by', 'contact_date', 'last_contact_date', - 'contact_date_ref', 'to_discuss', 'more_info', 'follow_up', - 'communication_history', 'cc_to', 'subject', 'message', 'lead_attachment_detail', - 'Create Customer', 'Create Opportunity', 'transaction_date', 'type', 'source']); - doc.source = 'Website'; - } - if(!doc.status) set_multiple(dt,dn,{status:'Open'}); - - if (!doc.date){ - doc.date = date.obj_to_str(new Date()); - } - // set naming series - if(user=='Guest') doc.naming_series = 'WebLead'; +wn.provide("erpnext"); +erpnext.LeadController = wn.ui.form.Controller.extend({ + setup: function() { + this.frm.fields_dict.customer.get_query = erpnext.utils.customer_query; + }, - cur_frm.add_fetch('customer', 'customer_name', 'company_name'); - - if(cur_frm.fields_dict.lead_owner.df.options.match(/^Profile/)) { - cur_frm.fields_dict.lead_owner.get_query = erpnext.utils.profile_query; - } - - if(cur_frm.fields_dict.contact_by.df.options.match(/^Profile/)) { - cur_frm.fields_dict.contact_by.get_query = erpnext.utils.profile_query; - } - - if(in_list(user_roles,'System Manager')) { - cur_frm.footer.help_area.innerHTML = '
\ -

Sales Email Settings
\ - Automatically extract Leads from a mail box e.g. "sales@example.com"

'; - } -} - -cur_frm.cscript.refresh_custom_buttons = function(doc) { - cur_frm.clear_custom_buttons(); - if(!doc.__islocal && !in_list(['Converted', 'Lead Lost'], doc.status)) { - if (doc.source != 'Existing Customer') { - cur_frm.add_custom_button('Create Customer', - cur_frm.cscript['Create Customer']); + onload: function() { + if(cur_frm.fields_dict.lead_owner.df.options.match(/^Profile/)) { + cur_frm.fields_dict.lead_owner.get_query = erpnext.utils.profile_query; } - cur_frm.add_custom_button('Create Opportunity', - cur_frm.cscript['Create Opportunity']); - cur_frm.add_custom_button('Send SMS', cur_frm.cscript.send_sms); + + if(cur_frm.fields_dict.contact_by.df.options.match(/^Profile/)) { + cur_frm.fields_dict.contact_by.get_query = erpnext.utils.profile_query; + } + + if(in_list(user_roles,'System Manager')) { + cur_frm.footer.help_area.innerHTML = '
\ +

Sales Email Settings
\ + Automatically extract Leads from a mail box e.g. "sales@example.com"

'; + } + }, + + refresh: function() { + erpnext.hide_naming_series(); + this.frm.clear_custom_buttons(); + + this.frm.__is_customer = this.frm.__is_customer || this.frm.doc.__is_customer; + if(!this.frm.doc.__islocal && !this.frm.__is_customer) { + this.frm.add_custom_button("Create Customer", this.frm.cscript['Create Customer']); + this.frm.add_custom_button("Create Opportunity", this.frm.cscript['Create Opportunity']); + this.frm.add_custom_button("Send SMS", this.frm.cscript.send_sms); + } + + cur_frm.communication_view = new wn.views.CommunicationList({ + list: wn.model.get("Communication", {"lead": this.frm.doc.name}), + parent: this.frm.fields_dict.communication_html.wrapper, + doc: this.frm.doc, + recipients: this.frm.doc.email_id + }); + + if(!this.frm.doc.__islocal) { + this.make_address_list(); + } + }, + + make_address_list: function() { + var me = this; + if(!this.frm.address_list) { + this.frm.address_list = new wn.ui.Listing({ + parent: this.frm.fields_dict['address_html'].wrapper, + page_length: 5, + new_doctype: "Address", + get_query: function() { + return 'select name, address_type, address_line1, address_line2, \ + city, state, country, pincode, fax, email_id, phone, \ + is_primary_address, is_shipping_address from tabAddress \ + where lead="'+me.frm.doc.name+'" and docstatus != 2 \ + order by is_primary_address, is_shipping_address desc' + }, + as_dict: 1, + no_results_message: 'No addresses created', + render_row: this.render_address_row, + }); + // note: render_address_row is defined in contact_control.js + } + this.frm.address_list.run(); } -} +}); -cur_frm.cscript.refresh = function(doc, cdt, cdn) { - cur_frm.cscript.refresh_custom_buttons(doc); - erpnext.hide_naming_series(); - - cur_frm.communication_view = new wn.views.CommunicationList({ - list: wn.model.get("Communication", {"lead": doc.name}), - parent: cur_frm.fields_dict.communication_html.wrapper, - doc: doc, - recipients: doc.email_id - }) -} - - - -cur_frm.cscript.status = function(doc, cdt, cdn){ - cur_frm.cscript.refresh(doc, cdt, cdn); -} +$.extend(cur_frm.cscript, new erpnext.LeadController({frm: cur_frm})); cur_frm.cscript['Create Customer'] = function(){ var doc = cur_frm.doc; @@ -147,11 +148,4 @@ cur_frm.cscript['Create Opportunity'] = function(){ } } ); -} - -//get query select Territory -cur_frm.fields_dict['territory'].get_query = function(doc,cdt,cdn) { - return 'SELECT `tabTerritory`.`name`,`tabTerritory`.`parent_territory` FROM `tabTerritory` WHERE `tabTerritory`.`is_group` = "No" AND `tabTerritory`.`docstatus`!= 2 AND `tabTerritory`.%(key)s LIKE "%s" ORDER BY `tabTerritory`.`name` ASC LIMIT 50'; -} - -cur_frm.fields_dict.customer.get_query = erpnext.utils.customer_query; \ No newline at end of file +} \ No newline at end of file diff --git a/selling/doctype/lead/lead.py b/selling/doctype/lead/lead.py index a54343a907..928eda7d85 100644 --- a/selling/doctype/lead/lead.py +++ b/selling/doctype/lead/lead.py @@ -38,6 +38,9 @@ class DocType(SellingController): def onload(self): self.add_communication_list() + customer = webnotes.conn.get_value("Customer", {"lead_name": self.doc.name}) + if customer: + self.doc.fields["__is_customer"] = customer def on_communication_sent(self, comm): webnotes.conn.set(self.doc, 'status', 'Replied') diff --git a/selling/doctype/lead/lead.txt b/selling/doctype/lead/lead.txt index 0b602ea7e1..eed87ba399 100644 --- a/selling/doctype/lead/lead.txt +++ b/selling/doctype/lead/lead.txt @@ -2,7 +2,7 @@ { "creation": "2013-04-10 11:45:37", "docstatus": 0, - "modified": "2013-06-14 16:20:17", + "modified": "2013-06-28 15:08:26", "modified_by": "Administrator", "owner": "Administrator" }, @@ -164,9 +164,33 @@ "doctype": "DocField", "fieldname": "contact_info", "fieldtype": "Section Break", - "label": "Contact Info", + "label": "Address & Contact", "oldfieldtype": "Column Break" }, + { + "depends_on": "eval:doc.__islocal", + "doctype": "DocField", + "fieldname": "address_desc", + "fieldtype": "HTML", + "hidden": 0, + "label": "Address Desc", + "options": "Addresses will appear only when you save the lead", + "print_hide": 1 + }, + { + "doctype": "DocField", + "fieldname": "address_html", + "fieldtype": "HTML", + "hidden": 0, + "label": "Address HTML", + "print_hide": 0, + "read_only": 1 + }, + { + "doctype": "DocField", + "fieldname": "column_break2", + "fieldtype": "Column Break" + }, { "doctype": "DocField", "fieldname": "remark", @@ -219,70 +243,6 @@ "options": "Territory", "print_hide": 1 }, - { - "doctype": "DocField", - "fieldname": "column_break2", - "fieldtype": "Column Break" - }, - { - "doctype": "DocField", - "fieldname": "address_line1", - "fieldtype": "Data", - "label": "Address Line 1", - "oldfieldname": "address_line1", - "oldfieldtype": "Data", - "print_hide": 1, - "reqd": 0 - }, - { - "doctype": "DocField", - "fieldname": "address_line2", - "fieldtype": "Data", - "label": "Address Line 2", - "oldfieldname": "address_line2", - "oldfieldtype": "Data", - "print_hide": 1 - }, - { - "doctype": "DocField", - "fieldname": "city", - "fieldtype": "Data", - "label": "City", - "oldfieldname": "city", - "oldfieldtype": "Select", - "print_hide": 1, - "reqd": 0 - }, - { - "doctype": "DocField", - "fieldname": "country", - "fieldtype": "Select", - "label": "Country", - "oldfieldname": "country", - "oldfieldtype": "Select", - "options": "link:Country", - "print_hide": 1, - "reqd": 0 - }, - { - "doctype": "DocField", - "fieldname": "state", - "fieldtype": "Data", - "label": "State", - "oldfieldname": "state", - "oldfieldtype": "Select", - "options": "Suggest", - "print_hide": 1 - }, - { - "doctype": "DocField", - "fieldname": "pincode", - "fieldtype": "Data", - "label": "Pin Code", - "oldfieldname": "pincode", - "oldfieldtype": "Data", - "print_hide": 1 - }, { "doctype": "DocField", "fieldname": "more_info", diff --git a/selling/doctype/quotation/quotation.txt b/selling/doctype/quotation/quotation.txt index 1a01e1645e..b9772c8379 100644 --- a/selling/doctype/quotation/quotation.txt +++ b/selling/doctype/quotation/quotation.txt @@ -2,7 +2,7 @@ { "creation": "2013-05-24 19:29:08", "docstatus": 0, - "modified": "2013-06-27 11:31:49", + "modified": "2013-06-28 12:47:10", "modified_by": "Administrator", "owner": "Administrator" }, @@ -690,6 +690,24 @@ "reqd": 0, "search_index": 0 }, + { + "doctype": "DocField", + "fieldname": "shipping_address_name", + "fieldtype": "Link", + "hidden": 0, + "label": "Shipping Address Name", + "options": "Address", + "print_hide": 1 + }, + { + "doctype": "DocField", + "fieldname": "shipping_address", + "fieldtype": "Small Text", + "hidden": 1, + "label": "Shipping Address", + "print_hide": 1, + "read_only": 1 + }, { "depends_on": "customer", "doctype": "DocField", diff --git a/selling/doctype/sales_common/sales_common.js b/selling/doctype/sales_common/sales_common.js index a027e9c4c1..77e77592e9 100644 --- a/selling/doctype/sales_common/sales_common.js +++ b/selling/doctype/sales_common/sales_common.js @@ -27,6 +27,9 @@ wn.require("app/js/transaction.js"); erpnext.selling.SellingController = erpnext.TransactionController.extend({ setup: function() { this.frm.add_fetch("sales_partner", "commission_rate", "commission_rate"); + + if(this.frm.fields_dict.shipping_address_name && this.frm.fields_dict.customer_address) + this.frm.fields_dict.shipping_address_name.get_query = this.frm.fields_dict['customer_address'].get_query; }, onload: function() { @@ -476,6 +479,27 @@ erpnext.selling.SellingController = erpnext.TransactionController.extend({ }); }, + shipping_address_name: function () { + var me = this; + if(this.frm.doc.shipping_address_name) { + wn.model.with_doc("Address", this.frm.doc.shipping_address_name, function(name) { + var address = wn.model.get_doc("Address", name); + + var out = $.map(["address_line1", "address_line2", "city"], + function(f) { return address[f]; }); + + var state_pincode = $.map(["state", "pincode"], function(f) { return address[f]; }).join(" "); + if(state_pincode) out.push(state_pincode); + + if(address["country"]) out.push(address["country"]); + + out.concat($.map([["Phone:", address["phone"]], ["Fax:", address["fax"]]], + function(val) { return val[1] ? val.join(" ") : null; })); + + me.frm.set_value("shipping_address", out.join("\n")); + }); + } + } }); // to save previous state of cur_frm.cscript diff --git a/selling/doctype/sales_order/sales_order.js b/selling/doctype/sales_order/sales_order.js index d8aab21fbd..d9e2d7b047 100644 --- a/selling/doctype/sales_order/sales_order.js +++ b/selling/doctype/sales_order/sales_order.js @@ -79,29 +79,6 @@ cur_frm.cscript.customer_address = cur_frm.cscript.contact_person = function(doc if(doc.customer) get_server_fields('get_customer_address', JSON.stringify({customer: doc.customer, address: doc.customer_address, contact: doc.contact_person}),'', doc, dt, dn, 1); } -cur_frm.fields_dict.shipping_address_name.get_query = cur_frm.fields_dict['customer_address'].get_query; - -cur_frm.cscript.shipping_address_name = function() { - if(cur_frm.doc.shipping_address_name) { - wn.model.with_doc("Address", cur_frm.doc.shipping_address_name, function(name) { - var address = wn.model.get_doc("Address", name); - - var out = $.map(["address_line1", "address_line2", "city"], - function(f) { return address[f]; }); - - var state_pincode = $.map(["state", "pincode"], function(f) { return address[f]; }).join(" "); - if(state_pincode) out.push(state_pincode); - - if(address["country"]) out.push(address["country"]); - - out.concat($.map([["Phone:", address["phone"]], ["Fax:", address["fax"]]], - function(val) { return val[1] ? val.join(" ") : null; })); - - cur_frm.set_value("shipping_address", out.join("\n")); - }); - } -}; - cur_frm.cscript.pull_quotation_details = function(doc,dt,dn) { var callback = function(r,rt){ var doc = locals[cur_frm.doctype][cur_frm.docname]; diff --git a/setup/doctype/contact_control/contact_control.js b/setup/doctype/contact_control/contact_control.js index 79e9de70da..c427ec09fd 100755 --- a/setup/doctype/contact_control/contact_control.js +++ b/setup/doctype/contact_control/contact_control.js @@ -59,8 +59,8 @@ cur_frm.cscript.render_address_row = function(wrapper, data) { // prepare data data.fullname = data.address_type; data.primary = ''; - if (data.is_primary_address) data.primary += ' [Primary]'; - if (data.is_shipping_address) data.primary += ' [Shipping]'; + if (data.is_primary_address) data.primary += ' [Preferred for Billing]'; + if (data.is_shipping_address) data.primary += ' [Preferred for Shipping]'; // prepare address var address = []; diff --git a/utilities/doctype/address/address.py b/utilities/doctype/address/address.py index cfcbea582f..c475da12b5 100644 --- a/utilities/doctype/address/address.py +++ b/utilities/doctype/address/address.py @@ -27,7 +27,7 @@ class DocType: def autoname(self): if not self.doc.address_title: - self.doc.address_title = self.doc.customer or self.doc.supplier or self.doc.sales_partner + self.doc.address_title = self.doc.customer or self.doc.supplier or self.doc.sales_partner or self.doc.lead if self.doc.address_title: self.doc.name = cstr(self.doc.address_title).strip() + "-" + cstr(self.doc.address_type).strip() @@ -35,40 +35,32 @@ class DocType: else: webnotes.msgprint("""Address Title is mandatory.""", raise_exception=True) - def validate(self): self.validate_primary_address() self.validate_shipping_address() def validate_primary_address(self): """Validate that there can only be one primary address for particular customer, supplier""" - sql = webnotes.conn.sql if self.doc.is_primary_address == 1: - if self.doc.customer: - sql("update tabAddress set is_primary_address=0 where customer = '%s'" % (self.doc.customer)) - elif self.doc.supplier: - sql("update tabAddress set is_primary_address=0 where supplier = '%s'" % (self.doc.supplier)) - elif self.doc.sales_partner: - sql("update tabAddress set is_primary_address=0 where sales_partner = '%s'" % (self.doc.sales_partner)) - elif not self.doc.is_shipping_address: - if self.doc.customer: - if not sql("select name from tabAddress where is_primary_address=1 and customer = '%s'" % (self.doc.customer)): - self.doc.is_primary_address = 1 - elif self.doc.supplier: - if not sql("select name from tabAddress where is_primary_address=1 and supplier = '%s'" % (self.doc.supplier)): - self.doc.is_primary_address = 1 - elif self.doc.sales_partner: - if not sql("select name from tabAddress where is_primary_address=1 and sales_partner = '%s'" % (self.doc.sales_partner)): - self.doc.is_primary_address = 1 - + self._unset_other("is_primary_address") + + elif self.doc.is_shipping_address != 1: + for fieldname in ["customer", "supplier", "sales_partner", "lead"]: + if self.doc.fields.get(fieldname): + if not webnotes.conn.sql("""select name from `tabAddress` where is_primary_address=1 + and `%s`=%s and name!=%s""" % (fieldname, "%s", "%s"), + (self.doc.fields[fieldname], self.doc.name)): + self.doc.is_primary_address = 1 + break def validate_shipping_address(self): """Validate that there can only be one shipping address for particular customer, supplier""" - sql = webnotes.conn.sql if self.doc.is_shipping_address == 1: - if self.doc.customer: - sql("update tabAddress set is_shipping_address=0 where customer = '%s'" % (self.doc.customer)) - elif self.doc.supplier: - sql("update tabAddress set is_shipping_address=0 where supplier = '%s'" % (self.doc.supplier)) - elif self.doc.sales_partner: - sql("update tabAddress set is_shipping_address=0 where sales_partner = '%s'" % (self.doc.sales_partner)) + self._unset_other("is_shipping_address") + + def _unset_other(self, is_address_type): + for fieldname in ["customer", "supplier", "sales_partner", "lead"]: + if self.doc.fields.get(fieldname): + webnotes.conn.sql("""update `tabAddress` set `%s`=0 where `%s`=%s and name!=%s""" % + (is_address_type, fieldname, "%s", "%s"), (self.doc.fields[fieldname], self.doc.name)) + break \ No newline at end of file diff --git a/utilities/doctype/address/address.txt b/utilities/doctype/address/address.txt index 757beb9656..ed39c759c5 100644 --- a/utilities/doctype/address/address.txt +++ b/utilities/doctype/address/address.txt @@ -2,7 +2,7 @@ { "creation": "2013-01-10 16:34:32", "docstatus": 0, - "modified": "2013-01-29 13:24:45", + "modified": "2013-06-28 17:06:32", "modified_by": "Administrator", "owner": "Administrator" }, @@ -46,11 +46,11 @@ "label": "Address Details" }, { - "description": "e.g. Office, Billing, Shipping", "doctype": "DocField", "fieldname": "address_type", - "fieldtype": "Data", + "fieldtype": "Select", "label": "Address Type", + "options": "Billing\nShipping\nOffice\nPersonal\nPlant\nPostal\nShop\nSubsidiary\nWarehouse\nOther", "reqd": 1 }, { @@ -145,7 +145,7 @@ "doctype": "DocField", "fieldname": "is_primary_address", "fieldtype": "Check", - "label": "Is Primary Address" + "label": "Preferred Billing Address" }, { "default": "0", @@ -154,7 +154,7 @@ "fieldname": "is_shipping_address", "fieldtype": "Check", "in_list_view": 1, - "label": "Is Shipping Address" + "label": "Preferred Shipping Address" }, { "doctype": "DocField", @@ -207,6 +207,27 @@ "label": "Sales Partner", "options": "Sales Partner" }, + { + "doctype": "DocField", + "fieldname": "column_break_22", + "fieldtype": "Column Break" + }, + { + "depends_on": "eval:!doc.supplier && !doc.sales_partner", + "doctype": "DocField", + "fieldname": "lead", + "fieldtype": "Link", + "label": "Lead", + "options": "Lead" + }, + { + "depends_on": "eval:!doc.supplier && !doc.sales_partner", + "doctype": "DocField", + "fieldname": "lead_name", + "fieldtype": "Data", + "label": "Lead Name", + "read_only": 1 + }, { "doctype": "DocPerm", "role": "Sales User" diff --git a/utilities/transaction_base.py b/utilities/transaction_base.py index c86a50ec19..2def0ea05f 100644 --- a/utilities/transaction_base.py +++ b/utilities/transaction_base.py @@ -170,21 +170,30 @@ class TransactionBase(StatusUpdater): # Get Lead Details # ----------------------- - def get_lead_details(self, name): - details = webnotes.conn.sql("select name, lead_name, address_line1, address_line2, city, country, state, pincode, territory, phone, mobile_no, email_id, company_name from `tabLead` where name = '%s'" %(name), as_dict = 1) - - extract = lambda x: details and details[0] and details[0].get(x,'') or '' - address_fields = [('','address_line1'),('\n','address_line2'),('\n','city'),(' ','pincode'),('\n','state'),('\n','country'),('\nPhone: ','contact_no')] - address_display = ''.join([a[0]+extract(a[1]) for a in address_fields if extract(a[1])]) - if address_display.startswith('\n'): address_display = address_display[1:] + def get_lead_details(self, name): + details = webnotes.conn.sql("""select name, lead_name, address_line1, address_line2, city, country, state, pincode + from `tabAddress` where lead=%s""", name, as_dict=True) + lead = webnotes.conn.get_value("Lead", name, + ["territory", "phone", "mobile_no", "email_id", "company_name", "lead_name"], as_dict=True) or {} + + address_display = "" + if details: + details = details[0] + for separator, fieldname in (('','address_line1'), ('\n','address_line2'), ('\n','city'), + (' ','pincode'), ('\n','state'), ('\n','country'), ('\nPhone: ', 'phone')): + if details.get(fieldname): + address_display += separator + details.get(fieldname) + + if address_display.startswith('\n'): + address_display = address_display[1:] ret = { - 'contact_display' : extract('lead_name'), + 'contact_display' : lead.get('lead_name'), 'address_display' : address_display, - 'territory' : extract('territory'), - 'contact_mobile' : extract('mobile_no'), - 'contact_email' : extract('email_id'), - 'customer_name' : extract('company_name') or extract('lead_name') + 'territory' : lead.get('territory'), + 'contact_mobile' : lead.get('mobile_no'), + 'contact_email' : lead.get('email_id'), + 'customer_name' : lead.get('company_name') or lead.get('lead_name') } return ret From 4bef02e46a547fc0a9bfb025cd62411bec67079a Mon Sep 17 00:00:00 2001 From: Anand Doshi Date: Fri, 28 Jun 2013 19:21:44 +0530 Subject: [PATCH 11/12] [minor] removed debug --- accounts/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/accounts/utils.py b/accounts/utils.py index fa93cb0628..4ca1b3a57a 100644 --- a/accounts/utils.py +++ b/accounts/utils.py @@ -351,4 +351,4 @@ def fix_total_debit_credit(): webnotes.conn.sql("""update `tabGL Entry` set %s = %s + %s where voucher_type = %s and voucher_no = %s and %s > 0 limit 1""" % (dr_or_cr, dr_or_cr, '%s', '%s', '%s', dr_or_cr), - (d.diff, d.voucher_type, d.voucher_no), debug=1) \ No newline at end of file + (d.diff, d.voucher_type, d.voucher_no)) \ No newline at end of file From 169cfafe13e97e8974121485fc64a83f61bca359 Mon Sep 17 00:00:00 2001 From: Anand Doshi Date: Sat, 29 Jun 2013 17:24:27 +0530 Subject: [PATCH 12/12] [customer] [fix] link customer in lead's address when creating customer from lead --- selling/doctype/customer/customer.py | 76 ++++------------------------ 1 file changed, 9 insertions(+), 67 deletions(-) diff --git a/selling/doctype/customer/customer.py b/selling/doctype/customer/customer.py index 65ac865304..10d2ce52fb 100644 --- a/selling/doctype/customer/customer.py +++ b/selling/doctype/customer/customer.py @@ -62,49 +62,6 @@ class DocType(TransactionBase): def validate(self): self.validate_values() - def create_customer_address(self): - addr_flds = [self.doc.address_line1, self.doc.address_line2, self.doc.city, self.doc.state, self.doc.country, self.doc.pincode] - address_line = "\n".join(filter(lambda x : (x!='' and x!=None),addr_flds)) - - if self.doc.phone_1: - address_line = address_line + "\n" + "Phone: " + cstr(self.doc.phone_1) - if self.doc.email_id: - address_line = address_line + "\n" + "E-mail: " + cstr(self.doc.email_id) - webnotes.conn.set(self.doc,'address', address_line) - - telephone = "(O): " + cstr(self.doc.phone_1) +"\n"+ cstr(self.doc.phone_2) + "\n" + "(M): " + "\n" + "(fax): " + cstr(self.doc.fax_1) - webnotes.conn.set(self.doc,'telephone',telephone) - - def create_p_contact(self,nm,phn_no,email_id,mob_no,fax,cont_addr): - c1 = Document('Contact') - c1.first_name = nm - c1.contact_name = nm - c1.contact_no = phn_no - c1.email_id = email_id - c1.mobile_no = mob_no - c1.fax = fax - c1.contact_address = cont_addr - c1.is_primary_contact = 'Yes' - c1.is_customer =1 - c1.customer = self.doc.name - c1.customer_name = self.doc.customer_name - c1.customer_address = self.doc.address - c1.customer_group = self.doc.customer_group - c1.save(1) - - def create_customer_contact(self): - contact = sql("select distinct name from `tabContact` where customer_name=%s", (self.doc.customer_name)) - contact = contact and contact[0][0] or '' - if not contact: - # create primary contact for individual customer - if self.doc.customer_type == 'Individual': - self.create_p_contact(self.doc.customer_name,self.doc.phone_1,self.doc.email_id,'',self.doc.fax_1,self.doc.address) - - # create primary contact for lead - elif self.doc.lead_name: - c_detail = sql("select lead_name, company_name, contact_no, mobile_no, email_id, fax, address from `tabLead` where name =%s", self.doc.lead_name, as_dict=1) - self.create_p_contact(c_detail and c_detail[0]['lead_name'] or '', c_detail and c_detail[0]['contact_no'] or '', c_detail and c_detail[0]['email_id'] or '', c_detail and c_detail[0]['mobile_no'] or '', c_detail and c_detail[0]['fax'] or '', c_detail and c_detail[0]['address'] or '') - def update_lead_status(self): if self.doc.lead_name: sql("update `tabLead` set status='Converted' where name = %s", self.doc.lead_name) @@ -139,31 +96,16 @@ class DocType(TransactionBase): def create_lead_address_contact(self): if self.doc.lead_name: - details = sql("select name, lead_name, address_line1, address_line2, city, country, state, pincode, phone, mobile_no, fax, email_id from `tabLead` where name = '%s'" %(self.doc.lead_name), as_dict = 1) - d = Document('Address') - d.address_line1 = details[0]['address_line1'] - d.address_line2 = details[0]['address_line2'] - d.city = details[0]['city'] - d.country = details[0]['country'] - d.pincode = details[0]['pincode'] - d.state = details[0]['state'] - d.fax = details[0]['fax'] - d.email_id = details[0]['email_id'] - d.phone = details[0]['phone'] - d.customer = self.doc.name - d.customer_name = self.doc.customer_name - d.is_primary_address = 1 - d.address_type = 'Office' - try: - d.save(1) - except NameError, e: - pass - + if not webnotes.conn.get_value("Address", {"lead": self.doc.lead_name, "customer": self.doc.customer}): + webnotes.conn.sql("""update `tabAddress` set customer=%s, customer_name=%s where lead=%s""", + (self.doc.name, self.doc.customer_name, self.doc.lead_name)) + + lead = webnotes.conn.get_value("Lead", self.doc.lead_name, ["lead_name", "email_id", "phone", "mobile_no"], as_dict=True) c = Document('Contact') - c.first_name = details[0]['lead_name'] - c.email_id = details[0]['email_id'] - c.phone = details[0]['phone'] - c.mobile_no = details[0]['mobile_no'] + c.first_name = lead.lead_name + c.email_id = lead.email_id + c.phone = lead.phone + c.mobile_no = lead.mobile_no c.customer = self.doc.name c.customer_name = self.doc.customer_name c.is_primary_contact = 1