diff --git a/addons/account/partner.py b/addons/account/partner.py
index d9bb6083f43..70f5280b861 100644
--- a/addons/account/partner.py
+++ b/addons/account/partner.py
@@ -27,13 +27,19 @@ from openerp.osv import fields, osv
class account_fiscal_position(osv.osv):
_name = 'account.fiscal.position'
_description = 'Fiscal Position'
+ _order = 'sequence'
_columns = {
+ 'sequence': fields.integer('Sequence'),
'name': fields.char('Fiscal Position', required=True),
'active': fields.boolean('Active', help="By unchecking the active field, you may hide a fiscal position without deleting it."),
'company_id': fields.many2one('res.company', 'Company'),
'account_ids': fields.one2many('account.fiscal.position.account', 'position_id', 'Account Mapping'),
'tax_ids': fields.one2many('account.fiscal.position.tax', 'position_id', 'Tax Mapping'),
'note': fields.text('Notes'),
+ 'auto_apply': fields.boolean('Automatic', help="Apply automatically this fiscal position."),
+ 'vat_required': fields.boolean('VAT required', help="Apply only if partner has a VAT number."),
+ 'country_id': fields.many2one('res.country', 'Countries', help="Apply only if delivery or invoicing country match."),
+ 'country_group_id': fields.many2one('res.country.group', 'Country Group', help="Apply only if delivery or invocing country match the group."),
}
_defaults = {
@@ -66,6 +72,33 @@ class account_fiscal_position(osv.osv):
break
return account_id
+ def get_fiscal_position(self, cr, uid, company_id, partner_id, delivery_id=None, context=None):
+ if not partner_id:
+ return False
+ # This can be easily overriden to apply more complex fiscal rules
+ part_obj = self.pool['res.partner']
+ partner = part_obj.browse(cr, uid, partner_id, context=context)
+
+ # partner manually set fiscal position always win
+ if partner.property_account_position:
+ return part.property_account_position.id
+
+ # if no delivery use invocing
+ if delivery_id:
+ delivery = part_obj.browse(cr, uid, delivery_id, context=context)
+ else:
+ delivery = partner
+
+ domain = [
+ ('auto_apply', '=', True),
+ '|', ('vat_required', '=', False), ('vat_required', '=', partner.vat_subjected),
+ '|', ('country_id', '=', None), ('country_id', '=', delivery.country_id.id),
+ '|', ('country_group_id', '=', None), ('country_group_id.country_ids', '=', delivery.country_id.id)
+ ]
+ fiscal_position_ids = self.search(cr, uid, domain, context=context)
+ if fiscal_position_ids:
+ return fiscal_position_ids[0]
+ return False
class account_fiscal_position_tax(osv.osv):
_name = 'account.fiscal.position.tax'
@@ -206,6 +239,7 @@ class res_partner(osv.osv):
return self.write(cr, uid, ids, {'last_reconciliation_date': time.strftime('%Y-%m-%d %H:%M:%S')}, context=context)
_columns = {
+ 'vat_subjected': fields.boolean('VAT Legal Statement', help="Check this box if the partner is subjected to the VAT. It will be used for the VAT legal statement."),
'credit': fields.function(_credit_debit_get,
fnct_search=_credit_search, string='Total Receivable', multi='dc', help="Total amount this customer owes you."),
'debit': fields.function(_credit_debit_get, fnct_search=_debit_search, string='Total Payable', multi='dc', help="Total amount you have to pay to this supplier."),
diff --git a/addons/account/partner_view.xml b/addons/account/partner_view.xml
index c2a01c7e40a..309296c7955 100644
--- a/addons/account/partner_view.xml
+++ b/addons/account/partner_view.xml
@@ -12,6 +12,13 @@
+
+
+
+
+
+
+
@@ -44,6 +51,7 @@
account.fiscal.position
+
diff --git a/addons/account/report/account_invoice_report_view.xml b/addons/account/report/account_invoice_report_view.xml
index 01e3dc72d68..35cb43467f2 100644
--- a/addons/account/report/account_invoice_report_view.xml
+++ b/addons/account/report/account_invoice_report_view.xml
@@ -21,7 +21,7 @@
-
+
diff --git a/addons/base_vat/base_vat.py b/addons/base_vat/base_vat.py
index 9b3862508b0..b7ac9592dd3 100644
--- a/addons/base_vat/base_vat.py
+++ b/addons/base_vat/base_vat.py
@@ -133,10 +133,6 @@ class res_partner(osv.osv):
def vat_change(self, cr, uid, ids, value, context=None):
return {'value': {'vat_subjected': bool(value)}}
- _columns = {
- 'vat_subjected': fields.boolean('VAT Legal Statement', help="Check this box if the partner is subjected to the VAT. It will be used for the VAT legal statement.")
- }
-
def _commercial_fields(self, cr, uid, context=None):
return super(res_partner, self)._commercial_fields(cr, uid, context=context) + ['vat_subjected']
diff --git a/addons/google_drive/google_drive_data.xml b/addons/google_drive/google_drive_data.xml
index f3f916b201b..72e541df56e 100644
--- a/addons/google_drive/google_drive_data.xml
+++ b/addons/google_drive/google_drive_data.xml
@@ -5,7 +5,7 @@
google_drive_client_id
598905559630.apps.googleusercontent.com
-
+
diff --git a/addons/mail/static/src/js/mail.js b/addons/mail/static/src/js/mail.js
index dbe7d54aade..b3fb0dab18f 100644
--- a/addons/mail/static/src/js/mail.js
+++ b/addons/mail/static/src/js/mail.js
@@ -1984,51 +1984,6 @@ openerp.mail = function (session) {
}
});
-
- /**
- * ------------------------------------------------------------
- * UserMenu
- * ------------------------------------------------------------
- *
- * Add a link on the top user bar for write a full mail
- */
- session.web.ComposeMessageTopButton = session.web.Widget.extend({
- template:'mail.ComposeMessageTopButton',
-
- start: function () {
- this.$el.on('click', this.on_compose_message);
- this._super();
- },
-
- on_compose_message: function (event) {
- event.preventDefault();
- event.stopPropagation();
- var action = {
- type: 'ir.actions.act_window',
- res_model: 'mail.compose.message',
- view_mode: 'form',
- view_type: 'form',
- views: [[false, 'form']],
- target: 'new',
- context: {},
- };
- session.client.action_manager.do_action(action);
- },
- });
-
- session.web.UserMenu.include({
- do_update: function(){
- var self = this;
- this._super.apply(this, arguments);
- this.update_promise.then(function() {
- var mail_button = new session.web.ComposeMessageTopButton();
- mail_button.appendTo(session.webclient.$el.parents().find('.oe_systray'));
- openerp.web.bus.trigger('resize'); // Re-trigger the reflow logic
- });
- },
- });
-
-
/**
* ------------------------------------------------------------
* Sub-widgets loading
diff --git a/addons/mail/static/src/xml/mail.xml b/addons/mail/static/src/xml/mail.xml
index c79b95224b1..86041cfcdae 100644
--- a/addons/mail/static/src/xml/mail.xml
+++ b/addons/mail/static/src/xml/mail.xml
@@ -335,18 +335,6 @@
-
-
-
-
-
-
-
-
-
diff --git a/addons/purchase_requisition/test/purchase_requisition.yml b/addons/purchase_requisition/test/purchase_requisition.yml
index 5ac152d3d29..355e13e0677 100644
--- a/addons/purchase_requisition/test/purchase_requisition.yml
+++ b/addons/purchase_requisition/test/purchase_requisition.yml
@@ -69,11 +69,11 @@
purchase_requisition = self.pool.get("purchase.requisition")
purchase_ids = self.search(cr, uid, [('requisition_id','=',ref("requisition1"))])
assert purchase_ids, "RFQ is not created."
- rfq = self.browse(cr, uid, purchase_ids[0], context=context)
- requisition = rfq.requisition_id
- supplier = rfq.partner_id
- assert supplier.id == ref('base.res_partner_12'), "RFQ Partner is not correspond."
- assert len(rfq.order_line) == len(requisition.line_ids), "Lines are not correspond."
+ for rfq in self.browse(cr, uid, purchase_ids, context=context):
+ if rfq.partner_id.id == ref('base.res_partner_12'):
+ break
+ else:
+ assert False, "No PO found for res_partner_12."
-
I confirmed RFQ which has best price.
-
diff --git a/addons/sale/sale.py b/addons/sale/sale.py
index 87ad2d80be0..5e44ad5b0f4 100644
--- a/addons/sale/sale.py
+++ b/addons/sale/sale.py
@@ -320,6 +320,16 @@ class sale_order(osv.osv):
context_lang.update({'lang': partner_lang})
return self.pool.get('res.users').browse(cr, uid, uid, context=context_lang).company_id.sale_note
+ def onchange_delivery_id(self, cr, uid, ids, company_id, partner_id, delivery_id, fiscal_position, context=None):
+ r = {'value': {}}
+ if not fiscal_position:
+ if not company_id:
+ company_id = self._get_default_company(cr, uid, context=context)
+ fiscal_position = self.pool['account.fiscal.position'].get_fiscal_position(cr, uid, company_id, partner_id, delivery_id, context=context)
+ if fiscal_position:
+ r['value']['fiscal_position'] = fiscal_position
+ return r
+
def onchange_partner_id(self, cr, uid, ids, part, context=None):
if not part:
return {'value': {'partner_invoice_id': False, 'partner_shipping_id': False, 'payment_term': False, 'fiscal_position': False}}
@@ -328,15 +338,15 @@ class sale_order(osv.osv):
addr = self.pool.get('res.partner').address_get(cr, uid, [part.id], ['delivery', 'invoice', 'contact'])
pricelist = part.property_product_pricelist and part.property_product_pricelist.id or False
payment_term = part.property_payment_term and part.property_payment_term.id or False
- fiscal_position = part.property_account_position and part.property_account_position.id or False
dedicated_salesman = part.user_id and part.user_id.id or uid
val = {
'partner_invoice_id': addr['invoice'],
'partner_shipping_id': addr['delivery'],
'payment_term': payment_term,
- 'fiscal_position': fiscal_position,
'user_id': dedicated_salesman,
}
+ delivery_onchange = self.onchange_delivery_id(cr, uid, ids, False, part.id, addr['delivery'], False, context=context)
+ val.update(delivery_onchange['value'])
if pricelist:
val['pricelist_id'] = pricelist
sale_note = self.get_salenote(cr, uid, ids, part.id, context=context)
@@ -345,11 +355,14 @@ class sale_order(osv.osv):
def create(self, cr, uid, vals, context=None):
if context is None:
- context = {}
+ context = {}
if vals.get('name', '/') == '/':
vals['name'] = self.pool.get('ir.sequence').get(cr, uid, 'sale.order') or '/'
- if vals.get('partner_id') and any(f not in vals for f in ['partner_invoice_id', 'partner_shipping_id', 'pricelist_id']):
- defaults = self.onchange_partner_id(cr, uid, [], vals['partner_id'], context)['value']
+ if vals.get('partner_id') and any(f not in vals for f in ['partner_invoice_id', 'partner_shipping_id', 'pricelist_id', 'fiscal_position']):
+ defaults = self.onchange_partner_id(cr, uid, [], vals['partner_id'], context=context)['value']
+ if not vals.get('fiscal_position') and vals.get('partner_shipping_id'):
+ delivery_onchange = self.onchange_delivery_id(cr, uid, [], vals.get('company_id'), None, vals['partner_id'], vals.get('partner_shipping_id'), context=context)
+ defaults.update(delivery_onchange['value'])
vals = dict(defaults, **vals)
context.update({'mail_create_nolog': True})
new_id = super(sale_order, self).create(cr, uid, vals, context=context)
diff --git a/addons/sale/sale_view.xml b/addons/sale/sale_view.xml
index d5d072c4513..f61c518918e 100644
--- a/addons/sale/sale_view.xml
+++ b/addons/sale/sale_view.xml
@@ -102,7 +102,7 @@
-
+
diff --git a/addons/web/static/src/js/chrome.js b/addons/web/static/src/js/chrome.js
index 9fcbdff1f89..3534de032eb 100644
--- a/addons/web/static/src/js/chrome.js
+++ b/addons/web/static/src/js/chrome.js
@@ -1377,7 +1377,6 @@ instance.web.WebClient = instance.web.Client.extend({
var state = event.getState(true);
if(!state.action && state.menu_id) {
self.menu.is_bound.done(function() {
- self.menu.do_reload();
self.menu.menu_click(state.menu_id);
});
} else {
diff --git a/addons/website/tests/__init__.py b/addons/website/tests/__init__.py
index b7eb1831806..468a16017e8 100644
--- a/addons/website/tests/__init__.py
+++ b/addons/website/tests/__init__.py
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
import test_converter
-import test_requests
+import test_crawl
import test_ui
import test_views
diff --git a/addons/website/tests/cases.py b/addons/website/tests/cases.py
deleted file mode 100644
index 3e777293ff9..00000000000
--- a/addons/website/tests/cases.py
+++ /dev/null
@@ -1,35 +0,0 @@
-# -*- coding: utf-8 -*-
-import unittest2
-
-class URLCase(unittest2.TestCase):
- """
- URLCase moved out of test_requests, otherwise discovery attempts to
- instantiate and run it
- """
- def __init__(self, user, url, source, result):
- super(URLCase, self).__init__()
- self.user = user
- self.url = url
- self.source = source
- self.result = result
-
- @property
- def username(self):
- return self.user or "Anonymous Coward"
-
- def __str__(self):
- if self.source:
- return "%s (from %s, as %s)" % (self.url, self.source, self.username)
- return "%s (as %s)" % (self.url, self.username)
-
- __repr__ = __str__
-
- def shortDescription(self):
- return ""
-
- def runTest(self):
- code = self.result.getcode()
- self.assertIn(
- code, xrange(200, 300),
- "Fetching %s as %s returned an error response (%d)" % (
- self.url, self.username, code))
diff --git a/addons/website/tests/test_crawl.py b/addons/website/tests/test_crawl.py
new file mode 100644
index 00000000000..2d68410f6f9
--- /dev/null
+++ b/addons/website/tests/test_crawl.py
@@ -0,0 +1,90 @@
+# -*- coding: utf-8 -*-
+import logging
+import urlparse
+import unittest2
+import urllib2
+import time
+import werkzeug.urls
+
+import lxml.html
+
+import openerp
+from openerp import tools
+
+_logger = logging.getLogger(__name__)
+
+class Crawler(openerp.tests.HttpCase):
+ """ Test suite crawling an openerp CMS instance and checking that all
+ internal links lead to a 200 response.
+
+ If a username and a password are provided, authenticates the user before
+ starting the crawl
+ """
+
+ at_install = False
+ post_install = True
+
+ def crawl(self, url, seen=None, msg=''):
+ if seen == None:
+ seen = set()
+ if url in seen:
+ return seen
+ else:
+ seen.add(url)
+
+ _logger.info("%s %s", msg, url)
+ r = self.url_open(url)
+ code = r.getcode()
+ self.assertIn( code, xrange(200, 300), "%s Fetching %s returned error response (%d)" % (msg, url, code))
+
+ if r.info().gettype() == 'text/html':
+ doc = lxml.html.fromstring(r.read())
+ for link in doc.xpath('//a[@href]'):
+ href = link.get('href')
+
+ parts = urlparse.urlsplit(href)
+ # href with any fragment removed
+ href = urlparse.urlunsplit((
+ parts.scheme,
+ parts.netloc,
+ parts.path,
+ parts.query,
+ ''
+ ))
+
+ # FIXME: handle relative link (not parts.path.startswith /)
+ if parts.netloc or \
+ not parts.path.startswith('/') or \
+ parts.path == '/web' or\
+ parts.path.startswith('/web/') or \
+ parts.path.startswith('/en_US/') or \
+ (parts.scheme and parts.scheme not in ('http', 'https')):
+ continue
+
+ self.crawl(href, seen, msg)
+ return seen
+
+
+ def test_10_crawl_public(self):
+ t0 = time.time()
+ seen = self.crawl('/', msg='Anonymous Coward')
+ count = len(seen)
+ duration = time.time() - t0
+ _logger.log(25, "public crawled %s urls in %.2fs, %.3fs per query", count, duration, duration/count)
+
+ def test_20_crawl_demo(self):
+ t0 = time.time()
+ self.authenticate('demo', 'demo')
+ seen = self.crawl('/', msg='demo')
+ count = len(seen)
+ duration = time.time() - t0
+ _logger.log(25, "demo crawled %s urls in %.2fs, %.3fs per query", count, duration, duration/count)
+
+ def test_30_crawl_admin(self):
+ t0 = time.time()
+ self.authenticate('admin', 'admin')
+ seen = self.crawl('/', msg='admin')
+ count = len(seen)
+ duration = time.time() - t0
+ _logger.log(25, "admin crawled %s urls in %.2fs, %.3fs per query", count, duration, duration/count)
+
diff --git a/addons/website/tests/test_requests.py b/addons/website/tests/test_requests.py
deleted file mode 100644
index 2a1eea4c877..00000000000
--- a/addons/website/tests/test_requests.py
+++ /dev/null
@@ -1,155 +0,0 @@
-# -*- coding: utf-8 -*-
-import urlparse
-import unittest2
-import urllib2
-import werkzeug.urls
-
-import lxml.html
-
-import openerp
-from openerp import tools
-
-import cases
-
-__all__ = ['load_tests', 'CrawlSuite']
-
-class RedirectHandler(urllib2.HTTPRedirectHandler):
- """
- HTTPRedirectHandler is predicated upon HTTPErrorProcessor being used and
- works by intercepting 3xy "errors".
-
- Inherit from it to handle 3xy non-error responses instead, as we're not
- using the error processor
- """
-
- def http_response(self, request, response):
- code, msg, hdrs = response.code, response.msg, response.info()
-
- if 300 <= code < 400:
- return self.parent.error(
- 'http', request, response, code, msg, hdrs)
-
- return response
-
- https_response = http_response
-
-class CrawlSuite(unittest2.TestSuite):
- """ Test suite crawling an openerp CMS instance and checking that all
- internal links lead to a 200 response.
-
- If a username and a password are provided, authenticates the user before
- starting the crawl
- """
-
- at_install = False
- post_install = True
-
- def __init__(self, user=None, password=None):
- super(CrawlSuite, self).__init__()
-
- registry = openerp.registry(tools.config['db_name'])
- try:
- # switch registry to test mode, so that requests can be made
- registry.enter_test_mode()
-
- self.opener = urllib2.OpenerDirector()
- self.opener.add_handler(urllib2.UnknownHandler())
- self.opener.add_handler(urllib2.HTTPHandler())
- self.opener.add_handler(urllib2.HTTPSHandler())
- self.opener.add_handler(urllib2.HTTPCookieProcessor())
- self.opener.add_handler(RedirectHandler())
-
- self._authenticate(user, password)
- self.user = user
-
- finally:
- registry.leave_test_mode()
-
- def _request(self, path):
- return self.opener.open(urlparse.urlunsplit([
- 'http', 'localhost:%s' % tools.config['xmlrpc_port'],
- path, '', ''
- ]))
-
- def _authenticate(self, user, password):
- # force tools.config['db_name'] in user session so opening `/` doesn't
- # blow up in multidb situations
- self.opener.open('http://localhost:{port}/web/?db={db}'.format(
- port=tools.config['xmlrpc_port'],
- db=werkzeug.urls.url_quote_plus(tools.config['db_name']),
- ))
- if user is not None:
- url = 'http://localhost:{port}/login?{query}'.format(
- port=tools.config['xmlrpc_port'],
- query=werkzeug.urls.url_encode({
- 'db': tools.config['db_name'],
- 'login': user,
- 'key': password,
- })
- )
- auth = self.opener.open(url)
- assert auth.getcode() < 400, "Auth failure %d" % auth.getcode()
-
- def _wrapped_run(self, result, debug=False):
- registry = openerp.registry(tools.config['db_name'])
- try:
- # switch registry to test mode, so that requests can be made
- registry.enter_test_mode()
-
- paths = [URL('/')]
- seen = set(paths)
-
- while paths:
- url = paths.pop(0)
- r = self._request(url.url)
- url.to_case(self.user, r).run(result)
-
- if r.info().gettype() != 'text/html':
- continue
-
- doc = lxml.html.fromstring(r.read())
- for link in doc.xpath('//a[@href]'):
- href = link.get('href')
-
- parts = urlparse.urlsplit(href)
- # href with any fragment removed
- href = urlparse.urlunsplit((
- parts.scheme,
- parts.netloc,
- parts.path,
- parts.query,
- ''
- ))
-
- # avoid repeats, even for links we won't crawl no need to
- # bother splitting them if we've already ignored them
- # previously
- if href in seen: continue
- seen.add(href)
-
- # FIXME: handle relative link (not parts.path.startswith /)
- if parts.netloc or \
- not parts.path.startswith('/') or \
- parts.path == '/web' or\
- parts.path.startswith('/web/') or \
- (parts.scheme and parts.scheme not in ('http', 'https')):
- continue
-
- paths.append(URL(href, url.url))
-
- finally:
- registry.leave_test_mode()
-
-class URL(object):
- def __init__(self, url, source=None):
- self.url = url
- self.source = source
-
- def to_case(self, user, result):
- return cases.URLCase(user, self.url, self.source, result)
-
-def load_tests(loader, base, _):
- base.addTest(CrawlSuite())
- base.addTest(CrawlSuite('admin', 'admin'))
- base.addTest(CrawlSuite('demo', 'demo'))
- return base
diff --git a/addons/website_sale/controllers/main.py b/addons/website_sale/controllers/main.py
index f187e6799ac..656f581bc63 100644
--- a/addons/website_sale/controllers/main.py
+++ b/addons/website_sale/controllers/main.py
@@ -456,6 +456,7 @@ class website_sale(http.Controller):
if partner_id and request.website.partner_id.id != partner_id:
orm_partner.write(cr, SUPERUSER_ID, [partner_id], billing_info, context=context)
else:
+ # create partner
partner_id = orm_partner.create(cr, SUPERUSER_ID, billing_info, context=context)
# create a new shipping partner
@@ -472,7 +473,9 @@ class website_sale(http.Controller):
'partner_invoice_id': partner_id,
'partner_shipping_id': shipping_id or partner_id
}
- order_info.update(registry.get('sale.order').onchange_partner_id(cr, SUPERUSER_ID, [], partner_id, context=context)['value'])
+ order_info.update(order_obj.onchange_partner_id(cr, SUPERUSER_ID, [], partner_id, context=context)['value'])
+ order_info.update(order_obj.onchange_delivery_id(cr, SUPERUSER_ID, [], order.company_id.id, partner_id, shipping_id, None, context=context)['value'])
+
order_info.pop('user_id')
order_obj.write(cr, SUPERUSER_ID, [order.id], order_info, context=context)
@@ -512,6 +515,8 @@ class website_sale(http.Controller):
self.checkout_form_save(values["checkout"])
request.session['sale_last_order_id'] = order.id
+ request.website.sale_get_order(update_pricelist=True, context=context)
+
return request.redirect("/shop/payment")
#------------------------------------------------------
diff --git a/addons/website_sale/models/sale_order.py b/addons/website_sale/models/sale_order.py
index 165030f9f1a..7c0700a555a 100644
--- a/addons/website_sale/models/sale_order.py
+++ b/addons/website_sale/models/sale_order.py
@@ -128,7 +128,7 @@ class website(orm.Model):
def sale_product_domain(self, cr, uid, ids, context=None):
return [("sale_ok", "=", True)]
- def sale_get_order(self, cr, uid, ids, force_create=False, code=None, context=None):
+ def sale_get_order(self, cr, uid, ids, force_create=False, code=None, update_pricelist=None, context=None):
sale_order_obj = self.pool['sale.order']
sale_order_id = request.session.get('sale_order_id')
sale_order = None
@@ -157,26 +157,20 @@ class website(orm.Model):
request.session['sale_order_id'] = None
return None
- def update_pricelist(pricelist_id):
- values = {'pricelist_id': pricelist_id}
- values.update(sale_order.onchange_pricelist_id(pricelist_id, None)['value'])
- sale_order.write(values)
- for line in sale_order.order_line:
- sale_order._cart_update(product_id=line.product_id.id, add_qty=0)
-
# check for change of pricelist with a coupon
if code and code != sale_order.pricelist_id.code:
pricelist_ids = self.pool['product.pricelist'].search(cr, SUPERUSER_ID, [('code', '=', code)], context=context)
if pricelist_ids:
pricelist_id = pricelist_ids[0]
request.session['sale_order_code_pricelist_id'] = pricelist_id
- update_pricelist(pricelist_id)
+ update_pricelist = True
request.session['sale_order_code_pricelist_id'] = False
+ pricelist_id = request.session.get('sale_order_code_pricelist_id') or partner.property_product_pricelist.id
+
# check for change of partner_id ie after signup
if sale_order.partner_id.id != partner.id and request.website.partner_id.id != partner.id:
flag_pricelist = False
- pricelist_id = request.session.get('sale_order_code_pricelist_id') or partner.property_product_pricelist.id
if pricelist_id != sale_order.pricelist_id.id:
flag_pricelist = True
fiscal_position = sale_order.fiscal_position and sale_order.fiscal_position.id or False
@@ -190,7 +184,15 @@ class website(orm.Model):
sale_order_obj.write(cr, SUPERUSER_ID, [sale_order_id], values, context=context)
if flag_pricelist or values.get('fiscal_position') != fiscal_position:
- update_pricelist(pricelist_id)
+ update_pricelist = True
+
+ # update the pricelist
+ if update_pricelist:
+ values = {'pricelist_id': pricelist_id}
+ values.update(sale_order.onchange_pricelist_id(pricelist_id, None)['value'])
+ sale_order.write(values)
+ for line in sale_order.order_line:
+ sale_order._cart_update(product_id=line.product_id.id, add_qty=0)
# update browse record
if (code and code != sale_order.pricelist_id.code) or sale_order.partner_id.id != partner.id:
diff --git a/odoo.py b/odoo.py
index a998c3f8db6..b8f93ad19d5 100755
--- a/odoo.py
+++ b/odoo.py
@@ -68,7 +68,7 @@ def cmd_setup_git():
git_dir = os.getcwd()
if git_dir:
# push sane config for git < 2.0, and hooks
- run('git','config','push.default','simple')
+ #run('git','config','push.default','simple')
# alias
run('git','config','alias.st','status')
# merge bzr style
diff --git a/openerp/addons/base/ir/ir_ui_menu.py b/openerp/addons/base/ir/ir_ui_menu.py
index 8ace579defb..718dcb0c36c 100644
--- a/openerp/addons/base/ir/ir_ui_menu.py
+++ b/openerp/addons/base/ir/ir_ui_menu.py
@@ -349,6 +349,7 @@ class ir_ui_menu(osv.osv):
menu_domain = [('parent_id', '=', False)]
return self.search(cr, uid, menu_domain, context=context)
+ @tools.ormcache()
def load_menus(self, cr, uid, context=None):
""" Loads all menu items (all applications and their sub-menus).
diff --git a/openerp/addons/base/res/res_country.py b/openerp/addons/base/res/res_country.py
index 10f82df2ee8..f086f1440e8 100644
--- a/openerp/addons/base/res/res_country.py
+++ b/openerp/addons/base/res/res_country.py
@@ -84,6 +84,14 @@ addresses belonging to this country.\n\nYou can use the python-style string pate
context=context)
+class CountryGroup(osv.osv):
+ _description="Country Group"
+ _name = 'res.country.group'
+ _columns = {
+ 'name': fields.char('Name', required=True),
+ 'country_ids': fields.many2many('res.country', string='Countries'),
+ }
+
class CountryState(osv.osv):
_description="Country state"
_name = 'res.country.state'
diff --git a/openerp/addons/base/res/res_country_data.xml b/openerp/addons/base/res/res_country_data.xml
index 633b4cc42c4..1bdd6a73460 100644
--- a/openerp/addons/base/res/res_country_data.xml
+++ b/openerp/addons/base/res/res_country_data.xml
@@ -1516,5 +1516,18 @@
+
+
+
+
+ Europe
+
+
diff --git a/openerp/addons/base/res/res_country_view.xml b/openerp/addons/base/res/res_country_view.xml
index ad46235c5c6..f1316844dd7 100644
--- a/openerp/addons/base/res/res_country_view.xml
+++ b/openerp/addons/base/res/res_country_view.xml
@@ -50,6 +50,38 @@
+
+ res.country.group.tree
+ res.country.group
+
+
+
+
+
+
+
+
+ res.country.group.form
+ res.country.group
+
+
+
+
+
+
+ Country Group
+ ir.actions.act_window
+ res.country.group
+ form
+ Display and manage the list of all countries group. You can create or delete country group to make sure the ones you are working on will be maintained.
+
+
+
+
+
diff --git a/openerp/addons/base/security/ir.model.access.csv b/openerp/addons/base/security/ir.model.access.csv
index c70733b5140..bbf579486a4 100644
--- a/openerp/addons/base/security/ir.model.access.csv
+++ b/openerp/addons/base/security/ir.model.access.csv
@@ -45,8 +45,10 @@
"access_res_company_group_user","res_company group_user","model_res_company",,1,0,0,0
"access_res_country_group_all","res_country group_user_all","model_res_country",,1,0,0,0
"access_res_country_state_group_all","res_country_state group_user_all","model_res_country_state",,1,0,0,0
+"access_res_country_group_group_all","res_country_group group_user_all","model_res_country_group",,1,0,0,0
"access_res_country_group_user","res_country group_user","model_res_country","group_partner_manager",1,1,1,1
"access_res_country_state_group_user","res_country_state group_user","model_res_country_state","group_partner_manager",1,1,1,1
+"access_res_country_group_group_user","res_country_group group_user","model_res_country_group","group_partner_manager",1,1,1,1
"access_res_currency_group_all","res_currency group_all","model_res_currency",,1,0,0,0
"access_res_currency_rate_group_all","res_currency_rate group_all","model_res_currency_rate",,1,0,0,0
"access_res_currency_rate_type_group_all","res_currency_rate_type group_all","model_res_currency_rate_type",,1,0,0,0
diff --git a/openerp/modules/loading.py b/openerp/modules/loading.py
index d8391f4757c..b9b421a20ca 100644
--- a/openerp/modules/loading.py
+++ b/openerp/modules/loading.py
@@ -226,17 +226,17 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules=
if hasattr(package, kind):
delattr(package, kind)
- _logger.log(25, "%s loaded in %.2fs", package.name, time.time() - tm0)
+ #_logger.log(25, "%s loaded in %.2fs", package.name, time.time() - tm0)
registry._init_modules.add(package.name)
cr.commit()
- _logger.log(25, "%s modules loaded in %.2fs", len(graph), time.time() - ta0)
+ _logger.log(25, "%s modules loaded in %.2fs", len(graph), time.time() - ta0)
# The query won't be valid for models created later (i.e. custom model
# created after the registry has been loaded), so empty its result.
registry.fields_by_model = None
-
+
cr.commit()
return loaded_modules, processed_modules
diff --git a/openerp/modules/module.py b/openerp/modules/module.py
index 18a0cdbf727..66119ce31f1 100644
--- a/openerp/modules/module.py
+++ b/openerp/modules/module.py
@@ -428,7 +428,8 @@ def run_unit_tests(module_name, dbname, position=runs_at_install):
tm0 = time.time()
_logger.info('%s running tests.', m.__name__)
result = unittest2.TextTestRunner(verbosity=2, stream=TestStream(m.__name__)).run(suite)
- _logger.log(25, "%s tested in %.2fs", m.__name__, time.time() - tm0)
+ if time.time() - tm0 > 5:
+ _logger.log(25, "%s tested in %.2fs", m.__name__, time.time() - tm0)
if not result.wasSuccessful():
r = False
_logger.error("Module %s: %d failures, %d errors", module_name, len(result.failures), len(result.errors))
diff --git a/openerp/netsvc.py b/openerp/netsvc.py
index 0703c654f80..856faa8f61d 100644
--- a/openerp/netsvc.py
+++ b/openerp/netsvc.py
@@ -68,6 +68,8 @@ def LocalService(name):
with registry.cursor() as cr:
return registry['ir.actions.report.xml']._lookup_report(cr, name[len('report.'):])
+path_prefix = os.path.realpath(os.path.dirname(os.path.dirname(__file__)))
+
class PostgreSQLHandler(logging.Handler):
""" PostgreSQL Loggin Handler will store logs in the database, by default
the current database, can be set using --log-db=DBNAME
@@ -87,7 +89,8 @@ class PostgreSQLHandler(logging.Handler):
msg = "%s\n%s" % (msg, traceback)
# we do not use record.levelname because it may have been changed by ColoredFormatter.
levelname = logging.getLevelName(record.levelno)
- val = ('server', ct_db, record.name, levelname, msg, record.pathname, record.lineno, record.funcName)
+
+ val = ('server', ct_db, record.name, levelname, msg, record.pathname[len(path_prefix)+1:], record.lineno, record.funcName)
cr.execute("""
INSERT INTO ir_logging(create_date, type, dbname, name, level, message, path, line, func)
VALUES (NOW() at time zone 'UTC', %s, %s, %s, %s, %s, %s, %s, %s)
diff --git a/openerp/tests/common.py b/openerp/tests/common.py
index 658ef89423d..50b85555d04 100644
--- a/openerp/tests/common.py
+++ b/openerp/tests/common.py
@@ -5,6 +5,7 @@ helpers and classes to write tests.
"""
import errno
+import glob
import json
import logging
import os
@@ -130,6 +131,25 @@ class SingleTransactionCase(BaseCase):
cls.cr.rollback()
cls.cr.close()
+class RedirectHandler(urllib2.HTTPRedirectHandler):
+ """
+ HTTPRedirectHandler is predicated upon HTTPErrorProcessor being used and
+ works by intercepting 3xy "errors".
+
+ Inherit from it to handle 3xy non-error responses instead, as we're not
+ using the error processor
+ """
+
+ def http_response(self, request, response):
+ code, msg, hdrs = response.code, response.msg, response.info()
+
+ if 300 <= code < 400:
+ return self.parent.error(
+ 'http', request, response, code, msg, hdrs)
+
+ return response
+
+ https_response = http_response
class HttpCase(TransactionCase):
""" Transactionnal HTTP TestCase with url_open and phantomjs helpers.
@@ -151,19 +171,29 @@ class HttpCase(TransactionCase):
self.session_id = self.session.sid
self.session.db = DB
openerp.http.root.session_store.save(self.session)
- self.localstorage_path = mkdtemp()
+ # setup an url opener helper
+ self.opener = urllib2.OpenerDirector()
+ self.opener.add_handler(urllib2.UnknownHandler())
+ self.opener.add_handler(urllib2.HTTPHandler())
+ self.opener.add_handler(urllib2.HTTPSHandler())
+ self.opener.add_handler(urllib2.HTTPCookieProcessor())
+ self.opener.add_handler(RedirectHandler())
+ self.opener.addheaders.append(('Cookie', 'session_id=%s' % self.session_id))
def tearDown(self):
- rmtree(self.localstorage_path)
self.registry.leave_test_mode()
super(HttpCase, self).tearDown()
def url_open(self, url, data=None, timeout=10):
- opener = urllib2.build_opener()
- opener.addheaders.append(('Cookie', 'session_id=%s' % self.session_id))
if url.startswith('/'):
url = "http://localhost:%s%s" % (PORT, url)
- return opener.open(url, data, timeout)
+ return self.opener.open(url, data, timeout)
+
+ def authenticate(self, user, password):
+ if user is not None:
+ url = '/login?%s' % werkzeug.urls.url_encode({'db': DB,'login': user, 'key': password})
+ auth = self.url_open(url)
+ assert auth.getcode() < 400, "Auth failure %d" % auth.getcode()
def phantom_poll(self, phantom, timeout):
""" Phantomjs Test protocol.
@@ -223,6 +253,11 @@ class HttpCase(TransactionCase):
def phantom_run(self, cmd, timeout):
_logger.info('phantom_run executing %s', ' '.join(cmd))
+
+ ls_glob = os.path.expanduser('~/.qws/share/data/Ofi Labs/PhantomJS/http_localhost_%s.*'%PORT)
+ for i in glob.glob(ls_glob):
+ _logger.info('phantomjs unlink localstorage %s', i)
+ os.unlink(i)
try:
phantom = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
except OSError:
@@ -265,7 +300,6 @@ class HttpCase(TransactionCase):
# phantom.args[1] == options
cmd = [
'phantomjs',
- '--local-storage-path', self.localstorage_path,
jsfile, phantomtest, json.dumps(options)
]
self.phantom_run(cmd, timeout)
diff --git a/openerp/tests/phantomtest.js b/openerp/tests/phantomtest.js
index 84f886c5dbb..49da52e07d9 100644
--- a/openerp/tests/phantomtest.js
+++ b/openerp/tests/phantomtest.js
@@ -116,6 +116,8 @@ function PhantomTest() {
phantom.exit(1);
} else {
console.log('loaded', url, status);
+ // clear localstorage leftovers
+ self.page.evaluate(function () { localStorage.clear() });
// process ready
waitFor(function() {
console.log("PhantomTest.run: wait for condition:", ready);