[webshop] shopping cart settings, update price list, taxes and shipping rule on change of address, apply defaults on creation of fresh quotation

This commit is contained in:
Anand Doshi 2013-07-04 17:13:53 +05:30
parent 259e0b978c
commit 99100a4135
40 changed files with 1225 additions and 474 deletions

View File

@ -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"""

View File

@ -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)
@ -31,3 +29,11 @@ class DocType:
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))
# 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()

View File

@ -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": "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"
}
],
]

View File

@ -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()

View File

@ -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",

View File

@ -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"

View File

@ -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",

View File

@ -355,7 +355,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 + ")";
@ -401,7 +401,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] =

View File

@ -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)

View File

@ -55,7 +55,11 @@ 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)
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"))
@ -71,22 +75,32 @@ 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})
webnotes.conn.get_value(tax_master_doctype, {"is_default": 1})
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_doctype + " Master", self.doc.fields.get(tax_master_field))
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:

View File

@ -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",

View File

@ -28,48 +28,62 @@ 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()
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
if self.meta.get_field("shipping_address"):
self.doc.fields.update(self.get_shipping_address(self.doc.customer))
# 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
if shipping_amount:
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

View File

@ -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);
}
});

View File

@ -17,6 +17,30 @@
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){
@ -50,17 +74,6 @@ cur_frm.cscript.get_items = function(doc, dt, dn) {
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);
}

View File

@ -2,7 +2,7 @@
{
"creation": "2013-04-10 11:45:37",
"docstatus": 0,
"modified": "2013-06-28 15:08:26",
"modified": "2013-07-02 16:03:50",
"modified_by": "Administrator",
"owner": "Administrator"
},
@ -303,13 +303,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",

View File

@ -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();
@ -96,28 +120,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);
}
@ -146,7 +148,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']);
}

View File

@ -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'

View File

@ -80,7 +80,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');
}
}

View File

@ -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() {
@ -397,7 +465,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 + ")";
@ -442,7 +510,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] =
@ -502,15 +570,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;
@ -534,61 +593,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;

View File

@ -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 <http://www.gnu.org/licenses/>.
$.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));
}
}
});

View File

@ -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"
}
]

View File

@ -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
}]
]

View File

@ -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)

View File

@ -1,8 +1,59 @@
test_records = [
[{
[
{
"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"
}
],
]

View File

@ -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",
}],
]

View File

@ -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",

View File

@ -39,8 +39,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""" % (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):

View File

@ -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')

View File

@ -14,6 +14,28 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
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',

View File

@ -14,6 +14,31 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
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);
}

View File

@ -14,6 +14,28 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
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()});
@ -30,17 +52,6 @@ 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);
}

View File

@ -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");
},

View File

@ -23,45 +23,88 @@ 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)
address_text, address_name = self.get_address_text(**args)
ret.update({
# customer_address
(party_type + "_address"): address_name,
"address_display": address_text
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)
# 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
})
ret.update(self.get_contact_text(**args))
return ret
# 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))
# add child
self.doclist.append(sales_person)
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))
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())
if self.doc.doctype != 'Quotation' and args != 'onload':
self.get_customer_details(self.doc.customer)
self.get_sales_person(self.doc.customer)
# Get Customer Address
# -----------------------
@ -92,12 +135,13 @@ class TransactionBase(StatusUpdater):
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):
@ -313,19 +299,111 @@ 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 _prepare_for_display(address_dict, address_sequence)
return address_display
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"""

View File

@ -9,42 +9,67 @@ 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})]
# for displaying message
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)
# 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)
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 +79,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
@ -71,4 +96,38 @@ class DocType(DocListController):
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)
if territory_name_map.get(territory):
name = territory_name_map.get(territory)[0]
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)[0]
break
return name
def get_price_list(self, billing_territory):
return self.get_name_from_territory(billing_territory, "price_lists", "price_list")
def get_tax_master(self, billing_territory):
return self.get_name_from_territory(billing_territory, "sales_taxes_and_charges_masters",
"sales_taxes_and_charges_master")
def get_shipping_rule(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]

View File

@ -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:00: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": "shopping_cart_shipping_rules",
"fieldtype": "Table",
"label": "Shopping Cart Shipping Rules",
"options": "Shopping Cart Shipping Rule",
"reqd": 0
},
{
"doctype": "DocField",
"fieldname": "company",

View File

@ -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()

View File

@ -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

View File

@ -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"
}
]

View File

@ -70,6 +70,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)
@webnotes.whitelist()
@ -131,8 +133,7 @@ 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
@ -140,6 +141,16 @@ def get_lead_or_customer():
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:
if d.item_code:
@ -168,41 +179,83 @@ 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)
return party.default_price_list
cart_settings = webnotes.get_obj("Shopping Cart Settings")
def get_price_list_using_geoip():
country = webnotes.session.get("session_country")
price_list_name = None
billing_territory = get_address_territory(quotation.doc.customer_address) or \
party.territory
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)
set_price_list_and_rate(quotation, cart_settings, billing_territory)
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})
set_taxes(quotation, cart_settings, billing_territory)
if not price_list_name:
raise WebsitePriceListMissingError, "No website Price List specified"
# set shipping rule based on shipping territory
shipping_territory = get_address_territory(quotation.doc.shipping_address_name) or \
party.territory
return price_list_name
apply_shipping_rule(quotation, cart_settings, shipping_territory)
quotation.run_method("calculate_taxes_and_totals")
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)
def apply_shipping_rule(quotation, cart_settings, shipping_territory):
quotation.doc.shipping_rule = cart_settings.get_shipping_rule(shipping_territory)
quotation.run_method("apply_shipping_rule")
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
def get_cart_price_list(territory_list, territory_name_map):
pass
@webnotes.whitelist()
def checkout():