[MERGE] forward port branch saas-14 up to 53ba83579b
This commit is contained in:
@@ -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')
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 );
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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')
|
||||
|
||||
+7
-4
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user