From 8a84f96641c6f0330a420ad0943c42ff24a0f4f1 Mon Sep 17 00:00:00 2001 From: Nicolas Martinelli Date: Thu, 9 May 2019 12:04:39 +0000 Subject: [PATCH 01/10] [FIX] sale: timesheet in hours - Create a SO - Add a product service that will generate a task to the confirmation. Its UOM must be Hour(s) - Confirm the so an go into the task - Add a timesheet line with "00h20min" and save it Quantity saved into the delivery qty of the SO is 0.34 instead of 0.33. The default rounding method is `UP`, so we use `HALF-UP` instead. opw-1975014 closes odoo/odoo#33276 Signed-off-by: Nicolas Martinelli (nim) --- addons/sale/models/sale_analytic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/sale/models/sale_analytic.py b/addons/sale/models/sale_analytic.py index 77c2bb0f8c6..e2c5f53c324 100644 --- a/addons/sale/models/sale_analytic.py +++ b/addons/sale/models/sale_analytic.py @@ -34,7 +34,7 @@ class SaleOrderLine(models.Model): lines.setdefault(line, 0.0) uom = self.env['product.uom'].browse(d['product_uom_id'][0]) if line.product_uom.category_id == uom.category_id: - qty = uom._compute_quantity(d['unit_amount'], line.product_uom) + qty = uom._compute_quantity(d['unit_amount'], line.product_uom, rounding_method='HALF-UP') else: qty = d['unit_amount'] lines[line] += qty From 394642e1dc31b18b32c3b3f37774a4d5e531fd46 Mon Sep 17 00:00:00 2001 From: Denis Ledoux Date: Fri, 10 May 2019 15:29:21 +0000 Subject: [PATCH 02/10] [FIX] base: do not recompute `res.users.share` on add/remove multi-company This is a performance patch. Writing on the field `users` of `res.groups` had the side effect to recompute the field `share` of `res.users`. This is because the trigger of this field is `@api.depends('groups_id')`. The more users you had, the more time it took to add or remove the second company of a user. This also slowed down the creation of employee users when the multi-company group was added to the inherited groups of the employee group (basically when checking "Multi-Company" in the General Settings) as then all new employee matched the condition `if len(user.company_ids) <= 1 and user.id in group_multi_company.users.ids:` (Multi-Company group checked, but only one company set, by default). closes odoo/odoo#33305 Signed-off-by: Denis Ledoux --- odoo/addons/base/res/res_users.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/odoo/addons/base/res/res_users.py b/odoo/addons/base/res/res_users.py index 12b31da37ec..98c57af7760 100644 --- a/odoo/addons/base/res/res_users.py +++ b/odoo/addons/base/res/res_users.py @@ -853,9 +853,9 @@ class UsersView(models.Model): group_multi_company = self.env.ref('base.group_multi_company', False) if group_multi_company and 'company_ids' in values: if len(user.company_ids) <= 1 and user.id in group_multi_company.users.ids: - group_multi_company.write({'users': [(3, user.id)]}) + user.write({'groups_id': [(3, group_multi_company.id)]}) elif len(user.company_ids) > 1 and user.id not in group_multi_company.users.ids: - group_multi_company.write({'users': [(4, user.id)]}) + user.write({'groups_id': [(4, group_multi_company.id)]}) return user @api.multi @@ -866,9 +866,9 @@ class UsersView(models.Model): if group_multi_company and 'company_ids' in values: for user in self: if len(user.company_ids) <= 1 and user.id in group_multi_company.users.ids: - group_multi_company.write({'users': [(3, user.id)]}) + user.write({'groups_id': [(3, group_multi_company.id)]}) elif len(user.company_ids) > 1 and user.id not in group_multi_company.users.ids: - group_multi_company.write({'users': [(4, user.id)]}) + user.write({'groups_id': [(4, group_multi_company.id)]}) return res def _remove_reified_groups(self, values): From 9054d23d02d09dc44520c9da9c10c1e664374b95 Mon Sep 17 00:00:00 2001 From: Arnold Moyaux Date: Tue, 2 Oct 2018 11:26:36 +0000 Subject: [PATCH 03/10] [FIX] stock: inventory display archived product Usecase to reproduce: - Archive a stockable product with quantity on hand - Start an inventory with all products. -> Archived product is displayed It happens because the search is an SQL request that is build and it thus does not benefits from the automatic search on active objects. This commit add an active search on quant for inventory lines. closes odoo/odoo#33077 backport odoo/odoo#27384 from 11.0 closes odoo/odoo#33098 Signed-off-by: Nicolas Martinelli (nim) --- addons/stock/models/stock_inventory.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/addons/stock/models/stock_inventory.py b/addons/stock/models/stock_inventory.py index ed3d354b295..84a50bdb4d0 100644 --- a/addons/stock/models/stock_inventory.py +++ b/addons/stock/models/stock_inventory.py @@ -224,7 +224,7 @@ class Inventory(models.Model): def _get_inventory_lines_values(self): # TDE CLEANME: is sql really necessary ? I don't think so locations = self.env['stock.location'].search([('id', 'child_of', [self.location_id.id])]) - domain = ' location_id in %s' + domain = ' location_id in %s AND active = TRUE' args = (tuple(locations.ids),) vals = [] @@ -265,6 +265,8 @@ class Inventory(models.Model): self.env.cr.execute("""SELECT product_id, sum(qty) as product_qty, location_id, lot_id as prod_lot_id, package_id, owner_id as partner_id FROM stock_quant + LEFT JOIN product_product + ON product_product.id = stock_quant.product_id WHERE %s GROUP BY product_id, location_id, lot_id, package_id, partner_id """ % domain, args) From b03af37462c6b1fc1f3be7907871c0562ad5c0db Mon Sep 17 00:00:00 2001 From: Gert Pellin Date: Tue, 8 Jan 2019 13:59:25 +0000 Subject: [PATCH 04/10] [FIX] point_of_sale: negative value payment before fix negative values will be added after -0.00 resulting in a value like for instance -0.008543 while is should be -8543.00. Also when removing the value there will be a traceback for '-' not being a number. added extra checks for negative values on input. resolves https://github.com/odoo/odoo/issues/29910 resolves https://github.com/odoo/odoo/issues/29909 closes odoo/odoo#30035 Signed-off-by: Nicolas Martinelli (nim) --- addons/point_of_sale/static/src/js/gui.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/addons/point_of_sale/static/src/js/gui.js b/addons/point_of_sale/static/src/js/gui.js index 6107d3a979e..fdc73a13987 100644 --- a/addons/point_of_sale/static/src/js/gui.js +++ b/addons/point_of_sale/static/src/js/gui.js @@ -457,7 +457,9 @@ var Gui = core.Class.extend({ newbuf = newbuf.substring(1,newbuf.length); } } else if (input === '-') { - if ( newbuf[0] === '-' ) { + if (options.firstinput) { + newbuf = '-0'; + } else if ( newbuf[0] === '-' ) { newbuf = newbuf.substring(1,newbuf.length); } else { newbuf = '-' + newbuf; @@ -471,6 +473,9 @@ var Gui = core.Class.extend({ newbuf += input; } } + if (newbuf === "-") { + newbuf = ""; + } // End of input buffer at 12 characters. if (newbuf.length > buffer.length && newbuf.length > 12) { From 928e91c624d227eecb90160fba92ed755806387b Mon Sep 17 00:00:00 2001 From: Gert Pellin Date: Mon, 13 May 2019 09:48:43 +0000 Subject: [PATCH 05/10] [FIX] hw_escpos: cashdrawer doesn't open With some escpos printers the cash drawer did still not open after previous fixes. One of the problemetic printers is the Epson TM-m30. This fix will check the status of the drawer and try to open it up to 5 times. The fix is successfully tested with the TM-m30. closes odoo/odoo#33328 Signed-off-by: pimodoo --- addons/hw_escpos/escpos/escpos.py | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/addons/hw_escpos/escpos/escpos.py b/addons/hw_escpos/escpos/escpos.py index d278ede1787..de7e9121463 100644 --- a/addons/hw_escpos/escpos/escpos.py +++ b/addons/hw_escpos/escpos/escpos.py @@ -885,14 +885,23 @@ class Escpos: self._raw(PAPER_FULL_CUT) - def cashdraw(self, pin): - """ Send pulse to kick the cash drawer """ - if pin == 2: - self._raw(CD_KICK_2) - elif pin == 5: - self._raw(CD_KICK_5) - else: - raise CashDrawerError() + def cashdraw(self, pin, tries=5): + """ Send pulse to kick the cash drawer + + With some printers the drawer will not open after one pulse, for this reason we will check + the drawer status up to 'tries' times and send a new pulse if the drawer is still closed. If the + drawer status is open, we will stop sending pulses. + """ + for i in range(tries): + if pin == 2: + self._raw(CD_KICK_2) + elif pin == 5: + self._raw(CD_KICK_5) + else: + raise CashDrawerError() + + if i != tries - 1 and not self.get_printer_status()['printer']['drawer_pin_high']: + break def hw(self, hw): From 5d6656d739feff7abb8e191fdc038cc03b542d3b Mon Sep 17 00:00:00 2001 From: Denis Ledoux Date: Wed, 15 May 2019 09:16:23 +0000 Subject: [PATCH 06/10] [FIX] mail: avoid concurrent update of notifications This revision is a backport of revision 56596e5240ef920df14d99087451ce6f06ac6d36 As the bug mentioned in the above revision occurs as well in Odoo 10.0 and 11.0. opw-1984293 closes odoo/odoo#33382 Signed-off-by: Denis Ledoux --- addons/mail/models/mail_mail.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/addons/mail/models/mail_mail.py b/addons/mail/models/mail_mail.py index 87305781784..27313fdb59d 100644 --- a/addons/mail/models/mail_mail.py +++ b/addons/mail/models/mail_mail.py @@ -267,6 +267,19 @@ class MailMail(models.Model): }) mail_sent = False + # Update notification in a transient exception state to avoid concurrent + # update in case an email bounces while sending all emails related to current + # mail record. + notifs = self.env['mail.notification'].search([ + ('is_email', '=', True), + ('mail_message_id', 'in', mail.mapped('mail_message_id').ids), + ('email_status', 'not in', ('sent', 'canceled')) + ]) + if notifs: + notifs.sudo().write({ + 'email_status': 'exception', + }) + # build an RFC2822 email.message.Message object and send it without queuing res = None for email in email_list: From 0164c9dfcb8f94dd09ffc5a0ce056e4e23308abe Mon Sep 17 00:00:00 2001 From: Nathan de Pryck Date: Thu, 2 May 2019 08:26:25 +0000 Subject: [PATCH 07/10] [FIX] Account:rounding poorly applied Rounding each element of the sum decrease the precision and prevent to reconcile large recordset. closes odoo/odoo#33100 Signed-off-by: Laurent Smet --- addons/account/models/account_move.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py index 9de4266646e..7208e14f935 100644 --- a/addons/account/models/account_move.py +++ b/addons/account/models/account_move.py @@ -322,7 +322,7 @@ class AccountMoveLine(models.Model): else: date = partial_line.credit_move_id.date if partial_line.debit_move_id == line else partial_line.debit_move_id.date rate = line.currency_id.with_context(date=date).rate - amount_residual_currency += sign_partial_line * line.currency_id.round(partial_line.amount * rate) + amount_residual_currency += sign_partial_line * partial_line.amount * rate #computing the `reconciled` field. As we book exchange rate difference on each partial matching, #we can only check the amount in company currency From 2a14eea095e9758eaba4d1bee1e0783c7a9c3304 Mon Sep 17 00:00:00 2001 From: Johan Demaret Rivarola Date: Mon, 13 May 2019 15:35:31 +0000 Subject: [PATCH 08/10] [FIX] website_{crm_partner_assign,customer}: fix 404 when crawling When website_customer is not installed, some endpoints are not handled by the router, causing the following tests to fail: - test_10_crawl_public - test_20_crawl_demo - test_30_crawl_admin Those tests were failing due to /customers responding with 404. Indeed, the route /customers is defined in website_customer, but had some usage within website_crm_partner_assign, causing tests to fail when the former module is not installed. The new behavior can be explained as follows: - module website_crm_partner_assign on its own will lose the hyperlink. - module website_customer will now inherit the view definition in order to wrap the targeted HTML text elements with an anchor tag. Closes #33344 Signed-off-by: Christophe Simonis --- .../views/website_crm_partner_assign_templates.xml | 12 +++--------- .../views/website_customer_templates.xml | 9 +++++++++ 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/addons/website_crm_partner_assign/views/website_crm_partner_assign_templates.xml b/addons/website_crm_partner_assign/views/website_crm_partner_assign_templates.xml index cbae6b073e1..3806cce6491 100644 --- a/addons/website_crm_partner_assign/views/website_crm_partner_assign_templates.xml +++ b/addons/website_crm_partner_assign/views/website_crm_partner_assign_templates.xml @@ -170,18 +170,12 @@ diff --git a/addons/website_customer/views/website_customer_templates.xml b/addons/website_customer/views/website_customer_templates.xml index d4c743cdcd5..81b83e89e1d 100644 --- a/addons/website_customer/views/website_customer_templates.xml +++ b/addons/website_customer/views/website_customer_templates.xml @@ -208,4 +208,13 @@ + + From 272359b86c030c814a1a68fca1577c57bbfbc5be Mon Sep 17 00:00:00 2001 From: Denis Ledoux Date: Thu, 16 May 2019 12:17:01 +0000 Subject: [PATCH 09/10] [FIX] mail: accurate message notifications marked as sent When multiple emails (`mail.mail`) are linked to the same message (`mail.message`), all the message notifications were being marked as sent as soon as one of the mail was sent, and not all of them. This is indeed possible to have multiple emails linked to a single message, for instance when there are employees and customers in the followers of a thread, and some must therefore have the buttons meant for the employees ("Assign me") and some not. In other words, it was possible notifications were marked as sent while their associated email was not sent yet. This revision also comes as an addition to 5d6656d739feff7abb8e191fdc038cc03b542d3b as it was possible to receive a bounce coming from the previous mail sent which wrote on the same notifications `email_status` than the current message being sent before we could acquire the lock on them (see commit message of the above mentioned revision), as it was writing the `email_status` on more notifications that it should have, including notifications of the previous email. e.g - Mail 1 - Message 1 - Recipient A, B, C - Mail 2 - Message 1 - Recipient D, E Let's say recipient A bounces If the email notifying the bounce arrives during the processing of the "Mail 1", it will be blocked until we release the lock on the `mail.notification` `email_status` we acquire thanks to having written `exception` on `email_status` before sending the email. But, as soon as it is unblocked (after the `cr.commit()`), it will immediately re-attempt the delivery of the bounce email, therefore writting "bounced" in `email_status` of recipient A, while the transaction of the second email already began. When the second email sending will try to write the `email_status` of recipient A as well (as it used to write the `email_status` on all the message notifications, not just the one related to the current email), it raised a concurrent update. Therefore: 1. Interrupting the loop, as psycopg2 exceptions are raised and not passed, 2. Replay the sending of the second email a second time. opw-1984293 closes odoo/odoo#33431 Signed-off-by: Denis Ledoux --- addons/mail/models/mail_mail.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/addons/mail/models/mail_mail.py b/addons/mail/models/mail_mail.py index 27313fdb59d..5135503ff11 100644 --- a/addons/mail/models/mail_mail.py +++ b/addons/mail/models/mail_mail.py @@ -146,6 +146,7 @@ class MailMail(models.Model): if notif_emails: notifications = self.env['mail.notification'].search([ ('mail_message_id', 'in', notif_emails.mapped('mail_message_id').ids), + ('res_partner_id', 'in', notif_emails.mapped('recipient_ids').ids), ('is_email', '=', True)]) if mail_sent: notifications.write({ @@ -273,6 +274,7 @@ class MailMail(models.Model): notifs = self.env['mail.notification'].search([ ('is_email', '=', True), ('mail_message_id', 'in', mail.mapped('mail_message_id').ids), + ('res_partner_id', 'in', mail.mapped('recipient_ids').ids), ('email_status', 'not in', ('sent', 'canceled')) ]) if notifs: From 90d86e07dd530755cd52774932f9f613f17a3b49 Mon Sep 17 00:00:00 2001 From: Mohammed Shekha Date: Fri, 30 Nov 2018 13:34:09 +0000 Subject: [PATCH 10/10] [FIX] tools: allow using datetime in function call Traceback generated when trying to pass a datetime object into a function tag in xml. Used to fail. With this commit now one can pass time, datetime, timedelta, relativedelta, version, ref, pytz in function tag in xml Task-id: 1772614 Closes odoo/odoo#29212 Signed-off-by: Martin Trigaux (mat) Co-authored-by: Dhaval Limbuwala --- odoo/addons/test_convert/__init__.py | 1 + odoo/addons/test_convert/__manifest__.py | 1 + odoo/addons/test_convert/ir.model.access.csv | 2 ++ odoo/addons/test_convert/models.py | 16 ++++++++++++++ .../addons/test_convert/tests/test_convert.py | 21 +++++++++++++++++++ odoo/tools/convert.py | 7 ++++--- 6 files changed, 45 insertions(+), 3 deletions(-) create mode 100644 odoo/addons/test_convert/ir.model.access.csv create mode 100644 odoo/addons/test_convert/models.py diff --git a/odoo/addons/test_convert/__init__.py b/odoo/addons/test_convert/__init__.py index e69de29bb2d..0650744f6bc 100644 --- a/odoo/addons/test_convert/__init__.py +++ b/odoo/addons/test_convert/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/odoo/addons/test_convert/__manifest__.py b/odoo/addons/test_convert/__manifest__.py index af6cd1af6e4..a9c67447615 100644 --- a/odoo/addons/test_convert/__manifest__.py +++ b/odoo/addons/test_convert/__manifest__.py @@ -5,4 +5,5 @@ 'name': 'test_convert', 'description': "Data for xml conversion tests", 'version': '0.0.1', + 'data': ['ir.model.access.csv'], } diff --git a/odoo/addons/test_convert/ir.model.access.csv b/odoo/addons/test_convert/ir.model.access.csv new file mode 100644 index 00000000000..014e4d1c827 --- /dev/null +++ b/odoo/addons/test_convert/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_test_convert_test_model,access_test_convert_test_model,model_test_convert_test_model,,1,1,1,1 \ No newline at end of file diff --git a/odoo/addons/test_convert/models.py b/odoo/addons/test_convert/models.py new file mode 100644 index 00000000000..0f059e56e86 --- /dev/null +++ b/odoo/addons/test_convert/models.py @@ -0,0 +1,16 @@ +from odoo import api, models + +class TestModel(models.Model): + _name = 'test_convert.test_model' + + @api.model + def action_test_date(self, today_date): + return True + + @api.model + def action_test_time(self, cur_time): + return True + + @api.model + def action_test_timezone(self, timezone): + return True diff --git a/odoo/addons/test_convert/tests/test_convert.py b/odoo/addons/test_convert/tests/test_convert.py index e81526dd43c..1bb79619c21 100644 --- a/odoo/addons/test_convert/tests/test_convert.py +++ b/odoo/addons/test_convert/tests/test_convert.py @@ -4,8 +4,10 @@ import collections import unittest +from lxml import etree as ET from lxml.builder import E +import odoo from odoo.tests import common from odoo.tools.convert import _eval_xml @@ -16,6 +18,25 @@ class TestEvalXML(common.TransactionCase): def eval_xml(self, node, obj=None): return _eval_xml(obj, node, self.env) + def test_function_eval(self): + def id_get(): pass + Obj = collections.namedtuple('Obj', ['module', 'idref', 'id_get']) + obj = Obj('test_convert', {}, id_get) + + try: + test_datetime = ET.XML("") + self.eval_xml(node=test_datetime, obj=obj) + test_time = ET.XML("") + self.eval_xml(node=test_time, obj=obj) + test_timedelta = ET.XML("") + self.eval_xml(node=test_timedelta, obj=obj) + test_relativedelta = ET.XML("") + self.eval_xml(node=test_relativedelta, obj=obj) + test_timezone = ET.XML("") + self.eval_xml(node=test_timezone, obj=obj) + except ValueError as e: + self.fail(e.message) + def test_char(self): self.assertEqual( self.eval_xml(Field("foo")), diff --git a/odoo/tools/convert.py b/odoo/tools/convert.py index 284b1f783ce..4cd7693aa2c 100644 --- a/odoo/tools/convert.py +++ b/odoo/tools/convert.py @@ -177,15 +177,16 @@ def _eval_xml(self, node, env): elif node.tag == "function": args = [] a_eval = node.get('eval','') + model_str = node.get('model', '') # FIXME: should probably be exclusive if a_eval: - self.idref['ref'] = self.id_get - args = safe_eval(a_eval, self.idref) + idref2 = _get_idref(self, env, model_str, self.idref) + args = safe_eval(a_eval, idref2) for n in node: return_val = _eval_xml(self, n, env) if return_val is not None: args.append(return_val) - model = env[node.get('model', '')] + model = env[model_str] method = node.get('name') # this one still depends on the old API return odoo.api.call_kw(model, method, args, {})