diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py index ea07610662e..9ca7859339f 100644 --- a/addons/account/models/account_invoice.py +++ b/addons/account/models/account_invoice.py @@ -500,12 +500,12 @@ class AccountInvoice(models.Model): msg = _('Cannot find a chart of accounts for this company, You should configure it. \nPlease go to Account Configuration.') raise RedirectWarning(msg, action.id, _('Go to the configuration panel')) - if type in ('out_invoice', 'out_refund'): - account_id = rec_account.id - payment_term_id = p.property_payment_term_id.id - else: + if type in ('in_invoice', 'in_refund'): account_id = pay_account.id payment_term_id = p.property_supplier_payment_term_id.id + else: + account_id = rec_account.id + payment_term_id = p.property_payment_term_id.id delivery_partner_id = self.get_delivery_partner_id() fiscal_position = self.env['account.fiscal.position'].get_fiscal_position(self.partner_id.id, delivery_id=delivery_partner_id) diff --git a/addons/barcodes/static/src/js/field_float_scannable.js b/addons/barcodes/static/src/js/field_float_scannable.js index 801cf32b5e2..5497a488c79 100644 --- a/addons/barcodes/static/src/js/field_float_scannable.js +++ b/addons/barcodes/static/src/js/field_float_scannable.js @@ -22,6 +22,10 @@ var FieldFloatScannable = form_widgets.FieldFloat.extend({ return formats.parse_value(val, {type: "float"}, def); }, + format_value: function(val, def) { + return formats.format_value(val, {type: "float"}, def); + }, + simulateKeypress: function (e) { /* only simulate a keypress if it has been previously prevented */ if (e.originalEvent.dispatched_by_barcode_reader !== true) { diff --git a/addons/hr_gamification/wizard/gamification_badge_user_wizard.py b/addons/hr_gamification/wizard/gamification_badge_user_wizard.py index 5bbb6848bd3..4c66db60db9 100644 --- a/addons/hr_gamification/wizard/gamification_badge_user_wizard.py +++ b/addons/hr_gamification/wizard/gamification_badge_user_wizard.py @@ -10,7 +10,7 @@ class GamificationBadgeUserWizard(models.TransientModel): employee_id = fields.Many2one('hr.employee', string='Employee', required=True) user_id = fields.Many2one('res.users', string='User', - related='employee_id.user_id', store=True, readonly=True) + related='employee_id.user_id', store=True, readonly=True, compute_sudo=True) # TODO 12.0/master remove this hack by changing the model @api.model diff --git a/addons/hr_holidays/models/hr_holidays.py b/addons/hr_holidays/models/hr_holidays.py index a4b9a48c67c..0573211a517 100644 --- a/addons/hr_holidays/models/hr_holidays.py +++ b/addons/hr_holidays/models/hr_holidays.py @@ -174,7 +174,7 @@ class Holidays(models.Model): payslip_status = fields.Boolean('Reported in last payslips', help='Green this button when the leave has been taken into account in the payslip.') report_note = fields.Text('HR Comments') - user_id = fields.Many2one('res.users', string='User', related='employee_id.user_id', related_sudo=True, store=True, default=lambda self: self.env.uid, readonly=True) + user_id = fields.Many2one('res.users', string='User', related='employee_id.user_id', related_sudo=True, compute_sudo=True, store=True, default=lambda self: self.env.uid, readonly=True) date_from = fields.Datetime('Start Date', readonly=True, index=True, copy=False, states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]}, track_visibility='onchange') date_to = fields.Datetime('End Date', readonly=True, copy=False, diff --git a/addons/hr_timesheet_sheet/models/hr_timesheet_sheet.py b/addons/hr_timesheet_sheet/models/hr_timesheet_sheet.py index f970d38ea32..75bb0cd9175 100644 --- a/addons/hr_timesheet_sheet/models/hr_timesheet_sheet.py +++ b/addons/hr_timesheet_sheet/models/hr_timesheet_sheet.py @@ -46,7 +46,7 @@ class HrTimesheetSheet(models.Model): name = fields.Char(string="Note", states={'confirm': [('readonly', True)], 'done': [('readonly', True)]}) employee_id = fields.Many2one('hr.employee', string='Employee', default=_default_employee, required=True) - user_id = fields.Many2one('res.users', related='employee_id.user_id', string='User', store=True, readonly=True) + user_id = fields.Many2one('res.users', related='employee_id.user_id', string='User', store=True, readonly=True, compute_sudo=True) date_from = fields.Date(string='Date From', default=_default_date_from, required=True, index=True, readonly=True, states={'new': [('readonly', False)]}) date_to = fields.Date(string='Date To', default=_default_date_to, required=True, diff --git a/addons/point_of_sale/static/src/css/pos.css b/addons/point_of_sale/static/src/css/pos.css index d4d48d1578a..dbe43fe5011 100644 --- a/addons/point_of_sale/static/src/css/pos.css +++ b/addons/point_of_sale/static/src/css/pos.css @@ -1472,6 +1472,8 @@ td { body { margin: 0; color: white !important; + /* avoid black background if backgrounds are printed */ + background: initial; } .oe_leftbar, .oe_loading, diff --git a/addons/sale/models/res_partner.py b/addons/sale/models/res_partner.py index 0ac1409cd00..05d3f21f05f 100644 --- a/addons/sale/models/res_partner.py +++ b/addons/sale/models/res_partner.py @@ -14,11 +14,17 @@ class ResPartner(models.Model): sale_warn_msg = fields.Text('Message for Sales Order') 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']) - 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 - 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) + # retrieve all children partners and prefetch 'parent_id' on them + all_partners = self.search([('id', 'child_of', self.ids)]) + all_partners.read(['parent_id']) + + sale_order_groups = self.env['sale.order'].read_group( + domain=[('partner_id', 'in', all_partners.ids)], + fields=['partner_id'], groupby=['partner_id'] + ) + for group in sale_order_groups: + partner = self.browse(group['partner_id'][0]) + while partner: + if partner in self: + partner.sale_order_count += group['partner_id_count'] + partner = partner.parent_id diff --git a/addons/stock/models/product.py b/addons/stock/models/product.py index 8192b4eec4c..a04ad250d9b 100644 --- a/addons/stock/models/product.py +++ b/addons/stock/models/product.py @@ -4,6 +4,7 @@ from odoo import api, fields, models, _ from odoo.addons import decimal_precision as dp from odoo.exceptions import UserError +from odoo.osv import expression from odoo.tools.float_utils import float_round from datetime import datetime import operator as py_operator @@ -370,6 +371,15 @@ class Product(models.Model): def action_view_routes(self): return self.mapped('product_tmpl_id').action_view_routes() + @api.model + def search(self, domain=None, *args, **kwargs): + # ONLY FOR 10.0 UP TO SAAS-15 + # ignore dummy fields used for search context² + for index in range(len(domain or [])): + if domain[index][0] in ('location_id', 'warehouse_id'): + domain[index] = expression.TRUE_LEAF + return super(Product, self).search(domain, *args, **kwargs) + @api.multi def write(self, values): res = super(Product, self).write(values) @@ -507,6 +517,15 @@ class ProductTemplate(models.Model): def onchange_tracking(self): return self.mapped('product_variant_ids').onchange_tracking() + @api.model + def search(self, domain=None, *args, **kwargs): + # ONLY FOR 10.0 UP TO SAAS-15 + # ignore dummy fields used for search context + for index in range(len(domain or [])): + if domain[index][0] in ('location_id', 'warehouse_id'): + domain[index] = expression.TRUE_LEAF + return super(ProductTemplate, self).search(domain, *args, **kwargs) + @api.multi def write(self, vals): if 'uom_id' in vals: diff --git a/addons/stock/models/stock_move.py b/addons/stock/models/stock_move.py index 5babba5f035..a88cc22c484 100644 --- a/addons/stock/models/stock_move.py +++ b/addons/stock/models/stock_move.py @@ -193,8 +193,8 @@ class StockMove(models.Model): if self.state == 'done': self.availability = self.product_qty else: - quants = self.env['stock.quant'].search([('location_id', 'child_of', self.location_id.id), ('product_id', '=', self.product_id.id), ('reservation_id', '=', False)]) - self.availability = min(self.product_qty, sum(quants.mapped('qty'))) + qty_tot = self.env['stock.quant'].read_group([('location_id', 'child_of', self.location_id.id), ('product_id', '=', self.product_id.id), ('reservation_id', '=', False)], ['qty'], [])[0] + self.availability = min(self.product_qty, qty_tot['qty'] or 0) @api.multi def _compute_string_qty_information(self): diff --git a/addons/stock/models/stock_quant.py b/addons/stock/models/stock_quant.py index b427acbd748..ab567103eaa 100644 --- a/addons/stock/models/stock_quant.py +++ b/addons/stock/models/stock_quant.py @@ -110,12 +110,11 @@ class Quant(models.Model): res = super(Quant, self).read_group(domain, fields, groupby, offset=offset, limit=limit, orderby=orderby, lazy=lazy) if 'inventory_value' in fields: for line in res: - if '__domain' in line: - lines = self.search(line['__domain']) - inv_value = 0.0 - for line2 in lines: - inv_value += line2.inventory_value - line['inventory_value'] = inv_value + lines = self.search(line.get('__domain', domain)) + inv_value = 0.0 + for line2 in lines: + inv_value += line2.inventory_value + line['inventory_value'] = inv_value return res @api.multi diff --git a/addons/web/static/src/js/framework/formats.js b/addons/web/static/src/js/framework/formats.js index c4d001a12e1..6d8e94d6541 100644 --- a/addons/web/static/src/js/framework/formats.js +++ b/addons/web/static/src/js/framework/formats.js @@ -47,6 +47,7 @@ function format_value (value, descriptor, value_if_empty) { _.str.sprintf('%d', value)); case 'monetary': case 'float': + case 'field_float_scannable': var digits = descriptor.digits ? descriptor.digits : [69,2]; digits = typeof digits === "string" ? py.eval(digits) : digits; var precision = digits[1]; @@ -135,6 +136,7 @@ function parse_value (value, descriptor, value_if_empty) { return tmp; case 'monetary': case 'float': + case 'field_float_scannable': var tmp2 = value; do { tmp = tmp2; diff --git a/addons/web/static/src/js/views/form_relational_widgets.js b/addons/web/static/src/js/views/form_relational_widgets.js index 466b92c1350..fb7bcafc630 100644 --- a/addons/web/static/src/js/views/form_relational_widgets.js +++ b/addons/web/static/src/js/views/form_relational_widgets.js @@ -265,7 +265,7 @@ var FieldMany2One = common.AbstractField.extend(common.CompletionFieldMixin, com }); // avoid ignoring autocomplete="off" by obfuscating placeholder, see #30439 - if (this.$input.attr('placeholder')) { + if ($.browser.chrome && this.$input.attr('placeholder')) { this.$input.attr('placeholder', function (index, val) { return val.split('').join('\ufeff'); }); diff --git a/addons/website_mail/views/website_mail_templates.xml b/addons/website_mail/views/website_mail_templates.xml index 824abeb0727..ee03fd2d722 100644 --- a/addons/website_mail/views/website_mail_templates.xml +++ b/addons/website_mail/views/website_mail_templates.xml @@ -77,7 +77,7 @@ on - +
diff --git a/doc/cla/corporate/savoirfairelinux.md b/doc/cla/corporate/savoirfairelinux.md index 6dca085c4ce..1a0d055ce13 100644 --- a/doc/cla/corporate/savoirfairelinux.md +++ b/doc/cla/corporate/savoirfairelinux.md @@ -21,6 +21,7 @@ Julien Jezequel-Breard julien.jezequel-breard@savoirfairelinux.com https://githu Quentin Lavallée-Bourdeau quentin.lavallee@savoirfairelinux.com https://github.com/qtiplb Rim Ben Dhaou rim.bendhaou@savoirfairelinux.com https://github.com/rimbendhaou Yasmine El Mrini yasmine.elmrini@savoirfairelinux.com https://github.com/yasmineelmrini +Dimitar Apostolov dimitar.apostolov@savoirfairelinux.com https://github.com/dapostolov-sfl Jérome Boisvert-Chouinard jerome.boisvertchouinard@savoirfairelinux.com https://github.com/jbchouinard (up to 2017-10-17) Julie Moussu julie.mousse@savoirfairelinux.com https://github.com/JulieSFL (up to 2017-10-17) Pierre Lamarche pierre.lamarche@savoirfairelinux.com https://github.com/plamarche (up to 2017-10-17) diff --git a/odoo/addons/base/ir/ir_qweb/qweb.py b/odoo/addons/base/ir/ir_qweb/qweb.py index 1670dfa1e51..a3db7664a0a 100644 --- a/odoo/addons/base/ir/ir_qweb/qweb.py +++ b/odoo/addons/base/ir/ir_qweb/qweb.py @@ -20,6 +20,8 @@ except ImportError: import logging +from odoo.tools.parse_version import parse_version + unsafe_eval = eval _logger = logging.getLogger(__name__) @@ -146,7 +148,7 @@ class QWebException(Exception): return str(self) # Avoid DeprecationWarning while still remaining compatible with werkzeug pre-0.9 -escape = (lambda text: _escape(text, quote=True)) if getattr(werkzeug, '__version__', '0.0') < '0.9.0' else _escape +escape = (lambda text: _escape(text, quote=True)) if parse_version(getattr(werkzeug, '__version__', '0.0')) < parse_version('0.9.0') else _escape def unicodifier(val): if val is None or val is False: diff --git a/odoo/addons/base/ir/ir_rule.py b/odoo/addons/base/ir/ir_rule.py index 13068389cce..a319b74a1a2 100644 --- a/odoo/addons/base/ir/ir_rule.py +++ b/odoo/addons/base/ir/ir_rule.py @@ -96,6 +96,8 @@ class IrRule(models.Model): group_domains.append(dom) # combine global domains and group domains + if not group_domains: + return expression.AND(global_domains) return expression.AND(global_domains + [expression.OR(group_domains)]) @api.model diff --git a/odoo/addons/base/tests/test_expression.py b/odoo/addons/base/tests/test_expression.py index 365876a9e16..9d3252fd7f2 100644 --- a/odoo/addons/base/tests/test_expression.py +++ b/odoo/addons/base/tests/test_expression.py @@ -612,6 +612,36 @@ class TestExpression(TransactionCase): not_be = Partner.with_context(lang='fr_FR').search([('country_id', '!=', 'Belgique')]) self.assertNotIn(agrolait, not_be) + def test_proper_combine_unit_leaves(self): + # test that unit leaves (TRUE_LEAF, FALSE_LEAF) are properly handled in specific cases + false = expression.FALSE_DOMAIN + true = expression.TRUE_DOMAIN + normal = [('foo', '=', 'bar')] + # OR with single FALSE_LEAF + expr = expression.OR([false]) + self.assertEqual(expr, false) + # OR with multiple FALSE_LEAF + expr = expression.OR([false, false]) + self.assertEqual(expr, false) + # OR with FALSE_LEAF and a normal leaf + expr = expression.OR([false, normal]) + self.assertEqual(expr, normal) + # OR with AND of single TRUE_LEAF and normal leaf + expr = expression.OR([expression.AND([true]), normal]) + self.assertEqual(expr, true) + # AND with single TRUE_LEAF + expr = expression.AND([true]) + self.assertEqual(expr, true) + # AND with multiple TRUE_LEAF + expr = expression.AND([true, true]) + self.assertEqual(expr, true) + # AND with TRUE_LEAF and normal leaves + expr = expression.AND([true, normal]) + self.assertEqual(expr, normal) + # AND with OR with single FALSE_LEAF and normal leaf + expr = expression.AND([expression.OR([false]), normal]) + self.assertEqual(expr, false) + class TestAutoJoin(TransactionCase): diff --git a/odoo/models.py b/odoo/models.py index 95f8d530ce3..58e63c228b0 100644 --- a/odoo/models.py +++ b/odoo/models.py @@ -965,6 +965,7 @@ class BaseModel(object): except ValidationError, e: raise except Exception, e: + _logger.exception('Exception while validating constraint') raise ValidationError("%s\n\n%s" % (_("Error while validating constraint"), tools.ustr(e))) @api.model diff --git a/odoo/osv/expression.py b/odoo/osv/expression.py index bef1cb1c523..3025472e0d0 100644 --- a/odoo/osv/expression.py +++ b/odoo/osv/expression.py @@ -233,7 +233,7 @@ def combine(operator, unit, zero, domains): result += domain count += 1 result = [operator] * (count - 1) + result - return result + return result or unit def AND(domains):