diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py index 894adf3e278..ea07610662e 100644 --- a/addons/account/models/account_invoice.py +++ b/addons/account/models/account_invoice.py @@ -111,7 +111,12 @@ class AccountInvoice(models.Model): def _get_outstanding_info_JSON(self): self.outstanding_credits_debits_widget = json.dumps(False) if self.state == 'open': - domain = [('account_id', '=', self.account_id.id), ('partner_id', '=', self.env['res.partner']._find_accounting_partner(self.partner_id).id), ('reconciled', '=', False), '|', ('amount_residual', '!=', 0.0), ('amount_residual_currency', '!=', 0.0)] + domain = [('account_id', '=', self.account_id.id), + ('partner_id', '=', self.env['res.partner']._find_accounting_partner(self.partner_id).id), + ('reconciled', '=', False), + '|', + '&', ('amount_residual_currency', '!=', 0.0), ('currency_id','!=', None), + '&', ('amount_residual_currency', '=', 0.0), '&', ('currency_id','=', None), ('amount_residual', '!=', 0.0)] if self.type in ('out_invoice', 'in_refund'): domain.extend([('credit', '>', 0), ('debit', '=', 0)]) type_payment = _('Outstanding credits') diff --git a/addons/product/models/product_pricelist.py b/addons/product/models/product_pricelist.py index b7652b5a226..bca9dcc4f1e 100644 --- a/addons/product/models/product_pricelist.py +++ b/addons/product/models/product_pricelist.py @@ -291,37 +291,44 @@ class Pricelist(models.Model): def _get_partner_pricelist(self, partner_id, company_id=None): """ Retrieve the applicable pricelist for a given partner in a given company. - :param company_id: if passed, used for looking up properties, instead of current user's company """ + res = self._get_partner_pricelist_multi([partner_id], company_id) + return res[partner_id].id + + def _get_partner_pricelist_multi(self, partner_ids, company_id=None): + """ Retrieve the applicable pricelist for given partners in a given company. + :param company_id: if passed, used for looking up properties, + instead of current user's company + :return: a dict {partner_id: pricelist} + """ Partner = self.env['res.partner'] Property = self.env['ir.property'].with_context(force_company=company_id or self.env.user.company_id.id) + Pricelist = self.env['product.pricelist'] - p = Partner.browse(partner_id) - pl = Property.get('property_product_pricelist', Partner._name, '%s,%s' % (Partner._name, p.id)) - if pl: - pl = pl[0].id + # retrieve values of property + result = Property.get_multi('property_product_pricelist', Partner._name, partner_ids) - if not pl: - if p.country_id.code: - pls = self.env['product.pricelist'].search([('country_group_ids.country_ids.code', '=', p.country_id.code)], limit=1) - pl = pls and pls[0].id + remaining_partner_ids = [pid for pid, val in result.items() if not val] + if remaining_partner_ids: + # get fallback pricelist when no pricelist for a given country + pl_fallback = ( + Pricelist.search([('country_group_ids', '=', False)], limit=1) or + Property.get('property_product_pricelist', 'res.partner') or + Pricelist.search([], limit=1) + ) + # group partners by country, and find a pricelist for each country + domain = [('id', 'in', remaining_partner_ids)] + groups = Partner.read_group(domain, ['country_id'], ['country_id']) + for group in groups: + country_id = group['country_id'] and group['country_id'][0] + pl = Pricelist.search([('country_group_ids.country_ids', '=', country_id)], limit=1) + pl = pl or pl_fallback + for pid in Partner.search(group['__domain']).ids: + result[pid] = pl - if not pl: - # search pl where no country - pls = self.env['product.pricelist'].search([('country_group_ids', '=', False)], limit=1) - pl = pls and pls[0].id - - if not pl: - prop = Property.get('property_product_pricelist', 'res.partner') - pl = prop and prop[0].id - - if not pl: - pls = self.env['product.pricelist'].search([], limit=1) - pl = pls and pls[0].id - - return pl + return result class ResCountryGroup(models.Model): diff --git a/addons/product/models/res_partner.py b/addons/product/models/res_partner.py index d307bd6ab09..96ec08ffa75 100644 --- a/addons/product/models/res_partner.py +++ b/addons/product/models/res_partner.py @@ -8,17 +8,18 @@ class Partner(models.Model): _name = 'res.partner' _inherit = 'res.partner' + # NOT A REAL PROPERTY property_product_pricelist = fields.Many2one( 'product.pricelist', 'Sale Pricelist', compute='_compute_product_pricelist', - inverse="_inverse_product_pricelist", company_dependent=False, # NOT A REAL PROPERTY + inverse="_inverse_product_pricelist", company_dependent=False, help="This pricelist will be used, instead of the default one, for sales to the current partner") @api.multi @api.depends('country_id') def _compute_product_pricelist(self): + res = self.env['product.pricelist']._get_partner_pricelist_multi(self.ids) for p in self: - if not isinstance(p.id, models.NewId): # if not onchange - p.property_product_pricelist = self.env['product.pricelist']._get_partner_pricelist(p.id) + p.property_product_pricelist = res.get(p.id) @api.one def _inverse_product_pricelist(self): diff --git a/addons/sale/models/res_partner.py b/addons/sale/models/res_partner.py index 27a70360d45..0ac1409cd00 100644 --- a/addons/sale/models/res_partner.py +++ b/addons/sale/models/res_partner.py @@ -16,12 +16,9 @@ class ResPartner(models.Model): def _compute_sale_order_count(self): sale_data = self.env['sale.order'].read_group(domain=[('partner_id', 'child_of', self.ids)], fields=['partner_id'], groupby=['partner_id']) - # read to keep the child/parent relation while aggregating the read_group result in the loop - partner_child_ids = self.read(['child_ids']) mapped_data = dict([(m['partner_id'][0], m['partner_id_count']) for m in sale_data]) for partner in self: - # let's obtain the partner id and all its child ids from the read up there - partner_ids = filter(lambda r: r['id'] == partner.id, partner_child_ids)[0] - partner_ids = [partner_ids.get('id')] + partner_ids.get('child_ids') + # let's obtain the partner id and all its child ids + partner_ids = [partner.id] + partner.child_ids.ids # then we can sum for all the partner's child partner.sale_order_count = sum(mapped_data.get(child, 0) for child in partner_ids) diff --git a/addons/stock_account/models/stock.py b/addons/stock_account/models/stock.py index d2a853f2253..ca40bd9aebc 100644 --- a/addons/stock_account/models/stock.py +++ b/addons/stock_account/models/stock.py @@ -288,7 +288,7 @@ class StockMove(models.Model): valuation_amount = self._context.get('force_valuation_amount') else: if self.product_id.cost_method == 'average': - valuation_amount = cost if self.location_id.usage == 'supplier' and self.location_dest_id.usage == 'internal' else self.product_id.standard_price + valuation_amount = cost if self.location_id.usage in ['supplier', 'production'] and self.location_dest_id.usage == 'internal' else self.product_id.standard_price else: valuation_amount = cost if self.product_id.cost_method == 'real' else self.product_id.standard_price # the standard_price of the product may be in another decimal precision, or not compatible with the coinage of diff --git a/addons/website_event/static/src/js/website_geolocation.js b/addons/website_event/static/src/js/website_geolocation.js index 43fb2209aef..040a436363c 100644 --- a/addons/website_event/static/src/js/website_geolocation.js +++ b/addons/website_event/static/src/js/website_geolocation.js @@ -4,11 +4,12 @@ odoo.define('website_event.geolocation', function (require) { var animation = require('web_editor.snippets.animation'); animation.registry.visitor = animation.Class.extend({ - selector: ".oe_country_events", + selector: ".oe_country_events, .country_events", start: function () { + var self = this; $.get("/event/get_country_event_list").then(function( data ) { if(data){ - $( ".country_events_list" ).replaceWith( data ); + self.$(".country_events_list").replaceWith( data ); } }); } diff --git a/odoo/addons/base/res/ir_property.py b/odoo/addons/base/res/ir_property.py index 810e41d17ff..02a7e49621c 100644 --- a/odoo/addons/base/res/ir_property.py +++ b/odoo/addons/base/res/ir_property.py @@ -160,11 +160,30 @@ class Property(models.Model): # note: order by 'company_id asc' will return non-null values first props = self.search(domain, order='company_id asc') result = {} - for prop in props: - # for a given res_id, take the first property only - id = refs.pop(prop.res_id, None) - if id is not None: - result[id] = prop.get_by_record() + field = self.env[model]._fields[name] + if field.type == 'many2one': + # optimization for many2one fields + Comodel = self.env[field.comodel_name] + co_ids = set() + for prop in props: + # for a given res_id, take the first property only + id = refs.pop(prop.res_id, None) + if id is not None: + val = prop.value_reference + if val: + val = int(val.split(',')[1]) + co_ids.add(val) + result[id] = val + # check for existence in batch, and update result accordingly + existing = {rec.id: rec for rec in Comodel.browse(co_ids).exists()} + result = {id: existing.get(val, Comodel) for id, val in result.items()} + + else: + for prop in props: + # for a given res_id, take the first property only + id = refs.pop(prop.res_id, None) + if id is not None: + result[id] = prop.get_by_record() # set the default value to the ids that are not in result default_value = result.pop(False, False) diff --git a/odoo/addons/base/tests/test_db_cursor.py b/odoo/addons/base/tests/test_db_cursor.py index c4fc2aac691..776f38abf2a 100644 --- a/odoo/addons/base/tests/test_db_cursor.py +++ b/odoo/addons/base/tests/test_db_cursor.py @@ -4,6 +4,7 @@ import unittest import odoo +from odoo.sql_db import TestCursor from odoo.tests import common from odoo.tools.misc import mute_logger @@ -28,3 +29,81 @@ class TestExecute(unittest.TestCase): cr.execute("SELECT id FROM res_users WHERE id=%s", 1) with self.assertRaises(ValueError): cr.execute("SELECT id FROM res_users WHERE id=%s", '1') + + +class TestTestCursor(common.TransactionCase): + @classmethod + def setUpClass(cls): + super(TestTestCursor, cls).setUpClass() + registry().enter_test_mode() + + @classmethod + def tearDownClass(cls): + registry().leave_test_mode() + super(TestTestCursor, cls).tearDownClass() + + def setUp(self): + super(TestTestCursor, self).setUp() + self.record = self.env['res.partner'].create({'name': 'Foo'}) + + def write(self, record, value): + record.ref = value + + def check(self, record, value): + self.assertEqual(record.read(['ref'])[0]['ref'], value) + + def test_single_cursor(self): + """ Check the behavior of a single test cursor. """ + self.assertIsInstance(self.cr, TestCursor) + self.write(self.record, 'A') + self.cr.commit() + + self.write(self.record, 'B') + self.cr.rollback() + self.check(self.record, 'A') + + self.write(self.record, 'C') + self.cr.rollback() + self.check(self.record, 'A') + + def test_sub_commit(self): + """ Check the behavior of a subcursor that commits. """ + self.assertIsInstance(self.cr, TestCursor) + self.write(self.record, 'A') + self.cr.commit() + + self.write(self.record, 'B') + + # check behavior of a "sub-cursor" that commits + with self.registry.cursor() as cr: + self.assertIsInstance(cr, TestCursor) + record = self.record.with_env(self.env(cr=cr)) + self.check(record, 'B') + self.write(record, 'C') + + self.check(self.record, 'C') + + self.cr.rollback() + self.check(self.record, 'A') + + def test_sub_rollback(self): + """ Check the behavior of a subcursor that rollbacks. """ + self.assertIsInstance(self.cr, TestCursor) + self.write(self.record, 'A') + self.cr.commit() + + self.write(self.record, 'B') + + # check behavior of a "sub-cursor" that rollbacks + with self.assertRaises(ValueError): + with self.registry.cursor() as cr: + self.assertIsInstance(cr, TestCursor) + record = self.record.with_env(self.env(cr=cr)) + self.check(record, 'B') + self.write(record, 'C') + raise ValueError(42) + + self.check(self.record, 'B') + + self.cr.rollback() + self.check(self.record, 'A') diff --git a/odoo/sql_db.py b/odoo/sql_db.py index 4cc6163a96f..74575c4e810 100644 --- a/odoo/sql_db.py +++ b/odoo/sql_db.py @@ -432,16 +432,18 @@ class TestCursor(Cursor): """ def __init__(self, *args, **kwargs): super(TestCursor, self).__init__(*args, **kwargs) - # in order to simulate commit and rollback, the cursor maintains a - # savepoint at its last commit - self.execute("SAVEPOINT test_cursor") # we use a lock to serialize concurrent requests self._lock = threading.RLock() def acquire(self): self._lock.acquire() + # the cursor maintains a savepoint at its last commit point + self.execute("SAVEPOINT test_cursor") def release(self): + # the cursor should be right after the savepoint; release it to make + # former savepoints directly accessible + self.execute("RELEASE SAVEPOINT test_cursor") self._lock.release() def force_close(self): @@ -456,12 +458,13 @@ class TestCursor(Cursor): _logger.debug("TestCursor.autocommit(%r) does nothing", on) def commit(self): + # move the savepoint to the current position self.execute("RELEASE SAVEPOINT test_cursor") self.execute("SAVEPOINT test_cursor") def rollback(self): + # this does not release the savepoint; release() will do it self.execute("ROLLBACK TO SAVEPOINT test_cursor") - self.execute("SAVEPOINT test_cursor") class LazyCursor(object): """ A proxy object to a cursor. The cursor itself is allocated only if it is