From cb3e415a7cbaa169deb46711858d16a90ca1c89e Mon Sep 17 00:00:00 2001 From: Goffin Simon Date: Wed, 21 Dec 2016 11:57:04 +0100 Subject: [PATCH 01/15] [FIX] mail: Allow modifying followers in multi company/with private channel It's a backport for https://github.com/odoo/odoo/commit/b795d69ce8aca2e88182c6d6d6b7af54d536f9dd It also backports this fix https://github.com/odoo/odoo/commit/c49265afa1b0610d52fd356df26491546f1905bf It also fixes #13578 --- addons/mail/controllers/main.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/addons/mail/controllers/main.py b/addons/mail/controllers/main.py index d9e55765df1..2a632d09f9a 100644 --- a/addons/mail/controllers/main.py +++ b/addons/mail/controllers/main.py @@ -112,7 +112,14 @@ class MailController(http.Controller): def read_followers(self, follower_ids): result = [] is_editable = request.env.user.has_group('base.group_no_one') - for follower in request.env['mail.followers'].browse(follower_ids): + follower_recs = request.env['mail.followers'].sudo().browse(follower_ids) + if follower_recs: + mapped_data = {} + for follower_rec in follower_recs: + mapped_data.setdefault(follower_rec.res_model, set()).add(follower_rec.res_id) + for res_model, res_ids in mapped_data.iteritems(): + request.env[res_model].browse(list(res_ids)).check_access_rule("read") + for follower in follower_recs: result.append({ 'id': follower.id, 'name': follower.partner_id.name or follower.channel_id.name, From 46129e753051816acb6bb356212bf6f95c243c17 Mon Sep 17 00:00:00 2001 From: rgarnau Date: Tue, 12 Sep 2017 16:32:11 +0200 Subject: [PATCH 02/15] [FIX] stock: scrapping consumable products allowing negative quantities As for consumables stock levels are not that important, it should be ok to scrap a consumable at all times, even if the stock becomes negative. Tests were added to check what happens with a consumable/stockable product if stays positive/goes negative. --- addons/stock/models/stock_scrap.py | 21 ++--- addons/stock/tests/__init__.py | 1 + addons/stock/tests/test_stock_scrap.py | 107 +++++++++++++++++++++++++ 3 files changed, 119 insertions(+), 10 deletions(-) create mode 100644 addons/stock/tests/test_stock_scrap.py diff --git a/addons/stock/models/stock_scrap.py b/addons/stock/models/stock_scrap.py index 16be4d9b73b..7707ea90523 100644 --- a/addons/stock/models/stock_scrap.py +++ b/addons/stock/models/stock_scrap.py @@ -80,16 +80,17 @@ class StockScrap(models.Model): for scrap in self: moves = scrap._get_origin_moves() or self.env['stock.move'] move = self.env['stock.move'].create(scrap._prepare_move_values()) - quants = self.env['stock.quant'].quants_get_preferred_domain( - move.product_qty, move, - domain=[ - ('qty', '>', 0), - ('lot_id', '=', self.lot_id.id), - ('package_id', '=', self.package_id.id)], - preferred_domain_list=scrap._get_preferred_domain()) - if any([not x[0] for x in quants]): - raise UserError(_('You cannot scrap a move without having available stock for %s. You can correct it with an inventory adjustment.') % move.product_id.name) - self.env['stock.quant'].quants_reserve(quants, move) + if move.product_id.type == 'product': + quants = self.env['stock.quant'].quants_get_preferred_domain( + move.product_qty, move, + domain=[ + ('qty', '>', 0), + ('lot_id', '=', self.lot_id.id), + ('package_id', '=', self.package_id.id)], + preferred_domain_list=scrap._get_preferred_domain()) + if any([not x[0] for x in quants]): + raise UserError(_('You cannot scrap a move without having available stock for %s. You can correct it with an inventory adjustment.') % move.product_id.name) + self.env['stock.quant'].quants_reserve(quants, move) move.action_done() scrap.write({'move_id': move.id, 'state': 'done'}) moves.recalculate_move_state() diff --git a/addons/stock/tests/__init__.py b/addons/stock/tests/__init__.py index bbc9cde189e..06e4428afda 100644 --- a/addons/stock/tests/__init__.py +++ b/addons/stock/tests/__init__.py @@ -4,3 +4,4 @@ from . import test_stock_flow import test_product import test_warehouse import test_shipment +import test_stock_scrap diff --git a/addons/stock/tests/test_stock_scrap.py b/addons/stock/tests/test_stock_scrap.py new file mode 100644 index 00000000000..b5dc521f4b7 --- /dev/null +++ b/addons/stock/tests/test_stock_scrap.py @@ -0,0 +1,107 @@ +# -*- coding: utf-8 -*- + +from openerp.tests.common import TransactionCase +from openerp.exceptions import UserError + + +class TestStockScrap(TransactionCase): + + def setUp(self): + super(TestStockScrap, self).setUp() + self.user_demo = self.env.ref('base.user_demo') + self.scrap_obj = self.env['stock.scrap'] + self.picking_obj = self.env['stock.picking'] + self.stock_loc = self.browse_ref('stock.stock_location_stock') + self.customer_loc = self.browse_ref('stock.stock_location_customers') + + # Stockable product + self.product_stockable = self.env['product.product'].create({ + 'type': 'product', + 'name': 'Stockable Product', + 'uom_id': self.env.ref('product.product_uom_unit').id, + 'uom_po_id': self.env.ref('product.product_uom_unit').id, + }) + + # Consumable product + self.product_consumable = self.env['product.product'].create({ + 'type': 'consu', + 'name': 'Consumable Product', + 'uom_id': self.env.ref('product.product_uom_unit').id, + 'uom_po_id': self.env.ref('product.product_uom_unit').id, + }) + + self._update_product_qty(self.product_stockable) + + def _update_product_qty(self, product): + product_qty = self.env['stock.change.product.qty'].create({ + 'location_id': self.stock_loc.id, + 'product_id': product.id, + 'new_quantity': 100.0, + }) + product_qty.change_product_qty() + return product_qty + + def _prepare_picking(self, product): + picking = self.picking_obj.create({ + 'name': 'picking - test', + 'location_id': self.stock_loc.id, + 'location_dest_id': self.customer_loc.id, + 'picking_type_id': self.ref('stock.picking_type_out'), + 'move_lines': [(0, 0, { + 'name': product.name, + 'product_id': product.id, + 'product_uom_qty': 20.0, + 'product_uom': product.uom_id.id, + })] + }) + return picking + + def test_stock_scrap_01(self): + """ Standalone scrap a stockable product """ + with self.assertRaises(UserError): + self.scrap_obj.create({ + 'name': 'scrap - grand test', + 'product_id': self.product_stockable.id, + 'product_uom_id': self.ref('product.product_uom_unit'), + 'scrap_qty': 120, + }) + self.assertEquals(self.product_stockable.qty_available, 100.0) + + def test_stock_scrap_02(self): + """ Scrap a stockable product from a picking """ + picking = self._prepare_picking(self.product_stockable) + scrap = self.scrap_obj.create({ + 'name': 'scrap - grand test', + 'product_id': self.product_stockable.id, + 'product_uom_id': self.ref('product.product_uom_unit'), + 'picking_id': picking.id, + 'scrap_qty': 80, + }) + self.assertEquals(scrap.state, 'done') + self.assertEquals(self.product_stockable.qty_available, 20.0) + + def test_stock_scrap_03(self): + """ Standalone scrap a consumable product """ + scrap = self.scrap_obj.create({ + 'name': 'scrap - grand test', + 'product_id': self.product_consumable.id, + 'product_uom_id': self.ref('product.product_uom_unit'), + 'scrap_qty': 100, + }) + self.assertEquals(scrap.state, 'done') + self.assertEquals(self.product_consumable.qty_available, -100.0) + with self.assertRaises(UserError): + scrap.sudo(self.user_demo).unlink() + + def test_stock_scrap_04(self): + """ Scrap a consumable product from a picking """ + picking = self._prepare_picking(self.product_consumable) + scrap = self.scrap_obj.create({ + 'name': 'scrap - grand test', + 'product_id': self.product_consumable.id, + 'product_uom_id': self.ref('product.product_uom_unit'), + 'picking_id': picking.id, + 'scrap_qty': 100, + }) + self.assertEquals(scrap.state, 'done') + self.assertEquals(self.product_consumable.qty_available, -100.0) From fe15b7a1f03959cd2f453ce97b26920f20453398 Mon Sep 17 00:00:00 2001 From: rgarnau Date: Tue, 12 Sep 2017 17:07:46 +0200 Subject: [PATCH 03/15] Update Eficent's CLA --- doc/cla/corporate/eficent.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/cla/corporate/eficent.md b/doc/cla/corporate/eficent.md index 3723fbf0740..2b5b8c830c4 100644 --- a/doc/cla/corporate/eficent.md +++ b/doc/cla/corporate/eficent.md @@ -14,6 +14,7 @@ List of contributors: Aarón Henríquez ahenriquez@eficent.com https://github.com/aheficent Lois Rilo lois.rilo@eficent.com https://github.com/lreficent -Gisela Mora gisela.mora@eficent.com https://github.com/gmeficent Miquel Raich miquel.raich@eficent.com https://github.com/mreficent Jordi Ballester jordi.ballester@eficent.com https://github.com/jbeficent +Hector Villarreal hector.villarreal@eficent.com https://github.com/hveficent +Roser Garcia roser.garcia@eficent.com https://github.com/rgarnau (up to 2018-05-01) From 58e85bdec08f6f07afe7b8cdfaea6f7cef2776d1 Mon Sep 17 00:00:00 2001 From: mehdi-ghezal Date: Sun, 12 Nov 2017 10:37:08 +0100 Subject: [PATCH 04/15] [FIX] sale_stock: qty calculation in procurement creation When updating the qty on a confirmed sale order line, we want the system to add new procurement qty for the cancelled procurements. That way you can leave the cancelled procurements for what they are. --- addons/sale/sale.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/sale/sale.py b/addons/sale/sale.py index 62b1598db66..f238511deb1 100644 --- a/addons/sale/sale.py +++ b/addons/sale/sale.py @@ -594,7 +594,7 @@ class SaleOrderLine(models.Model): if line.state != 'sale' or not line.product_id._need_procurement(): continue qty = 0.0 - for proc in line.procurement_ids: + for proc in line.procurement_ids.filtered(lambda r: r.state != 'cancel'): qty += proc.product_qty if float_compare(qty, line.product_uom_qty, precision_digits=precision) >= 0: continue From e60a8762fb99c2290a14077216f8f1f7ce870c07 Mon Sep 17 00:00:00 2001 From: Nicolas Lempereur Date: Thu, 7 Jun 2018 10:25:11 +0200 Subject: [PATCH 05/15] [FIX] base: depends if no depends is () With saas-11.2's 3fbd86b some trigger towards other fields are improved but this caused an issue when a inherited field towards a custom computed field had no depends. When we created a computed field x_custo on res.partner, we have a inherited x_custo on res.users automatically created. Both these fields have a depends computed to `('',)`, this caused an error for the one on res.partner: but this was ignored with dd048ae7. Since saas-11.2's 3fbd86b this also caused an error on the inherited field but since an inherited field is not "manual" the error was not ignored. Thus it was not possible to create custom field on a model inheriting of another model if the fields had no depends. opw-1854786 closes #25146 Co-authored-by: Raphael Collet --- odoo/addons/base/ir/ir_model.py | 2 +- odoo/addons/test_new_api/tests/test_new_fields.py | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/odoo/addons/base/ir/ir_model.py b/odoo/addons/base/ir/ir_model.py index 3ab7d494c1f..2a1eb0fad37 100644 --- a/odoo/addons/base/ir/ir_model.py +++ b/odoo/addons/base/ir/ir_model.py @@ -32,7 +32,7 @@ SAFE_EVAL_BASE = { def make_compute(text, deps): """ Return a compute function from its code body and dependencies. """ func = lambda self: safe_eval(text, SAFE_EVAL_BASE, {'self': self}, mode="exec") - deps = [arg.strip() for arg in (deps or "").split(",")] + deps = [arg.strip() for arg in deps.split(",")] if deps else [] return api.depends(*deps)(func) diff --git a/odoo/addons/test_new_api/tests/test_new_fields.py b/odoo/addons/test_new_api/tests/test_new_fields.py index f0a086c989c..aac6e13298c 100644 --- a/odoo/addons/test_new_api/tests/test_new_fields.py +++ b/odoo/addons/test_new_api/tests/test_new_fields.py @@ -61,6 +61,19 @@ class TestFields(common.TransactionCase): self.assertTrue(field.store) self.assertTrue(field.readonly) + def test_10_computed_custom(self): + """ check definition of custom computed fields """ + self.env['ir.model.fields'].create({ + 'name': 'x_bool_false_computed', + 'model_id': self.env.ref('test_new_api.model_test_new_api_message').id, + 'field_description': 'A boolean computed to false', + 'compute': "for r in self: r['x_bool_false_computed'] = False", + 'store': False, + 'ttype': 'boolean' + }) + field = self.env['test_new_api.message']._fields['x_bool_false_computed'] + self.assertFalse(field.depends) + def test_10_non_stored(self): """ test non-stored fields """ # a field declared with store=False should not have a column From 375bff66190cb84d88b9d398e6d4547a89feb2dd Mon Sep 17 00:00:00 2001 From: Andrea Stirpe Date: Tue, 12 Jun 2018 14:50:17 +0200 Subject: [PATCH 06/15] [FIX] l10n_nl: use tax tag 5b There was a difference in the tax codes Import Buiten EU (2), some had the tag '5B Voorbelasting' and other had the tag 'Voorbelasting BTW Bis'. Link all the codes to '5B Voorbelasting' instead. This is more consistent with the rest. Closes #23119 --- addons/l10n_nl/data/account_tax_template.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/l10n_nl/data/account_tax_template.xml b/addons/l10n_nl/data/account_tax_template.xml index 07558101a49..b534e9b7369 100644 --- a/addons/l10n_nl/data/account_tax_template.xml +++ b/addons/l10n_nl/data/account_tax_template.xml @@ -549,7 +549,7 @@ none - + 20 @@ -680,7 +680,7 @@ none - + 20 From ce6b6e64588f8ba22bd7eb29651d862f43850b17 Mon Sep 17 00:00:00 2001 From: Jairo Llopis Date: Tue, 20 Feb 2018 12:40:58 +0000 Subject: [PATCH 07/15] [FIX] event: do not suggest public user email in registration chatter Chatter suggests to include recipients on several objects, notably on registrations. When a registration is done through a public interface like website in website_event associated partner is the public user. In that case proposing to mail the public user does not make sense. It should propose the real email stored on the registration record. To fix that we filter the partner if it is linked to the pubic groups. The fix includes a sudo + context switch in a loop. As this method is called only on a recordset of one element this has no impact on real use case. Purpose is to keep the diff minimal. Closes #23187 . --- addons/event/models/event.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/addons/event/models/event.py b/addons/event/models/event.py index 81e9e2ff398..c1cb4df14a0 100644 --- a/addons/event/models/event.py +++ b/addons/event/models/event.py @@ -390,9 +390,14 @@ class EventRegistration(models.Model): @api.multi def message_get_suggested_recipients(self): recipients = super(EventRegistration, self).message_get_suggested_recipients() + public_users = self.env['res.users'].sudo() + public_groups = self.env.ref("base.group_public", raise_if_not_found=False) + if public_groups: + public_users = public_groups.sudo().with_context(active_test=False).mapped("users") try: for attendee in self: - if attendee.partner_id: + is_public = attendee.sudo().with_context(active_test=False).partner_id.user_ids in public_users if public_users else False + if attendee.partner_id and not is_public: attendee._message_add_suggested_recipient(recipients, partner=attendee.partner_id, reason=_('Customer')) elif attendee.email: attendee._message_add_suggested_recipient(recipients, email=attendee.email, reason=_('Customer Email')) From ac68eb5b3ddead4887477d09a09f21bbfac4326a Mon Sep 17 00:00:00 2001 From: Carlos Dauden Date: Wed, 16 May 2018 14:17:18 +0200 Subject: [PATCH 08/15] [FIX] base: ir_qweb fix timezone for datetime with widget=date Before this commit, display a datetime field with the widget="date" was ignoring the timezone. This commit closes #24761 --- odoo/addons/base/ir/ir_qweb/fields.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/odoo/addons/base/ir/ir_qweb/fields.py b/odoo/addons/base/ir/ir_qweb/fields.py index 4beec41db7c..28d60940978 100644 --- a/odoo/addons/base/ir/ir_qweb/fields.py +++ b/odoo/addons/base/ir/ir_qweb/fields.py @@ -173,7 +173,11 @@ class DateConverter(models.AbstractModel): locale = babel.Locale.parse(lang.code) if isinstance(value, basestring): - value = fields.Datetime.from_string(value[:10]) + if len(value) > 10: # datetime to be displayed as date + value = fields.Datetime.from_string(value) + value = fields.Datetime.context_timestamp(self, value) + else: + value = fields.Datetime.from_string(value) if options and 'format' in options: pattern = options['format'] From 51acdeba4f9f8ed00ee85271e7da24574ab8dfbb Mon Sep 17 00:00:00 2001 From: Jeremy Kersten Date: Tue, 12 Jun 2018 12:43:28 +0200 Subject: [PATCH 09/15] [ADD] qweb: add tests for widget=date with datetime This commit is related to #24761 --- .../web/static/lib/qweb/qweb-test-widgets.xml | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 addons/web/static/lib/qweb/qweb-test-widgets.xml diff --git a/addons/web/static/lib/qweb/qweb-test-widgets.xml b/addons/web/static/lib/qweb/qweb-test-widgets.xml new file mode 100644 index 00000000000..503880bcc34 --- /dev/null +++ b/addons/web/static/lib/qweb/qweb-test-widgets.xml @@ -0,0 +1,26 @@ + + + {"value": "1988-09-16"} + 1988-09-16 + + + {"value": "1988-09-16 14:00:00"} + 1988-09-16 14:00:00 + + + {"value": "1988-09-16 14:00:00"} + 09/16/1988 16:00:00 + + + {"value": "1988-09-16 14:00:00"} + 09/16/1988 + + + {"value": "1988-09-16 01:00:00"} + 09/16/1988 + + + {"value": "1988-09-16 23:00:00"} + 09/17/1988 + + From 3fc6624631eb06f65bf487265f9bdcf0f7121ab5 Mon Sep 17 00:00:00 2001 From: Subodh Dahal Date: Fri, 9 Feb 2018 18:51:49 +0100 Subject: [PATCH 10/15] [CLA] SubodhDahal signs Odoo's CLA Done at #22971 --- doc/cla/individual/subodhdahal.md | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 doc/cla/individual/subodhdahal.md diff --git a/doc/cla/individual/subodhdahal.md b/doc/cla/individual/subodhdahal.md new file mode 100644 index 00000000000..4e4c389bc99 --- /dev/null +++ b/doc/cla/individual/subodhdahal.md @@ -0,0 +1,11 @@ +Nepal, 2018-02-09 + +I hereby agree to the terms of the Odoo Individual Contributor License +Agreement v1.0. + +I declare that I am authorized and able to make this agreement and sign this +declaration. + +Signed, + +Subodh Dahal contact.subodhdahal@gmail.com https://github.com/SubodhDahal From c2f20f12b9b426942b8adee06fbe5668a13b2a6b Mon Sep 17 00:00:00 2001 From: Subodh Dahal Date: Fri, 9 Feb 2018 18:36:05 +0100 Subject: [PATCH 11/15] [FIX] doc: recommand to xpath on snippet_structure Fixed the xpath expression for inserting snippet into Structure tab Closes #22971 --- doc/howtos/themes.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/howtos/themes.rst b/doc/howtos/themes.rst index a66068a7b00..b6b9849c885 100644 --- a/doc/howtos/themes.rst +++ b/doc/howtos/themes.rst @@ -614,7 +614,7 @@ The previous code will create the snippet’s content, but we still need to plac .. code-block:: xml