diff --git a/addons/account/security/account_security.xml b/addons/account/security/account_security.xml index 69f3fca8bc8..b7ec99e56c0 100644 --- a/addons/account/security/account_security.xml +++ b/addons/account/security/account_security.xml @@ -20,18 +20,6 @@ - - - - - - - - - - - - Show Full Accounting Features @@ -75,6 +63,16 @@ + + + + + + + + + + diff --git a/addons/account/views/report_invoice.xml b/addons/account/views/report_invoice.xml index 043c5a3cc47..8c3da3ae101 100644 --- a/addons/account/views/report_invoice.xml +++ b/addons/account/views/report_invoice.xml @@ -51,12 +51,16 @@ + +
Description Source Document Quantity Unit Price Disc.(%) + + Taxes diff --git a/addons/board/static/src/scss/dashboard.scss b/addons/board/static/src/scss/dashboard.scss index ad718403495..7bd942eb0e3 100644 --- a/addons/board/static/src/scss/dashboard.scss +++ b/addons/board/static/src/scss/dashboard.scss @@ -123,8 +123,8 @@ } // Override height of graph. min-height doesn't do the trick - .o_graph_controller { - height: 500px; + .o_graph_svg_container { + height: 250px; } } } diff --git a/addons/calendar/models/res_partner.py b/addons/calendar/models/res_partner.py index 403d70b18fc..261b955fc8f 100644 --- a/addons/calendar/models/res_partner.py +++ b/addons/calendar/models/res_partner.py @@ -11,7 +11,7 @@ from odoo.addons.calendar.models.calendar import get_real_ids class Partner(models.Model): _inherit = 'res.partner' - calendar_last_notif_ack = fields.Datetime('Last notification marked as read from base Calendar') + calendar_last_notif_ack = fields.Datetime('Last notification marked as read from base Calendar', default=fields.Datetime.now) @api.multi def get_attendee_detail(self, meeting_id): diff --git a/addons/mail/models/ir_attachment.py b/addons/mail/models/ir_attachment.py index 4330059c495..527b22409d6 100644 --- a/addons/mail/models/ir_attachment.py +++ b/addons/mail/models/ir_attachment.py @@ -13,9 +13,22 @@ class IrAttachment(models.Model): """ super(IrAttachment, self)._post_add_create() for record in self: - record.register_as_main_attachment() + record.register_as_main_attachment(force=False) - def register_as_main_attachment(self): + @api.multi + def unlink(self): + self.remove_as_main_attachment() + super(IrAttachment, self).unlink() + + @api.multi + def remove_as_main_attachment(self): + for attachment in self: + related_record = self.env[attachment.res_model].browse(attachment.res_id) + if related_record and hasattr(related_record, 'message_main_attachment_id'): + if related_record.message_main_attachment_id == attachment: + related_record.message_main_attachment_id = False + + def register_as_main_attachment(self, force=True): """ Registers this attachment as the main one of the model it is attached to. """ @@ -24,4 +37,5 @@ class IrAttachment(models.Model): # message_main_attachment_id field can be empty, that's why we compare to False; # we are just checking that it exists on the model before writing it if related_record and hasattr(related_record, 'message_main_attachment_id'): - related_record.message_main_attachment_id = self + if force or not related_record.message_main_attachment_id: + related_record.message_main_attachment_id = self diff --git a/addons/mail/static/src/js/chatter.js b/addons/mail/static/src/js/chatter.js index 72971fbeee9..5f9b11198e3 100644 --- a/addons/mail/static/src/js/chatter.js +++ b/addons/mail/static/src/js/chatter.js @@ -81,6 +81,7 @@ var Chatter = Widget.extend({ var nodeOptions = fieldsInfo[mailFields.mail_thread].options || {}; this.hasLogButton = options.display_log_button || nodeOptions.display_log_button; this.postRefresh = nodeOptions.post_refresh || 'never'; + this.reloadOnUploadAttachment = this.postRefresh === 'always'; } }, /** @@ -572,6 +573,9 @@ var Chatter = Widget.extend({ * @private */ _onReloadAttachmentBox: function () { + if (this.reloadOnUploadAttachment) { + this.trigger_up('reload'); + } this._reloadAttachmentBox(); }, /** diff --git a/addons/mrp/wizard/mrp_product_produce.py b/addons/mrp/wizard/mrp_product_produce.py index b88b7a45eda..d29fc0c140d 100644 --- a/addons/mrp/wizard/mrp_product_produce.py +++ b/addons/mrp/wizard/mrp_product_produce.py @@ -189,7 +189,7 @@ class MrpProductProduceLine(models.TransientModel): product_produce_id = fields.Many2one('mrp.product.produce') product_id = fields.Many2one('product.product', 'Product') - product_tracking = fields.Selection(related="product_id.tracking", readonly=False) + product_tracking = fields.Selection(related="product_id.tracking") lot_id = fields.Many2one('stock.production.lot', 'Lot/Serial Number') qty_to_consume = fields.Float('To Consume', digits=dp.get_precision('Product Unit of Measure')) product_uom_id = fields.Many2one('uom.uom', 'Unit of Measure') diff --git a/addons/product/models/product_template.py b/addons/product/models/product_template.py index 0928aab1815..08b1aa26a8c 100644 --- a/addons/product/models/product_template.py +++ b/addons/product/models/product_template.py @@ -523,6 +523,7 @@ class ProductTemplate(models.Model): variants_to_create.append({ 'product_tmpl_id': tmpl_id.id, 'attribute_value_ids': [(6, 0, list(value_ids))], + 'active': tmpl_id.active, }) if len(variants_to_create) > 1000: raise UserError(_( @@ -962,8 +963,10 @@ class ProductTemplate(models.Model): if not self.active: return _("The product template is archived so no combination is possible.") - ptal_stack = [self._get_valid_product_template_attribute_lines()] - combination_stack = [self.env['product.template.attribute.value']] + necessary_values = necessary_values or self.env['product.template.attribute.value'] + necessary_attributes = necessary_values.mapped('attribute_id') + ptal_stack = [self.valid_product_template_attribute_line_ids.filtered(lambda ptal: ptal.attribute_id not in necessary_attributes)] + combination_stack = [necessary_values] # keep going while we have attribute lines to test while len(ptal_stack): @@ -972,7 +975,7 @@ class ProductTemplate(models.Model): if not attribute_lines: # full combination, if it's possible return it, otherwise skip it - if self._is_combination_possible(combination, parent_combination) and all(v in combination for v in (necessary_values or [])): + if self._is_combination_possible(combination, parent_combination): yield(combination) else: # we have remaining attribute lines to consider diff --git a/addons/product/tests/test_product_attribute_value_config.py b/addons/product/tests/test_product_attribute_value_config.py index aa1de67d16b..e966adbdbdc 100644 --- a/addons/product/tests/test_product_attribute_value_config.py +++ b/addons/product/tests/test_product_attribute_value_config.py @@ -1,6 +1,8 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. +import time + from odoo.tests import tagged from odoo.tests.common import TransactionCase @@ -423,6 +425,47 @@ class TestProductAttributeValueConfig(TestProductAttributeValueSetup): self.assertEqual(self.computer._get_closest_possible_combination(computer_ssd_256 + computer_ram_8 + computer_hdd_4 + computer_hdd_2), computer_ssd_256 + computer_ram_8 + computer_hdd_4) + # Make sure this is not extremely slow: + product_template = self.env['product.template'].create({ + 'name': 'many combinations', + }) + + for i in range(10): + # create the attributes + product_attribute = self.env['product.attribute'].create({ + 'name': "att %s" % i, + 'create_variant': 'dynamic', + 'sequence': i, + }) + + for j in range(10): + # create the attribute values + self.env['product.attribute.value'].create([{ + 'name': "val %s/%s" % (i, j), + 'attribute_id': product_attribute.id, + 'sequence': j, + }]) + + # set attribute and attribute values on the template + self.env['product.template.attribute.line'].create([{ + 'attribute_id': product_attribute.id, + 'product_tmpl_id': product_template.id, + 'value_ids': [(6, 0, product_attribute.value_ids.ids)] + }]) + + # Get a value in the middle for each attribute to make sure it would + # take time to reach it (if looping one by one like before the fix). + combination = self.env['product.template.attribute.value'] + for ptal in product_template.attribute_line_ids: + combination += ptal.product_template_value_ids[5] + + started_at = time.time() + self.assertEqual(product_template._get_closest_possible_combination(combination), combination) + elapsed = time.time() - started_at + # It should take around 10ms, but to avoid false positives we check an + # higher value. Before the fix it would take hours. + self.assertLess(elapsed, 0.5) + def test_clear_caches(self): """The goal of this test is to make sure the cache is invalidated when it should be.""" diff --git a/addons/sale/report/sale_report_templates.xml b/addons/sale/report/sale_report_templates.xml index 380596eb181..c8c9612326a 100644 --- a/addons/sale/report/sale_report_templates.xml +++ b/addons/sale/report/sale_report_templates.xml @@ -70,11 +70,15 @@ + +
Description Quantity Unit Price Disc.(%) + + Taxes diff --git a/addons/sale/views/sale_portal_templates.xml b/addons/sale/views/sale_portal_templates.xml index 1518f30391d..ddabd43b725 100644 --- a/addons/sale/views/sale_portal_templates.xml +++ b/addons/sale/views/sale_portal_templates.xml @@ -423,11 +423,15 @@ + + diff --git a/addons/stock/report/report_stockinventory.xml b/addons/stock/report/report_stockinventory.xml index 90abd6b4109..19a988dfe59 100644 --- a/addons/stock/report/report_stockinventory.xml +++ b/addons/stock/report/report_stockinventory.xml @@ -6,6 +6,7 @@
+

