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}.
-
+
${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
-
+
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
-
+
-
+
@@ -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 @@
-
+
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 @@
-
+
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',