diff --git a/addons/account/data/email_template_data_invoice.xml b/addons/account/data/email_template_data_invoice.xml index 87b42b829cc..2ebbac31845 100644 --- a/addons/account/data/email_template_data_invoice.xml +++ b/addons/account/data/email_template_data_invoice.xml @@ -72,13 +72,13 @@ from ${object.company_id.name}. % set record = ctx.get('record') % set company = record and record.company_id or user.company_id - +
- +
@@ -96,7 +96,7 @@ from ${object.company_id.name}.
- +
${object.body | safe} @@ -109,7 +109,7 @@ from ${object.company_id.name}.
- +
diff --git a/addons/mail/models/res_partner.py b/addons/mail/models/res_partner.py index abebd022fea..b07b7c8b118 100644 --- a/addons/mail/models/res_partner.py +++ b/addons/mail/models/res_partner.py @@ -55,7 +55,6 @@ class Partner(models.Model): website_url = 'http://%s' % user.company_id.website if not user.company_id.website.lower().startswith(('http:', 'https:')) else user.company_id.website else: website_url = False - company_name = user.company_id.name model_name = False if message.model: @@ -75,9 +74,15 @@ class Partner(models.Model): if message.res_id and message.model in self.env: record = self.env[message.model].browse(message.res_id) + company = user.company_id; + if record and hasattr(record, 'company_id'): + company = record.company_id; + company_name = company.name; + return { 'signature': signature, 'website_url': website_url, + 'company': company, 'company_name': company_name, 'model_name': model_name, 'record': record, diff --git a/addons/mail/wizard/email_template_preview_view.xml b/addons/mail/wizard/email_template_preview_view.xml index 7d5cd1cb6d6..67068f5dc84 100644 --- a/addons/mail/wizard/email_template_preview_view.xml +++ b/addons/mail/wizard/email_template_preview_view.xml @@ -28,6 +28,9 @@ +
+
diff --git a/addons/product/models/product.py b/addons/product/models/product.py index 27c86e5be53..f284f709eda 100644 --- a/addons/product/models/product.py +++ b/addons/product/models/product.py @@ -284,7 +284,8 @@ class ProductProduct(models.Model): for value in product.attribute_value_ids: if value.attribute_id in attributes: raise ValidationError(_('Error! It is not allowed to choose more than one value for a given attribute.')) - attributes |= value.attribute_id + if value.attribute_id.create_variant: + attributes |= value.attribute_id return True @api.onchange('uom_id', 'uom_po_id') diff --git a/addons/sale/__init__.py b/addons/sale/__init__.py index 42f44d32a03..5f529bc560b 100644 --- a/addons/sale/__init__.py +++ b/addons/sale/__init__.py @@ -19,6 +19,6 @@ def uninstall_hook(cr, registry): if 'crm.team' in env: recs = env['crm.team'].search([]) for rec in recs: - rec._onchange_team_type() + rec._onchange_team_type() - cr.after("commit", partial(update_dashboard_graph_model, cr.dbname)) \ No newline at end of file + cr.after("commit", partial(update_dashboard_graph_model, cr.dbname)) diff --git a/addons/sale/data/mail_template_data.xml b/addons/sale/data/mail_template_data.xml index 23eaa72eac3..d77bb25d7dd 100644 --- a/addons/sale/data/mail_template_data.xml +++ b/addons/sale/data/mail_template_data.xml @@ -71,13 +71,13 @@ from ${object.company_id.name}. % set record = ctx.get('record') % set company = record and record.company_id or user.company_id -
${company.name}
diff --git a/addons/account/models/account.py b/addons/account/models/account.py index 46428f2389a..9ef2252e3dc 100644 --- a/addons/account/models/account.py +++ b/addons/account/models/account.py @@ -799,7 +799,7 @@ class AccountTax(models.Model): # (145 - 15) / (1.0 + ((10 + 20) / 100.0)) = 130 / 1.3 = 100 if fixed_amount == 0.0 and percent_amount == 0.0: return base_amount - return round((base_amount - fixed_amount) / (1.0 + percent_amount / 100.0), prec) + return (base_amount - fixed_amount) / (1.0 + percent_amount / 100.0) base = round(price_unit * quantity, prec) @@ -863,7 +863,7 @@ class AccountTax(models.Model): 'id': tax.id, 'name': tax.with_context(**{'lang': partner.lang} if partner else {}).name, 'amount': sign * tax_amount, - 'base': sign * tax_base, + 'base': round(sign * tax_base, prec), 'sequence': tax.sequence, 'account_id': tax.account_id.id, 'refund_account_id': tax.refund_account_id.id, @@ -874,7 +874,7 @@ class AccountTax(models.Model): 'taxes': taxes_vals, 'total_excluded': sign * (currency.round(total_excluded) if round_total else total_excluded), 'total_included': sign * (currency.round(total_included) if round_total else total_included), - 'base': sign * base, + 'base': round(sign * base, prec), } @api.model diff --git a/addons/account/tests/test_tax.py b/addons/account/tests/test_tax.py index 5ea51d3077d..b39d36f7bf3 100644 --- a/addons/account/tests/test_tax.py +++ b/addons/account/tests/test_tax.py @@ -28,6 +28,12 @@ class TestTax(AccountTestUsers): 'amount': 10, 'sequence': 3, }) + self.percent_tax_bis = self.tax_model.create({ + 'name': "Percent tax bis", + 'amount_type': 'percent', + 'amount': 21, + 'sequence': 3, + }) self.division_tax = self.tax_model.create({ 'name': "Division tax", 'amount_type': 'division', @@ -172,6 +178,21 @@ class TestTax(AccountTestUsers): ], res_percent ) + self.percent_tax_bis.price_include = True + self.percent_tax_bis.include_base_amount = True + res_percent = self.percent_tax_bis.compute_all(7.0) + self._check_compute_all_results( + 7.0, # 'base' + 7.0, # 'total_included' + 5.79, # 'total_excluded' + [ + # base , amount | seq | amount | incl | incl_base + # --------------------------------------------------- + (5.79, 1.21), # | 3 | 21% | t | t + # --------------------------------------------------- + ], + res_percent + ) def test_tax_sequence_normalized_set(self): self.division_tax.sequence = 1 diff --git a/addons/account_asset/models/account_invoice.py b/addons/account_asset/models/account_invoice.py index 5f1c90dcc55..b993f40e907 100644 --- a/addons/account_asset/models/account_invoice.py +++ b/addons/account_asset/models/account_invoice.py @@ -12,6 +12,16 @@ import odoo.addons.decimal_precision as dp class AccountInvoice(models.Model): _inherit = 'account.invoice' + @api.model + def _refund_cleanup_lines(self, lines): + result = super(AccountInvoice, self)._refund_cleanup_lines(lines) + for i, line in enumerate(lines): + for name, field in line._fields.items(): + if name == 'asset_category_id': + result[i][2][name] = False + break + return result + @api.multi def action_cancel(self): res = super(AccountInvoice, self).action_cancel() diff --git a/addons/auth_oauth/controllers/main.py b/addons/auth_oauth/controllers/main.py index 0d09f1e68aa..e231cd0f6d0 100644 --- a/addons/auth_oauth/controllers/main.py +++ b/addons/auth_oauth/controllers/main.py @@ -6,6 +6,7 @@ import logging import json +import werkzeug.urls import werkzeug.utils from werkzeug.exceptions import BadRequest @@ -148,7 +149,11 @@ class OAuthController(http.Controller): url = '/web#action=%s' % action elif menu: url = '/web#menu_id=%s' % menu - return login_and_redirect(*credentials, redirect_url=url) + resp = login_and_redirect(*credentials, redirect_url=url) + # Since /web is hardcoded, verify user has right to land on it + if werkzeug.urls.url_parse(resp.location).path == '/web' and not request.env.user.has_group('base.group_user'): + resp.location = '/' + return resp except AttributeError: # auth_signup is not installed _logger.error("auth_signup not installed on database %s: oauth sign up cancelled." % (dbname,)) diff --git a/addons/base_vat/models/res_partner.py b/addons/base_vat/models/res_partner.py index 1de5c95aeab..69d7204bf9a 100644 --- a/addons/base_vat/models/res_partner.py +++ b/addons/base_vat/models/res_partner.py @@ -18,6 +18,12 @@ from odoo import api, models, _ from odoo.tools.misc import ustr from odoo.exceptions import ValidationError +_eu_country_vat = { + 'GR': 'EL' +} + +_eu_country_vat_inverse = {v: k for k, v in _eu_country_vat.items()} + _ref_vat = { 'at': 'ATU12345675', 'be': 'BE0477472701', @@ -79,6 +85,7 @@ class ResPartner(models.Model): # Foreign companies that trade with non-enterprises in the EU # may have a VATIN starting with "EU" instead of a country code. return True + country_code = _eu_country_vat_inverse.get(country_code, country_code) return bool(self.env['res.country'].search([('code', '=ilike', country_code)])) return check_func(vat_number) @@ -96,6 +103,19 @@ class ResPartner(models.Model): # country code or empty VAT number), so we fall back to the simple check. return self.simple_vat_check(country_code, vat_number) + @api.model + def fix_eu_vat_number(self, country_id, vat): + europe = self.env.ref('base.europe') + country = self.env["res.country"].browse(country_id) + if not europe: + europe = self.env["res.country.group"].search([('name', '=', 'Europe')], limit=1) + if europe and country and country.id in europe.country_ids.ids: + vat = re.sub('[^A-Za-z0-9]', '', vat).upper() + country_code = _eu_country_vat.get(country.code, country.code).upper() + if vat[:2] != country_code: + vat = country_code + vat + return vat + @api.constrains('vat', 'commercial_partner_country_id') def check_vat(self): if self.env.context.get('company_id'): diff --git a/addons/event_sale/models/account_invoice.py b/addons/event_sale/models/account_invoice.py index b8f4e309bb4..df995d576f5 100644 --- a/addons/event_sale/models/account_invoice.py +++ b/addons/event_sale/models/account_invoice.py @@ -9,7 +9,7 @@ class AccountInvoice(models.Model): @api.multi def action_invoice_paid(self): - """ When an invoice linked to a sales order selling registrations is + """ When an invoice linked to a sales order selling registrations is paid confirm attendees. Attendees should indeed not be confirmed before full payment. """ res = super(AccountInvoice, self).action_invoice_paid() diff --git a/addons/hw_proxy/controllers/main.py b/addons/hw_proxy/controllers/main.py index f047e0145a4..8a0ee204701 100644 --- a/addons/hw_proxy/controllers/main.py +++ b/addons/hw_proxy/controllers/main.py @@ -18,9 +18,9 @@ _logger = logging.getLogger(__name__) # Those are the builtin raspberry pi USB modules, they should # not appear in the list of connected devices. BANNED_DEVICES = set([ - "0424:9514", # Standard Microsystem Corp. Builtin Ethernet module - "1d6b:0002", # Linux Foundation 2.0 root hub - "0424:ec00", # Standard Microsystem Corp. Other Builtin Ethernet module + "0424:9514", # Standard Microsystem Corp. Builtin Ethernet module + "1d6b:0002", # Linux Foundation 2.0 root hub + "0424:ec00", # Standard Microsystem Corp. Other Builtin Ethernet module ]) @@ -107,7 +107,7 @@ class Proxy(http.Controller): device_name = device[device.find('ID')+2:] device_id = device_name.split()[0] if not (device_id in BANNED_DEVICES): - resp+= "
"+device_name+"
\n" + resp+= "
"+device_name+"
\n" count += 1 if count == 0: diff --git a/addons/mail/data/mail_data.xml b/addons/mail/data/mail_data.xml index 36c26f56eb4..34789916a48 100644 --- a/addons/mail/data/mail_data.xml +++ b/addons/mail/data/mail_data.xml @@ -71,6 +71,7 @@ % if ctx.get('has_button_access'):
@@ -110,7 +111,7 @@ % endif
- ${user.company_id.name} + ${company.name}
+
- +
@@ -95,7 +95,7 @@ from ${object.company_id.name}.
- +
${object.body | safe} @@ -108,7 +108,7 @@ from ${object.company_id.name}.
- +
${company.name}
diff --git a/addons/sale/models/sale.py b/addons/sale/models/sale.py index ad10234a329..d378c8a5fd5 100644 --- a/addons/sale/models/sale.py +++ b/addons/sale/models/sale.py @@ -868,7 +868,8 @@ class SaleOrderLine(models.Model): return price else: from_currency = self.order_id.company_id.currency_id - return from_currency.compute(pricelist_item and product[pricelist_item.base] or product.lst_price, self.order_id.pricelist_id.currency_id) + product_price = product[pricelist_item.base] if (pricelist_item and pricelist_item.base != 'pricelist') else product.lst_price + return from_currency.compute(product_price, self.order_id.pricelist_id.currency_id) @api.multi @api.onchange('product_id') diff --git a/addons/sale_stock/models/procurement.py b/addons/sale_stock/models/procurement.py index 5710bff5af6..f6b51ae6473 100644 --- a/addons/sale_stock/models/procurement.py +++ b/addons/sale_stock/models/procurement.py @@ -14,5 +14,5 @@ class ProcurementOrder(models.Model): return vals class ProcurementGroup(models.Model): - _inherit = 'procurement.group' - sale_order_id = fields.Many2one('sale.order', string='Sale Order') + _inherit = 'procurement.group' + sale_order_id = fields.Many2one('sale.order', string='Sale Order') diff --git a/addons/sale_stock/models/sale_order.py b/addons/sale_stock/models/sale_order.py index 93799a63b39..0fe0876155d 100644 --- a/addons/sale_stock/models/sale_order.py +++ b/addons/sale_stock/models/sale_order.py @@ -108,11 +108,13 @@ class SaleOrderLine(models.Model): @api.multi @api.depends('product_id') def _compute_qty_delivered_updateable(self): - for line in self: - if line.product_id.type not in ('consu', 'product'): - super(SaleOrderLine, line)._compute_qty_delivered_updateable() - else: - line.qty_delivered_updateable = False + # prefetch field before filtering + self.mapped('product_id') + # on consumable or stockable products, qty_delivered_updateable defaults + # to False; on other lines use the original computation + lines = self.filtered(lambda line: line.product_id.type not in ('consu', 'product')) + lines = lines.with_prefetch(self._prefetch) + super(SaleOrderLine, lines)._compute_qty_delivered_updateable() @api.onchange('product_id') def _onchange_product_id_set_customer_lead(self): diff --git a/addons/stock/models/stock_pack_operation.py b/addons/stock/models/stock_pack_operation.py index a9116730e5c..80db63ce141 100644 --- a/addons/stock/models/stock_pack_operation.py +++ b/addons/stock/models/stock_pack_operation.py @@ -267,10 +267,10 @@ class PackOperationLot(models.Model): _description = "Lot/Serial number for pack ops" operation_id = fields.Many2one('stock.pack.operation') - qty = fields.Float('Done', default=1.0) + qty = fields.Float('Done', default=1.0, digits=dp.get_precision('Product Unit of Measure')) lot_id = fields.Many2one('stock.production.lot', 'Lot/Serial Number') lot_name = fields.Char('Lot/Serial Number') - qty_todo = fields.Float('To Do', default=0.0) + qty_todo = fields.Float('To Do', default=0.0, digits=dp.get_precision('Product Unit of Measure')) plus_visible = fields.Boolean(compute='_compute_plus_visible', default=True) _sql_constraints = [ diff --git a/addons/web/static/src/less/modal.less b/addons/web/static/src/less/modal.less index 2a0fbb1fa36..2d6c7562fe2 100644 --- a/addons/web/static/src/less/modal.less +++ b/addons/web/static/src/less/modal.less @@ -52,6 +52,8 @@ .modal-body { overflow: auto; + // fix iOS issue https://github.com/scottjehl/Device-Bugs/issues/8 + -webkit-transform: translate3d(0, 0, 0); min-height: 0; &.o_dialog_error { diff --git a/addons/web/views/webclient_templates.xml b/addons/web/views/webclient_templates.xml index 612b0fe156b..dc8feb8e64a 100644 --- a/addons/web/views/webclient_templates.xml +++ b/addons/web/views/webclient_templates.xml @@ -238,7 +238,7 @@ - + <link type="image/x-icon" rel="shortcut icon" t-att-href="x_icon or '/web/static/src/img/favicon.ico'"/> diff --git a/addons/web_editor/static/src/js/rte.summernote.js b/addons/web_editor/static/src/js/rte.summernote.js index a3bf079aa30..866d0d68e84 100644 --- a/addons/web_editor/static/src/js/rte.summernote.js +++ b/addons/web_editor/static/src/js/rte.summernote.js @@ -771,7 +771,7 @@ eventHandler.attach = function (oLayoutInfo, options) { * Open Link Dialog on double click on a link/button. * Shows a tooltip on click to say to the user he can double click. */ - create_dblclick_feature("a[href], .btn", function () { + create_dblclick_feature("a[href], a.btn, button.btn", function () { eventHandler.modules.linkDialog.show(oLayoutInfo); }); diff --git a/addons/website/views/res_config_views.xml b/addons/website/views/res_config_views.xml index 854436faf0a..de26ecd5ef9 100644 --- a/addons/website/views/res_config_views.xml +++ b/addons/website/views/res_config_views.xml @@ -131,7 +131,7 @@ </div> <div class="col-xs-12 col-md-6 o_setting_box"> <div class="o_setting_left_pane"> - <field name="module_website_version"/> + <field name="module_website_version" widget="upgrade_boolean"/> </div> <div class="o_setting_right_pane"> <label for="module_website_version"/> diff --git a/addons/website_sale/controllers/main.py b/addons/website_sale/controllers/main.py index 32348b4355e..5517b3ba508 100644 --- a/addons/website_sale/controllers/main.py +++ b/addons/website_sale/controllers/main.py @@ -487,6 +487,8 @@ class WebsiteSale(http.Controller): # vat validation Partner = request.env['res.partner'] if data.get("vat") and hasattr(Partner, "check_vat"): + if data.get("country_id"): + data["vat"] = Partner.fix_eu_vat_number(data.get("country_id"), data.get("vat")) check_func = request.website.company_id.vat_check_vies and Partner.vies_vat_check or Partner.simple_vat_check vat_country, vat_number = Partner._split_vat(data.get("vat")) if not check_func(vat_country, vat_number): diff --git a/addons/website_sale/models/__init__.py b/addons/website_sale/models/__init__.py index 9bd76239ea3..046e82f1786 100644 --- a/addons/website_sale/models/__init__.py +++ b/addons/website_sale/models/__init__.py @@ -4,4 +4,5 @@ from . import product from . import sale_order from . import crm_team from . import res_config +from . import ir_model_fields from . import website diff --git a/addons/website_sale/models/ir_model_fields.py b/addons/website_sale/models/ir_model_fields.py new file mode 100644 index 00000000000..e109ab809b5 --- /dev/null +++ b/addons/website_sale/models/ir_model_fields.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import api, models + +# !! Attention, this is a hack of a hack, do not try this at home !! +# This is done because website_sale defines the website_description +# field which is also defined by website_quote, but these two modules +# are independent of each other, the part of the ORM that generates +# xmlids does not support this case therefore it only creates a single +# pair of xmlids for whichever module is installed first, therefore +# when uninstalling any of the two modules the website_descripion field +# will be deleted from the db and the other module won't be able to +# use this field, resulting in a crash and data-loss. +# See opw-776464 for more details. +# +# This is hotfixed by overriding the unlink method so as to not delete +# the website_description field if website_sale is uninstalled, it's +# an ugly hack but it works and big changes can't be done to the ORM +# in stable versions, Odoo v12+ will properly fix this + + +class IrModelFields(models.Model): + _inherit = "ir.model.fields" + + @api.multi + def unlink(self): + # Prevent the deletion of the field "website_description" + self = self.filtered( + lambda rec: not ( + rec.model in ('product.product', 'product.template') and + rec.name == 'website_description' + ) + ) + return super(IrModelFields, self).unlink() diff --git a/doc/cla/corporate/acsone.md b/doc/cla/corporate/acsone.md index cb97dc6b20d..1060d7ccc02 100644 --- a/doc/cla/corporate/acsone.md +++ b/doc/cla/corporate/acsone.md @@ -25,3 +25,4 @@ Philippe Schmidt philippe.schmidt@acsone.eu https://github.com/phschmidt Stéphane Bidoul stephane.bidoul@acsone.eu https://github.com/sbidoul Thomas Binsfeld thomas.binsfeld@acsone.eu https://github.com/ThomasBinsfeld Benjamin Willig benjamin.willig@acsone.eu https://github.com/benwillig +François Honoré francois.honore@acsone.eu https://github.com/acsonefho diff --git a/doc/cla/corporate/savoirfairelinux.md b/doc/cla/corporate/savoirfairelinux.md index 784518b093e..6dca085c4ce 100644 --- a/doc/cla/corporate/savoirfairelinux.md +++ b/doc/cla/corporate/savoirfairelinux.md @@ -1,4 +1,4 @@ -Canada, France, 2017-05-12 +Canada, France, 2017-10-17 Savoir-faire Linux agrees to the terms of the Odoo Corporate Contributor License Agreement v1.0. @@ -8,17 +8,22 @@ declaration. Signed, -Jérome Boisvert-Chouinard jerome.boisvertchouinard@savoirfairelinux.com https://github.com/jbchouinard +João Alfredo Gama Batista joao.gama@savoirfairelinux.com https://github.com/joaoalf List of contributors: Agathe Mollé agathe.molle@savoirfairelinux.com https://github.com/Ehtaga Bruno Joliveau bruno.joliveau@savoirfairelinux.com https://github.com/bjoliveau David Dufresne david.dufresne@savoirfairelinux.com https://github.com/dufresnedavid -Jérome Boisvert-Chouinard jerome.boisvertchouinard@savoirfairelinux.com https://github.com/jbchouinard -Joao Alfredo Gama Batista joao.gama@savoirfairelinux.com https://github.com/joaoalf -Julie Moussu julie.mousse@savoirfairelinux.com https://github.com/JulieSFL -Pierre Lamarche pierre.lamarche@savoirfairelinux.com https://github.com/plamarche +Istvan SZALAÏ istvan.szalai@savoirfairelinux.com https://github.com/ventilooo +João Alfredo Gama Batista joao.gama@savoirfairelinux.com https://github.com/joaoalf +Julien Jezequel-Breard julien.jezequel-breard@savoirfairelinux.com https://github.com/jjbreard +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 +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) David Cormier david.cormier@savoirfairelinux.com https://github.com/cormier (up to 2017-05-12) Davin Baragiotta davin.baragiotta@savoirfairelinux.com https://github.com/giotta (up to 2017-05-12) El Hadji Dem elhadji.dem@savoirfairelinux.com https://github.com/ehdem (up to 2017-05-12) diff --git a/odoo/addons/test_pylint/tests/test_pylint.py b/odoo/addons/test_pylint/tests/test_pylint.py index 1ae544e56a6..06ab616de60 100644 --- a/odoo/addons/test_pylint/tests/test_pylint.py +++ b/odoo/addons/test_pylint/tests/test_pylint.py @@ -26,6 +26,7 @@ class TestPyLint(TransactionCase): 'W0123', # eval used 'W0101', # unreachable code + 'mixed-indentation', 'misplaced-future', 'relative-import', 'deprecated-module',