From c7247c07b6cba78d6c980e1da160c2535a2b7073 Mon Sep 17 00:00:00 2001 From: Gert Pellin Date: Wed, 27 Jun 2018 11:57:46 +0200 Subject: [PATCH 01/10] [FIX] product: Product variants attributes are not shown in pricelist report (#25497) Product variants are only shown by product name and price with no distinction between them. related to task #1861988 --- addons/product/report/product_pricelist_templates.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/addons/product/report/product_pricelist_templates.xml b/addons/product/report/product_pricelist_templates.xml index 43a127cdc6a..25d5f040330 100644 --- a/addons/product/report/product_pricelist_templates.xml +++ b/addons/product/report/product_pricelist_templates.xml @@ -45,6 +45,11 @@ [] + + - + + + Date: Wed, 27 Jun 2018 10:50:47 +0200 Subject: [PATCH 02/10] [FIX] base: print multi record report no outlines In b446930dc there has been a number of changes in report to improve performances. In one of these change, printing several documents with result such as: ```

``` The system will know it is rendering 2 mymodel records (with IDs: 1, 3) and will try from the document to determines what part (called "outlines") of the PDF corresponds to these IDs. This is done by splitting the document over semantical delimitations such as h1, h2, *. If all is fine: each part of the PDF will be saved for each record, so if we print records 1 and 3 they will be saved separately and will be possibly used as is the next time they are to be printed. But in the instance of printing a report which has: - field that are displayed, - several records printed at the same time, - save and getting document back from attachment enabled, - no h1 or other semantic delimitation The matching of part of PDF and outlines can't be done since there is no outlines, this was not expected by the code and caused an error. With this change, if there is no outline each part of the PDF will not be saved as attachment. opw-1856469 closes #25496 --- odoo/addons/base/ir/ir_actions_report.py | 32 +++++++++++++----------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/odoo/addons/base/ir/ir_actions_report.py b/odoo/addons/base/ir/ir_actions_report.py index 3d66287289a..aacff994d15 100644 --- a/odoo/addons/base/ir/ir_actions_report.py +++ b/odoo/addons/base/ir/ir_actions_report.py @@ -519,20 +519,24 @@ class IrActionsReport(models.Model): # we look on the pdf structure using pypdf to compute the outlines_pages that is # an array like [0, 3, 5] that means a new document start at page 0, 3 and 5. reader = PdfFileReader(pdf_content_stream) - outlines_pages = sorted( - [outline.getObject()[0] for outline in reader.trailer['/Root']['/Dests'].values()]) - assert len(outlines_pages) == len(res_ids) - for i, num in enumerate(outlines_pages): - to = outlines_pages[i + 1] if i + 1 < len(outlines_pages) else reader.numPages - attachment_writer = PdfFileWriter() - for j in range(num, to): - attachment_writer.addPage(reader.getPage(j)) - stream = io.BytesIO() - attachment_writer.write(stream) - if res_ids[i] and res_ids[i] not in save_in_attachment: - self.postprocess_pdf_report(record_map[res_ids[i]], stream) - streams.append(stream) - close_streams([pdf_content_stream]) + if reader.trailer['/Root'].get('/Dests'): + outlines_pages = sorted( + [outline.getObject()[0] for outline in reader.trailer['/Root']['/Dests'].values()]) + assert len(outlines_pages) == len(res_ids) + for i, num in enumerate(outlines_pages): + to = outlines_pages[i + 1] if i + 1 < len(outlines_pages) else reader.numPages + attachment_writer = PdfFileWriter() + for j in range(num, to): + attachment_writer.addPage(reader.getPage(j)) + stream = io.BytesIO() + attachment_writer.write(stream) + if res_ids[i] and res_ids[i] not in save_in_attachment: + self.postprocess_pdf_report(record_map[res_ids[i]], stream) + streams.append(stream) + close_streams([pdf_content_stream]) + else: + # If no outlines available, do not save each record + streams.append(pdf_content_stream) # If attachment_use is checked, the records already having an existing attachment # are not been rendered by wkhtmltopdf. So, create a new stream for each of them. From 787a156f0da9eb1a03b7de787d3262752287b804 Mon Sep 17 00:00:00 2001 From: Goffin Simon Date: Tue, 26 Jun 2018 14:45:21 +0200 Subject: [PATCH 03/10] [FIX] purchase_requisition: origin and partner_ref erased by a requisition Steps to reproduce the bug: - Create a product P with routes MTO and BUY - Set a vendor V on this product - Create a BOM B with P - Create a MO M with B - A RFQ is created with M as origin - Create a purchase requisition PR with vendor V - Set PR as purchase agreement on the RFQ Bug: - The field origin with M was erased by the name of PR - The field partner_ref was erased by the name of PR The fix is inspired by: c914df5a369eb49e4e72a698c0811200b5825f22 opw:1860430 --- .../purchase_requisition/models/purchase_requisition.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/addons/purchase_requisition/models/purchase_requisition.py b/addons/purchase_requisition/models/purchase_requisition.py index b2c1df32fb3..526ce255a9f 100644 --- a/addons/purchase_requisition/models/purchase_requisition.py +++ b/addons/purchase_requisition/models/purchase_requisition.py @@ -203,8 +203,12 @@ class PurchaseOrder(models.Model): self.payment_term_id = payment_term.id, self.company_id = requisition.company_id.id self.currency_id = currency.id - self.origin = requisition.name - self.partner_ref = requisition.name # to control vendor bill based on agreement reference + if not self.origin or requisition.name not in self.origin.split(', '): + if self.origin: + if requisition.name: + self.origin = self.origin + ', ' + requisition.name + else: + self.origin = requisition.name self.notes = requisition.description self.date_order = requisition.date_end or fields.Datetime.now() self.picking_type_id = requisition.picking_type_id.id From 39f0f5db77f793c7fb996d53195524de0e035de4 Mon Sep 17 00:00:00 2001 From: Christophe Matthieu Date: Wed, 27 Jun 2018 09:50:01 +0200 Subject: [PATCH 04/10] [FIX] ir_qweb: send template options in context to the fields Application of a fix more specific to the problem of the contact widget which can not serialize the dictionary because it contains the values of the context of which browse record. (because contact try to json.dumps options for multi-edition feature in web_editor) opw-1859848 https://github.com/odoo/odoo/commit/ab85e9594651fd33ffd2747f4d3c5b360e7c7922 --- addons/web/models/ir_qweb.py | 2 +- addons/web_editor/models/ir_qweb.py | 1 + odoo/addons/base/ir/ir_qweb/fields.py | 4 ++-- odoo/addons/base/ir/ir_qweb/ir_qweb.py | 22 +++++----------------- 4 files changed, 9 insertions(+), 20 deletions(-) diff --git a/addons/web/models/ir_qweb.py b/addons/web/models/ir_qweb.py index 0a5e0deb4e9..972ae4d813d 100644 --- a/addons/web/models/ir_qweb.py +++ b/addons/web/models/ir_qweb.py @@ -65,7 +65,7 @@ class Image(models.AbstractModel): atts["data-zoom"] = src_zoom and u'1' or None atts["data-zoom-image"] = src_zoom - atts = self.env['ir.qweb']._post_processing_att('img', atts, options) + atts = self.env['ir.qweb']._post_processing_att('img', atts, options.get('template_options')) img = [' Date: Fri, 22 Jun 2018 00:07:36 +0530 Subject: [PATCH 05/10] [FIX] hr_payroll: prevents maximum recursion depth When computing a salary sheet, a maximum recursion error could happen if there is a cycle in the rules or in the categories. Add constrains for salary rules and categories. Closes #25405 --- addons/hr_payroll/i18n/hr_payroll.pot | 16 ++++++++++++++-- addons/hr_payroll/models/hr_salary_rule.py | 10 ++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/addons/hr_payroll/i18n/hr_payroll.pot b/addons/hr_payroll/i18n/hr_payroll.pot index 3cc581d0c34..9239496c949 100644 --- a/addons/hr_payroll/i18n/hr_payroll.pot +++ b/addons/hr_payroll/i18n/hr_payroll.pot @@ -6,8 +6,8 @@ msgid "" msgstr "" "Project-Id-Version: Odoo Server 11.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-03-16 15:45+0000\n" -"PO-Revision-Date: 2018-03-16 15:45+0000\n" +"POT-Creation-Date: 2018-06-27 15:42+0000\n" +"PO-Revision-Date: 2018-06-27 15:42+0000\n" "Last-Translator: <>\n" "Language-Team: \n" "MIME-Version: 1.0\n" @@ -605,6 +605,18 @@ msgstr "" msgid "Error ! You cannot create a recursive Salary Structure." msgstr "" +#. module: hr_payroll +#: code:addons/hr_payroll/models/hr_salary_rule.py:91 +#, python-format +msgid "Error! You cannot create recursive hierarchy of Salary Rule Category." +msgstr "" + +#. module: hr_payroll +#: code:addons/hr_payroll/models/hr_salary_rule.py:176 +#, python-format +msgid "Error! You cannot create recursive hierarchy of Salary Rules." +msgstr "" + #. module: hr_payroll #: model:ir.model.fields,help:hr_payroll.field_hr_payslip_line_register_id #: model:ir.model.fields,help:hr_payroll.field_hr_salary_rule_register_id diff --git a/addons/hr_payroll/models/hr_salary_rule.py b/addons/hr_payroll/models/hr_salary_rule.py index f8a342a354b..acea3c86543 100644 --- a/addons/hr_payroll/models/hr_salary_rule.py +++ b/addons/hr_payroll/models/hr_salary_rule.py @@ -85,6 +85,11 @@ class HrSalaryRuleCategory(models.Model): company_id = fields.Many2one('res.company', string='Company', default=lambda self: self.env['res.company']._company_default_get()) + @api.constrains('parent_id') + def _check_parent_id(self): + if not self._check_recursion(): + raise ValidationError(_('Error! You cannot create recursive hierarchy of Salary Rule Category.')) + class HrSalaryRule(models.Model): _name = 'hr.salary.rule' @@ -165,6 +170,11 @@ class HrSalaryRule(models.Model): input_ids = fields.One2many('hr.rule.input', 'input_id', string='Inputs', copy=True) note = fields.Text(string='Description') + @api.constrains('parent_rule_id') + def _check_parent_rule_id(self): + if not self._check_recursion(parent='parent_rule_id'): + raise ValidationError(_('Error! You cannot create recursive hierarchy of Salary Rules.')) + @api.multi def _recursive_search_of_rules(self): """ From d44a4df8638a3f627d15caa5793f72dc9c0a5cdc Mon Sep 17 00:00:00 2001 From: Christophe Simonis Date: Wed, 27 Jun 2018 20:13:37 +0200 Subject: [PATCH 06/10] [FIX] stock: correctly check UoM and Locations modifications --- addons/stock/models/product.py | 24 ++++++++++++++---------- addons/stock/models/stock_location.py | 6 +++--- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/addons/stock/models/product.py b/addons/stock/models/product.py index 632983fab30..5e5c6f1d7fb 100644 --- a/addons/stock/models/product.py +++ b/addons/stock/models/product.py @@ -591,15 +591,19 @@ class ProductUoM(models.Model): def write(self, values): # Users can not update the factor if open stock moves are based on it if 'factor' in values or 'factor_inv' in values or 'category_id' in values: - stock_move_lines = self.env['stock.move.line'].search_count([ - ('product_uom_id.category_id', '=', self.category_id.id), - ('state', '!=', 'cancel'), - ]) + changed = self.filtered( + lambda u: any(u[f] != values[f] if f in values else False + for f in {'factor', 'factor_inv', 'category_id'})) + if changed: + stock_move_lines = self.env['stock.move.line'].search_count([ + ('product_uom_id.category_id', 'in', changed.mapped('category_id.id')), + ('state', '!=', 'cancel'), + ]) - if stock_move_lines: - raise UserError(_( - "You cannot change the ratio of this unit of mesure as some" - " products with this UoM have already been moved or are " - "currently reserved." - )) + if stock_move_lines: + raise UserError(_( + "You cannot change the ratio of this unit of mesure as some" + " products with this UoM have already been moved or are " + "currently reserved." + )) return super(ProductUoM, self).write(values) diff --git a/addons/stock/models/stock_location.py b/addons/stock/models/stock_location.py index 5a06cc2425e..bce2de0ecd2 100644 --- a/addons/stock/models/stock_location.py +++ b/addons/stock/models/stock_location.py @@ -84,10 +84,10 @@ class Location(models.Model): if self.mapped('quant_ids'): raise UserError(_("This location's usage cannot be changed to view as it contains products.")) if 'usage' in values or 'scrap_location' in values: + modified_locations = self.filtered( - lambda l: l.usage != values.get('usage') or - l.scrap_location != values.get('scrap_location') - ) + lambda l: any(l[f] != values[f] if f in values else False + for f in {'usage', 'scrap_location'})) reserved_quantities = self.env['stock.move.line'].search_count([ ('location_id', 'in', modified_locations.ids), ('product_qty', '>', 0), From 9dcc32c3041e7ad3549a09c5ed68ee3ef22c7a26 Mon Sep 17 00:00:00 2001 From: Christophe Simonis Date: Wed, 27 Jun 2018 20:14:47 +0200 Subject: [PATCH 07/10] [FIX] stock: avoid useless changes on UoM in tests setUp --- addons/stock/tests/common.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/addons/stock/tests/common.py b/addons/stock/tests/common.py index 6a695af9363..6058cc40acc 100644 --- a/addons/stock/tests/common.py +++ b/addons/stock/tests/common.py @@ -43,10 +43,6 @@ class TestStockCommon(common.TransactionCase): # Configure unit of measure. self.uom_kg = self.UomObj.create({ 'name': 'Test-KG', - 'category_id': self.categ_kgm, - 'factor_inv': 1, - 'factor': 1, - 'uom_type': 'reference', 'rounding': 0.000001}) self.uom_tone = self.UomObj.create({ 'name': 'Test-Tone', @@ -69,9 +65,6 @@ class TestStockCommon(common.TransactionCase): # Check Unit self.uom_unit = self.UomObj.create({ 'name': 'Test-Unit', - 'category_id': self.categ_unit, - 'factor': 1, - 'uom_type': 'reference', 'rounding': 1.0}) self.uom_dozen = self.UomObj.create({ 'name': 'Test-DozenA', From 50a10fd736e3285f05fbc56ba59ad47c7a0c867f Mon Sep 17 00:00:00 2001 From: Nicolas Martinelli Date: Wed, 27 Jun 2018 16:30:56 +0200 Subject: [PATCH 08/10] [FIX] stock: search route name opw-1862275 --- addons/stock/views/stock_location_views.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/addons/stock/views/stock_location_views.xml b/addons/stock/views/stock_location_views.xml index 4f3cf4998ad..4ba9dbc9567 100644 --- a/addons/stock/views/stock_location_views.xml +++ b/addons/stock/views/stock_location_views.xml @@ -284,6 +284,7 @@ stock.location.route + From a0173bbbcef2ec3cad3458fa13142d4fe5ccdb8c Mon Sep 17 00:00:00 2001 From: Nicolas Martinelli Date: Thu, 28 Jun 2018 08:09:42 +0200 Subject: [PATCH 09/10] [FIX] stock: fix test setup --- addons/stock/tests/common.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/addons/stock/tests/common.py b/addons/stock/tests/common.py index 6058cc40acc..09f55bb2ac3 100644 --- a/addons/stock/tests/common.py +++ b/addons/stock/tests/common.py @@ -43,6 +43,7 @@ class TestStockCommon(common.TransactionCase): # Configure unit of measure. self.uom_kg = self.UomObj.create({ 'name': 'Test-KG', + 'category_id': self.categ_kgm, 'rounding': 0.000001}) self.uom_tone = self.UomObj.create({ 'name': 'Test-Tone', @@ -65,6 +66,7 @@ class TestStockCommon(common.TransactionCase): # Check Unit self.uom_unit = self.UomObj.create({ 'name': 'Test-Unit', + 'category_id': self.categ_unit, 'rounding': 1.0}) self.uom_dozen = self.UomObj.create({ 'name': 'Test-DozenA', From f6d36d81f7a601eb8902552d08915cb493d917f6 Mon Sep 17 00:00:00 2001 From: Nicolas Lempereur Date: Wed, 27 Jun 2018 15:03:53 +0200 Subject: [PATCH 10/10] [FIX] mail: discuss smileys on several lines In the discuss composer on google chrome, when when the smileys have more width than available, they stayed on one line and would increase page size (adding an horizontal scrollbar). This happened really easily in saas-11.3 because we have more smileys, but still happened in 11.0 depending on the screen size. opw-1861191 closes #25507 Co-authored-by: qsm-odoo --- addons/mail/static/src/less/composer.less | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/mail/static/src/less/composer.less b/addons/mail/static/src/less/composer.less index 9213fb6c416..a22a626c584 100644 --- a/addons/mail/static/src/less/composer.less +++ b/addons/mail/static/src/less/composer.less @@ -136,7 +136,7 @@ &.o_chat_inline_composer { .o_composer_container { - .o-flex(1, 0, auto); + .o-flex(1, 1, auto); } .o_composer { padding: @o-mail-chatter-gap @o-mail-chatter-gap 0 @o-mail-chatter-gap;