Stock Inventory

Products Quantity Unit Price Discount + + Taxes diff --git a/addons/sale_management/models/sale_order.py b/addons/sale_management/models/sale_order.py index 6ebed05a2bc..6a95dfeaeeb 100644 --- a/addons/sale_management/models/sale_order.py +++ b/addons/sale_management/models/sale_order.py @@ -31,7 +31,8 @@ class SaleOrder(models.Model): @api.onchange('partner_id') def onchange_partner_id(self): super(SaleOrder, self).onchange_partner_id() - self.note = self.sale_order_template_id.note or self.note + template = self.sale_order_template_id.with_context(lang=self.partner_id.lang) + self.note = template.note or self.note def _compute_line_data_for_template_change(self, line): return { diff --git a/addons/sale_management/models/sale_order_template.py b/addons/sale_management/models/sale_order_template.py index 8eef6efd405..493386f33c7 100644 --- a/addons/sale_management/models/sale_order_template.py +++ b/addons/sale_management/models/sale_order_template.py @@ -18,7 +18,7 @@ class SaleOrderTemplate(models.Model): name = fields.Char('Quotation Template', required=True) sale_order_template_line_ids = fields.One2many('sale.order.template.line', 'sale_order_template_id', 'Lines', copy=True) - note = fields.Text('Terms and conditions') + note = fields.Text('Terms and conditions', translate=True) sale_order_template_option_ids = fields.One2many('sale.order.template.option', 'sale_order_template_id', 'Optional Products', copy=True) number_of_days = fields.Integer('Quotation Duration', help='Number of days for the validity date computation of the quotation') diff --git a/addons/sale_management/views/sale_portal_templates.xml b/addons/sale_management/views/sale_portal_templates.xml index 90d2100ba4a..f29a538dd0e 100644 --- a/addons/sale_management/views/sale_portal_templates.xml +++ b/addons/sale_management/views/sale_portal_templates.xml @@ -15,6 +15,7 @@ +
diff --git a/addons/stock_account/views/product_views.xml b/addons/stock_account/views/product_views.xml index d4920effef1..1a42d5fcb58 100644 --- a/addons/stock_account/views/product_views.xml +++ b/addons/stock_account/views/product_views.xml @@ -67,7 +67,7 @@
+ widget='monetary' options="{'currency_field': 'cost_currency_id', 'field_digits': True}" class="float-left"/>