Merge branch 'responsive' of github.com:webnotes/erpnext into responsive
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 61e52d2..9d3851d 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -19,10 +19,4 @@
### Copyright
-The ERPNext code is licensed as GNU General Public License (v3) and the Documentation is licensed as Creative Commons (CC-BY-SA-3.0) and the copyright is owned by Web Notes Technologies Pvt Ltd (Web Notes). Unless otherwise asserted in the code files, Web Notes will own the copyright of all contributions too. That means Web Notes holds the rights to change the license in the future or offer Commercial Licenses.
-
-We will only accept copyright assertions in case of a significant contribution like a whole new functionality or a major rewrite. We believe if your contribution is significant then you should have a say in what license Web Notes selects in the future and/or have a right to any revenue Web Notes gets from a Commercial License. Either ways Web Notes will have the right to decide what is a "significant" contribution.
-
-Note: At the moment, Web Notes does not give Commercial License for ERPNext nor has specific plans do so in the future.
-
-The brand name ERPNext and the logo are trademarks of Web Notes Technologies Pvt. Ltd.
+Please see README.md
\ No newline at end of file
diff --git a/README.md b/README.md
index 0b10f36..c95c801 100644
--- a/README.md
+++ b/README.md
@@ -4,40 +4,89 @@
Includes Accounting, Inventory, CRM, Sales, Purchase, Projects, HRMS. Built on Python / MySQL.
-## Platform
+ERPNext is built on [wnframework](https://github.com/webnotes/wnframework)
-ERPNext is built on [wnframework](https://github.com/webnotes/wnframework) (Version 2.0)
+- [User Guide](https://erpnext.org/docs.user.html)
+- [Getting Help](https://erpnext.org/docs.user.help.html)
+- [Developer Forum](http://groups.google.com/group/erpnext-developer-forum)
+- [User Forum](http://groups.google.com/group/erpnext-user-forum)
-## User Guide
+---
-[See wiki](https://github.com/webnotes/erpnext/wiki/User-Guide)
+### Download and Install
-## Download and Install
+##### Virtual Image:
-First install all the pre-requisites, then
+- [ERPNext Download](http://erpnext.com/erpnext-download)
- $ git clone git://github.com/webnotes/erpnext.git
- $ cd erpnext
- $ python erpnext_install.py
+##### On Linux:
+
+1. Switch to root user using `sudo su`
+1. create a folder where you want to install erpnext
+1. go to the new folder
+1. `wget https://raw.github.com/webnotes/erpnext/master/install_erpnext.py`
+1. `python install_erpnext.py`
[See installation notes](https://github.com/webnotes/erpnext/wiki/How-to-Install-ERPNext)
-## Patch and update
+##### Patch and update
To patch and update from the latest git repository the erpnext folder and run.
You will have to set your origin in git remote
$ lib/wnf.py --update origin master
-## Forums
-
-Please join our forums for more questions:
-
-- [Developer Forum](http://groups.google.com/group/erpnext-developer-forum)
-- [User Forum](http://groups.google.com/group/erpnext-user-forum)
+---
## License
-GNU/General Public License (see licence.txt)
+GNU/General Public License (see LICENSE.txt)
-Along with the GPL, the name "ERPNext" must be retained in all derivatives.
\ No newline at end of file
+The ERPNext code is licensed as GNU General Public License (v3) and the Documentation is licensed as Creative Commons (CC-BY-SA-3.0) and the copyright is owned by Web Notes Technologies Pvt Ltd (Web Notes).
+
+### Copyright for Contributors
+
+Unless otherwise asserted in the code files, Web Notes will own the copyright of all contributions too. That means Web Notes holds the rights to change the license in the future or offer Commercial Licenses.
+
+Web Notes will only accept copyright assertions in case of a significant contribution like a whole new functionality or a major rewrite. We believe if your contribution is significant then you should have a say in what license Web Notes selects in the future and/or have a right to any revenue Web Notes gets from a Commercial License. Either ways Web Notes will have the right to decide what is a "significant" contribution.
+
+Note: At the moment, Web Notes does not give Commercial License for ERPNext nor has specific plans do so in the future.
+
+---
+
+## Logo and Trademark
+
+The brand name ERPNext and the logo are trademarks of Web Notes Technologies Pvt. Ltd.
+
+### Introduction
+
+Web Notes Technologies Pvt. Ltd. (Web Notes) owns and oversees the trademarks for the ERPNext name and logos. We have developed this trademark usage policy with the following goals in mind:
+
+- We’d like to make it easy for anyone to use the ERPNext name or logo for community-oriented efforts that help spread and improve ERPNext.
+- We’d like to make it clear how ERPNext-related businesses and projects can (and cannot) use the ERPNext name and logo.
+- We’d like to make it hard for anyone to use the ERPNext name and logo to unfairly profit from, trick or confuse people who are looking for official ERPNext resources.
+
+### Web Notes Trademark Usage Policy
+
+Permission from Web Notes is required to use the ERPNext name or logo as part of any project, product, service, domain or company name.
+
+We will grant permission to use the ERPNext name and logo for projects that meet the following criteria:
+
+- The primary purpose of your project is to promote the spread and improvement of the ERPNext software.
+- Your project is non-commercial in nature (it can make money to cover its costs or contribute to non-profit entities, but it cannot be run as a for-profit project or business).
+Your project neither promotes nor is associated with entities that currently fail to comply with the GPL license under which ERPNext is distributed.
+- If your project meets these criteria, you will be permitted to use the ERPNext name and logo to promote your project in any way you see fit with one exception: Please do not use ERPNext as part of a domain name.
+
+Use of the ERPNext name and logo is additionally allowed in the following situations:
+
+All other ERPNext-related businesses or projects can use the ERPNext name and logo to refer to and explain their services, but they cannot use them as part of a product, project, service, domain, or company name and they cannot use them in any way that suggests an affiliation with or endorsement by the ERPNext or WebNotes or the ERPNext open source project. For example, a consulting company can describe its business as “123 Web Services, offering ERPNext consulting for small businesses,” but cannot call its business “The ERPNext Consulting Company.”
+
+Similarly, it’s OK to use the ERPNext logo as part of a page that describes your products or services, but it is not OK to use it as part of your company or product logo or branding itself. Under no circumstances is it permitted to use ERPNext as part of a top-level domain name.
+
+We do not allow the use of the trademark in advertising, including AdSense/AdWords.
+
+Please note that it is not the goal of this policy to limit commercial activity around ERPNext. We encourage ERPNext-based businesses, and we would love to see hundreds of them.
+
+When in doubt about your use of the ERPNext name or logo, please contact the Web Notes Technologies for clarification.
+
+(inspired from Wordpress)
diff --git a/accounts/doctype/sales_taxes_and_charges_master/sales_taxes_and_charges_master.js b/accounts/doctype/sales_taxes_and_charges_master/sales_taxes_and_charges_master.js
index 6d7f2db..a2c3321 100644
--- a/accounts/doctype/sales_taxes_and_charges_master/sales_taxes_and_charges_master.js
+++ b/accounts/doctype/sales_taxes_and_charges_master/sales_taxes_and_charges_master.js
@@ -8,19 +8,20 @@
//
// 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 <http://www.gnu.org/licenses/>.
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
//--------- ONLOAD -------------
cur_frm.cscript.onload = function(doc, cdt, cdn) {
- //
+ if(doc.doctype === "Sales Taxes and Charges Master")
+ erpnext.add_for_territory();
}
cur_frm.cscript.refresh = function(doc, cdt, cdn) {
- cur_frm.set_footnote(wn.markdown(cur_frm.meta.description));
+ cur_frm.set_footnote(wn.markdown(cur_frm.meta.description));
}
// For customizing print
@@ -41,7 +42,7 @@
}
cur_frm.pformat.other_charges= function(doc){
- //function to make row of table
+ //function to make row of table
var make_row = function(title,val,bold){
var bstart = '<b>'; var bend = '</b>';
return '<tr><td style="width:50%;">'+(bold?bstart:'')+title+(bold?bend:'')+'</td>'
@@ -63,7 +64,7 @@
if (!doc.print_without_amount) {
var cl = getchildren('Sales Taxes and Charges',doc.name,'other_charges');
- // outer table
+ // outer table
var out='<div><table class="noborder" style="width:100%"><tr><td style="width: 60%"></td><td>';
// main table
@@ -77,7 +78,7 @@
if(cl.length){
for(var i=0;i<cl.length;i++){
if(convert_rate(cl[i].tax_amount)!=0 && !cl[i].included_in_print_rate)
- out += make_row(cl[i].description,convert_rate(cl[i].tax_amount),0);
+ out += make_row(cl[i].description,convert_rate(cl[i].tax_amount),0);
}
}
@@ -96,98 +97,98 @@
out += '<table><tr><td style="width:25%;"><b>In Words</b></td>'
out+= '<td style="width:50%;">'+doc.in_words_export+'</td></tr>'
}
- out +='</table></td></tr></table></div>';
+ out +='</table></td></tr></table></div>';
}
return out;
}
cur_frm.cscript.charge_type = function(doc, cdt, cdn) {
- var d = locals[cdt][cdn];
- if(d.idx == 1 && (d.charge_type == 'On Previous Row Amount' || d.charge_type == 'On Previous Row Total')){
- alert("You cannot select Charge Type as 'On Previous Row Amount' or 'On Previous Row Total' for first row");
- d.charge_type = '';
- }
- validated = false;
- refresh_field('charge_type',d.name,'other_charges');
- cur_frm.cscript.row_id(doc, cdt, cdn);
- cur_frm.cscript.rate(doc, cdt, cdn);
- cur_frm.cscript.tax_amount(doc, cdt, cdn);
+ var d = locals[cdt][cdn];
+ if(d.idx == 1 && (d.charge_type == 'On Previous Row Amount' || d.charge_type == 'On Previous Row Total')){
+ alert("You cannot select Charge Type as 'On Previous Row Amount' or 'On Previous Row Total' for first row");
+ d.charge_type = '';
+ }
+ validated = false;
+ refresh_field('charge_type',d.name,'other_charges');
+ cur_frm.cscript.row_id(doc, cdt, cdn);
+ cur_frm.cscript.rate(doc, cdt, cdn);
+ cur_frm.cscript.tax_amount(doc, cdt, cdn);
}
cur_frm.cscript.row_id = function(doc, cdt, cdn) {
- var d = locals[cdt][cdn];
- if(!d.charge_type && d.row_id){
- alert("Please select Charge Type first");
- d.row_id = '';
- }
- else if((d.charge_type == 'Actual' || d.charge_type == 'On Net Total') && d.row_id) {
- alert("You can Enter Row only if your Charge Type is 'On Previous Row Amount' or ' Previous Row Total'");
- d.row_id = '';
- }
- else if((d.charge_type == 'On Previous Row Amount' || d.charge_type == 'On Previous Row Total') && d.row_id){
- if(d.row_id >= d.idx){
- alert("You cannot Enter Row no. greater than or equal to current row no. for this Charge type");
- d.row_id = '';
- }
- }
- validated = false;
- refresh_field('row_id',d.name,'other_charges');
+ var d = locals[cdt][cdn];
+ if(!d.charge_type && d.row_id){
+ alert("Please select Charge Type first");
+ d.row_id = '';
+ }
+ else if((d.charge_type == 'Actual' || d.charge_type == 'On Net Total') && d.row_id) {
+ alert("You can Enter Row only if your Charge Type is 'On Previous Row Amount' or ' Previous Row Total'");
+ d.row_id = '';
+ }
+ else if((d.charge_type == 'On Previous Row Amount' || d.charge_type == 'On Previous Row Total') && d.row_id){
+ if(d.row_id >= d.idx){
+ alert("You cannot Enter Row no. greater than or equal to current row no. for this Charge type");
+ d.row_id = '';
+ }
+ }
+ validated = false;
+ refresh_field('row_id',d.name,'other_charges');
}
/*---------------------- Get rate if account_head has account_type as TAX or CHARGEABLE-------------------------------------*/
cur_frm.fields_dict['other_charges'].grid.get_field("account_head").get_query = function(doc,cdt,cdn) {
- return{
- filters:[
- ['Account', 'group_or_ledger', '=', 'Ledger'],
- ['Account', 'account_type', 'in', 'Tax, Chargeable, Income Account'],
- ['Account', 'company', '=', doc.company]
- ]
- }
+ return{
+ filters:[
+ ['Account', 'group_or_ledger', '=', 'Ledger'],
+ ['Account', 'account_type', 'in', 'Tax, Chargeable, Income Account'],
+ ['Account', 'company', '=', doc.company]
+ ]
+ }
}
cur_frm.fields_dict['other_charges'].grid.get_field("cost_center").get_query = function(doc) {
- return{
- 'company': doc.company,
- 'group_or_ledger': "Ledger"
- }
+ return{
+ 'company': doc.company,
+ 'group_or_ledger': "Ledger"
+ }
}
cur_frm.cscript.account_head = function(doc, cdt, cdn) {
- var d = locals[cdt][cdn];
- if(!d.charge_type && d.account_head){
- alert("Please select Charge Type first");
- validated = false;
- d.account_head = '';
- }
- else if(d.account_head && d.charge_type) {
- arg = "{'charge_type' : '" + d.charge_type +"', 'account_head' : '" + d.account_head + "'}";
- get_server_fields('get_rate', arg, 'other_charges', doc, cdt, cdn, 1);
- }
- refresh_field('account_head',d.name,'other_charges');
+ var d = locals[cdt][cdn];
+ if(!d.charge_type && d.account_head){
+ alert("Please select Charge Type first");
+ validated = false;
+ d.account_head = '';
+ }
+ else if(d.account_head && d.charge_type) {
+ arg = "{'charge_type' : '" + d.charge_type +"', 'account_head' : '" + d.account_head + "'}";
+ get_server_fields('get_rate', arg, 'other_charges', doc, cdt, cdn, 1);
+ }
+ refresh_field('account_head',d.name,'other_charges');
}
cur_frm.cscript.rate = function(doc, cdt, cdn) {
- var d = locals[cdt][cdn];
- if(!d.charge_type && d.rate) {
- alert("Please select Charge Type first");
- d.rate = '';
- }
- validated = false;
- refresh_field('rate',d.name,'other_charges');
+ var d = locals[cdt][cdn];
+ if(!d.charge_type && d.rate) {
+ alert("Please select Charge Type first");
+ d.rate = '';
+ }
+ validated = false;
+ refresh_field('rate',d.name,'other_charges');
}
cur_frm.cscript.tax_amount = function(doc, cdt, cdn) {
- var d = locals[cdt][cdn];
- if(!d.charge_type && d.tax_amount){
- alert("Please select Charge Type first");
- d.tax_amount = '';
- }
- else if(d.charge_type && d.tax_amount) {
- alert("You cannot directly enter Amount and if your Charge Type is Actual enter your amount in Rate");
- d.tax_amount = '';
- }
- validated = false;
- refresh_field('tax_amount',d.name,'other_charges');
+ var d = locals[cdt][cdn];
+ if(!d.charge_type && d.tax_amount){
+ alert("Please select Charge Type first");
+ d.tax_amount = '';
+ }
+ else if(d.charge_type && d.tax_amount) {
+ alert("You cannot directly enter Amount and if your Charge Type is Actual enter your amount in Rate");
+ d.tax_amount = '';
+ }
+ validated = false;
+ refresh_field('tax_amount',d.name,'other_charges');
};
\ No newline at end of file
diff --git a/accounts/doctype/shipping_rule/shipping_rule.js b/accounts/doctype/shipping_rule/shipping_rule.js
new file mode 100644
index 0000000..8e8580b
--- /dev/null
+++ b/accounts/doctype/shipping_rule/shipping_rule.js
@@ -0,0 +1,5 @@
+$.extend(cur_frm.cscript, {
+ onload: function() {
+ erpnext.add_for_territory();
+ }
+});
\ No newline at end of file
diff --git a/controllers/accounts_controller.py b/controllers/accounts_controller.py
index 70151a6..7bcc92e 100644
--- a/controllers/accounts_controller.py
+++ b/controllers/accounts_controller.py
@@ -30,11 +30,6 @@
self.validate_date_with_fiscal_year()
if self.meta.get_field("currency"):
- self.company_currency = get_company_currency(self.doc.company)
-
- validate_conversion_rate(self.doc.currency, self.doc.conversion_rate,
- self.meta.get_label("conversion_rate"), self.doc.company)
-
self.calculate_taxes_and_totals()
self.validate_value("grand_total", ">=", 0)
self.set_total_in_words()
@@ -66,7 +61,7 @@
"price_list_name": self.doc.price_list_name,
"buying_or_selling": buying_or_selling
}))
-
+
if self.doc.price_list_currency:
if not self.doc.plc_conversion_rate:
company_currency = get_company_currency(self.doc.company)
@@ -129,6 +124,14 @@
self.doclist.append(tax)
def calculate_taxes_and_totals(self):
+ # validate conversion rate
+ if not self.doc.currency:
+ self.doc.currency = get_company_currency(self.doc.company)
+ self.doc.conversion_rate = 1.0
+ else:
+ validate_conversion_rate(self.doc.currency, self.doc.conversion_rate,
+ self.meta.get_label("conversion_rate"), self.doc.company)
+
self.doc.conversion_rate = flt(self.doc.conversion_rate)
self.item_doclist = self.doclist.get({"parentfield": self.fname})
self.tax_doclist = self.doclist.get({"parentfield": self.other_fname})
diff --git a/docs/docs.dev.install.md b/docs/docs.dev.install.md
index 1d69a9d..4c8ea2f 100644
--- a/docs/docs.dev.install.md
+++ b/docs/docs.dev.install.md
@@ -17,7 +17,7 @@
1. Switch to root user using `sudo su`
1. create a folder where you want to install erpnext
1. go to the new folder
-1. `wget https://gist.github.com/anandpdoshi/5991402/raw/5b3b451720a8575f8708e58a640b9a760d048392/install_erpnext.py`
+1. `wget https://raw.github.com/webnotes/erpnext/master/install_erpnext.py`
1. `python install_erpnext.py`
> If you are installing on your server for deployment, remember to change Administrator's password!
diff --git a/install_erpnext.py b/install_erpnext.py
index badb4dd..5b3b451 100644
--- a/install_erpnext.py
+++ b/install_erpnext.py
@@ -1,170 +1,343 @@
-#!/usr/bin/python
+#!/usr/bin/env python
from __future__ import unicode_literals
-import os, commands, sys
+import os, sys
-def install():
- # get required details
- root_pwd = get_root_password()
- db_name, db_pwd = get_new_db_details()
+apache_user = None
+is_redhat = is_debian = None
+root_password = None
+
+def install(install_path=None):
+ install_pre_requisites()
- # install path
- install_path = os.getcwd()
+ if not install_path:
+ install_path = os.getcwd()
+ install_erpnext(install_path)
+ post_install(install_path)
+
+def install_pre_requisites():
+ global is_redhat, is_debian
+ is_redhat, is_debian = validate_install()
+ if is_redhat:
+ install_using_yum()
+ elif is_debian:
+ install_using_apt()
+
+ install_python_modules()
+
+ print "-"*80
+ print "Pre-requisites Installed"
+ print "-"*80
+
+def validate_install():
+ import platform
+
+ # check os
+ operating_system = platform.system()
+ print "Operating System =", operating_system
+ if operating_system != "Linux":
+ raise Exception, "Sorry! This installer works only for Linux based Operating Systems"
+
+ # check python version
+ python_version = sys.version.split(" ")[0]
+ print "Python Version =", python_version
+ if not (python_version and int(python_version.split(".")[0])==2 and int(python_version.split(".")[1]) >= 6):
+ raise Exception, "Hey! ERPNext needs Python version to be 2.6+"
+
+ # check distribution
+ distribution = platform.linux_distribution()[0].lower().replace('"', '')
+ print "Distribution = ", distribution
+ is_redhat = distribution in ("redhat", "centos", "fedora")
+ is_debian = distribution in ("debian", "ubuntu", "elementary os")
+
+ if not (is_redhat or is_debian):
+ raise Exception, "Sorry! This installer works only with yum or apt-get package management"
+
+ return is_redhat, is_debian
+
+def install_using_yum():
+ packages = "python python-setuptools MySQL-python httpd git memcached ntp vim-enhanced screen"
+
+ print "-"*80
+ print "Installing Packages: (This may take some time)"
+ print packages
+ print "-"*80
+ exec_in_shell("yum install -y %s" % packages)
+
+ if not exec_in_shell("which mysql"):
+ packages = "mysql mysql-server mysql-devel"
+ print "Installing Packages:", packages
+ exec_in_shell("yum install -y %s" % packages)
+ exec_in_shell("service mysqld restart")
+
+ # set a root password post install
+ global root_password
+ print "Please create a password for root user of MySQL"
+ root_password = (get_root_password() or "erpnext").strip()
+ exec_in_shell('mysqladmin -u root password "%s"' % (root_password,))
+ print "Root password set as", root_password
+
+ # install htop
+ if not exec_in_shell("which htop"):
+ try:
+ exec_in_shell("cd /tmp && rpm -i --force http://packages.sw.be/rpmforge-release/rpmforge-release-0.5.2-2.el6.rf.x86_64.rpm && yum install -y htop")
+ except:
+ pass
+
+ update_config_for_redhat()
+
+def update_config_for_redhat():
+ import re
+
+ global apache_user
+ apache_user = "apache"
+
+ # update memcache user
+ with open("/etc/sysconfig/memcached", "r") as original:
+ memcached_conf = original.read()
+ with open("/etc/sysconfig/memcached", "w") as modified:
+ modified.write(re.sub('USER.*', 'USER="%s"' % apache_user, memcached_conf))
+
+ # set to autostart on startup
+ for service in ("mysqld", "httpd", "memcached", "ntpd"):
+ exec_in_shell("chkconfig --level 2345 %s on" % service)
+ exec_in_shell("service %s restart" % service)
+
+def install_using_apt():
+ packages = "python python-setuptools python-mysqldb apache2 git memcached ntp vim screen htop"
+ print "-"*80
+ print "Installing Packages: (This may take some time)"
+ print packages
+ print "-"*80
+ exec_in_shell("apt-get install -y %s" % packages)
+
+ if not exec_in_shell("which mysql"):
+ packages = "mysql-server libmysqlclient-dev"
+ print "Installing Packages:", packages
+ exec_in_shell("apt-get install -y %s" % packages)
+
+ update_config_for_debian()
+
+def update_config_for_debian():
+ global apache_user
+ apache_user = "www-data"
+
+ # update memcache user
+ with open("/etc/memcached.conf", "r") as original:
+ memcached_conf = original.read()
+ with open("/etc/memcached.conf", "w") as modified:
+ modified.write(memcached_conf.replace("-u memcache", "-u %s" % apache_user))
+
+ exec_in_shell("a2enmod rewrite")
+
+ for service in ("mysql", "apache2", "memcached", "ntpd"):
+ exec_in_shell("service %s restart" % service)
+
+def install_python_modules():
+ python_modules = "pytz python-dateutil jinja2 markdown2 termcolor python-memcached requests chardet dropbox google-api-python-client pygeoip gitpython"
+
+ print "-"*80
+ print "Installing Python Modules: (This may take some time)"
+ print python_modules
+ print "-"*80
+
+ exec_in_shell("easy_install pip")
+ exec_in_shell("pip install -q %s" % python_modules)
+
+def install_erpnext(install_path):
+ print
+ print "-"*80
+ print "Installing ERPNext"
+ print "-"*80
+
+ # ask for details
+ global root_password
+ if not root_password:
+ root_password = get_root_password()
+ test_root_connection(root_password)
+
+ db_name = raw_input("ERPNext Database Name: ")
+ if not db_name:
+ raise Exception, "Sorry! You must specify ERPNext Database Name"
+
+ # install folders and conf
setup_folders(install_path)
-
- setup_conf(install_path, db_name, db_pwd)
+ setup_conf(install_path, db_name)
# setup paths
- sys.path.append('.')
- sys.path.append('lib')
- sys.path.append('app')
+ sys.path.extend([".", "lib", "app"])
- setup_db(install_path, root_pwd, db_name)
+ # install database, run patches, update schema
+ setup_db(install_path, root_password, db_name)
- apply_patches(install_path)
+ setup_cron(install_path)
- show_remaining_steps()
-
-def setup_folders(path):
- execute_in_shell("git clone git://github.com/webnotes/wnframework.git lib", verbose=1)
- execute_in_shell("git clone git://github.com/webnotes/erpnext.git app", verbose=1)
- public = os.path.join(path, "public")
- os.mkdir(public)
- os.mkdir(os.path.join(public, "files"))
- os.mkdir(os.path.join(public, "backups"))
- os.mkdir(os.path.join(path, "logs"))
-
-def setup_conf(path, db_name, db_pwd):
- # read template conf file
- with open(os.path.join(path, 'lib', 'conf', 'conf.py'), 'r') as template:
- content = template.read()
-
- # manipulate content
- import re
-
- # set new_dbname, new_dbpassword, files_path, backup_path, log_file_name
- content = re.sub("db_name.*", "db_name = '%s'" % db_name, content)
- content = re.sub("db_password.*", "db_password = '%s'" % db_pwd, content)
-
- # write conf file
- with open(os.path.join(path, 'conf.py'), 'w') as new_conf:
- new_conf.write(content)
-
-def setup_db(path, root_pwd, db_name):
- source = os.path.join(path, 'app', "master.sql")
- execute_in_shell("gunzip -c %s.gz > %s" % (source, source), verbose=1)
-
- from webnotes.install_lib.install import Installer
- inst = Installer('root', root_pwd)
- inst.import_from_db(db_name, source_path=source, verbose = 1)
- execute_in_shell("rm %s" % source)
-
-def apply_patches(path):
- # need to build before patches, once, so that all-web.js and all-web.css exists
- execute_in_shell("./lib/wnf.py -b", verbose=1)
- execute_in_shell("./lib/wnf.py --patch_sync_build", verbose=1)
-
- # set filemode false
- execute_in_shell("cd app && git config core.filemode false", verbose=1)
- execute_in_shell("cd lib && git config core.filemode false", verbose=1)
+ setup_apache_conf(install_path)
def get_root_password():
# ask for root mysql password
import getpass
-
root_pwd = None
- while not root_pwd:
- root_pwd = getpass.getpass("MySQL Root user's Password: ")
-
- test_root_connection(root_pwd)
-
+ root_pwd = getpass.getpass("MySQL Root user's Password: ")
return root_pwd
def test_root_connection(root_pwd):
- err, out = execute_in_shell("mysql -u root -p%s -e 'exit'" % \
- root_pwd.replace('$', '\$').replace(' ', '\ '))
+ out = exec_in_shell("mysql -u root %s -e 'exit'" % \
+ (("-p"+root_pwd) if root_pwd else "").replace('$', '\$').replace(' ', '\ '))
if "access denied" in out.lower():
raise Exception("Incorrect MySQL Root user's password")
+
+def setup_folders(install_path):
+ from git import Repo
-def get_new_db_details():
- return get_input("New ERPNext Database Name: "), \
- get_input("New ERPNext Database's Password: ")
+ app = os.path.join(install_path, "app")
+ if not os.path.exists(app):
+ print "Cloning erpnext"
+ Repo.clone_from("https://github.com/webnotes/erpnext.git", app)
+ exec_in_shell("cd app && git config core.filemode false")
-def get_input(msg):
- val = None
- while not val:
- val = raw_input(msg)
- return val
+ lib = os.path.join(install_path, "lib")
+ if not os.path.exists(lib):
+ print "Cloning wnframework"
+ Repo.clone_from("https://github.com/webnotes/wnframework.git", lib)
+ exec_in_shell("cd lib && git config core.filemode false")
+
+ public = os.path.join(install_path, "public")
+ for p in [public, os.path.join(public, "files"), os.path.join(public, "backups"),
+ os.path.join(install_path, "logs")]:
+ if not os.path.exists(p):
+ os.mkdir(p)
+
+def setup_conf(install_path, db_name):
+ import os, string, random, re
-def show_remaining_steps():
- steps_remaining = """
- Notes:
- ------
+ # generate db password
+ char_range = string.ascii_letters + string.digits
+ db_password = "".join((random.choice(char_range) for n in xrange(16)))
+
+ # make conf file
+ with open(os.path.join(install_path, "lib", "conf", "conf.py"), "r") as template:
+ conf = template.read()
+
+ conf = re.sub("db_name.*", 'db_name = "%s"' % (db_name,), conf)
+ conf = re.sub("db_password.*", 'db_password = "%s"' % (db_password,), conf)
+
+ with open(os.path.join(install_path, "conf.py"), "w") as conf_file:
+ conf_file.write(conf)
+
+ return db_password
+
+def setup_db(install_path, root_password, db_name):
+ master_sql = os.path.join(install_path, "app", "master.sql")
+ exec_in_shell("gunzip -c %s.gz > %s" % (master_sql, master_sql))
+
+ from webnotes.install_lib.install import Installer
+ inst = Installer("root", root_password)
+ inst.import_from_db(db_name, source_path=master_sql, verbose=1)
- sample apache conf file
- #-----------------------------------------------------------
- SetEnv PYTHON_EGG_CACHE /var/www
+ exec_in_shell("rm -f %s" % (master_sql,))
+
+ # run patches and sync
+ exec_in_shell("./lib/wnf.py -b --no_cms")
+ exec_in_shell("./lib/wnf.py --patch_sync_build")
+
+def setup_cron(install_path):
+
+
+ erpnext_cron_entries = [
+ "*/3 * * * * cd %s && python lib/wnf.py --run_scheduler >> /var/log/erpnext-sch.log 2>&1" % install_path,
+ "0 */6 * * * cd %s && python lib/wnf.py --backup >> /var/log/erpnext-backup.log 2>&1" % install_path
+ ]
+
+ for row in erpnext_cron_entries:
+ try:
+ existing_cron = exec_in_shell("crontab -l")
+ if row not in existing_cron:
+ exec_in_shell('{ crontab -l; echo "%s"; } | crontab' % row)
+ except:
+ exec_in_shell('echo "%s" | crontab' % row)
+
+def setup_apache_conf(install_path):
+ apache_conf_content = """Listen 8080
+NameVirtualHost *:8080
+<VirtualHost *:8080>
+ ServerName localhost
+ DocumentRoot %s/public/
+
+ AddHandler cgi-script .cgi .xml .py
+ AddType application/vnd.ms-fontobject .eot
+ AddType font/ttf .ttf
+ AddType font/otf .otf
+ AddType application/x-font-woff .woff
- # you can change 99 to any other port
+ <Directory %s/public/>
+ # directory specific options
+ Options -Indexes +FollowSymLinks +ExecCGI
+
+ # directory's index file
+ DirectoryIndex web.py
+
+ AllowOverride all
+ Order Allow,Deny
+ Allow from all
- Listen 99
- NameVirtualHost *:99
- <VirtualHost *:99>
- ServerName localhost
- DocumentRoot {path to erpnext's folder}/public
- AddHandler cgi-script .cgi .xml .py
+ # rewrite rule
+ RewriteEngine on
+ RewriteCond %%{REQUEST_FILENAME} !-f
+ RewriteCond %%{REQUEST_FILENAME} !-d
+ RewriteCond %%{REQUEST_FILENAME} !-l
+ RewriteRule ^([^/]+)$ /web.py?page=$1 [QSA,L]
+ </Directory>
+</VirtualHost>""" % (install_path, install_path)
+
+ new_apache_conf_path = os.path.join(install_path, os.path.basename(install_path)+".conf")
+ with open(new_apache_conf_path, "w") as apache_conf_file:
+ apache_conf_file.write(apache_conf_content)
- <Directory {path to erpnext's folder}/public/>
- # directory specific options
- Options -Indexes +FollowSymLinks +ExecCGI
+def post_install(install_path):
+ global apache_user
+ exec_in_shell("chown -R %s %s" % (apache_user, install_path))
+
+ apache_conf_filename = os.path.basename(install_path)+".conf"
+ if is_redhat:
+ os.symlink(os.path.join(install_path, apache_conf_filename),
+ os.path.join("/etc/httpd/conf.d", apache_conf_filename))
+ exec_in_shell("service httpd restart")
+
+ elif is_debian:
+ os.symlink(os.path.join(install_path, apache_conf_filename),
+ os.path.join("/etc/apache2/sites-enabled", apache_conf_filename))
+ exec_in_shell("service apache2 restart")
+
+ print
+ print "-"*80
+ print "Installation complete"
+ print "Open your browser and go to http://localhost:8080"
+ print "Login using username = Administrator and password = admin"
- # directory's index file
- DirectoryIndex web.py
-
- # rewrite rule
- RewriteEngine on
-
- # condition 1:
- # ignore login-page.html, app.html, blank.html, unsupported.html
- RewriteCond %{REQUEST_URI} ^((?!app\.html|blank\.html|unsupported\.html).)*$
-
- # condition 2: if there are no slashes
- # and file is .html or does not containt a .
- RewriteCond %{REQUEST_URI} ^(?!.+/)((.+\.html)|([^.]+))$
-
- # rewrite if both of the above conditions are true
- RewriteRule ^(.+)$ web.py?page=$1 [NC,L]
-
- AllowOverride all
- Order Allow,Deny
- Allow from all
- </Directory>
- </VirtualHost>
- #-----------------------------------------------------------
-
- To Do:
-
- * Configure apache/http conf file to point to public folder
- * chown recursively all files in your folder to apache user
- * login using: user="Administrator" and password="admin"
-
- """
-
- print steps_remaining
-
-def execute_in_shell(cmd, verbose=0):
+def exec_in_shell(cmd):
# using Popen instead of os.system - as recommended by python docs
- from subprocess import Popen, PIPE
- p = Popen(cmd, shell=True, stdout=PIPE, stderr=PIPE)
+ from subprocess import Popen
+ import tempfile
- # get err and output
- err, out = p.stderr.read(), p.stdout.read()
+ with tempfile.TemporaryFile() as stdout:
+ with tempfile.TemporaryFile() as stderr:
+ p = Popen(cmd, shell=True, stdout=stdout, stderr=stderr)
+ p.wait()
- if verbose:
- if err: print err
- if out: print out
+ stdout.seek(0)
+ out = stdout.read()
- return err, out
+ stderr.seek(0)
+ err = stderr.read()
-if __name__=="__main__":
+ if err and any((kw in err.lower() for kw in ["traceback", "error", "exception"])):
+ print out
+ raise Exception, err
+ else:
+ print "."
+
+ return out
+
+if __name__ == "__main__":
install()
\ No newline at end of file
diff --git a/patches/july_2013/p04_merge_duplicate_leads.py b/patches/july_2013/p04_merge_duplicate_leads.py
new file mode 100644
index 0000000..d3a5d20
--- /dev/null
+++ b/patches/july_2013/p04_merge_duplicate_leads.py
@@ -0,0 +1,13 @@
+import webnotes
+from webnotes.utils import extract_email_id
+
+def execute():
+ email_lead = {}
+ for name, email in webnotes.conn.sql("""select name, email_id from `tabLead`
+ where ifnull(email_id, '')!='' order by creation asc"""):
+ email = extract_email_id(email)
+ if email:
+ if email not in email_lead:
+ email_lead[email] = name
+ else:
+ webnotes.rename("Lead", name, email_lead[email], merge=True)
\ No newline at end of file
diff --git a/patches/patch_list.py b/patches/patch_list.py
index 80e3102..d6f4424 100644
--- a/patches/patch_list.py
+++ b/patches/patch_list.py
@@ -197,7 +197,6 @@
"execute:webnotes.reload_doc('website', 'doctype', 'blog_post') #2013-03-25",
"execute:webnotes.reload_doc('website', 'doctype', 'web_page') #2013-03-25",
"execute:webnotes.reload_doc('setup', 'doctype', 'sales_partner') #2013-06-25",
- "execute:webnotes.bean('Style Settings').save() #2013-03-25",
"execute:webnotes.conn.set_value('Email Settings', None, 'send_print_in_body_and_attachment', 1)",
"patches.march_2013.p09_unset_user_type_partner",
"patches.march_2013.p10_set_fiscal_year_for_stock",
@@ -251,4 +250,6 @@
"execute:webnotes.delete_doc('Report', 'Received Items To Be Billed')",
"patches.july_2013.p02_copy_shipping_address",
"patches.july_2013.p03_cost_center_company",
+ "execute:webnotes.bean('Style Settings').save() #2013-07-16",
+ "patches.july_2013.p04_merge_duplicate_leads",
]
\ No newline at end of file
diff --git a/public/js/utils.js b/public/js/utils.js
index b44a875..743c02b 100644
--- a/public/js/utils.js
+++ b/public/js/utils.js
@@ -39,5 +39,14 @@
cur_frm.toggle_display("company", false);
}
}
- }
+ },
+
+ add_for_territory: function() {
+ if(cur_frm.doc.__islocal &&
+ wn.model.get_doclist(cur_frm.doc.doctype, cur_frm.doc.name).length === 1) {
+ var territory = wn.model.add_child(cur_frm.doc, "For Territory",
+ "valid_for_territories");
+ territory.territory = wn.defaults.get_default("territory");
+ }
+ },
});
\ No newline at end of file
diff --git a/selling/doctype/sales_common/sales_common.js b/selling/doctype/sales_common/sales_common.js
index 919de22..9c11eea 100644
--- a/selling/doctype/sales_common/sales_common.js
+++ b/selling/doctype/sales_common/sales_common.js
@@ -492,6 +492,21 @@
}
},
+ shipping_rule: function() {
+ var me = this;
+ if(this.frm.doc.shipping_rule) {
+ this.frm.call({
+ doc: this.frm.doc,
+ method: "apply_shipping_rule",
+ callback: function(r) {
+ if(!r.exc) {
+ me.calculate_taxes_and_totals();
+ }
+ }
+ })
+ }
+ },
+
set_dynamic_labels: function() {
this._super();
set_sales_bom_help(this.frm.doc);
diff --git a/setup/doctype/global_defaults/global_defaults.txt b/setup/doctype/global_defaults/global_defaults.txt
index 6fc07b6..b59ca94 100644
--- a/setup/doctype/global_defaults/global_defaults.txt
+++ b/setup/doctype/global_defaults/global_defaults.txt
@@ -2,7 +2,7 @@
{
"creation": "2013-05-02 17:53:24",
"docstatus": 0,
- "modified": "2013-07-05 14:39:17",
+ "modified": "2013-07-15 15:03:01",
"modified_by": "Administrator",
"owner": "Administrator"
},
@@ -179,7 +179,6 @@
"fieldname": "hr",
"fieldtype": "Section Break",
"label": "HR",
- "options": "<div style=\"padding-top: 8px;\" class=\"columnHeading\">HR</div>",
"read_only": 0
},
{
diff --git a/setup/doctype/price_list/price_list.js b/setup/doctype/price_list/price_list.js
index 3bfa63a..26c9854 100644
--- a/setup/doctype/price_list/price_list.js
+++ b/setup/doctype/price_list/price_list.js
@@ -14,43 +14,46 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
-cur_frm.cscript.onload = function() {
- cur_frm.cscript.show_item_prices();
-}
-
-cur_frm.cscript.refresh = function(doc, cdt, cdn) {
- cur_frm.set_intro("");
- if(doc.__islocal) {
- cur_frm.toggle_display("item_prices_section", false);
- cur_frm.set_intro("Save this list to begin.");
- return;
- } else {
+$.extend(cur_frm.cscript, {
+ onload: function() {
cur_frm.cscript.show_item_prices();
+ erpnext.add_for_territory();
+ },
+
+ refresh: function(doc) {
+ cur_frm.set_intro("");
+ if(doc.__islocal) {
+ cur_frm.toggle_display("item_prices_section", false);
+ cur_frm.set_intro("Save this list to begin.");
+ return;
+ } else {
+ cur_frm.cscript.show_item_prices();
+ }
+ },
+
+ show_item_prices: function() {
+ var item_price = wn.model.get("Item Price", {price_list_name: cur_frm.doc.name});
+
+ var show = item_price && item_price.length;
+
+ cur_frm.toggle_display("item_prices_section", show);
+ $(cur_frm.fields_dict.item_prices.wrapper).empty();
+ if (!show) return;
+
+ var out = '<table class="table table-striped table-bordered">\
+ <thead><tr>\
+ <th>' + wn._("Item Code") + '</th>\
+ <th>' + wn._("Price") + '</th>\
+ </tr></thead>\
+ <tbody>'
+ + $.map(item_price.sort(function(a, b) { return a.parent.localeCompare(b.parent); }), function(d) {
+ return '<tr>'
+ + '<td><a href="#Form/Item/' + encodeURIComponent(d.parent) +'">' + d.parent + '</a></td>'
+ + '<td style="text-align: right;">' + format_currency(d.ref_rate, d.ref_currency) + '</td>'
+ + '</tr>'
+ }).join("\n")
+ + '</tbody>\
+ </table>';
+ $(out).appendTo($(cur_frm.fields_dict.item_prices.wrapper));
}
-}
-
-cur_frm.cscript.show_item_prices = function() {
- var item_price = wn.model.get("Item Price", {price_list_name: cur_frm.doc.name});
-
- var show = item_price && item_price.length;
-
- cur_frm.toggle_display("item_prices_section", show);
- $(cur_frm.fields_dict.item_prices.wrapper).empty();
- if (!show) return;
-
- var out = '<table class="table table-striped table-bordered">\
- <thead><tr>\
- <th>' + wn._("Item Code") + '</th>\
- <th>' + wn._("Price") + '</th>\
- </tr></thead>\
- <tbody>'
- + $.map(item_price.sort(function(a, b) { return a.parent.localeCompare(b.parent); }), function(d) {
- return '<tr>'
- + '<td><a href="#Form/Item/' + encodeURIComponent(d.parent) +'">' + d.parent + '</a></td>'
- + '<td style="text-align: right;">' + format_currency(d.ref_rate, d.ref_currency) + '</td>'
- + '</tr>'
- }).join("\n")
- + '</tbody>\
- </table>';
- $(out).appendTo($(cur_frm.fields_dict.item_prices.wrapper));
-}
+});
\ No newline at end of file
diff --git a/startup/webutils.py b/startup/webutils.py
index 4c1f528..87d12e4 100644
--- a/startup/webutils.py
+++ b/startup/webutils.py
@@ -82,10 +82,10 @@
phone = party.phone
else:
mobile_no, phone = webnotes.conn.get_value("Contact", {"email_id": webnotes.session.user,
- "customer": party.name})
+ "customer": party.name}, ["mobile_no", "phone"])
return {
- "company_name": party.customer_name if party.doctype == "Customer" else party.company_name,
- "mobile_no": mobile_no,
- "phone": phone
+ "company_name": cstr(party.customer_name if party.doctype == "Customer" else party.company_name),
+ "mobile_no": cstr(mobile_no),
+ "phone": cstr(phone)
}
\ No newline at end of file
diff --git a/website/css/website.css b/website/css/website.css
index b71ff13..f2880e8 100644
--- a/website/css/website.css
+++ b/website/css/website.css
@@ -186,3 +186,8 @@
background-color: #a7a9aa;
border-color: #a7a9aa;
}
+
+.breadcrumb {
+ margin: 0 -20px 20px;
+ border-radius: 0px;
+}
\ No newline at end of file
diff --git a/website/doctype/shopping_cart_settings/shopping_cart_settings.js b/website/doctype/shopping_cart_settings/shopping_cart_settings.js
new file mode 100644
index 0000000..74988dc
--- /dev/null
+++ b/website/doctype/shopping_cart_settings/shopping_cart_settings.js
@@ -0,0 +1,7 @@
+$.extend(cur_frm.cscript, {
+ onload: function() {
+ if(cur_frm.doc.__quotation_series) {
+ cur_frm.fields_dict.quotation_series.df.options = cur_frm.doc.__quotation_series;
+ }
+ }
+});
\ No newline at end of file
diff --git a/website/doctype/shopping_cart_settings/shopping_cart_settings.py b/website/doctype/shopping_cart_settings/shopping_cart_settings.py
index 7fe14c3..853eeb4 100644
--- a/website/doctype/shopping_cart_settings/shopping_cart_settings.py
+++ b/website/doctype/shopping_cart_settings/shopping_cart_settings.py
@@ -9,6 +9,9 @@
class ShoppingCartSetupError(webnotes.ValidationError): pass
class DocType(DocListController):
+ def onload(self):
+ self.doc.fields["__quotation_series"] = webnotes.get_doctype("Quotation").get_options("naming_series")
+
def validate(self):
if self.doc.enabled:
self.validate_price_lists()
@@ -17,6 +20,7 @@
def on_update(self):
webnotes.conn.set_default("shopping_cart_enabled", self.doc.fields.get("enabled") or 0)
+ webnotes.conn.set_default("shopping_cart_quotation_series", self.doc.fields.get("quotation_series"))
def validate_overlapping_territories(self, parentfield, fieldname):
# for displaying message
@@ -91,16 +95,17 @@
expected_to_exist = [currency + "-" + company_currency
for currency in price_list_currency_map.values()
if currency != company_currency]
-
- exists = webnotes.conn.sql_list("""select name from `tabCurrency Exchange`
- where name in (%s)""" % (", ".join(["%s"]*len(expected_to_exist)),),
- tuple(expected_to_exist))
+
+ if expected_to_exist:
+ exists = webnotes.conn.sql_list("""select name from `tabCurrency Exchange`
+ where name in (%s)""" % (", ".join(["%s"]*len(expected_to_exist)),),
+ tuple(expected_to_exist))
- missing = list(set(expected_to_exist).difference(exists))
+ missing = list(set(expected_to_exist).difference(exists))
- if missing:
- msgprint(_("Missing Currency Exchange Rates for" + ": " + comma_and(missing)),
- raise_exception=ShoppingCartSetupError)
+ 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
diff --git a/website/doctype/shopping_cart_settings/shopping_cart_settings.txt b/website/doctype/shopping_cart_settings/shopping_cart_settings.txt
index 7455864..21e6ee3 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-07-10 18:42:29",
+ "modified": "2013-07-15 17:33:05",
"modified_by": "Administrator",
"owner": "Administrator"
},
@@ -80,6 +80,13 @@
},
{
"doctype": "DocField",
+ "fieldname": "quotation_series",
+ "fieldtype": "Select",
+ "label": "Quotation Series",
+ "reqd": 1
+ },
+ {
+ "doctype": "DocField",
"fieldname": "section_break_6",
"fieldtype": "Section Break"
},
diff --git a/website/helpers/cart.py b/website/helpers/cart.py
index ea72e40..2149534 100644
--- a/website/helpers/cart.py
+++ b/website/helpers/cart.py
@@ -58,6 +58,10 @@
qty = flt(qty)
if qty == 0:
quotation.set_doclist(quotation.doclist.get({"item_code": ["!=", item_code]}))
+ if not quotation.doclist.get({"parentfield": "quotation_details"}) and \
+ not quotation.doc.fields.get("__islocal"):
+ quotation.__delete = True
+
else:
quotation_items = quotation.doclist.get({"item_code": item_code})
if not quotation_items:
@@ -72,8 +76,12 @@
apply_cart_settings(quotation=quotation)
- quotation.ignore_permissions = True
- quotation.save()
+ if hasattr(quotation, "__delete"):
+ webnotes.delete_doc("Quotation", quotation.doc.name, ignore_permissions=True)
+ quotation = _get_cart_quotation()
+ else:
+ quotation.ignore_permissions = True
+ quotation.save()
set_cart_count(quotation)
@@ -191,7 +199,7 @@
for d in doclist:
if d.item_code:
d.fields.update(webnotes.conn.get_value("Item", d.item_code,
- ["website_image", "web_short_description", "page_name"], as_dict=True))
+ ["website_image", "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:
@@ -215,7 +223,7 @@
else:
qbean = webnotes.bean({
"doctype": "Quotation",
- "naming_series": "QTN-CART-",
+ "naming_series": webnotes.defaults.get_user_default("shopping_cart_quotation_series") or "QTN-CART-",
"quotation_to": party.doctype,
"company": webnotes.defaults.get_user_default("company"),
"order_type": "Shopping Cart",
@@ -224,7 +232,10 @@
(party.doctype.lower()): party.name
})
- # map_contact_fields(qbean, party)
+ if party.doctype == "Customer":
+ qbean.doc.contact_person = webnotes.conn.get_value("Contact", {"email_id": webnotes.session.user,
+ "customer": party.name})
+ qbean.run_method("set_contact_fields")
qbean.run_method("onload_post_render")
apply_cart_settings(party, qbean)
@@ -259,10 +270,11 @@
party_bean.save()
qbean = _get_cart_quotation(party)
- qbean.doc.customer_name = company_name or fullname
- qbean.run_method("set_contact_fields")
- qbean.ignore_permissions = True
- qbean.save()
+ if not qbean.doc.fields.get("__islocal"):
+ qbean.doc.customer_name = company_name or fullname
+ qbean.run_method("set_contact_fields")
+ qbean.ignore_permissions = True
+ qbean.save()
def apply_cart_settings(party=None, quotation=None):
if not party:
@@ -374,27 +386,26 @@
return territory
-@webnotes.whitelist()
-def checkout():
- quotation = _get_cart_quotation()
-
- quotation.ignore_permissions = True
- quotation.submit()
-
- from selling.doctype.quotation.quotation import make_sales_order
-
- sales_order = webnotes.bean(make_sales_order(quotation.doc.name))
-
- sales_order.ignore_permissions = True
- sales_order.insert()
- sales_order.submit()
-
- return sales_order
-
import unittest
-test_dependencies = ["Item", "Price List", "Contact"]
+test_dependencies = ["Item", "Price List", "Contact", "Shopping Cart Settings"]
class TestCart(unittest.TestCase):
+ def tearDown(self):
+ return
+
+ cart_settings = webnotes.bean("Shopping Cart Settings")
+ cart_settings.ignore_permissions = True
+ cart_settings.doc.enabled = 0
+ cart_settings.save()
+
+ def enable_shopping_cart(self):
+ return
+ if not webnotes.conn.get_value("Shopping Cart Settings", None, "enabled"):
+ cart_settings = webnotes.bean("Shopping Cart Settings")
+ cart_settings.ignore_permissions = True
+ cart_settings.doc.enabled = 1
+ cart_settings.save()
+
def test_get_lead_or_customer(self):
webnotes.session.user = "test@example.com"
party1 = get_lead_or_customer()
@@ -407,7 +418,9 @@
self.assertEquals(party.name, "_Test Customer")
def test_add_to_cart(self):
+ self.enable_shopping_cart()
webnotes.session.user = "test@example.com"
+
update_cart("_Test Item", 1)
quotation = _get_cart_quotation()
@@ -440,8 +453,9 @@
quotation_items = quotation.doclist.get({"parentfield": "quotation_details", "item_code": "_Test Item"})
self.assertEquals(quotation_items, [])
- def test_checkout(self):
+ def test_place_order(self):
quotation = self.test_update_cart()
- sales_order = checkout()
+ sales_order_name = place_order()
+ sales_order = webnotes.bean("Sales Order", sales_order_name)
self.assertEquals(sales_order.doclist.getone({"item_code": "_Test Item"}).prevdoc_docname, quotation.doc.name)
\ No newline at end of file
diff --git a/website/templates/css/product_page.css b/website/templates/css/product_page.css
index 566b6b5..457fc62 100644
--- a/website/templates/css/product_page.css
+++ b/website/templates/css/product_page.css
@@ -7,4 +7,7 @@
font-size: 18px;
line-height: 200%;
}
+ .item-stock {
+ margin-bottom: 10px !important;
+ }
</style>
\ No newline at end of file
diff --git a/website/templates/html/product_page.html b/website/templates/html/product_page.html
index 73520ef..4f04cec 100644
--- a/website/templates/html/product_page.html
+++ b/website/templates/html/product_page.html
@@ -32,8 +32,7 @@
<p class="help">Item Code: <span itemprop="productID">{{ name }}</span></p>
<h4>Product Description</h4>
<div itemprop="description">
- {{ web_long_description or web_short_description or description or
- "[No description given]" }}
+ {{ web_long_description or description or "[No description given]" }}
</div>
<div style="min-height: 100px; margin: 10px 0;">
<div class="item-price-info" style="display: none;">
diff --git a/website/templates/js/cart.js b/website/templates/js/cart.js
index bb3fcb9..2f5ad31 100644
--- a/website/templates/js/cart.js
+++ b/website/templates/js/cart.js
@@ -143,7 +143,7 @@
'<div style="height: 120px; overflow: hidden;"><img src="' + doc.image + '" /></div>' :
'{% include "app/website/templates/html/product_missing_image.html" %}';
- if(!doc.web_short_description) doc.web_short_description = doc.description;
+ if(doc.description === doc.item_name) doc.description = "";
$(repl('<div class="row">\
<div class="col col-lg-9 col-sm-9">\
@@ -151,7 +151,7 @@
<div class="col col-lg-3">%(image_html)s</div>\
<div class="col col-lg-9">\
<h4><a href="%(page_name)s">%(item_name)s</a></h4>\
- <p>%(web_short_description)s</p>\
+ <p>%(description)s</p>\
</div>\
</div>\
</div>\