From f3de712d7624d9385b098c6657a660fbce3c0aad Mon Sep 17 00:00:00 2001 From: Adrian Torres Date: Fri, 11 Jan 2019 16:03:44 +0000 Subject: [PATCH 1/8] [FIX] ir_model: add constraint on domain field Previous to this commit, if one were to create an ir.model.field with a poorly constructed domain (read: SyntaxError), the server would properly send an error message stating that an Error occurred, however this would be too late as the registry with the bad code would have already been reloaded, this meant that the registry would be left in an unstable state (read: crashed). With this commit, a constraint on the domain is added so that we confirm that the code in the domain field is properly constructed, thus no need to reload the registry and therefore no crash. closes odoo/odoo#30157 --- odoo/addons/base/ir/ir_model.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/odoo/addons/base/ir/ir_model.py b/odoo/addons/base/ir/ir_model.py index ec58bf422ac..fd63d0c590d 100644 --- a/odoo/addons/base/ir/ir_model.py +++ b/odoo/addons/base/ir/ir_model.py @@ -287,6 +287,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: From 169843d8d33bef60827a76f5f7cb8e5f6bcf1758 Mon Sep 17 00:00:00 2001 From: Filippo Iovine Date: Tue, 27 Nov 2018 11:35:45 +0000 Subject: [PATCH 2/8] [CLA] add Filippo Iovine to levelprime CCLA closes odoo/odoo#29072 --- doc/cla/corporate/levelprime.md | 3 +++ 1 file changed, 3 insertions(+) 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 From a909bb3391f28622778942a4f8fe151e0d1e6607 Mon Sep 17 00:00:00 2001 From: Christophe Simonis Date: Fri, 18 Jan 2019 10:35:17 +0000 Subject: [PATCH 3/8] [FIX] bus: correctly compute presence of partners with multiple users closes odoo/odoo#30344 --- addons/bus/models/res_partner.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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: From c5a8e39758004be784999dacd322d94fb077318e Mon Sep 17 00:00:00 2001 From: qdp-odoo Date: Mon, 21 Jan 2019 11:00:02 +0000 Subject: [PATCH 4/8] [FIX] l10n_be_intrastat: adapt to new regulation for belgian intrastat This is essentially a backport of module l10n_be_intrastat_2019 made for Enterprise v12 + the refactoring needed on the original v10 module to allow the changes to apply where needed. Was requested by opw-1887019 closes odoo/odoo#30400 --- addons/l10n_be_intrastat/wizard/xml_decl.py | 228 ++++++++++-------- addons/l10n_be_intrastat_2019/__init__.py | 4 + addons/l10n_be_intrastat_2019/__manifest__.py | 15 ++ .../i18n/l10n_be_intrastat_2019.pot | 32 +++ .../l10n_be_intrastat_2019/models/__init__.py | 4 + .../models/account_intrastat_report.py | 33 +++ .../models/account_invoice_line.py | 9 + .../views/account_invoice_line_view.xml | 13 + 8 files changed, 233 insertions(+), 105 deletions(-) create mode 100644 addons/l10n_be_intrastat_2019/__init__.py create mode 100644 addons/l10n_be_intrastat_2019/__manifest__.py create mode 100644 addons/l10n_be_intrastat_2019/i18n/l10n_be_intrastat_2019.pot create mode 100644 addons/l10n_be_intrastat_2019/models/__init__.py create mode 100644 addons/l10n_be_intrastat_2019/models/account_intrastat_report.py create mode 100644 addons/l10n_be_intrastat_2019/models/account_invoice_line.py create mode 100644 addons/l10n_be_intrastat_2019/views/account_invoice_line_view.xml diff --git a/addons/l10n_be_intrastat/wizard/xml_decl.py b/addons/l10n_be_intrastat/wizard/xml_decl.py index 1758b650641..067be44ea06 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 sale 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 sale 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 + + + + + + + + From a409627602ab6c03364b492b5ba7a6fbaba6c5d6 Mon Sep 17 00:00:00 2001 From: Denis Ledoux Date: Tue, 15 Jan 2019 15:28:54 +0000 Subject: [PATCH 5/8] [FIX] ir_ui_view: do not change view mode when just changing the inherit Some views have the primary mode while having an inherit_id view In such views, if the user wants to change the inherit_id, the mode must remain primary. The use case behind this is a user who want to change the inherit_id view of the view product.product.form (product.product_normal_form_view) to another view. This view is in primary mode while having an inherit_id (product.product_template_form_view). In such a case, the primary mode must remain, otherwise the view will no longer be opened by default when opening a product. Indeed, when searching the default form view of a model, it only searches for primary mode views for this model. The `all` is there because this is an `api.multi` method. We could consider adding a `self.ensure_one`, but it breaks the API if someones calls this method with multiple records. This is likely to happen, as this method is used in `write` which is likely to be called with multiple records. In my opinion, in master, this should be replaced by an onchange so the user can see the change of mode when he adds or remove an inherit_id for a view. opw-1916324 closes odoo/odoo#30241 --- odoo/addons/base/ir/ir_ui_view.py | 5 ++++- odoo/addons/base/tests/test_views.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/odoo/addons/base/ir/ir_ui_view.py b/odoo/addons/base/ir/ir_ui_view.py index 2e65e331a8e..feb2218711b 100644 --- a/odoo/addons/base/ir/ir_ui_view.py +++ b/odoo/addons/base/ir/ir_ui_view.py @@ -330,7 +330,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 de7d2e060e6..187e2e443d8 100644 --- a/odoo/addons/base/tests/test_views.py +++ b/odoo/addons/base/tests/test_views.py @@ -845,6 +845,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({ @@ -857,6 +863,7 @@ class ViewModeField(ViewCase): 'arch': '' }) self.assertEqual(view.mode, 'primary') + self.assertEqual(view2.mode, 'primary') with self.assertRaises(IntegrityError): self.View.create({ @@ -908,6 +915,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): From d3affc57988f3bde23f9ad5eaa008237b9acf765 Mon Sep 17 00:00:00 2001 From: Florent de Labarre Date: Tue, 22 Jan 2019 20:28:21 +0000 Subject: [PATCH 6/8] [FIX] sale: don't send duplicate email closes odoo/odoo#30455 --- addons/sale/models/sale.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/sale/models/sale.py b/addons/sale/models/sale.py index eabb9a28912..314c0594684 100644 --- a/addons/sale/models/sale.py +++ b/addons/sale/models/sale.py @@ -463,7 +463,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() From 90c1af1151c42114c9f3ec26f2beca73c21fa9e6 Mon Sep 17 00:00:00 2001 From: Nicolas Lempereur Date: Tue, 22 Jan 2019 16:31:14 +0000 Subject: [PATCH 7/8] [FIX] web: no chrome autocompete on many2one widget For editing many2one fields (and partially many2many) most widget show an autocompleting list of targeted records. They thus have `autocomplete="off"` to prevent browser completion. But chromium has an history of breaking `autocomplete="off"`, see: - https://caniuse.com/#search=autocomplete - https://crbug.com/468153 - https://crbug.com/587466 - https://crbug.com/914451 - https://crbug.com/923895 It seems that since chromium 71, the heuristic to ignore `autocomplete="off"` has become more aggressive and for example if there is at least 3 fields like an address in a page, chromium will ignore `autocomplete="off"` for the fields like an address. So for example the eidting the many2One field with placeholder "Country" in a contact page now has a browser autocomplete menu that is: - hidding the many2one autocomplete - going to save empty country it appeared visually filled if the autocomplete result was selected. With this changeset, the placeholder in the many2one instance is interspersed with U+FEFF charcters (ZERO WIDTH NO-BREAK SPACE) so the browser does enable the autocomplete feature by force. This should thus remove the issue (until it is fixed by chromium) in the case of field named "Country" or matching other regexes in this file: https://github.com/chromium/chromium/blob/cdb1b2073f12/components/autofill/core/common/autofill_regex_constants.cc U+FEFF has been chosen instead of more recommended characters because other have been shown erroneous for printing in some windows configuration (see cb2a3afa7). 10.0 version of #30439 opw-1930588 closes #30439 closes #30449 --- addons/web/static/src/js/views/form_relational_widgets.js | 6 ++++++ 1 file changed, 6 insertions(+) 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 2ca483eac2b..1dd61553ca2 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({ From 3f4d1e1fbe2da53112d2791c71c6cfb4033c29a4 Mon Sep 17 00:00:00 2001 From: Nicolas Seinlet Date: Wed, 16 Jan 2019 14:08:18 +0100 Subject: [PATCH 8/8] [IMP] mrp,stock_account: faster computation Use read_group is faster than the python equivalent, and then don't push ids in ORM cache, which makes the ORM faster later in the process. Also unlink in batch. --- addons/mrp/models/stock_move.py | 22 ++++++++++++++++++---- addons/stock_account/models/stock.py | 2 +- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/addons/mrp/models/stock_move.py b/addons/mrp/models/stock_move.py index 868bd08d121..2e0b4ce374c 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/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.