diff --git a/addons/bus/models/res_partner.py b/addons/bus/models/res_partner.py
index b0b4c23d74f..a476e0c08dc 100644
--- a/addons/bus/models/res_partner.py
+++ b/addons/bus/models/res_partner.py
@@ -15,13 +15,14 @@ class ResPartner(models.Model):
self.env.cr.execute("""
SELECT
U.partner_id as id,
- CASE WHEN age(now() AT TIME ZONE 'UTC', B.last_poll) > interval %s THEN 'offline'
- WHEN age(now() AT TIME ZONE 'UTC', B.last_presence) > interval %s THEN 'away'
+ CASE WHEN age(now() AT TIME ZONE 'UTC', max(B.last_poll)) > interval %s THEN 'offline'
+ WHEN age(now() AT TIME ZONE 'UTC', max(B.last_presence)) > interval %s THEN 'away'
ELSE 'online'
END as status
FROM bus_presence B
JOIN res_users U ON B.user_id = U.id
WHERE U.partner_id IN %s AND U.active = 't'
+ GROUP BY U.partner_id
""", ("%s seconds" % DISCONNECTION_TIMER, "%s seconds" % AWAY_TIMER, tuple(self.ids)))
res = dict(((status['id'], status['status']) for status in self.env.cr.dictfetchall()))
for partner in self:
diff --git a/addons/l10n_be_intrastat/wizard/xml_decl.py b/addons/l10n_be_intrastat/wizard/xml_decl.py
index aaf67485a11..988f70716d2 100644
--- a/addons/l10n_be_intrastat/wizard/xml_decl.py
+++ b/addons/l10n_be_intrastat/wizard/xml_decl.py
@@ -102,35 +102,140 @@ class XmlDeclaration(models.TransientModel):
'res_id': self.id,
}
+ def _build_intrastat_line(self, numlgn, item, linekey, amounts, dispatchmode, extendedmode):
+ self._set_Dim(item, 'EXSEQCODE', unicode(numlgn))
+ self._set_Dim(item, 'EXTRF', unicode(linekey.EXTRF))
+ self._set_Dim(item, 'EXCNT', unicode(linekey.EXCNT))
+ self._set_Dim(item, 'EXTTA', unicode(linekey.EXTTA))
+ self._set_Dim(item, 'EXREG', unicode(linekey.EXREG))
+ self._set_Dim(item, 'EXTGO', unicode(linekey.EXGO))
+ if extendedmode:
+ self._set_Dim(item, 'EXTPC', unicode(linekey.EXTPC))
+ self._set_Dim(item, 'EXDELTRM', unicode(linekey.EXDELTRM))
+ self._set_Dim(item, 'EXTXVAL', unicode(round(amounts[0], 0)).replace(".", ","))
+ self._set_Dim(item, 'EXWEIGHT', unicode(round(amounts[1], 0)).replace(".", ","))
+ self._set_Dim(item, 'EXUNITS', unicode(round(amounts[2], 0)).replace(".", ","))
+
+ def _get_intrastat_linekey(self, declcode, inv_line, dispatchmode, extendedmode):
+ IntrastatRegion = self.env['l10n_be_intrastat.region']
+ company = self.company_id
+
+ #Check type of transaction
+ if inv_line.intrastat_transaction_id:
+ extta = inv_line.intrastat_transaction_id.code
+ else:
+ extta = "1"
+ #Check country
+ if inv_line.invoice_id.intrastat_country_id:
+ excnt = inv_line.invoice_id.intrastat_country_id.code
+ else:
+ excnt = inv_line.invoice_id.partner_shipping_id.country_id.code or inv_line.invoice_id.partner_id.country_id.code
+
+ #Check region
+ #If purchase, comes from purchase order, linked to a location,
+ #which is linked to the warehouse
+ #if sales, the sales order is linked to the warehouse
+ #if sales, from a delivery order, linked to a location,
+ #which is linked to the warehouse
+ #If none found, get the company one.
+ exreg = None
+ if inv_line.invoice_id.type in ('in_invoice', 'in_refund'):
+ #comes from purchase
+ po_lines = self.env['purchase.order.line'].search([('invoice_lines', 'in', inv_line.id)], limit=1)
+ if po_lines:
+ if self._is_situation_triangular(company, po_line=po_lines):
+ return
+ location = self.env['stock.location'].browse(po_lines.order_id._get_destination_location())
+ region_id = self.env['stock.warehouse'].get_regionid_from_locationid(location)
+ if region_id:
+ exreg = IntrastatRegion.browse(region_id).code
+ elif inv_line.invoice_id.type in ('out_invoice', 'out_refund'):
+ #comes from sales
+ so_lines = self.env['sale.order.line'].search([('invoice_lines', 'in', inv_line.id)], limit=1)
+ if so_lines:
+ if self._is_situation_triangular(company, so_line=so_lines):
+ return
+ saleorder = so_lines.order_id
+ if saleorder and saleorder.warehouse_id and saleorder.warehouse_id.region_id:
+ exreg = IntrastatRegion.browse(saleorder.warehouse_id.region_id.id).code
+
+ if not exreg:
+ if company.region_id:
+ exreg = company.region_id.code
+ else:
+ self._company_warning(_('The Intrastat Region of the selected company is not set, '
+ 'please make sure to configure it first.'))
+
+ #Check commodity codes
+ intrastat_id = inv_line.product_id.get_intrastat_recursively()
+ if intrastat_id:
+ exgo = self.env['report.intrastat.code'].browse(intrastat_id).name
+ else:
+ raise exceptions.Warning(
+ _('Product "%s" has no intrastat code, please configure it') % inv_line.product_id.display_name)
+
+ #In extended mode, 2 more fields required
+ if extendedmode:
+ #Check means of transport
+ if inv_line.invoice_id.transport_mode_id:
+ extpc = inv_line.invoice_id.transport_mode_id.code
+ elif company.transport_mode_id:
+ extpc = company.transport_mode_id.code
+ else:
+ self._company_warning(_('The default Intrastat transport mode of your company '
+ 'is not set, please make sure to configure it first.'))
+
+ #Check incoterm
+ if inv_line.invoice_id.incoterm_id:
+ exdeltrm = inv_line.invoice_id.incoterm_id.code
+ elif company.incoterm_id:
+ exdeltrm = company.incoterm_id.code
+ else:
+ self._company_warning(_('The default Incoterm of your company is not set, '
+ 'please make sure to configure it first.'))
+ else:
+ extpc = ""
+ exdeltrm = ""
+ intrastatkey = namedtuple("intrastatkey",
+ ['EXTRF', 'EXCNT', 'EXTTA', 'EXREG',
+ 'EXGO', 'EXTPC', 'EXDELTRM'])
+ return intrastatkey(EXTRF=declcode, EXCNT=excnt,
+ EXTTA=extta, EXREG=exreg, EXGO=exgo,
+ EXTPC=extpc, EXDELTRM=exdeltrm)
+
+ def _get_reception_code(self, extended):
+ return 'EX19E' if extended else 'EX19S'
+
+ def _get_reception_form(self, extended):
+ return 'EXF19E' if extended else 'EXF19S'
+
+ def _get_expedition_code(self, extended):
+ return 'EX29E' if extended else 'EX29S'
+
+ def _get_expedition_form(self, extended):
+ return 'EXF29E' if extended else 'EXF29S'
+
@api.multi
def _get_lines(self, dispatchmode=False, extendedmode=False):
company = self.company_id
- IntrastatRegion = self.env['l10n_be_intrastat.region']
if dispatchmode:
mode1 = 'out_invoice'
mode2 = 'in_refund'
- declcode = "29"
+ declcode = self._get_expedition_code(extendedmode)
+ declform = self._get_expedition_form(extendedmode)
else:
mode1 = 'in_invoice'
mode2 = 'out_refund'
- declcode = "19"
+ declcode = self._get_reception_code(extendedmode)
+ declform = self._get_reception_form(extendedmode)
decl = ET.Element('Report')
- if not extendedmode:
- decl.set('code', 'EX%sS' % declcode)
- else:
- decl.set('code', 'EX%sE' % declcode)
+ decl.set('code', declcode)
decl.set('date', '%s-%s' % (self.year, self.month))
datas = ET.SubElement(decl, 'Data')
- if not extendedmode:
- datas.set('form', 'EXF%sS' % declcode)
- else:
- datas.set('form', 'EXF%sE' % declcode)
+ datas.set('form', declform)
datas.set('close', 'true')
- intrastatkey = namedtuple("intrastatkey",
- ['EXTRF', 'EXCNT', 'EXTTA', 'EXREG',
- 'EXGO', 'EXTPC', 'EXDELTRM'])
entries = {}
query = """
@@ -166,86 +271,10 @@ class XmlDeclaration(models.TransientModel):
invoicelines = self.env['account.invoice.line'].browse(invoicelines_ids)
for inv_line in invoicelines:
+ linekey = self._get_intrastat_linekey(declcode, inv_line, dispatchmode, extendedmode)
+ if linekey is None:
+ continue
- #Check type of transaction
- if inv_line.intrastat_transaction_id:
- extta = inv_line.intrastat_transaction_id.code
- else:
- extta = "1"
- #Check country
- if inv_line.invoice_id.intrastat_country_id:
- excnt = inv_line.invoice_id.intrastat_country_id.code
- else:
- excnt = inv_line.invoice_id.partner_shipping_id.country_id.code or inv_line.invoice_id.partner_id.country_id.code
-
- #Check region
- #If purchase, comes from purchase order, linked to a location,
- #which is linked to the warehouse
- #if sales, the sales order is linked to the warehouse
- #if sales, from a delivery order, linked to a location,
- #which is linked to the warehouse
- #If none found, get the company one.
- exreg = None
- if inv_line.invoice_id.type in ('in_invoice', 'in_refund'):
- #comes from purchase
- po_lines = self.env['purchase.order.line'].search([('invoice_lines', 'in', inv_line.id)], limit=1)
- if po_lines:
- if self._is_situation_triangular(company, po_line=po_lines):
- continue
- location = self.env['stock.location'].browse(po_lines.order_id._get_destination_location())
- region_id = self.env['stock.warehouse'].get_regionid_from_locationid(location)
- if region_id:
- exreg = IntrastatRegion.browse(region_id).code
- elif inv_line.invoice_id.type in ('out_invoice', 'out_refund'):
- #comes from sales
- so_lines = self.env['sale.order.line'].search([('invoice_lines', 'in', inv_line.id)], limit=1)
- if so_lines:
- if self._is_situation_triangular(company, so_line=so_lines):
- continue
- saleorder = so_lines.order_id
- if saleorder and saleorder.warehouse_id and saleorder.warehouse_id.region_id:
- exreg = IntrastatRegion.browse(saleorder.warehouse_id.region_id.id).code
-
- if not exreg:
- if company.region_id:
- exreg = company.region_id.code
- else:
- self._company_warning(_('The Intrastat Region of the selected company is not set, '
- 'please make sure to configure it first.'))
-
- #Check commodity codes
- intrastat_id = inv_line.product_id.get_intrastat_recursively()
- if intrastat_id:
- exgo = self.env['report.intrastat.code'].browse(intrastat_id).name
- else:
- raise exceptions.Warning(
- _('Product "%s" has no intrastat code, please configure it') % inv_line.product_id.display_name)
-
- #In extended mode, 2 more fields required
- if extendedmode:
- #Check means of transport
- if inv_line.invoice_id.transport_mode_id:
- extpc = inv_line.invoice_id.transport_mode_id.code
- elif company.transport_mode_id:
- extpc = company.transport_mode_id.code
- else:
- self._company_warning(_('The default Intrastat transport mode of your company '
- 'is not set, please make sure to configure it first.'))
-
- #Check incoterm
- if inv_line.invoice_id.incoterm_id:
- exdeltrm = inv_line.invoice_id.incoterm_id.code
- elif company.incoterm_id:
- exdeltrm = company.incoterm_id.code
- else:
- self._company_warning(_('The default Incoterm of your company is not set, '
- 'please make sure to configure it first.'))
- else:
- extpc = ""
- exdeltrm = ""
- linekey = intrastatkey(EXTRF=declcode, EXCNT=excnt,
- EXTTA=extta, EXREG=exreg, EXGO=exgo,
- EXTPC=extpc, EXDELTRM=exdeltrm)
#We have the key
#calculate amounts
if inv_line.price_unit and inv_line.quantity:
@@ -269,18 +298,7 @@ class XmlDeclaration(models.TransientModel):
continue
numlgn += 1
item = ET.SubElement(datas, 'Item')
- self._set_Dim(item, 'EXSEQCODE', unicode(numlgn))
- self._set_Dim(item, 'EXTRF', unicode(linekey.EXTRF))
- self._set_Dim(item, 'EXCNT', unicode(linekey.EXCNT))
- self._set_Dim(item, 'EXTTA', unicode(linekey.EXTTA))
- self._set_Dim(item, 'EXREG', unicode(linekey.EXREG))
- self._set_Dim(item, 'EXTGO', unicode(linekey.EXGO))
- if extendedmode:
- self._set_Dim(item, 'EXTPC', unicode(linekey.EXTPC))
- self._set_Dim(item, 'EXDELTRM', unicode(linekey.EXDELTRM))
- self._set_Dim(item, 'EXTXVAL', unicode(round(amounts[0], 0)).replace(".", ","))
- self._set_Dim(item, 'EXWEIGHT', unicode(round(amounts[1], 0)).replace(".", ","))
- self._set_Dim(item, 'EXUNITS', unicode(round(amounts[2], 0)).replace(".", ","))
+ self._build_intrastat_line(numlgn, item, linekey, amounts, dispatchmode, extendedmode)
if numlgn == 0:
#no datas
diff --git a/addons/l10n_be_intrastat_2019/__init__.py b/addons/l10n_be_intrastat_2019/__init__.py
new file mode 100644
index 00000000000..dc5e6b693d1
--- /dev/null
+++ b/addons/l10n_be_intrastat_2019/__init__.py
@@ -0,0 +1,4 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from . import models
diff --git a/addons/l10n_be_intrastat_2019/__manifest__.py b/addons/l10n_be_intrastat_2019/__manifest__.py
new file mode 100644
index 00000000000..b3de01cd46b
--- /dev/null
+++ b/addons/l10n_be_intrastat_2019/__manifest__.py
@@ -0,0 +1,15 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+{
+ 'name': 'Belgian Intrastat Declaration - Complement for 2019',
+ 'category': 'Accounting',
+ 'description': """
+Adds the possibility to specify the origin country of goods and the partner VAT in the Intrastat XML report.
+ """,
+ 'depends': ['l10n_be_intrastat'],
+ 'data': [
+ 'views/account_invoice_line_view.xml',
+ ],
+ 'auto_install': True,
+}
diff --git a/addons/l10n_be_intrastat_2019/i18n/l10n_be_intrastat_2019.pot b/addons/l10n_be_intrastat_2019/i18n/l10n_be_intrastat_2019.pot
new file mode 100644
index 00000000000..5953a7be43c
--- /dev/null
+++ b/addons/l10n_be_intrastat_2019/i18n/l10n_be_intrastat_2019.pot
@@ -0,0 +1,32 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * l10n_be_intrastat_2019
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Odoo Server 10.0+e\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2019-01-21 11:04+0000\n"
+"PO-Revision-Date: 2019-01-21 11:04+0000\n"
+"Last-Translator: <>\n"
+"Language-Team: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: \n"
+
+#. module: l10n_be_intrastat_2019
+#: model:ir.model,name:l10n_be_intrastat_2019.model_l10n_be_intrastat_xml_xml_decl
+msgid "Intrastat XML Declaration"
+msgstr ""
+
+#. module: l10n_be_intrastat_2019
+#: model:ir.model,name:l10n_be_intrastat_2019.model_account_invoice_line
+msgid "Invoice Line"
+msgstr ""
+
+#. module: l10n_be_intrastat_2019
+#: model:ir.model.fields,field_description:l10n_be_intrastat_2019.field_account_invoice_line_intrastat_product_origin_country_id
+msgid "Origin Country of Product"
+msgstr ""
+
diff --git a/addons/l10n_be_intrastat_2019/models/__init__.py b/addons/l10n_be_intrastat_2019/models/__init__.py
new file mode 100644
index 00000000000..9115b4d66b6
--- /dev/null
+++ b/addons/l10n_be_intrastat_2019/models/__init__.py
@@ -0,0 +1,4 @@
+# -*- coding: utf-8 -*-
+
+from . import account_intrastat_report
+from . import account_invoice_line
diff --git a/addons/l10n_be_intrastat_2019/models/account_intrastat_report.py b/addons/l10n_be_intrastat_2019/models/account_intrastat_report.py
new file mode 100644
index 00000000000..2a091901a26
--- /dev/null
+++ b/addons/l10n_be_intrastat_2019/models/account_intrastat_report.py
@@ -0,0 +1,33 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import models
+from collections import namedtuple
+
+
+class XmlDeclaration(models.TransientModel):
+ """
+ Intrastat XML Declaration
+ """
+ _inherit = "l10n_be_intrastat_xml.xml_decl"
+
+ def _build_intrastat_line(self, numlgn, item, linekey, amounts, dispatchmode, extendedmode):
+ super(XmlDeclaration, self)._build_intrastat_line(numlgn, item, linekey, amounts, dispatchmode, extendedmode)
+ if dispatchmode:
+ self._set_Dim(item, 'EXCNTORI', unicode(linekey.EXCNTORI))
+ self._set_Dim(item, 'PARTNERID', unicode(linekey.PARTNERID))
+
+ def _get_intrastat_linekey(self, declcode, inv_line, dispatchmode, extendedmode):
+ res = super(XmlDeclaration, self)._get_intrastat_linekey(declcode, inv_line, dispatchmode, extendedmode)
+ if res and dispatchmode:
+ res_dict = res._asdict()
+ res_dict['EXCNTORI'] = inv_line.intrastat_product_origin_country_id.code or 'QU'
+ res_dict['PARTNERID'] = inv_line.invoice_id.partner_id.vat or 'QV999999999999'
+ return namedtuple('intrastatkey', res_dict.keys())(**res_dict)
+ return res
+
+ def _get_expedition_code(self, extended):
+ return 'INTRASTAT_X_E' if extended else 'INTRASTAT_X_S'
+
+ def _get_expedition_form(self, extended):
+ return 'INTRASTAT_X_EF' if extended else 'INTRASTAT_X_SF'
diff --git a/addons/l10n_be_intrastat_2019/models/account_invoice_line.py b/addons/l10n_be_intrastat_2019/models/account_invoice_line.py
new file mode 100644
index 00000000000..01199b0174a
--- /dev/null
+++ b/addons/l10n_be_intrastat_2019/models/account_invoice_line.py
@@ -0,0 +1,9 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import api, fields, models
+
+class AccountInvoiceLine(models.Model):
+ _inherit = 'account.invoice.line'
+
+ intrastat_product_origin_country_id = fields.Many2one('res.country', string='Origin Country of Product')
diff --git a/addons/l10n_be_intrastat_2019/views/account_invoice_line_view.xml b/addons/l10n_be_intrastat_2019/views/account_invoice_line_view.xml
new file mode 100644
index 00000000000..187e32b161e
--- /dev/null
+++ b/addons/l10n_be_intrastat_2019/views/account_invoice_line_view.xml
@@ -0,0 +1,13 @@
+
+
+
+ account.invoice.form.inherit.account.be.intrastat
+ account.invoice
+
+
+
+
+
+
+
+
diff --git a/addons/mrp/models/stock_move.py b/addons/mrp/models/stock_move.py
index 982995a9e1f..bf3e24661a4 100644
--- a/addons/mrp/models/stock_move.py
+++ b/addons/mrp/models/stock_move.py
@@ -116,9 +116,24 @@ class StockMove(models.Model):
@api.multi
@api.depends('move_lot_ids', 'move_lot_ids.quantity_done', 'quantity_done_store')
def _qty_done_compute(self):
+ move_lot_ids = self.env['stock.move.lots'].sudo().read_group(
+ [('move_id', 'in', self.ids), ('lot_id', '!=', False)],
+ ['move_id'],
+ ['move_id']
+ )
+ move_lot_count = {x['move_id'][0]: x['move_id_count'] for x in move_lot_ids}
+ move_qty_ids = self.env['stock.move.lots'].sudo().read_group(
+ [
+ ('move_id', 'in', self.ids),
+ ('done_wo', '!=', False)
+ ],
+ ['move_id', 'quantity_done'],
+ ['move_id']
+ )
+ move_qty = {x['move_id'][0]: x['quantity_done'] for x in move_qty_ids}
for move in self:
- if move.has_tracking != 'none' or move.sudo().move_lot_ids.mapped('lot_id'):
- move.quantity_done = sum(move.move_lot_ids.filtered(lambda x: x.done_wo).mapped('quantity_done')) #TODO: change with active_move_lot_ids?
+ if move.has_tracking != 'none' or move_lot_count.get(move.id, 0) > 0:
+ move.quantity_done = move_qty.get(move.id, 0)
else:
move.quantity_done = move.quantity_done_store
@@ -162,9 +177,8 @@ class StockMove(models.Model):
@api.multi
def create_lots(self):
lots = self.env['stock.move.lots']
+ lots.search([('move_id', 'in', self.ids), ('done_wo', '=', True), ('quantity_done', '=', 0)]).sudo().unlink()
for move in self:
- unlink_move_lots = move.move_lot_ids.filtered(lambda x : (x.quantity_done == 0) and x.done_wo)
- unlink_move_lots.sudo().unlink()
group_new_quant = {}
old_move_lot = {}
for movelot in move.move_lot_ids:
diff --git a/addons/sale/models/sale.py b/addons/sale/models/sale.py
index 5fe5d2ba3df..561b903fb67 100644
--- a/addons/sale/models/sale.py
+++ b/addons/sale/models/sale.py
@@ -471,7 +471,7 @@ class SaleOrder(models.Model):
order.state = 'sale'
order.confirmation_date = fields.Datetime.now()
if self.env.context.get('send_email'):
- self.force_quotation_send()
+ order.force_quotation_send()
order.order_line._action_procurement_create()
if self.env['ir.values'].get_default('sale.config.settings', 'auto_done_setting'):
self.action_done()
diff --git a/addons/stock_account/models/stock.py b/addons/stock_account/models/stock.py
index 0ba1f76102a..d2a853f2253 100644
--- a/addons/stock_account/models/stock.py
+++ b/addons/stock_account/models/stock.py
@@ -150,7 +150,7 @@ class StockQuant(models.Model):
def _quant_create_from_move(self, qty, move, lot_id=False, owner_id=False, src_package_id=False, dest_package_id=False, force_location_from=False, force_location_to=False):
quant = super(StockQuant, self)._quant_create_from_move(qty, move, lot_id=lot_id, owner_id=owner_id, src_package_id=src_package_id, dest_package_id=dest_package_id, force_location_from=force_location_from, force_location_to=force_location_to)
quant._account_entry_move(move)
- if move.product_id.valuation == 'real_time':
+ if move.product_id.product_tmpl_id.valuation == 'real_time':
# If the precision required for the variable quant cost is larger than the accounting
# precision, inconsistencies between the stock valuation and the accounting entries
# may arise.
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 19307cb67b7..466b92c1350 100644
--- a/addons/web/static/src/js/views/form_relational_widgets.js
+++ b/addons/web/static/src/js/views/form_relational_widgets.js
@@ -264,6 +264,12 @@ var FieldMany2One = common.AbstractField.extend(common.CompletionFieldMixin, com
}
});
+ // avoid ignoring autocomplete="off" by obfuscating placeholder, see #30439
+ if (this.$input.attr('placeholder')) {
+ this.$input.attr('placeholder', function (index, val) {
+ return val.split('').join('\ufeff');
+ });
+ }
var isSelecting = false;
// autocomplete
this.$input.autocomplete({
diff --git a/doc/cla/corporate/levelprime.md b/doc/cla/corporate/levelprime.md
index bca6d6aa7d9..d2d579302d8 100644
--- a/doc/cla/corporate/levelprime.md
+++ b/doc/cla/corporate/levelprime.md
@@ -9,9 +9,12 @@ declaration.
Signed,
Roberto Fichera roberto.fichera@levelprime.com https://github.com/robyf70
+Filippo Iovine - filippo.iovine@levelprime.com - https://github.com/FilippoIOVINE
List of contributors:
Roberto Fichera roberto.fichera@levelprime.com https://github.com/robyf70
Roberto Fichera robyf@tekno-soft.it https://github.com/robyf70
+Filippo Iovine - filippo.iovine@levelprime.com - https://github.com/FilippoIOVINE
+Filippo Iovine - filippo.jovine@gmail.com - https://github.com/FilippoIOVINE
diff --git a/odoo/addons/base/ir/ir_model.py b/odoo/addons/base/ir/ir_model.py
index a73431f58bb..308d64d32b7 100644
--- a/odoo/addons/base/ir/ir_model.py
+++ b/odoo/addons/base/ir/ir_model.py
@@ -352,6 +352,11 @@ class IrModelFields(models.Model):
raise UserError(_("The Selection Options expression is not a valid Pythonic expression."
"Please provide an expression in the [('key','Label'), ...] format."))
+ @api.constrains('domain')
+ def _check_domain(self):
+ for field in self:
+ safe_eval(field.domain or '[]')
+
@api.constrains('name', 'state')
def _check_name(self):
for field in self:
diff --git a/odoo/addons/base/ir/ir_ui_view.py b/odoo/addons/base/ir/ir_ui_view.py
index 133aecec2e1..827db67cd3c 100644
--- a/odoo/addons/base/ir/ir_ui_view.py
+++ b/odoo/addons/base/ir/ir_ui_view.py
@@ -364,7 +364,10 @@ actual arch.
def _compute_defaults(self, values):
if 'inherit_id' in values:
- values.setdefault('mode', 'extension' if values['inherit_id'] else 'primary')
+ # Do not automatically change the mode if the view already has an inherit_id,
+ # and the user change it to another.
+ if not values['inherit_id'] or all(not view.inherit_id for view in self):
+ values.setdefault('mode', 'extension' if values['inherit_id'] else 'primary')
return values
@api.model
diff --git a/odoo/addons/base/tests/test_views.py b/odoo/addons/base/tests/test_views.py
index bd462932e5f..16b85946439 100644
--- a/odoo/addons/base/tests/test_views.py
+++ b/odoo/addons/base/tests/test_views.py
@@ -905,6 +905,12 @@ class ViewModeField(ViewCase):
})
self.assertEqual(view2.mode, 'extension')
+ view2.write({'inherit_id': None})
+ self.assertEqual(view2.mode, 'primary')
+
+ view2.write({'inherit_id': view.id})
+ self.assertEqual(view2.mode, 'extension')
+
@mute_logger('odoo.sql_db')
def testModeExplicit(self):
view = self.View.create({
@@ -917,6 +923,7 @@ class ViewModeField(ViewCase):
'arch': ''
})
self.assertEqual(view.mode, 'primary')
+ self.assertEqual(view2.mode, 'primary')
with self.assertRaises(IntegrityError):
self.View.create({
@@ -968,6 +975,27 @@ class ViewModeField(ViewCase):
view.write({'mode': 'primary'})
+ def testChangeInheritOfPrimary(self):
+ """
+ A primary view with an inherit_id must remain primary when changing the inherit_id
+ """
+ base1 = self.View.create({
+ 'inherit_id': None,
+ 'arch': '',
+ })
+ base2 = self.View.create({
+ 'inherit_id': None,
+ 'arch': '',
+ })
+ view = self.View.create({
+ 'mode': 'primary',
+ 'inherit_id': base1.id,
+ 'arch': '',
+ })
+ self.assertEqual(view.mode, 'primary')
+ view.write({'inherit_id': base2.id})
+ self.assertEqual(view.mode, 'primary')
+
class TestDefaultView(ViewCase):
def testDefaultViewBase(self):