diff --git a/accounts/doctype/sales_invoice/sales_invoice.py b/accounts/doctype/sales_invoice/sales_invoice.py index 5ebbd0e7b0..c1556363bc 100644 --- a/accounts/doctype/sales_invoice/sales_invoice.py +++ b/accounts/doctype/sales_invoice/sales_invoice.py @@ -251,7 +251,7 @@ class DocType(SellingController): # fetch charges if self.doc.charge and not len(self.doclist.get({"parentfield": "other_charges"})): - self.set_taxes() + self.set_taxes("other_charges", "charge") def get_customer_account(self): """Get Account Head to which amount needs to be Debited based on Customer""" diff --git a/accounts/doctype/sales_taxes_and_charges_master/sales_taxes_and_charges_master.py b/accounts/doctype/sales_taxes_and_charges_master/sales_taxes_and_charges_master.py index bc55701f45..27316cae3a 100644 --- a/accounts/doctype/sales_taxes_and_charges_master/sales_taxes_and_charges_master.py +++ b/accounts/doctype/sales_taxes_and_charges_master/sales_taxes_and_charges_master.py @@ -16,12 +16,10 @@ from __future__ import unicode_literals import webnotes +from webnotes.utils import cint +from webnotes.model.controller import DocListController -class DocType: - def __init__(self, doc, doclist=[]): - self.doc = doc - self.doclist = doclist - +class DocType(DocListController): def get_rate(self, arg): from webnotes.model.code import get_obj return get_obj('Sales Common').get_rate(arg) @@ -30,4 +28,12 @@ class DocType: if self.doc.is_default == 1: webnotes.conn.sql("""update `tabSales Taxes and Charges Master` set is_default = 0 where ifnull(is_default,0) = 1 and name != %s and company = %s""", - (self.doc.name, self.doc.company)) \ No newline at end of file + (self.doc.name, self.doc.company)) + + # at least one territory + self.validate_table_has_rows("valid_for_territories") + + def on_update(self): + cart_settings = webnotes.get_obj("Shopping Cart Settings") + if cint(cart_settings.doc.enabled): + cart_settings.validate_tax_masters() \ No newline at end of file diff --git a/accounts/doctype/sales_taxes_and_charges_master/test_sales_taxes_and_charges_master.py b/accounts/doctype/sales_taxes_and_charges_master/test_sales_taxes_and_charges_master.py new file mode 100644 index 0000000000..9bea271816 --- /dev/null +++ b/accounts/doctype/sales_taxes_and_charges_master/test_sales_taxes_and_charges_master.py @@ -0,0 +1,146 @@ +test_records = [ + [ + { + "doctype": "Sales Taxes and Charges Master", + "title": "_Test Sales Taxes and Charges Master", + "company": "_Test Company" + }, + { + "account_head": "_Test Account VAT - _TC", + "charge_type": "On Net Total", + "description": "VAT", + "doctype": "Sales Taxes and Charges", + "parentfield": "other_charges", + "rate": 6, + }, + { + "account_head": "_Test Account Service Tax - _TC", + "charge_type": "On Net Total", + "description": "Service Tax", + "doctype": "Sales Taxes and Charges", + "parentfield": "other_charges", + "rate": 6.36, + }, + { + "doctype": "For Territory", + "parentfield": "valid_for_territories", + "territory": "All Territories" + } + ], + [ + { + "doctype": "Sales Taxes and Charges Master", + "title": "_Test India Tax Master", + "company": "_Test Company" + }, + { + "doctype": "Sales Taxes and Charges", + "parentfield": "other_charges", + "charge_type": "Actual", + "account_head": "_Test Account Shipping Charges - _TC", + "cost_center": "_Test Cost Center - _TC", + "description": "Shipping Charges", + "rate": 100 + }, + { + "doctype": "Sales Taxes and Charges", + "parentfield": "other_charges", + "charge_type": "On Net Total", + "account_head": "_Test Account Customs Duty - _TC", + "cost_center": "_Test Cost Center - _TC", + "description": "Customs Duty", + "rate": 10 + }, + { + "doctype": "Sales Taxes and Charges", + "parentfield": "other_charges", + "charge_type": "On Net Total", + "account_head": "_Test Account Excise Duty - _TC", + "cost_center": "_Test Cost Center - _TC", + "description": "Excise Duty", + "rate": 12 + }, + { + "doctype": "Sales Taxes and Charges", + "parentfield": "other_charges", + "charge_type": "On Previous Row Amount", + "account_head": "_Test Account Education Cess - _TC", + "cost_center": "_Test Cost Center - _TC", + "description": "Education Cess", + "rate": 2, + "row_id": 3 + }, + { + "doctype": "Sales Taxes and Charges", + "parentfield": "other_charges", + "charge_type": "On Previous Row Amount", + "account_head": "_Test Account S&H Education Cess - _TC", + "cost_center": "_Test Cost Center - _TC", + "description": "S&H Education Cess", + "rate": 1, + "row_id": 3 + }, + { + "doctype": "Sales Taxes and Charges", + "parentfield": "other_charges", + "charge_type": "On Previous Row Total", + "account_head": "_Test Account CST - _TC", + "cost_center": "_Test Cost Center - _TC", + "description": "CST", + "rate": 2, + "row_id": 5 + }, + { + "doctype": "Sales Taxes and Charges", + "parentfield": "other_charges", + "charge_type": "On Net Total", + "account_head": "_Test Account VAT - _TC", + "cost_center": "_Test Cost Center - _TC", + "description": "VAT", + "rate": 12.5 + }, + { + "doctype": "Sales Taxes and Charges", + "parentfield": "other_charges", + "charge_type": "On Previous Row Total", + "account_head": "_Test Account Discount - _TC", + "cost_center": "_Test Cost Center - _TC", + "description": "Discount", + "rate": -10, + "row_id": 7 + }, + { + "doctype": "For Territory", + "parentfield": "valid_for_territories", + "territory": "_Test Territory India" + } + ], + [ + { + "doctype": "Sales Taxes and Charges Master", + "title": "_Test Sales Taxes and Charges Master 2", + "company": "_Test Company" + }, + { + "account_head": "_Test Account VAT - _TC", + "charge_type": "On Net Total", + "description": "VAT", + "doctype": "Sales Taxes and Charges", + "parentfield": "other_charges", + "rate": 12, + }, + { + "account_head": "_Test Account Service Tax - _TC", + "charge_type": "On Net Total", + "description": "Service Tax", + "doctype": "Sales Taxes and Charges", + "parentfield": "other_charges", + "rate": 4, + }, + { + "doctype": "For Territory", + "parentfield": "valid_for_territories", + "territory": "All Territories" + } + ], +] \ No newline at end of file diff --git a/accounts/doctype/shipping_rule/shipping_rule.py b/accounts/doctype/shipping_rule/shipping_rule.py index 87148656df..a363b18454 100644 --- a/accounts/doctype/shipping_rule/shipping_rule.py +++ b/accounts/doctype/shipping_rule/shipping_rule.py @@ -16,6 +16,7 @@ class DocType(DocListController): self.doc, self.doclist = d, dl def validate(self): + self.validate_value("calculate_based_on", "in", ["Net Total", "Net Weight"]) self.shipping_rule_conditions = self.doclist.get({"parentfield": "shipping_rule_conditions"}) self.validate_from_to_values() self.sort_shipping_rule_conditions() @@ -57,7 +58,7 @@ class DocType(DocListController): 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) + separate = (x1 <= x2 <= y1 <= y2) or (y1 <= y2 <= x1 <= x2) return (not separate) overlaps = [] diff --git a/accounts/doctype/shipping_rule/shipping_rule.txt b/accounts/doctype/shipping_rule/shipping_rule.txt index dd4fe8dfed..99886cac29 100644 --- a/accounts/doctype/shipping_rule/shipping_rule.txt +++ b/accounts/doctype/shipping_rule/shipping_rule.txt @@ -2,11 +2,12 @@ { "creation": "2013-06-25 11:48:03", "docstatus": 0, - "modified": "2013-06-25 12:15:21", + "modified": "2013-07-04 16:28:42", "modified_by": "Administrator", "owner": "Administrator" }, { + "autoname": "Prompt", "description": "Specify conditions to calculate shipping amount", "doctype": "DocType", "module": "Accounts", @@ -37,7 +38,7 @@ { "description": "example: Next Day Shipping", "doctype": "DocField", - "fieldname": "shipping_rule_label", + "fieldname": "label", "fieldtype": "Data", "in_list_view": 1, "label": "Shipping Rule Label", @@ -49,14 +50,16 @@ "fieldtype": "Column Break" }, { - "default": "Amount", + "default": "Net Total", "doctype": "DocField", "fieldname": "calculate_based_on", "fieldtype": "Select", + "hidden": 1, "in_list_view": 1, "label": "Calculate Based On", - "options": "Amount\nNet Weight", - "reqd": 1 + "options": "Net Total\nNet Weight", + "read_only": 1, + "reqd": 0 }, { "doctype": "DocField", diff --git a/accounts/doctype/shipping_rule/test_shipping_rule.py b/accounts/doctype/shipping_rule/test_shipping_rule.py index ff217bc5a7..23d204d040 100644 --- a/accounts/doctype/shipping_rule/test_shipping_rule.py +++ b/accounts/doctype/shipping_rule/test_shipping_rule.py @@ -32,7 +32,7 @@ test_records = [ [ { "doctype": "Shipping Rule", - "calculate_based_on": "Amount", + "calculate_based_on": "Net Total", "company": "_Test Company", "account": "_Test Account Shipping Charges - _TC", "cost_center": "_Test Cost Center - _TC" diff --git a/accounts/page/accounts_home/accounts_home.js b/accounts/page/accounts_home/accounts_home.js index 3ca51b8d37..1f41fcc69a 100644 --- a/accounts/page/accounts_home/accounts_home.js +++ b/accounts/page/accounts_home/accounts_home.js @@ -106,6 +106,11 @@ wn.module_page["Accounts"] = [ "doctype":"Shipping Rule", "description": wn._("Rules to calculate shipping amount for a sale") }, + { + "label": wn._("Currency Exchange"), + "doctype":"Currency Exchange", + "description": wn._("Manage exchange rates for currency conversion") + }, { "label": wn._("Point-of-Sale Setting"), "doctype":"POS Setting", diff --git a/buying/doctype/purchase_common/purchase_common.js b/buying/doctype/purchase_common/purchase_common.js index c8e4aa3585..0d4a615ec1 100644 --- a/buying/doctype/purchase_common/purchase_common.js +++ b/buying/doctype/purchase_common/purchase_common.js @@ -361,7 +361,7 @@ erpnext.buying.BuyingController = erpnext.TransactionController.extend({ var setup_field_label_map = function(fields_list, currency) { $.each(fields_list, function(i, fname) { - var docfield = wn.meta.get_docfield(me.frm.doc.doctype, fname); + var docfield = wn.meta.docfield_map[me.frm.doc.doctype][fname]; if(docfield) { var label = wn._(docfield.label || "").replace(/\([^\)]*\)/g, ""); field_label_map[fname] = label.trim() + " (" + currency + ")"; @@ -407,7 +407,7 @@ erpnext.buying.BuyingController = erpnext.TransactionController.extend({ var setup_field_label_map = function(fields_list, currency, parentfield) { var grid_doctype = me.frm.fields_dict[parentfield].grid.doctype; $.each(fields_list, function(i, fname) { - var docfield = wn.meta.get_docfield(grid_doctype, fname); + var docfield = wn.meta.docfield_map[grid_doctype][fname]; if(docfield) { var label = wn._(docfield.label || "").replace(/\([^\)]*\)/g, ""); field_label_map[grid_doctype + "-" + fname] = diff --git a/buying/doctype/purchase_order/test_purchase_order.py b/buying/doctype/purchase_order/test_purchase_order.py index be2e2946a9..34286dbd10 100644 --- a/buying/doctype/purchase_order/test_purchase_order.py +++ b/buying/doctype/purchase_order/test_purchase_order.py @@ -31,6 +31,7 @@ class TestPurchaseOrder(unittest.TestCase): from controllers.buying_controller import WrongWarehouseCompany po = webnotes.bean(copy=test_records[0]) po.doc.company = "_Test Company 1" + po.doc.conversion_rate = 0.0167 self.assertRaises(WrongWarehouseCompany, po.insert) diff --git a/controllers/accounts_controller.py b/controllers/accounts_controller.py index 69522c4852..5439030306 100644 --- a/controllers/accounts_controller.py +++ b/controllers/accounts_controller.py @@ -55,14 +55,18 @@ class AccountsController(TransactionBase): if self.doc.price_list_currency: if not self.doc.plc_conversion_rate: - exchange = self.doc.price_list_currency + "-" + get_company_currency(self.doc.company) - self.doc.plc_conversion_rate = flt(webnotes.conn.get_value("Currency Exchange", - exchange, "exchange_rate")) + company_currency = get_company_currency(self.doc.company) + if self.doc.price_list_currency == company_currency: + self.doc.plc_conversion_rate = 1.0 + else: + exchange = self.doc.price_list_currency + "-" + company_currency + self.doc.plc_conversion_rate = flt(webnotes.conn.get_value("Currency Exchange", + exchange, "exchange_rate")) if not self.doc.currency: self.doc.currency = self.doc.price_list_currency self.doc.conversion_rate = self.doc.plc_conversion_rate - + def set_missing_item_details(self, get_item_details): """set missing item values""" for item in self.doclist.get({"parentfield": self.fname}): @@ -71,34 +75,44 @@ class AccountsController(TransactionBase): ret = get_item_details(args) for fieldname, value in ret.items(): if self.meta.get_field(fieldname, parentfield=self.fname) and \ - item.fields.get(fieldname) is None: + item.fields.get(fieldname) is None and value is not None: item.fields[fieldname] = value - def set_taxes(self, tax_doctype, tax_parentfield, tax_master_field): + def set_taxes(self, tax_parentfield, tax_master_field): if not self.meta.get_field(tax_parentfield): return + tax_master_doctype = self.meta.get_field(tax_master_field).options + if not self.doclist.get({"parentfield": tax_parentfield}): if not self.doc.fields.get(tax_master_field): # get the default tax master self.doc.fields[tax_master_field] = \ - webnotes.conn.get_value(tax_doctype + " Master", {"is_default": 1}) - - if self.doc.fields.get(tax_master_field): - from webnotes.model import default_fields - tax_master = webnotes.bean(tax_doctype + " Master", self.doc.fields.get(tax_master_field)) - - for i, tax in enumerate(tax_master.doclist.get({"parentfield": tax_parentfield})): - for fieldname in default_fields: - tax.fields[fieldname] = None + webnotes.conn.get_value(tax_master_doctype, {"is_default": 1}) - tax.fields.update({ - "doctype": tax_doctype, - "parentfield": tax_parentfield, - "idx": i+1 - }) - - self.doclist.append(tax) + self.append_taxes_from_master(tax_parentfield, tax_master_field, tax_master_doctype) + + def append_taxes_from_master(self, tax_parentfield, tax_master_field, tax_master_doctype=None): + if self.doc.fields.get(tax_master_field): + if not tax_master_doctype: + tax_master_doctype = self.meta.get_field(tax_master_field).options + + tax_doctype = self.meta.get_field(tax_parentfield).options + + from webnotes.model import default_fields + tax_master = webnotes.bean(tax_master_doctype, self.doc.fields.get(tax_master_field)) + + for i, tax in enumerate(tax_master.doclist.get({"parentfield": tax_parentfield})): + for fieldname in default_fields: + tax.fields[fieldname] = None + + tax.fields.update({ + "doctype": tax_doctype, + "parentfield": tax_parentfield, + "idx": i+1 + }) + + self.doclist.append(tax) def calculate_taxes_and_totals(self): self.doc.conversion_rate = flt(self.doc.conversion_rate) diff --git a/controllers/buying_controller.py b/controllers/buying_controller.py index 885f9e54e6..e58eb03ca9 100644 --- a/controllers/buying_controller.py +++ b/controllers/buying_controller.py @@ -30,7 +30,7 @@ class BuyingController(StockController): def onload_post_render(self): # contact, address, item details self.set_missing_values() - self.set_taxes("Purchase Taxes and Charges", "purchase_tax_details", "purchase_other_charges") + self.set_taxes("purchase_tax_details", "purchase_other_charges") def validate(self): super(BuyingController, self).validate() @@ -55,7 +55,7 @@ class BuyingController(StockController): def get_purchase_tax_details(self): self.doclist = self.doc.clear_table(self.doclist, "purchase_tax_details") - self.set_taxes("Purchase Taxes and Charges", "purchase_tax_details", "purchase_other_charges") + self.set_taxes("purchase_tax_details", "purchase_other_charges") def validate_warehouse_belongs_to_company(self): for warehouse, company in webnotes.conn.get_values("Warehouse", diff --git a/controllers/selling_controller.py b/controllers/selling_controller.py index 140e0c0088..2f772957fa 100644 --- a/controllers/selling_controller.py +++ b/controllers/selling_controller.py @@ -28,48 +28,61 @@ class SellingController(StockController): # contact, address, item details and pos details (if applicable) self.set_missing_values() - self.set_taxes("Sales Taxes and Charges", "other_charges", "charge") + self.set_taxes("other_charges", "charge") def set_missing_values(self, for_validate=False): super(SellingController, self).set_missing_values(for_validate) - self.set_price_list_currency("Selling") - # set contact and address details for customer, if they are not mentioned self.set_missing_lead_customer_details() - - self.set_missing_item_details(get_item_details) + + self.set_price_list_and_item_details() def set_missing_lead_customer_details(self): if self.doc.customer: - if not (self.doc.contact_person and self.doc.customer_address): - for fieldname, val in self.get_default_address_and_contact("customer").items(): + if not (self.doc.contact_person and self.doc.customer_address and self.doc.customer_name): + for fieldname, val in self.get_customer_defaults().items(): if not self.doc.fields.get(fieldname) and self.meta.get_field(fieldname): self.doc.fields[fieldname] = val - customer_fetch = webnotes.conn.get_value("Customer", self.doc.customer, - ['customer_name', 'customer_group', 'territory'], as_dict=True) - for fieldname in ['customer_name', 'customer_group', 'territory']: - if not self.doc.fields.get(fieldname): - self.doc.fields[fieldname] = customer_fetch[fieldname] - elif self.doc.lead: - lead_fetch = webnotes.conn.get_value("Lead", self.doc.lead, - ['company_name', 'lead_name', 'territory'], as_dict=True) - if not self.doc.customer_name: - self.doc.customer_name = lead_fetch.company_name or lead_fetch.lead_name - if not self.doc.territory: - self.doc.territory = lead_fetch.territory - + if not (self.doc.customer_address and self.doc.customer_name and \ + self.doc.contact_display): + for fieldname, val in self.get_lead_defaults().items(): + if not self.doc.fields.get(fieldname) and self.meta.get_field(fieldname): + self.doc.fields[fieldname] = val + + def set_price_list_and_item_details(self): + self.set_price_list_currency("Selling") + self.set_missing_item_details(get_item_details) + def get_other_charges(self): self.doclist = self.doc.clear_table(self.doclist, "other_charges") - self.set_taxes("Sales Taxes and Charges", "other_charges", "charge") + self.set_taxes("other_charges", "charge") - def set_customer_defaults(self): - self.get_default_customer_address() - - if self.meta.get_field("shipping_address"): - self.doc.fields.update(self.get_shipping_address(self.doc.customer)) + def apply_shipping_rule(self): + if self.doc.shipping_rule: + shipping_rule = webnotes.bean("Shipping Rule", self.doc.shipping_rule) + value = self.doc.net_total + + # TODO + # shipping rule calculation based on item's net weight + + shipping_amount = 0.0 + for condition in shipping_rule.doclist.get({"parentfield": "shipping_rule_conditions"}): + if not condition.to_value or (flt(condition.from_value) <= value <= flt(condition.to_value)): + shipping_amount = condition.shipping_amount + break + + self.doclist.append({ + "doctype": "Sales Taxes and Charges", + "parentfield": "other_charges", + "charge_type": "Actual", + "account_head": shipping_rule.doc.account, + "cost_center": shipping_rule.doc.cost_center, + "description": shipping_rule.doc.label, + "rate": shipping_amount + }) def set_total_in_words(self): from webnotes.utils import money_in_words diff --git a/public/js/complete_setup.js b/public/js/complete_setup.js index 37c2fba52e..9d859797e2 100644 --- a/public/js/complete_setup.js +++ b/public/js/complete_setup.js @@ -84,6 +84,8 @@ $.extend(erpnext.complete_setup, { wn.container.wntoolbar.set_user_name(); setTimeout(function() { window.location.reload(); }, 3000); + } else { + $(this).done_working(); } }); diff --git a/public/js/website_utils.js b/public/js/website_utils.js index 59614c54a6..e714514e97 100644 --- a/public/js/website_utils.js +++ b/public/js/website_utils.js @@ -55,6 +55,15 @@ wn.call = function(opts) { $(opts.btn).addClass("btn-danger"); setTimeout(function() { $(opts.btn).removeClass("btn-danger"); }, 1000); } + try { + var err = JSON.parse(data.exc); + if($.isArray(err)) { + err = err.join("\n"); + } + console.error ? console.error(err) : console.log(err); + } catch(e) { + console.log(data.exc); + } } else{ if(opts.btn) { $(opts.btn).addClass("btn-success"); @@ -66,6 +75,9 @@ wn.call = function(opts) { } if(opts.callback) opts.callback(data); + }, + error: function(response) { + console.error ? console.error(response) : console.log(response); } }); diff --git a/selling/doctype/installation_note/installation_note.js b/selling/doctype/installation_note/installation_note.js index b0e005eb0b..61d0484a08 100644 --- a/selling/doctype/installation_note/installation_note.js +++ b/selling/doctype/installation_note/installation_note.js @@ -8,69 +8,82 @@ // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License -// along with this program. If not, see . +// along with this program. If not, see . cur_frm.cscript.tname = "Installation Note Item"; cur_frm.cscript.fname = "installed_item_details"; +wn.provide("erpnext.selling"); +// TODO commonify this code +erpnext.selling.InstallationNote = wn.ui.form.Controller.extend({ + customer: function() { + var me = this; + if(this.frm.doc.customer) { + this.frm.call({ + doc: this.frm.doc, + method: "set_customer_defaults", + callback: function(r) { + if(!r.exc) me.frm.refresh_fields(); + } + }); + + // TODO shift this to depends_on + unhide_field(['customer_address', 'contact_person', 'customer_name', + 'address_display', 'contact_display', 'contact_mobile', 'contact_email', + 'territory', 'customer_group']); + } + } +}); + +$.extend(cur_frm.cscript, new erpnext.selling.InstallationNote({frm: cur_frm})); + cur_frm.cscript.onload = function(doc, dt, dn) { - if(!doc.status) set_multiple(dt,dn,{status:'Draft'}); - if(doc.__islocal){ - set_multiple(dt,dn,{inst_date:get_today()}); - hide_field(['customer_address','contact_person','customer_name','address_display','contact_display','contact_mobile','contact_email','territory','customer_group']); - } - if (doc.customer) { - unhide_field(['customer_address','contact_person','customer_name','address_display','contact_display','contact_mobile','contact_email','territory','customer_group']); - } + if(!doc.status) set_multiple(dt,dn,{status:'Draft'}); + if(doc.__islocal){ + set_multiple(dt,dn,{inst_date:get_today()}); + hide_field(['customer_address','contact_person','customer_name','address_display','contact_display','contact_mobile','contact_email','territory','customer_group']); + } + if (doc.customer) { + unhide_field(['customer_address','contact_person','customer_name','address_display','contact_display','contact_mobile','contact_email','territory','customer_group']); + } } cur_frm.fields_dict['delivery_note_no'].get_query = function(doc) { - doc = locals[this.doctype][this.docname]; - var cond = ''; - if(doc.customer) { - cond = '`tabDelivery Note`.customer = "'+doc.customer+'" AND'; - } - return repl('SELECT DISTINCT `tabDelivery Note`.name, `tabDelivery Note`.customer_name FROM `tabDelivery Note`, `tabDelivery Note Item` WHERE `tabDelivery Note`.company = "%(company)s" AND `tabDelivery Note`.docstatus = 1 AND ifnull(`tabDelivery Note`.per_installed,0) < 99.99 AND %(cond)s `tabDelivery Note`.name LIKE "%s" ORDER BY `tabDelivery Note`.name DESC LIMIT 50', {company:doc.company, cond:cond}); + doc = locals[this.doctype][this.docname]; + var cond = ''; + if(doc.customer) { + cond = '`tabDelivery Note`.customer = "'+doc.customer+'" AND'; + } + return repl('SELECT DISTINCT `tabDelivery Note`.name, `tabDelivery Note`.customer_name FROM `tabDelivery Note`, `tabDelivery Note Item` WHERE `tabDelivery Note`.company = "%(company)s" AND `tabDelivery Note`.docstatus = 1 AND ifnull(`tabDelivery Note`.per_installed,0) < 99.99 AND %(cond)s `tabDelivery Note`.name LIKE "%s" ORDER BY `tabDelivery Note`.name DESC LIMIT 50', {company:doc.company, cond:cond}); } 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'; + 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.cscript.get_items = function(doc, dt, dn) { - var callback = function(r,rt) { - unhide_field(['customer_address','contact_person','customer_name','address_display','contact_display','contact_mobile','contact_email','territory','customer_group']); - cur_frm.refresh(); - } - get_server_fields('pull_delivery_note_details','','',doc, dt, dn,1,callback); + var callback = function(r,rt) { + unhide_field(['customer_address','contact_person','customer_name','address_display','contact_display','contact_mobile','contact_email','territory','customer_group']); + cur_frm.refresh(); + } + get_server_fields('pull_delivery_note_details','','',doc, dt, dn,1,callback); } -//customer -cur_frm.cscript.customer = function(doc,dt,dn) { - var callback = function(r,rt) { - var doc = locals[cur_frm.doctype][cur_frm.docname]; - cur_frm.refresh(); - } - - if(doc.customer) $c_obj(make_doclist(doc.doctype, doc.name), 'get_default_customer_address', '', callback); - if(doc.customer) unhide_field(['customer_address','contact_person','customer_name','address_display','contact_display','contact_mobile','contact_email','territory','customer_group']); -} - -cur_frm.cscript.customer_address = cur_frm.cscript.contact_person = function(doc,dt,dn) { - 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.cscript.customer_address = cur_frm.cscript.contact_person = function(doc,dt,dn) { + 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['customer_address'].get_query = function(doc, cdt, cdn) { - return 'SELECT name,address_line1,city FROM tabAddress WHERE customer = "'+ doc.customer +'" AND docstatus != 2 AND name LIKE "%s" ORDER BY name ASC LIMIT 50'; + return 'SELECT name,address_line1,city FROM tabAddress WHERE customer = "'+ doc.customer +'" AND docstatus != 2 AND name LIKE "%s" ORDER BY name ASC LIMIT 50'; } cur_frm.fields_dict['contact_person'].get_query = function(doc, cdt, cdn) { - return 'SELECT name,CONCAT(first_name," ",ifnull(last_name,"")) As FullName,department,designation FROM tabContact WHERE customer = "'+ doc.customer +'" AND docstatus != 2 AND name LIKE "%s" ORDER BY name ASC LIMIT 50'; + return 'SELECT name,CONCAT(first_name," ",ifnull(last_name,"")) As FullName,department,designation FROM tabContact WHERE customer = "'+ doc.customer +'" AND docstatus != 2 AND name LIKE "%s" ORDER BY name ASC LIMIT 50'; } cur_frm.fields_dict.customer.get_query = erpnext.utils.customer_query; \ No newline at end of file diff --git a/selling/doctype/lead/lead.txt b/selling/doctype/lead/lead.txt index 15f4dc0956..343eb1ee78 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-07-03 10:22:31", + "modified": "2013-07-03 10:24:00", "modified_by": "Administrator", "owner": "Administrator" }, @@ -314,13 +314,6 @@ "oldfieldtype": "Select", "options": "\nProduct Enquiry\nRequest for Information\nSuggestions\nOther" }, - { - "doctype": "DocField", - "fieldname": "default_price_list", - "fieldtype": "Link", - "label": "Default Price List", - "options": "Price List" - }, { "doctype": "DocField", "fieldname": "fiscal_year", diff --git a/selling/doctype/opportunity/opportunity.js b/selling/doctype/opportunity/opportunity.js index 583f25adf1..c3843cf466 100644 --- a/selling/doctype/opportunity/opportunity.js +++ b/selling/doctype/opportunity/opportunity.js @@ -16,6 +16,30 @@ wn.require('app/utilities/doctype/sms_control/sms_control.js'); +wn.provide("erpnext.selling"); +// TODO commonify this code +erpnext.selling.Opportunity = wn.ui.form.Controller.extend({ + customer: function() { + var me = this; + if(this.frm.doc.customer) { + this.frm.call({ + doc: this.frm.doc, + method: "set_customer_defaults", + callback: function(r) { + if(!r.exc) me.frm.refresh_fields(); + } + }); + + // TODO shift this to depends_on + unhide_field(['customer_address', 'contact_person', 'customer_name', + 'address_display', 'contact_display', 'contact_mobile', 'contact_email', + 'territory', 'customer_group']); + } + } +}); + +$.extend(cur_frm.cscript, new erpnext.selling.Opportunity({frm: cur_frm})); + cur_frm.cscript.refresh = function(doc, cdt, cdn){ erpnext.hide_naming_series(); @@ -117,28 +141,6 @@ cur_frm.cscript.lead_cust_show = function(doc,cdt,cdn){ } } -// customer -cur_frm.cscript.customer = function(doc,dt,dn) { - cur_frm.toggle_display("contact_info", doc.customer || doc.lead); - - if(doc.customer) { - cur_frm.call({ - doc: cur_frm.doc, - method: "get_default_customer_address", - args: { customer: doc.customer }, - callback: function(r) { - if(!r.exc) { - cur_frm.refresh(); - } - } - }); - - unhide_field(["customer_name", "customer_address", "contact_person", - "address_display", "contact_display", "contact_mobile", "contact_email", - "territory", "customer_group"]); - } -} - cur_frm.cscript.customer_address = cur_frm.cscript.contact_person = function(doc,dt,dn) { 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); } @@ -167,7 +169,15 @@ cur_frm.cscript.lead = function(doc, cdt, cdn) { cur_frm.toggle_display("contact_info", doc.customer || doc.lead); if(doc.lead) { - get_server_fields('get_lead_details', doc.lead,'', doc, cdt, cdn, 1); + cur_frm.call({ + doc: cur_frm.doc, + method: "set_lead_defaults", + callback: function(r) { + if(!r.exc) { + cur_frm.refresh_fields(); + } + } + }); unhide_field(['customer_name', 'address_display','contact_mobile', 'contact_email', 'territory']); } diff --git a/selling/doctype/opportunity/opportunity.py b/selling/doctype/opportunity/opportunity.py index 9fb061b228..c56915754f 100644 --- a/selling/doctype/opportunity/opportunity.py +++ b/selling/doctype/opportunity/opportunity.py @@ -24,9 +24,8 @@ from webnotes import msgprint sql = webnotes.conn.sql from utilities.transaction_base import TransactionBase - class DocType(TransactionBase): - def __init__(self,doc,doclist=[]): + def __init__(self,doc,doclist): self.doc = doc self.doclist = doclist self.fname = 'enq_details' diff --git a/selling/doctype/quotation/quotation.js b/selling/doctype/quotation/quotation.js index 259c8109f2..97fd714205 100644 --- a/selling/doctype/quotation/quotation.js +++ b/selling/doctype/quotation/quotation.js @@ -99,7 +99,15 @@ cur_frm.fields_dict.lead.get_query = erpnext.utils.lead_query; cur_frm.cscript.lead = function(doc, cdt, cdn) { if(doc.lead) { - get_server_fields('get_lead_details', doc.lead,'', doc, cdt, cdn, 1); + cur_frm.call({ + doc: cur_frm.doc, + method: "set_lead_defaults", + callback: function(r) { + if(!r.exc) { + cur_frm.refresh_fields(); + } + } + }); unhide_field('territory'); } } diff --git a/selling/doctype/sales_common/sales_common.js b/selling/doctype/sales_common/sales_common.js index 5534369b54..27c82f6cf4 100644 --- a/selling/doctype/sales_common/sales_common.js +++ b/selling/doctype/sales_common/sales_common.js @@ -26,10 +26,78 @@ wn.require("app/js/transaction.js"); erpnext.selling.SellingController = erpnext.TransactionController.extend({ setup: function() { + var me = this; + 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; + 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; + } + + this.frm.set_query("customer_address", function() { + return 'SELECT name, address_line1, city FROM tabAddress \ + WHERE customer = "'+ me.frm.doc.customer +'" AND docstatus != 2 AND \ + %(key)s LIKE "%s" ORDER BY name ASC LIMIT 50'; + }); + + this.frm.set_query("contact_person", function() { + return 'SELECT name, CONCAT(first_name," ",ifnull(last_name,"")) As FullName, \ + department, designation FROM tabContact WHERE customer = "'+ me.frm.doc.customer + + '" AND docstatus != 2 AND %(key)s LIKE "%s" ORDER BY name ASC LIMIT 50'; + }); + + if(this.frm.fields_dict.charge) { + this.frm.set_query("charge", function() { + return 'SELECT DISTINCT `tabSales Taxes and Charges Master`.name FROM \ + `tabSales Taxes and Charges Master` \ + WHERE `tabSales Taxes and Charges Master`.company = "' + me.frm.doc.company + + '" AND `tabSales Taxes and Charges Master`.company is not NULL \ + AND `tabSales Taxes and Charges Master`.docstatus != 2 \ + AND `tabSales Taxes and Charges Master`.%(key)s LIKE "%s" \ + ORDER BY `tabSales Taxes and Charges Master`.name LIMIT 50'; + }); + } + + this.frm.fields_dict.customer.get_query = erpnext.utils.customer_query; + + this.frm.fields_dict.lead && this.frm.set_query("lead", erpnext.utils.lead_query); + + if(!this.fname) { + return; + } + + if(this.frm.fields_dict[this.fname].grid.get_field('item_code')) { + this.frm.set_query("item_code", this.fname, function() { + return me.frm.doc.order_type === "Maintenance" ? + erpnext.queries.item({'ifnull(tabItem.is_service_item, "No")': "Yes"}) : + erpnext.queries.item({'ifnull(tabItem.is_sales_item, "No")': "Yes"}); + }); + } + + if(this.frm.fields_dict[this.fname].grid.get_field('batch_no')) { + this.frm.set_query("batch_no", this.fname, function(doc, cdt, cdn) { + var item = wn.model.get_doc(cdt, cdn); + if(!item.item_code) { + wn.throw("Please enter Item Code to get batch no"); + } else { + if(item.warehouse) { + return "select batch_no from `tabStock Ledger Entry` sle \ + where item_code = '" + item.item_code + + "' and warehouse = '" + item.warehouse + + "' and ifnull(is_cancelled, 'No') = 'No' and batch_no like '%s' \ + and exists(select * from `tabBatch` where \ + name = sle.batch_no and expiry_date >= '" + me.frm.doc.posting_date + + "' and docstatus != 2) group by batch_no having sum(actual_qty) > 0 \ + order by batch_no desc limit 50"; + } else { + return "SELECT name FROM tabBatch WHERE docstatus != 2 AND item = '" + + item.item_code + "' and expiry_date >= '" + me.frm.doc.posting_date + + "' AND name like '%s' ORDER BY name DESC LIMIT 50"; + } + } + }); + } }, onload: function() { @@ -403,7 +471,7 @@ erpnext.selling.SellingController = erpnext.TransactionController.extend({ var setup_field_label_map = function(fields_list, currency) { $.each(fields_list, function(i, fname) { - var docfield = wn.meta.get_docfield(me.frm.doc.doctype, fname); + var docfield = wn.meta.docfield_map[me.frm.doc.doctype][fname]; if(docfield) { var label = wn._(docfield.label || "").replace(/\([^\)]*\)/g, ""); field_label_map[fname] = label.trim() + " (" + currency + ")"; @@ -448,7 +516,7 @@ erpnext.selling.SellingController = erpnext.TransactionController.extend({ var setup_field_label_map = function(fields_list, currency, parentfield) { var grid_doctype = me.frm.fields_dict[parentfield].grid.doctype; $.each(fields_list, function(i, fname) { - var docfield = wn.meta.get_docfield(grid_doctype, fname); + var docfield = wn.meta.docfield_map[grid_doctype][fname]; if(docfield) { var label = wn._(docfield.label || "").replace(/\([^\)]*\)/g, ""); field_label_map[grid_doctype + "-" + fname] = @@ -508,15 +576,6 @@ erpnext.selling.SellingController = erpnext.TransactionController.extend({ } }); -// to save previous state of cur_frm.cscript -var prev_cscript = {}; -$.extend(prev_cscript, cur_frm.cscript); - -cur_frm.cscript = new erpnext.selling.SellingController({frm: cur_frm}); - -// for backward compatibility: combine new and previous states -$.extend(cur_frm.cscript, prev_cscript); - // Help for Sales BOM items var set_sales_bom_help = function(doc) { if(!cur_frm.fields_dict.packing_list) return; @@ -540,61 +599,3 @@ var set_sales_bom_help = function(doc) { } refresh_field('sales_bom_help'); } - -cur_frm.fields_dict[cur_frm.cscript.fname].grid.get_field("item_code").get_query = function(doc, cdt, cdn) { - if (doc.order_type == "Maintenance") { - return erpnext.queries.item({ - 'ifnull(tabItem.is_service_item, "No")': 'Yes' - }); - } else { - return erpnext.queries.item({ - 'ifnull(tabItem.is_sales_item, "No")': 'Yes' - }); - } -} - -cur_frm.fields_dict[cur_frm.cscript.fname].grid.get_field('batch_no').get_query = - function(doc, cdt, cdn) { - var d = locals[cdt][cdn]; - if(d.item_code) { - if (d.warehouse) { - return "select batch_no from `tabStock Ledger Entry` sle \ - where item_code = '" + d.item_code + "' and warehouse = '" + d.warehouse + - "' and ifnull(is_cancelled, 'No') = 'No' and batch_no like '%s' \ - and exists(select * from `tabBatch` where \ - name = sle.batch_no and expiry_date >= '" + doc.posting_date + - "' and docstatus != 2) group by batch_no having sum(actual_qty) > 0 \ - order by batch_no desc limit 50"; - } else { - return "SELECT name FROM tabBatch WHERE docstatus != 2 AND item = '" + - d.item_code + "' and expiry_date >= '" + doc.posting_date + - "' AND name like '%s' ORDER BY name DESC LIMIT 50"; - } - } else { - msgprint("Please enter Item Code to get batch no"); - } - } - -cur_frm.fields_dict['customer_address'].get_query = function(doc, cdt, cdn) { - return 'SELECT name, address_line1, city FROM tabAddress \ - WHERE customer = "'+ doc.customer +'" AND docstatus != 2 AND \ - %(key)s LIKE "%s" ORDER BY name ASC LIMIT 50'; -} - -cur_frm.fields_dict['contact_person'].get_query = function(doc, cdt, cdn) { - return 'SELECT name, CONCAT(first_name," ",ifnull(last_name,"")) As FullName, \ - department, designation FROM tabContact WHERE customer = "'+ doc.customer + - '" AND docstatus != 2 AND %(key)s LIKE "%s" ORDER BY name ASC LIMIT 50'; -} - -// ************* GET OTHER CHARGES BASED ON COMPANY ************* -cur_frm.fields_dict.charge.get_query = function(doc) { - return 'SELECT DISTINCT `tabSales Taxes and Charges Master`.name FROM \ - `tabSales Taxes and Charges Master` WHERE `tabSales Taxes and Charges Master`.company = "' - +doc.company+'" AND `tabSales Taxes and Charges Master`.company is not NULL \ - AND `tabSales Taxes and Charges Master`.docstatus != 2 \ - AND `tabSales Taxes and Charges Master`.%(key)s LIKE "%s" \ - ORDER BY `tabSales Taxes and Charges Master`.name LIMIT 50'; -} - -cur_frm.fields_dict.customer.get_query = erpnext.utils.customer_query; \ No newline at end of file diff --git a/setup/doctype/currency_exchange/currency_exchange.js b/setup/doctype/currency_exchange/currency_exchange.js new file mode 100644 index 0000000000..02cba115f0 --- /dev/null +++ b/setup/doctype/currency_exchange/currency_exchange.js @@ -0,0 +1,39 @@ +// ERPNext - web based ERP (http://erpnext.com) +// Copyright (C) 2012 Web Notes Technologies Pvt Ltd +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +$.extend(cur_frm.cscript, { + refresh: function() { + cur_frm.cscript.set_exchange_rate_label(); + }, + + from_currency: function() { + cur_frm.cscript.set_exchange_rate_label(); + }, + + to_currency: function() { + cur_frm.cscript.set_exchange_rate_label(); + }, + + set_exchange_rate_label: function() { + if(cur_frm.doc.from_currency && cur_frm.doc.to_currency) { + var default_label = wn._(wn.meta.docfield_map[cur_frm.doctype]["exchange_rate"].label); + console.log(default_label + + repl(" (1 %(from_currency)s = [?] %(to_currency)s)", cur_frm.doc)); + cur_frm.fields_dict.exchange_rate.set_label(default_label + + repl(" (1 %(from_currency)s = [?] %(to_currency)s)", cur_frm.doc)); + } + } +}); \ No newline at end of file diff --git a/setup/doctype/currency_exchange/currency_exchange.txt b/setup/doctype/currency_exchange/currency_exchange.txt index 1a05ebe908..94bb28ab47 100644 --- a/setup/doctype/currency_exchange/currency_exchange.txt +++ b/setup/doctype/currency_exchange/currency_exchange.txt @@ -2,7 +2,7 @@ { "creation": "2013-06-20 15:40:29", "docstatus": 0, - "modified": "2013-06-20 15:40:29", + "modified": "2013-07-03 17:11:40", "modified_by": "Administrator", "owner": "Administrator" }, @@ -23,6 +23,16 @@ "permlevel": 0, "reqd": 1 }, + { + "doctype": "DocPerm", + "name": "__common__", + "parent": "Currency Exchange", + "parentfield": "permissions", + "parenttype": "DocType", + "permlevel": 0, + "read": 1, + "report": 1 + }, { "doctype": "DocType", "name": "Currency Exchange" @@ -46,5 +56,24 @@ "fieldname": "exchange_rate", "fieldtype": "Float", "label": "Exchange Rate" + }, + { + "cancel": 1, + "create": 1, + "doctype": "DocPerm", + "role": "Accounts Manager", + "write": 1 + }, + { + "doctype": "DocPerm", + "role": "Accounts User" + }, + { + "doctype": "DocPerm", + "role": "Sales User" + }, + { + "doctype": "DocPerm", + "role": "Purchase User" } ] \ No newline at end of file diff --git a/setup/doctype/currency_exchange/test_currency_exchange.py b/setup/doctype/currency_exchange/test_currency_exchange.py new file mode 100644 index 0000000000..cd0ac21aa7 --- /dev/null +++ b/setup/doctype/currency_exchange/test_currency_exchange.py @@ -0,0 +1,14 @@ +test_records = [ + [{ + "doctype": "Currency Exchange", + "from_currency": "USD", + "to_currency": "INR", + "exchange_rate": 1 + }], + [{ + "doctype": "Currency Exchange", + "from_currency": "USD", + "to_currency": "EUR", + "exchange_rate": 0.773 + }] +] \ No newline at end of file diff --git a/setup/doctype/price_list/price_list.py b/setup/doctype/price_list/price_list.py index b40a46f040..a5f22d9da0 100644 --- a/setup/doctype/price_list/price_list.py +++ b/setup/doctype/price_list/price_list.py @@ -17,7 +17,7 @@ from __future__ import unicode_literals import webnotes from webnotes import msgprint, _ -from webnotes.utils import comma_or +from webnotes.utils import comma_or, cint from webnotes.model.controller import DocListController class DocType(DocListController): @@ -30,6 +30,14 @@ class DocType(DocListController): msgprint(_(self.meta.get_label("buying_or_selling")) + " " + _("must be one of") + " " + comma_or(["Buying", "Selling"]), raise_exception=True) + # at least one territory + self.validate_table_has_rows("valid_for_territories") + + def on_update(self): + cart_settings = webnotes.get_obj("Shopping Cart Settings") + if cint(cart_settings.doc.enabled): + cart_settings.validate_price_lists() + def on_trash(self): webnotes.conn.sql("""delete from `tabItem Price` where price_list_name = %s""", self.doc.name) \ No newline at end of file diff --git a/setup/doctype/price_list/test_price_list.py b/setup/doctype/price_list/test_price_list.py index 68d9a35521..c3633e78ec 100644 --- a/setup/doctype/price_list/test_price_list.py +++ b/setup/doctype/price_list/test_price_list.py @@ -1,8 +1,59 @@ test_records = [ - [{ - "doctype": "Price List", - "price_list_name": "_Test Price List", - "currency": "INR", - "buying_or_selling": "Selling" - }] + [ + { + "doctype": "Price List", + "price_list_name": "_Test Price List", + "currency": "INR", + "buying_or_selling": "Selling" + }, + { + "doctype": "For Territory", + "parentfield": "valid_for_territories", + "territory": "All Territories" + } + ], + [ + { + "doctype": "Price List", + "price_list_name": "_Test Price List 2", + "currency": "INR", + "buying_or_selling": "Selling" + }, + { + "doctype": "For Territory", + "parentfield": "valid_for_territories", + "territory": "_Test Territory Rest of the World" + } + ], + [ + { + "doctype": "Price List", + "price_list_name": "_Test Price List India", + "currency": "INR", + "buying_or_selling": "Selling" + }, + { + "doctype": "For Territory", + "parentfield": "valid_for_territories", + "territory": "_Test Territory India" + } + ], + [ + { + "doctype": "Price List", + "price_list_name": "_Test Price List Rest of the World", + "currency": "USD", + "buying_or_selling": "Selling" + }, + { + "doctype": "For Territory", + "parentfield": "valid_for_territories", + "territory": "_Test Territory Rest of the World" + }, + { + "doctype": "For Territory", + "parentfield": "valid_for_territories", + "territory": "_Test Territory United States" + } + ], ] \ No newline at end of file diff --git a/setup/doctype/setup_control/setup_control.py b/setup/doctype/setup_control/setup_control.py index a4af7f891a..c6b0b6ce28 100644 --- a/setup/doctype/setup_control/setup_control.py +++ b/setup/doctype/setup_control/setup_control.py @@ -87,18 +87,20 @@ class DocType: 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(), + for pl_type in ["Selling", "Buying"]: + webnotes.bean([ + { + "doctype": "Price List", + "price_list_name": "Standard " + pl_type, + "buying_or_selling": pl_type, + "currency": args["currency"] + }, + { + "doctype": "For Territory", + "parentfield": "valid_for_territories", + "territory": "All Territories" + } + ]).insert() def set_defaults(self, args): # enable default currency @@ -207,15 +209,18 @@ class DocType: # ------------------------ def get_fy_details(self, fy_start, last_year=False): st = {'1st Jan':'01-01','1st Apr':'04-01','1st Jul':'07-01', '1st Oct': '10-01'} - curr_year = getdate(nowdate()).year - if last_year: - curr_year = curr_year - 1 if cint(getdate(nowdate()).month) < cint((st[fy_start].split('-'))[0]): curr_year = getdate(nowdate()).year - 1 + else: + curr_year = getdate(nowdate()).year + + if last_year: + curr_year = curr_year - 1 + stdt = cstr(curr_year)+'-'+cstr(st[fy_start]) if(fy_start == '1st Jan'): - fy = cstr(getdate(nowdate()).year) + fy = cstr(curr_year) abbr = cstr(fy)[-2:] else: fy = cstr(curr_year) + '-' + cstr(curr_year+1) diff --git a/setup/doctype/territory/test_territory.py b/setup/doctype/territory/test_territory.py index 2124199c12..75003f2b56 100644 --- a/setup/doctype/territory/test_territory.py +++ b/setup/doctype/territory/test_territory.py @@ -4,5 +4,29 @@ test_records = [ "territory_name": "_Test Territory", "parent_territory": "All Territories", "is_group": "No", - }] + }], + [{ + "doctype": "Territory", + "territory_name": "_Test Territory India", + "parent_territory": "All Territories", + "is_group": "Yes", + }], + [{ + "doctype": "Territory", + "territory_name": "_Test Territory Maharashtra", + "parent_territory": "_Test Territory India", + "is_group": "No", + }], + [{ + "doctype": "Territory", + "territory_name": "_Test Territory Rest of the World", + "parent_territory": "All Territories", + "is_group": "No", + }], + [{ + "doctype": "Territory", + "territory_name": "_Test Territory United States", + "parent_territory": "All Territories", + "is_group": "No", + }], ] \ No newline at end of file diff --git a/setup/page/setup/setup.py b/setup/page/setup/setup.py index 348fce968c..baee676ca6 100644 --- a/setup/page/setup/setup.py +++ b/setup/page/setup/setup.py @@ -91,6 +91,8 @@ items = [ }, { "doctype": "Sales Taxes and Charges Master" }, { "doctype": "Purchase Taxes and Charges Master" }, + { "doctype": "Shipping Rule" }, + { "doctype": "Currency Exchange" }, { "type": "Section", "title": "Opening Accounts and Stock", diff --git a/setup/utils.py b/setup/utils.py index c343ed1db1..e1463fa0d6 100644 --- a/setup/utils.py +++ b/setup/utils.py @@ -40,8 +40,8 @@ def get_ancestors_of(doctype, name): """Get ancestor elements of a DocType with a tree structure""" lft, rgt = webnotes.conn.get_value(doctype, name, ["lft", "rgt"]) result = webnotes.conn.sql_list("""select name from `tab%s` - where lft<%s and rgt>%s and docstatus < 2""" % (doctype, "%s", "%s"), (lft, rgt)) - return result or None + where lft<%s and rgt>%s order by lft desc""" % (doctype, "%s", "%s"), (lft, rgt)) + return result or [] @webnotes.whitelist() def get_price_list_currency(args): diff --git a/stock/doctype/delivery_note/delivery_note.py b/stock/doctype/delivery_note/delivery_note.py index 8b2b55933c..5a3f0ca833 100644 --- a/stock/doctype/delivery_note/delivery_note.py +++ b/stock/doctype/delivery_note/delivery_note.py @@ -48,9 +48,6 @@ class DocType(SellingController): 'keyword': 'Delivered' }] - def set_customer_defaults(self): - self.get_default_customer_shipping_address() - def validate_fiscal_year(self): get_obj('Sales Common').validate_fiscal_year(self.doc.fiscal_year,self.doc.posting_date,'Posting Date') diff --git a/support/doctype/customer_issue/customer_issue.js b/support/doctype/customer_issue/customer_issue.js index 0f3007ea10..3a602b8bc9 100644 --- a/support/doctype/customer_issue/customer_issue.js +++ b/support/doctype/customer_issue/customer_issue.js @@ -14,6 +14,28 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +wn.provide("erpnext.support"); +// TODO commonify this code +erpnext.support.CustomerIssue = wn.ui.form.Controller.extend({ + customer: function() { + var me = this; + if(this.frm.doc.customer) { + this.frm.call({ + doc: this.frm.doc, + method: "set_customer_defaults", + callback: function(r) { + if(!r.exc) me.frm.refresh_fields(); + } + }); + + // TODO shift this to depends_on + unhide_field(['customer_address', 'contact_person']); + } + } +}); + +$.extend(cur_frm.cscript, new erpnext.support.CustomerIssue({frm: cur_frm})); + cur_frm.cscript.onload = function(doc,cdt,cdn){ if(!doc.status) set_multiple(dt,dn,{status:'Open'}); @@ -28,18 +50,6 @@ cur_frm.cscript.refresh = function(doc,ct,cdn){ cur_frm.cscript['Make Maintenance Visit']); } - -//customer -cur_frm.cscript.customer = function(doc,dt,dn) { - var callback = function(r,rt) { - var doc = locals[cur_frm.doctype][cur_frm.docname]; - cur_frm.refresh(); - } - - if(doc.customer) $c_obj(make_doclist(doc.doctype, doc.name), 'get_default_customer_address', '', callback); - if(doc.customer) unhide_field(['customer_address','contact_person']); -} - cur_frm.cscript.customer_address = cur_frm.cscript.contact_person = function(doc,dt,dn) { if(doc.customer) get_server_fields('get_customer_address', diff --git a/support/doctype/maintenance_schedule/maintenance_schedule.js b/support/doctype/maintenance_schedule/maintenance_schedule.js index 30fbecf832..6b3ce10f75 100644 --- a/support/doctype/maintenance_schedule/maintenance_schedule.js +++ b/support/doctype/maintenance_schedule/maintenance_schedule.js @@ -14,6 +14,31 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +wn.provide("erpnext.support"); +// TODO commonify this code +erpnext.support.MaintenanceSchedule = wn.ui.form.Controller.extend({ + customer: function() { + var me = this; + if(this.frm.doc.customer) { + this.frm.call({ + doc: this.frm.doc, + method: "set_customer_defaults", + callback: function(r) { + if(!r.exc) me.frm.refresh_fields(); + } + }); + + // TODO shift this to depends_on + unhide_field(['customer_address', 'contact_person', 'customer_name', + 'address_display', 'contact_display', 'contact_mobile', 'contact_email', + 'territory', 'customer_group']); + + } + } +}); + +$.extend(cur_frm.cscript, new erpnext.support.MaintenanceSchedule({frm: cur_frm})); + cur_frm.cscript.onload = function(doc, dt, dn) { if(!doc.status) set_multiple(dt,dn,{status:'Draft'}); @@ -23,18 +48,6 @@ cur_frm.cscript.onload = function(doc, dt, dn) { } } - -//customer -cur_frm.cscript.customer = function(doc,dt,dn) { - var callback = function(r,rt) { - var doc = locals[cur_frm.doctype][cur_frm.docname]; - cur_frm.refresh(); - } - - if(doc.customer) $c_obj(make_doclist(doc.doctype, doc.name), 'get_default_customer_address', '', callback); - if(doc.customer) unhide_field(['customer_address','contact_person','customer_name','address_display','contact_display','contact_mobile','contact_email','territory','customer_group']); -} - cur_frm.cscript.customer_address = cur_frm.cscript.contact_person = function(doc,dt,dn) { 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); } diff --git a/support/doctype/maintenance_visit/maintenance_visit.js b/support/doctype/maintenance_visit/maintenance_visit.js index 3514a3a710..3b8de757c9 100644 --- a/support/doctype/maintenance_visit/maintenance_visit.js +++ b/support/doctype/maintenance_visit/maintenance_visit.js @@ -8,16 +8,38 @@ // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License -// along with this program. If not, see . +// along with this program. If not, see . + +wn.provide("erpnext.support"); +// TODO commonify this code +erpnext.support.MaintenanceVisit = wn.ui.form.Controller.extend({ + customer: function() { + var me = this; + if(this.frm.doc.customer) { + this.frm.call({ + doc: this.frm.doc, + method: "set_customer_defaults", + callback: function(r) { + if(!r.exc) me.frm.refresh_fields(); + } + }); + + // TODO shift this to depends_on + hide_contact_info(this.frm.doc); + } + } +}); + +$.extend(cur_frm.cscript, new erpnext.support.MaintenanceVisit({frm: cur_frm})); cur_frm.cscript.onload = function(doc, dt, dn) { - if(!doc.status) set_multiple(dt,dn,{status:'Draft'}); - if(doc.__islocal) set_multiple(dt,dn,{mntc_date:get_today()}); - hide_contact_info(doc); + if(!doc.status) set_multiple(dt,dn,{status:'Draft'}); + if(doc.__islocal) set_multiple(dt,dn,{mntc_date:get_today()}); + hide_contact_info(doc); } var hide_contact_info = function(doc) { @@ -30,79 +52,68 @@ cur_frm.cscript.refresh = function(doc) { hide_contact_info(doc); } -//customer -cur_frm.cscript.customer = function(doc,dt,dn) { - var callback = function(r,rt) { - var doc = locals[cur_frm.doctype][cur_frm.docname]; - cur_frm.refresh(); - } - - if(doc.customer) $c_obj(make_doclist(doc.doctype, doc.name), 'get_default_customer_address', '', callback); - hide_contact_info(doc); -} - -cur_frm.cscript.customer_address = cur_frm.cscript.contact_person = function(doc,dt,dn) { - 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.cscript.customer_address = cur_frm.cscript.contact_person = function(doc,dt,dn) { + 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['customer_address'].get_query = function(doc, cdt, cdn) { - return 'SELECT name,address_line1,city FROM tabAddress WHERE customer = "'+ doc.customer +'" AND docstatus != 2 AND name LIKE "%s" ORDER BY name ASC LIMIT 50'; + return 'SELECT name,address_line1,city FROM tabAddress WHERE customer = "'+ doc.customer +'" AND docstatus != 2 AND name LIKE "%s" ORDER BY name ASC LIMIT 50'; } cur_frm.fields_dict['contact_person'].get_query = function(doc, cdt, cdn) { - return 'SELECT name,CONCAT(first_name," ",ifnull(last_name,"")) As FullName,department,designation FROM tabContact WHERE customer = "'+ doc.customer +'" AND docstatus != 2 AND name LIKE "%s" ORDER BY name ASC LIMIT 50'; + return 'SELECT name,CONCAT(first_name," ",ifnull(last_name,"")) As FullName,department,designation FROM tabContact WHERE customer = "'+ doc.customer +'" AND docstatus != 2 AND name LIKE "%s" ORDER BY name ASC LIMIT 50'; } cur_frm.cscript.get_items = function(doc, dt, dn) { - var callback = function(r,rt) { - hide_contact_info(doc); - cur_frm.refresh(); - } - get_server_fields('fetch_items','','',doc, dt, dn,1,callback); + var callback = function(r,rt) { + hide_contact_info(doc); + cur_frm.refresh(); + } + get_server_fields('fetch_items','','',doc, dt, dn,1,callback); } cur_frm.fields_dict['maintenance_visit_details'].grid.get_field('item_code').get_query = function(doc, cdt, cdn) { - return 'SELECT tabItem.name,tabItem.item_name,tabItem.description FROM tabItem WHERE tabItem.is_service_item="Yes" AND tabItem.docstatus != 2 AND tabItem.%(key)s LIKE "%s" LIMIT 50'; + return 'SELECT tabItem.name,tabItem.item_name,tabItem.description FROM tabItem WHERE tabItem.is_service_item="Yes" AND tabItem.docstatus != 2 AND tabItem.%(key)s LIKE "%s" LIMIT 50'; } cur_frm.cscript.item_code = function(doc, cdt, cdn) { - var fname = cur_frm.cscript.fname; - var d = locals[cdt][cdn]; - if (d.item_code) { - get_server_fields('get_item_details',d.item_code, 'maintenance_visit_details',doc,cdt,cdn,1); - } + var fname = cur_frm.cscript.fname; + var d = locals[cdt][cdn]; + if (d.item_code) { + get_server_fields('get_item_details',d.item_code, 'maintenance_visit_details',doc,cdt,cdn,1); + } } cur_frm.fields_dict['sales_order_no'].get_query = function(doc) { - doc = locals[this.doctype][this.docname]; - var cond = ''; - if(doc.customer) { - cond = '`tabSales Order`.customer = "'+doc.customer+'" AND'; - } - return repl('SELECT DISTINCT `tabSales Order`.name FROM `tabSales Order`, `tabSales Order Item`, `tabItem` WHERE `tabSales Order`.company = "%(company)s" AND `tabSales Order`.docstatus = 1 AND `tabSales Order Item`.parent = `tabSales Order`.name AND `tabSales Order Item`.item_code = `tabItem`.name AND `tabItem`.is_service_item = "Yes" AND %(cond)s `tabSales Order`.name LIKE "%s" ORDER BY `tabSales Order`.name DESC LIMIT 50', {company:doc.company, cond:cond}); + doc = locals[this.doctype][this.docname]; + var cond = ''; + if(doc.customer) { + cond = '`tabSales Order`.customer = "'+doc.customer+'" AND'; + } + return repl('SELECT DISTINCT `tabSales Order`.name FROM `tabSales Order`, `tabSales Order Item`, `tabItem` WHERE `tabSales Order`.company = "%(company)s" AND `tabSales Order`.docstatus = 1 AND `tabSales Order Item`.parent = `tabSales Order`.name AND `tabSales Order Item`.item_code = `tabItem`.name AND `tabItem`.is_service_item = "Yes" AND %(cond)s `tabSales Order`.name LIKE "%s" ORDER BY `tabSales Order`.name DESC LIMIT 50', {company:doc.company, cond:cond}); } cur_frm.fields_dict['customer_issue_no'].get_query = function(doc) { - doc = locals[this.doctype][this.docname]; - var cond = ''; - if(doc.customer) { - cond = '`tabCustomer Issue`.customer = "'+doc.customer+'" AND'; - } - return repl('SELECT `tabCustomer Issue`.name FROM `tabCustomer Issue` WHERE `tabCustomer Issue`.company = "%(company)s" AND %(cond)s `tabCustomer Issue`.docstatus = 1 AND (`tabCustomer Issue`.status = "Open" OR `tabCustomer Issue`.status = "Work In Progress") AND `tabCustomer Issue`.name LIKE "%s" ORDER BY `tabCustomer Issue`.name DESC LIMIT 50', {company:doc.company, cond:cond}); + doc = locals[this.doctype][this.docname]; + var cond = ''; + if(doc.customer) { + cond = '`tabCustomer Issue`.customer = "'+doc.customer+'" AND'; + } + return repl('SELECT `tabCustomer Issue`.name FROM `tabCustomer Issue` WHERE `tabCustomer Issue`.company = "%(company)s" AND %(cond)s `tabCustomer Issue`.docstatus = 1 AND (`tabCustomer Issue`.status = "Open" OR `tabCustomer Issue`.status = "Work In Progress") AND `tabCustomer Issue`.name LIKE "%s" ORDER BY `tabCustomer Issue`.name DESC LIMIT 50', {company:doc.company, cond:cond}); } cur_frm.fields_dict['maintenance_schedule'].get_query = function(doc) { - doc = locals[this.doctype][this.docname]; - var cond = ''; - if(doc.customer) { - cond = '`tabMaintenance Schedule`.customer = "'+doc.customer+'" AND'; - } - return repl('SELECT `tabMaintenance Schedule`.name FROM `tabMaintenance Schedule` WHERE `tabMaintenance Schedule`.company = "%(company)s" AND %(cond)s `tabMaintenance Schedule`.docstatus = 1 AND `tabMaintenance Schedule`.name LIKE "%s" ORDER BY `tabMaintenance Schedule`.name DESC LIMIT 50', {company:doc.company, cond:cond}); + doc = locals[this.doctype][this.docname]; + var cond = ''; + if(doc.customer) { + cond = '`tabMaintenance Schedule`.customer = "'+doc.customer+'" AND'; + } + return repl('SELECT `tabMaintenance Schedule`.name FROM `tabMaintenance Schedule` WHERE `tabMaintenance Schedule`.company = "%(company)s" AND %(cond)s `tabMaintenance Schedule`.docstatus = 1 AND `tabMaintenance Schedule`.name LIKE "%s" ORDER BY `tabMaintenance Schedule`.name DESC LIMIT 50', {company:doc.company, cond:cond}); } //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'; + 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 diff --git a/support/doctype/support_ticket/support_ticket.js b/support/doctype/support_ticket/support_ticket.js index beff192346..903f41cd58 100644 --- a/support/doctype/support_ticket/support_ticket.js +++ b/support/doctype/support_ticket/support_ticket.js @@ -16,6 +16,25 @@ cur_frm.fields_dict.customer.get_query = erpnext.utils.customer_query; +wn.provide("erpnext.support"); +// TODO commonify this code +erpnext.support.CustomerIssue = wn.ui.form.Controller.extend({ + customer: function() { + var me = this; + if(this.frm.doc.customer) { + this.frm.call({ + doc: this.frm.doc, + method: "set_customer_defaults", + callback: function(r) { + if(!r.exc) me.frm.refresh_fields(); + } + }); + } + } +}); + +$.extend(cur_frm.cscript, new erpnext.support.CustomerIssue({frm: cur_frm})); + $.extend(cur_frm.cscript, { onload: function(doc, dt, dn) { if(in_list(user_roles,'System Manager')) { @@ -67,17 +86,6 @@ $.extend(cur_frm.cscript, { }, - customer: function(doc, dt, dn) { - var callback = function(r,rt) { - var doc = locals[cur_frm.doctype][cur_frm.docname]; - if(!r.exc) { - cur_frm.refresh(); - } - } - if(doc.customer) $c_obj(make_doclist(doc.doctype, doc.name), - 'get_default_customer_address', '', callback); - }, - 'Close Ticket': function() { cur_frm.cscript.set_status("Closed"); }, diff --git a/utilities/transaction_base.py b/utilities/transaction_base.py index ab53575aee..7794444e27 100644 --- a/utilities/transaction_base.py +++ b/utilities/transaction_base.py @@ -23,46 +23,89 @@ from webnotes.model.doc import addchild from controllers.status_updater import StatusUpdater class TransactionBase(StatusUpdater): - def get_default_address_and_contact(self, party_type): + def get_default_address_and_contact(self, party_field, party_name=None): """get a dict of default field values of address and contact for a given party type party_type can be one of: customer, supplier""" - ret = {} + if not party_name: + party_name = self.doc.fields.get(party_field) - # {customer: self.doc.fields.get("customer")} - args = {party_type: self.doc.fields.get(party_type)} + return get_default_address_and_contact(party_field, party_name, + fetch_shipping_address=True if self.meta.get_field("shipping_address_name") else False) + + def get_customer_defaults(self): + out = self.get_default_address_and_contact("customer") + + customer = webnotes.doc("Customer", self.doc.customer) + for f in ['customer_name', 'customer_group', 'territory']: + out[f] = customer.fields.get(f) - address_text, address_name = self.get_address_text(**args) - ret.update({ - # customer_address - (party_type + "_address"): address_name, - "address_display": address_text - }) - ret.update(self.get_contact_text(**args)) - return ret + # fields prepended with default in Customer doctype + for f in ['sales_partner', 'commission_rate', 'currency', 'price_list']: + out[f] = customer.fields.get("default_" + f) + + return out + + def set_customer_defaults(self): + """ + For a customer: + 1. Sets default address and contact + 2. Sets values like Territory, Customer Group, etc. + 3. Clears existing Sales Team and fetches the one mentioned in Customer + """ + customer_defaults = self.get_customer_defaults() + + # hack! TODO - add shipping_address_field in Delivery Note + if self.doc.doctype == "Delivery Note": + customer_defaults["customer_address"] = customer_defaults["shipping_address_name"] + customer_defaults["address_display"] = customer_defaults["shipping_address"] + + customer_defaults["price_list"] = customer_defaults["price_list"] or \ + webnotes.conn.get_value("Customer Group", self.doc.customer_group, "default_price_list") or \ + self.doc.price_list + + self.doc.fields.update(customer_defaults) + + if self.meta.get_field("sales_team"): + self.set_sales_team_for_customer() + + def set_sales_team_for_customer(self): + from webnotes.model import default_fields + + # clear table + self.doclist = self.doc.clear_table(self.doclist, "sales_team") + + sales_team = webnotes.conn.sql("""select * from `tabSales Team` + where parenttype="Customer" and parent=%s""", self.doc.customer, as_dict=True) + for i, sales_person in enumerate(sales_team): + # remove default fields + for fieldname in default_fields: + if fieldname in sales_person: + del sales_person[fieldname] + + sales_person.update({ + "doctype": "Sales Team", + "parentfield": "sales_team", + "idx": i+1 + }) + + # add child + self.doclist.append(sales_person) - # Get Customer Default Primary Address - first load - def get_default_customer_address(self, args=''): - address_text, address_name = self.get_address_text(customer=self.doc.customer) - self.doc.customer_address = address_name or '' - self.doc.address_display = address_text or '' - self.doc.fields.update(self.get_contact_text(customer=self.doc.customer)) - - if args != 'onload': - self.get_customer_details(self.doc.customer) - self.get_sales_person(self.doc.customer) + def get_lead_defaults(self): + out = self.get_default_address_and_contact("lead") - # Get Customer Default Shipping Address - first load - # ----------------------- - def get_default_customer_shipping_address(self, args=''): - address_text, address_name = self.get_address_text(customer=self.doc.customer,is_shipping_address=1) - self.doc.customer_address = address_name or '' - self.doc.address_display = address_text or '' - self.doc.fields.update(self.get_contact_text(customer=self.doc.customer)) - - if self.doc.doctype != 'Quotation' and args != 'onload': - self.get_customer_details(self.doc.customer) - self.get_sales_person(self.doc.customer) + lead = webnotes.conn.get_value("Lead", self.doc.lead, + ["territory", "company_name", "lead_name"], as_dict=True) or {} + out["territory"] = lead.get("territory") + out["customer_name"] = lead.get("company_name") or lead.get("lead_name") + + return out + + def set_lead_defaults(self): + self.doc.fields.update(self.get_lead_defaults()) + + # Get Customer Address # ----------------------- def get_customer_address(self, args): @@ -91,13 +134,14 @@ class TransactionBase(StatusUpdater): details = webnotes.conn.sql("select name, address_line1, address_line2, city, country, pincode, state, phone, fax from `tabAddress` where %s and docstatus != 2 order by is_shipping_address desc, is_primary_address desc limit 1" % cond, as_dict = 1) else: details = webnotes.conn.sql("select name, address_line1, address_line2, city, country, pincode, state, phone, fax from `tabAddress` where %s and docstatus != 2 order by is_primary_address desc limit 1" % cond, 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'),('\n','state'),(' ','pincode'),('\n','country'),('\nPhone: ','phone'),('\nFax: ', 'fax')] - 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:] + + address_display = "" + + if details: + address_display = get_address_display(details[0]) address_name = details and details[0]['name'] or '' + return address_display, address_name # Get Contact Text @@ -126,41 +170,13 @@ class TransactionBase(StatusUpdater): "contact_department": details and details[0]["department"] or "", } - def get_customer_details(self, name): - """ - Get customer details like name, group, territory - and other such defaults - """ - customer_details = webnotes.conn.sql("""\ - select - customer_name, customer_group, territory, - default_sales_partner, default_commission_rate, default_currency, - default_price_list - from `tabCustomer` - where name = %s and docstatus < 2""", name, as_dict=1) - if customer_details: - for f in ['customer_name', 'customer_group', 'territory']: - self.doc.fields[f] = customer_details[0][f] or self.doc.fields.get(f) - - # fields prepended with default in Customer doctype - for f in ['sales_partner', 'commission_rate', 'currency']: - self.doc.fields[f] = customer_details[0]["default_%s" % f] or self.doc.fields.get(f) - - # optionally fetch default price list from Customer Group - self.doc.price_list_name = (customer_details[0]['default_price_list'] - or webnotes.conn.get_value('Customer Group', self.doc.customer_group, - 'default_price_list') - or self.doc.fields.get('price_list_name')) - - # Get Customer Shipping Address - # ----------------------- + # TODO deprecate this - used only in sales_order.js def get_shipping_address(self, name): details = webnotes.conn.sql("select name, address_line1, address_line2, city, country, pincode, state, phone from `tabAddress` where customer = '%s' and docstatus != 2 order by is_shipping_address desc, is_primary_address desc limit 1" %(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: ','phone')] - 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:] + address_display = "" + if details: + address_display = get_address_display(details[0]) ret = { 'shipping_address_name' : details and details[0]['name'] or '', @@ -168,36 +184,6 @@ class TransactionBase(StatusUpdater): } return ret - # 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 - 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' : lead.get('lead_name'), - 'address_display' : address_display, - '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 - - # Get Supplier Default Primary Address - first load # ----------------------- def get_default_supplier_address(self, args): @@ -312,20 +298,112 @@ class TransactionBase(StatusUpdater): }) webnotes.bean(event_doclist).insert() - + +def get_default_address_and_contact(party_field, party_name, fetch_shipping_address=False): + out = {} + + # get addresses + billing_address = get_address_dict(party_field, party_name) + if billing_address: + out[party_field + "_address"] = billing_address["name"] + out["address_display"] = get_address_display(billing_address) + else: + out[party_field + "_address"] = out["address_display"] = None + + if fetch_shipping_address: + shipping_address = get_address_dict(party_field, party_name, is_shipping_address=True) + if shipping_address: + out["shipping_address_name"] = shipping_address["name"] + out["shipping_address"] = get_address_display(shipping_address) + else: + out["shipping_address_name"] = out["shipping_address"] = None + + # get contact + if party_field == "lead": + out["customer_address"] = out.get("lead_address") + out.update(map_lead_fields(party_name)) + else: + out.update(map_contact_fields(party_field, party_name)) + + return out + +def get_address_dict(party_field, party_name, is_shipping_address=None): + order_by = "is_shipping_address desc, is_primary_address desc, name asc" if \ + is_shipping_address else "is_primary_address desc, name asc" + + address = webnotes.conn.sql("""select * from `tabAddress` where `%s`=%s order by %s + limit 1""" % (party_field, "%s", order_by), party_name, as_dict=True, + update={"doctype": "Address"}) + + return address[0] if address else None def get_address_display(address_dict): + def _prepare_for_display(a_dict, sequence): + display = "" + for separator, fieldname in sequence: + if a_dict.get(fieldname): + display += separator + a_dict.get(fieldname) + + return display.strip() + meta = webnotes.get_doctype("Address") address_sequence = (("", "address_line1"), ("\n", "address_line2"), ("\n", "city"), ("\n", "state"), ("\n" + meta.get_label("pincode") + ": ", "pincode"), ("\n", "country"), ("\n" + meta.get_label("phone") + ": ", "phone"), ("\n" + meta.get_label("fax") + ": ", "fax")) - address_display = "" - for separator, fieldname in address_sequence: - if address_dict.get(fieldname): - address_display += separator + address_dict.get(fieldname) - - return address_display + return _prepare_for_display(address_dict, address_sequence) + +def map_lead_fields(party_name): + out = {} + for fieldname in ["contact_display", "contact_email", "contact_mobile", "contact_phone"]: + out[fieldname] = None + + lead = webnotes.conn.sql("""select * from `tabLead` where name=%s""", party_name, as_dict=True) + if lead: + lead = lead[0] + out.update({ + "contact_display": lead.get("lead_name"), + "contact_email": lead.get("email_id"), + "contact_mobile": lead.get("mobile_no"), + "contact_phone": lead.get("phone"), + }) + + return out + +def map_contact_fields(party_field, party_name): + out = {} + for fieldname in ["contact_person", "contact_display", "contact_email", + "contact_mobile", "contact_phone", "contact_designation", "contact_department"]: + out[fieldname] = None + + contact = webnotes.conn.sql("""select * from `tabContact` where `%s`=%s + order by is_primary_contact desc, name asc limit 1""" % (party_field, "%s"), + (party_name,), as_dict=True) + if contact: + contact = contact[0] + out.update({ + "contact_person": contact.get("name"), + "contact_display": " ".join(filter(None, + [contact.get("first_name"), contact.get("last_name")])), + "contact_email": contact.get("email_id"), + "contact_mobile": contact.get("mobile_no"), + "contact_phone": contact.get("phone"), + "contact_designation": contact.get("designation"), + "contact_department": contact.get("department") + }) + + return out + +def get_address_territory(address_doc): + territory = None + for fieldname in ("city", "state", "country"): + value = address_doc.fields.get(fieldname) + if value: + territory = webnotes.conn.get_value("Territory", value.strip()) + if territory: + break + + return territory def validate_conversion_rate(currency, conversion_rate, conversion_rate_label, company): """common validation for currency and price list currency""" diff --git a/website/css/website.css b/website/css/website.css index 34ff983b0b..df37821bfe 100644 --- a/website/css/website.css +++ b/website/css/website.css @@ -10,6 +10,10 @@ a { cursor: pointer; } +img { + max-width: 100%; +} + .content { padding-bottom: 30px; } diff --git a/website/doctype/shopping_cart_settings/shopping_cart_settings.py b/website/doctype/shopping_cart_settings/shopping_cart_settings.py index 516c96834f..121422f9be 100644 --- a/website/doctype/shopping_cart_settings/shopping_cart_settings.py +++ b/website/doctype/shopping_cart_settings/shopping_cart_settings.py @@ -9,42 +9,70 @@ from webnotes.model.controller import DocListController class ShoppingCartSetupError(webnotes.ValidationError): pass class DocType(DocListController): - def __init__(self, d, dl): - self.doc, self.doclist = d, dl - def validate(self): if self.doc.enabled: - self.validate_overlapping_territories("shopping_cart_price_lists", "price_list") - self.validate_overlapping_territories("shopping_cart_taxes_and_charges_masters", - "sales_taxes_and_charges_master") - self.validate_shipping_rules() + self.validate_price_lists() + self.validate_tax_masters() self.validate_exchange_rates_exist() - - def validate_overlapping_territories(self, parentfield, fieldname): - names = [doc.fields(fieldname) for doc in self.doclist.get({"parentfield": parentfield})] - doctype = self.meta.get_field(parentfield).options - parenttype = self.meta.get_field(fieldname, parentfield=parentfield).options - - if not names: - msgprint(_("Please specify at least one") + " " + _(doctype), - raise_exception=ShoppingCartSetupError) - - territory_name = webnotes.conn.sql("""select territory, parent from `tabFor Territory` - where parenttype=%s and parent in (%s)""" % ("%s", ", ".join(["%s"]*names)), - tuple([parenttype] + names)) - - territory_name_map = {} - for territory, name in territory_name: - territory_name_map.setdefault(territory, []).append(name) + def validate_overlapping_territories(self, parentfield, fieldname): + # for displaying message + doctype = self.meta.get_field(parentfield).options + + # specify atleast one entry in the table + self.validate_table_has_rows(parentfield, raise_exception=ShoppingCartSetupError) + + territory_name_map = self.get_territory_name_map(parentfield, fieldname) for territory, names in territory_name_map.items(): if len(names) > 1: msgprint(_("Error for") + " " + _(doctype) + ": " + comma_and(names) + " " + _("have a common territory") + ": " + territory, raise_exception=ShoppingCartSetupError) - def validate_shipping_rules(self): - pass + return territory_name_map + + def validate_price_lists(self): + territory_name_map = self.validate_overlapping_territories("price_lists", + "price_list") + + # validate that a Shopping Cart Price List exists for the root territory + # as a catch all! + from setup.utils import get_root_of + root_territory = get_root_of("Territory") + + if root_territory not in territory_name_map.keys(): + msgprint(_("Please specify a Price List which is valid for Territory") + + ": " + root_territory, raise_exception=ShoppingCartSetupError) + + def validate_tax_masters(self): + self.validate_overlapping_territories("sales_taxes_and_charges_masters", + "sales_taxes_and_charges_master") + + def get_territory_name_map(self, parentfield, fieldname): + territory_name_map = {} + + # entries in table + names = [doc.fields.get(fieldname) for doc in self.doclist.get({"parentfield": parentfield})] + + if names: + # for condition in territory check + parenttype = self.meta.get_field(fieldname, parentfield=parentfield).options + + # to validate territory overlap + # make a map of territory: [list of names] + # if list against each territory has more than one element, raise exception + territory_name = webnotes.conn.sql("""select `territory`, `parent` + from `tabFor Territory` + where `parenttype`=%s and `parent` in (%s)""" % + ("%s", ", ".join(["%s"]*len(names))), tuple([parenttype] + names)) + + for territory, name in territory_name: + territory_name_map.setdefault(territory, []).append(name) + + if len(territory_name_map[territory]) > 1: + territory_name_map[territory].sort(key=lambda val: names.index(val)) + + return territory_name_map def validate_exchange_rates_exist(self): """check if exchange rates exist for all Price List currencies (to company's currency)""" @@ -54,7 +82,7 @@ class DocType(DocListController): raise_exception=ShoppingCartSetupError) price_list_currency_map = webnotes.conn.get_values("Price List", - [d.price_list for d in self.doclist.get({"parentfield": "shopping_cart_price_lists"})], + [d.price_list for d in self.doclist.get({"parentfield": "price_lists"})], "currency") expected_to_exist = [currency + "-" + company_currency @@ -70,5 +98,41 @@ class DocType(DocListController): if missing: msgprint(_("Missing Currency Exchange Rates for" + ": " + comma_and(missing)), raise_exception=ShoppingCartSetupError) + + def get_name_from_territory(self, territory, parentfield, fieldname): + name = None + territory_name_map = self.get_territory_name_map(parentfield, fieldname) - \ No newline at end of file + if territory_name_map.get(territory): + name = territory_name_map.get(territory) + else: + territory_ancestry = self.get_territory_ancestry(territory) + for ancestor in territory_ancestry: + if territory_name_map.get(ancestor): + name = territory_name_map.get(ancestor) + break + + return name + + def get_price_list(self, billing_territory): + price_list = self.get_name_from_territory(billing_territory, "price_lists", "price_list") + return price_list and price_list[0] or None + + def get_tax_master(self, billing_territory): + tax_master = self.get_name_from_territory(billing_territory, "sales_taxes_and_charges_masters", + "sales_taxes_and_charges_master") + return tax_master and tax_master[0] or None + + def get_shipping_rules(self, shipping_territory): + return self.get_name_from_territory(shipping_territory, "shipping_rules", "shipping_rule") + + def get_territory_ancestry(self, territory): + from setup.utils import get_ancestors_of + + if not hasattr(self, "_territory_ancestry"): + self._territory_ancestry = {} + + if not self._territory_ancestry.get(territory): + self._territory_ancestry[territory] = get_ancestors_of("Territory", territory) + + return self._territory_ancestry[territory] \ No newline at end of file diff --git a/website/doctype/shopping_cart_settings/shopping_cart_settings.txt b/website/doctype/shopping_cart_settings/shopping_cart_settings.txt index 82e3baed37..1d472b7f1b 100644 --- a/website/doctype/shopping_cart_settings/shopping_cart_settings.txt +++ b/website/doctype/shopping_cart_settings/shopping_cart_settings.txt @@ -2,7 +2,7 @@ { "creation": "2013-06-19 15:57:32", "docstatus": 0, - "modified": "2013-06-21 12:59:30", + "modified": "2013-07-03 21:01:00", "modified_by": "Administrator", "owner": "Administrator" }, @@ -53,7 +53,7 @@ }, { "doctype": "DocField", - "fieldname": "shopping_cart_price_lists", + "fieldname": "price_lists", "fieldtype": "Table", "label": "Shopping Cart Price Lists", "options": "Shopping Cart Price List", @@ -61,12 +61,20 @@ }, { "doctype": "DocField", - "fieldname": "shopping_cart_taxes_and_charges_masters", + "fieldname": "sales_taxes_and_charges_masters", "fieldtype": "Table", "label": "Shopping Cart Taxes and Charges Masters", "options": "Shopping Cart Taxes and Charges Master", "reqd": 0 }, + { + "doctype": "DocField", + "fieldname": "shipping_rules", + "fieldtype": "Table", + "label": "Shopping Cart Shipping Rules", + "options": "Shopping Cart Shipping Rule", + "reqd": 0 + }, { "doctype": "DocField", "fieldname": "company", diff --git a/website/doctype/shopping_cart_settings/test_shopping_cart_settings.py b/website/doctype/shopping_cart_settings/test_shopping_cart_settings.py new file mode 100644 index 0000000000..b4ae6fc5f7 --- /dev/null +++ b/website/doctype/shopping_cart_settings/test_shopping_cart_settings.py @@ -0,0 +1,78 @@ +# For license information, please see license.txt + +from __future__ import unicode_literals +import webnotes +import unittest +from website.doctype.shopping_cart_settings.shopping_cart_settings import ShoppingCartSetupError + +class TestShoppingCartSettings(unittest.TestCase): + def setUp(self): + webnotes.conn.sql("""delete from `tabSingles` where doctype="Shipping Cart Settings" """) + webnotes.conn.sql("""delete from `tabShopping Cart Price List`""") + webnotes.conn.sql("""delete from `tabShopping Cart Taxes and Charges Master`""") + webnotes.conn.sql("""delete from `tabShopping Cart Shipping Rule`""") + + def get_cart_settings(self): + return webnotes.bean({"doctype": "Shopping Cart Settings", + "company": "_Test Company"}) + + def test_price_list_territory_overlap(self): + cart_settings = self.get_cart_settings() + + def _add_price_list(price_list): + cart_settings.doclist.append({ + "doctype": "Shopping Cart Price List", + "parentfield": "price_lists", + "price_list": price_list + }) + + for price_list in ("_Test Price List Rest of the World", "_Test Price List India", + "_Test Price List"): + _add_price_list(price_list) + + controller = cart_settings.make_controller() + controller.validate_overlapping_territories("price_lists", "price_list") + + _add_price_list("_Test Price List 2") + + controller = cart_settings.make_controller() + self.assertRaises(ShoppingCartSetupError, controller.validate_overlapping_territories, + "price_lists", "price_list") + + return cart_settings + + def test_taxes_territory_overlap(self): + cart_settings = self.get_cart_settings() + + def _add_tax_master(tax_master): + cart_settings.doclist.append({ + "doctype": "Shopping Cart Taxes and Charges Master", + "parentfield": "sales_taxes_and_charges_masters", + "sales_taxes_and_charges_master": tax_master + }) + + for tax_master in ("_Test Sales Taxes and Charges Master", "_Test India Tax Master"): + _add_tax_master(tax_master) + + controller = cart_settings.make_controller() + controller.validate_overlapping_territories("sales_taxes_and_charges_masters", + "sales_taxes_and_charges_master") + + _add_tax_master("_Test Sales Taxes and Charges Master 2") + + controller = cart_settings.make_controller() + self.assertRaises(ShoppingCartSetupError, controller.validate_overlapping_territories, + "sales_taxes_and_charges_masters", "sales_taxes_and_charges_master") + + def test_exchange_rate_exists(self): + webnotes.conn.sql("""delete from `tabCurrency Exchange`""") + + cart_settings = self.test_price_list_territory_overlap() + controller = cart_settings.make_controller() + self.assertRaises(ShoppingCartSetupError, controller.validate_exchange_rates_exist) + + from setup.doctype.currency_exchange.test_currency_exchange import test_records as \ + currency_exchange_records + webnotes.bean(currency_exchange_records[0]).insert() + controller.validate_exchange_rates_exist() + diff --git a/website/doctype/shopping_cart_shipping_rule/__init__.py b/website/doctype/shopping_cart_shipping_rule/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/website/doctype/shopping_cart_shipping_rule/shopping_cart_shipping_rule.py b/website/doctype/shopping_cart_shipping_rule/shopping_cart_shipping_rule.py new file mode 100644 index 0000000000..928aa9ff9f --- /dev/null +++ b/website/doctype/shopping_cart_shipping_rule/shopping_cart_shipping_rule.py @@ -0,0 +1,8 @@ +# For license information, please see license.txt + +from __future__ import unicode_literals +import webnotes + +class DocType: + def __init__(self, d, dl): + self.doc, self.doclist = d, dl \ No newline at end of file diff --git a/website/doctype/shopping_cart_shipping_rule/shopping_cart_shipping_rule.txt b/website/doctype/shopping_cart_shipping_rule/shopping_cart_shipping_rule.txt new file mode 100644 index 0000000000..caeba3d96a --- /dev/null +++ b/website/doctype/shopping_cart_shipping_rule/shopping_cart_shipping_rule.txt @@ -0,0 +1,35 @@ +[ + { + "creation": "2013-07-03 13:15:34", + "docstatus": 0, + "modified": "2013-07-03 13:19:02", + "modified_by": "Administrator", + "owner": "Administrator" + }, + { + "doctype": "DocType", + "istable": 1, + "module": "Website", + "name": "__common__" + }, + { + "doctype": "DocField", + "fieldname": "shipping_rule", + "fieldtype": "Link", + "label": "Shipping Rule", + "name": "__common__", + "options": "Shipping Rule", + "parent": "Shopping Cart Shipping Rule", + "parentfield": "fields", + "parenttype": "DocType", + "permlevel": 0, + "reqd": 1 + }, + { + "doctype": "DocType", + "name": "Shopping Cart Shipping Rule" + }, + { + "doctype": "DocField" + } +] \ No newline at end of file diff --git a/website/helpers/cart.py b/website/helpers/cart.py index bc99e7bd4b..98edc07663 100644 --- a/website/helpers/cart.py +++ b/website/helpers/cart.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals import webnotes import webnotes.defaults -from webnotes.utils import cint, get_fullname, fmt_money +from webnotes.utils import flt, get_fullname, fmt_money class WebsitePriceListMissingError(webnotes.ValidationError): pass @@ -18,14 +18,15 @@ def get_cart_quotation(doclist=None): return { "doclist": decorate_quotation_doclist(doclist), "addresses": [{"name": address.name, "display": address.display} - for address in get_address_docs(party)] + for address in get_address_docs(party)], + "shipping_rules": get_applicable_shipping_rules(party) } @webnotes.whitelist() def update_cart(item_code, qty, with_doclist=0): quotation = _get_cart_quotation() - qty = cint(qty) + qty = flt(qty) if qty == 0: quotation.set_doclist(quotation.doclist.get({"item_code": ["!=", item_code]})) else: @@ -40,8 +41,7 @@ def update_cart(item_code, qty, with_doclist=0): else: quotation_items[0].qty = qty - quotation.ignore_permissions = True - quotation.save() + apply_cart_settings(quotation=quotation) if with_doclist: return get_cart_quotation(quotation.doclist) @@ -69,6 +69,8 @@ def update_cart_address(address_fieldname, address_name): quotation.ignore_permissions = True quotation.save() + + apply_cart_settings(quotation=quotation) return get_cart_quotation(quotation.doclist) @@ -131,14 +133,23 @@ def get_lead_or_customer(): "doctype": "Lead", "email_id": webnotes.session.user, "lead_name": get_fullname(webnotes.session.user), - "territory": webnotes.conn.get_value("Shopping Cart Settings", None, "territory") or \ - "All Territories", + "territory": guess_territory(), "status": "Open" # TODO: set something better??? }) lead_bean.ignore_permissions = True lead_bean.insert() return lead_bean.doc + +def guess_territory(): + territory = None + geoip_country = webnotes.session.get("session_country") + if geoip_country: + territory = webnotes.conn.get_value("Territory", geoip_country) + + return territory or \ + webnotes.conn.get_value("Shopping Cart Settings", None, "territory") or \ + "All Territories" def decorate_quotation_doclist(doclist): for d in doclist: @@ -147,7 +158,13 @@ def decorate_quotation_doclist(doclist): ["website_image", "web_short_description", "page_name"], as_dict=True)) d.formatted_rate = fmt_money(d.export_rate, currency=doclist[0].currency) d.formatted_amount = fmt_money(d.export_amount, currency=doclist[0].currency) + elif d.charge_type: + d.formatted_tax_amount = fmt_money(d.tax_amount / doclist[0].conversion_rate, + currency=doclist[0].currency) + doclist[0].formatted_grand_total_export = fmt_money(doclist[0].grand_total_export, + currency=doclist[0].currency) + return [d.fields for d in doclist] def _get_cart_quotation(party=None): @@ -168,42 +185,122 @@ def _get_cart_quotation(party=None): "order_type": "Shopping Cart", "status": "Draft", "__islocal": 1, - "price_list_name": get_price_list(party), (party.doctype.lower()): party.name }) + qbean.run_method("onload_post_render") + apply_cart_settings(party, qbean) return qbean -def get_price_list(party): - if not party.default_price_list: - party.default_price_list = get_price_list_using_geoip() - party.save() +def apply_cart_settings(party=None, quotation=None): + if not party: + party = get_lead_or_customer() + if not quotation: + quotation = _get_cart_quotation(party) + + cart_settings = webnotes.get_obj("Shopping Cart Settings") + + billing_territory = get_address_territory(quotation.doc.customer_address) or \ + party.territory + + set_price_list_and_rate(quotation, cart_settings, billing_territory) + + quotation.run_method("calculate_taxes_and_totals") + + set_taxes(quotation, cart_settings, billing_territory) + + _apply_shipping_rule(party, quotation, cart_settings) + + quotation.ignore_permissions = True + quotation.save() + +def set_price_list_and_rate(quotation, cart_settings, billing_territory): + """set price list based on billing territory""" + quotation.doc.price_list_name = cart_settings.get_price_list(billing_territory) + + # reset values + quotation.doc.price_list_currency = quotation.doc.currency = \ + quotation.doc.plc_conversion_rate = quotation.doc.conversion_rate = None + for item in quotation.doclist.get({"parentfield": "quotation_details"}): + item.ref_rate = item.adj_rate = item.export_rate = item.export_amount = None + + # refetch values + quotation.run_method("set_price_list_and_item_details") + +def set_taxes(quotation, cart_settings, billing_territory): + """set taxes based on billing territory""" + quotation.doc.charge = cart_settings.get_tax_master(billing_territory) + + # clear table + quotation.doclist = quotation.doc.clear_table(quotation.doclist, "other_charges") + + # append taxes + controller = quotation.make_controller() + controller.append_taxes_from_master("other_charges", "charge") + quotation.set_doclist(controller.doclist) + +@webnotes.whitelist() +def apply_shipping_rule(shipping_rule): + quotation = _get_cart_quotation() + + quotation.doc.shipping_rule = shipping_rule + + apply_cart_settings(quotation=quotation) + + quotation.save() + + return get_cart_quotation(quotation.doclist) + +def _apply_shipping_rule(party=None, quotation=None, cart_settings=None): + shipping_rules = get_shipping_rules(party, quotation, cart_settings) + + if not shipping_rules: + return - return party.default_price_list - -def get_price_list_using_geoip(): - country = webnotes.session.get("session_country") - price_list_name = None - - if country: - price_list_name = webnotes.conn.sql("""select parent - from `tabPrice List Country` plc - where country=%s and exists (select name from `tabPrice List` pl - where use_for_website=1 and ifnull(valid_for_all_countries, 0)=0 and - pl.name = plc.parent)""", country) + elif quotation.doc.shipping_rule not in shipping_rules: + quotation.doc.shipping_rule = shipping_rules[0] - if price_list_name: - price_list_name = price_list_name[0][0] - else: - price_list_name = webnotes.conn.get_value("Price List", - {"use_for_website": 1, "valid_for_all_countries": 1}) - - if not price_list_name: - raise WebsitePriceListMissingError, "No website Price List specified" + quotation.run_method("apply_shipping_rule") + quotation.run_method("calculate_taxes_and_totals") - return price_list_name - +def get_applicable_shipping_rules(party=None, quotation=None): + shipping_rules = get_shipping_rules(party, quotation) + + if shipping_rules: + rule_label_map = webnotes.conn.get_values("Shipping Rule", shipping_rules, "label") + # we need this in sorted order as per the position of the rule in the settings page + return [[rule, rule_label_map.get(rule)] for rule in shipping_rules] + +def get_shipping_rules(party=None, quotation=None, cart_settings=None): + if not party: + party = get_lead_or_customer() + if not quotation: + quotation = _get_cart_quotation() + if not cart_settings: + cart_settings = webnotes.get_obj("Shopping Cart Settings") + + # set shipping rule based on shipping territory + shipping_territory = get_address_territory(quotation.doc.shipping_address_name) or \ + party.territory + + shipping_rules = cart_settings.get_shipping_rules(shipping_territory) + + return shipping_rules + +def get_address_territory(address_name): + """Tries to match city, state and country of address to existing territory""" + territory = None + if address_name: + address_fields = webnotes.conn.get_value("Address", address_name, + ["city", "state", "country"]) + for value in address_fields: + territory = webnotes.conn.get_value("Territory", value) + if territory: + break + + return territory + @webnotes.whitelist() def checkout(): quotation = _get_cart_quotation() diff --git a/website/templates/js/cart.js b/website/templates/js/cart.js index db6c5714ec..f1002fc599 100644 --- a/website/templates/js/cart.js +++ b/website/templates/js/cart.js @@ -38,7 +38,6 @@ $(document).ready(function() { } else { wn.cart.render(r.message); } - } }); }); @@ -81,9 +80,10 @@ $.extend(wn.cart, { render: function(out) { var doclist = out.doclist; var addresses = out.addresses; - + var $cart_items = $("#cart-items").empty(); var $cart_taxes = $("#cart-taxes").empty(); + var $cart_totals = $("#cart-totals").empty(); var $cart_billing_address = $("#cart-billing-address").empty(); var $cart_shipping_address = $("#cart-shipping-address").empty(); @@ -94,12 +94,39 @@ $.extend(wn.cart, { return; } + var shipping_rule_added = false; + var taxes_exist = false; + var shipping_rule_labels = $.map(out.shipping_rules, function(rule) { return rule[1]; }); $.each(doclist, function(i, doc) { if(doc.doctype === "Quotation Item") { wn.cart.render_item_row($cart_items, doc); + } else if (doc.doctype === "Sales Taxes and Charges") { + if(out.shipping_rules && out.shipping_rules.length && + shipping_rule_labels.indexOf(doc.description)!==-1) { + shipping_rule_added = true; + wn.cart.render_tax_row($cart_taxes, doc, out.shipping_rules); + } else { + wn.cart.render_tax_row($cart_taxes, doc); + } + + taxes_exist = true; } }); + if(out.shipping_rules && out.shipping_rules.length && !shipping_rule_added) { + wn.cart.render_tax_row($cart_taxes, {description: "", formatted_tax_amount: ""}, + out.shipping_rules); + taxes_exist = true; + } + + if(taxes_exist) + $('
').appendTo($cart_taxes); + + wn.cart.render_tax_row($cart_totals, { + description: "Total", + formatted_tax_amount: "" + doclist[0].formatted_grand_total_export + "" + }); + if(!(addresses && addresses.length)) { $cart_shipping_address.html('
Hey! Go ahead and add an address
'); } else { @@ -125,10 +152,10 @@ $.extend(wn.cart, { \ \ \ -
\ +
\
\ \ + data-item-code="%(item_code)s" class="text-right">\
\ \ @@ -140,6 +167,53 @@ $.extend(wn.cart, {

', doc)).appendTo($cart_items); }, + render_tax_row: function($cart_taxes, doc, shipping_rules) { + var shipping_selector; + if(shipping_rules) { + shipping_selector = ''; + } + + var $tax_row = $(repl('
\ +
\ +
\ +
' + + (shipping_selector || '

%(description)s

') + + '
\ +
\ +
\ +
\ + %(formatted_tax_amount)s

\ +
\ +
', doc)).appendTo($cart_taxes); + + if(shipping_selector) { + $tax_row.find('select option').each(function(i, opt) { + if($(opt).html() == doc.description) { + $(opt).attr("selected", "selected"); + } + }); + $tax_row.find('select').on("change", function() { + wn.cart.apply_shipping_rule($(this).val(), this); + }); + } + }, + + apply_shipping_rule: function(rule, btn) { + wn.call({ + btn: btn, + type: "POST", + method: "website.helpers.cart.apply_shipping_rule", + args: { shipping_rule: rule }, + callback: function(r) { + if(!r.exc) { + wn.cart.render(r.message); + } + } + }); + }, + render_address: function($address_wrapper, addresses, address_name) { $.each(addresses, function(i, address) { $(repl('
\ diff --git a/website/templates/pages/cart.html b/website/templates/pages/cart.html index 9d5f100da9..cefcb5afa7 100644 --- a/website/templates/pages/cart.html +++ b/website/templates/pages/cart.html @@ -19,16 +19,17 @@
-
-

Item Details

+

Item Details

-

Qty

+

Qty, Amount


+
+

@@ -37,16 +38,15 @@
+ New Shipping Address

Billing Address

+ New Billing Address
-