diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py index 08afcf0e071..3d3f85276a6 100644 --- a/addons/account/models/account_move.py +++ b/addons/account/models/account_move.py @@ -342,7 +342,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. reconciled = False diff --git a/addons/hw_escpos/escpos/escpos.py b/addons/hw_escpos/escpos/escpos.py index 3573efb1d98..bc1c5e1842f 100644 --- a/addons/hw_escpos/escpos/escpos.py +++ b/addons/hw_escpos/escpos/escpos.py @@ -888,14 +888,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): diff --git a/addons/mail/models/mail_mail.py b/addons/mail/models/mail_mail.py index 3027fc43edc..9fde6ebe773 100644 --- a/addons/mail/models/mail_mail.py +++ b/addons/mail/models/mail_mail.py @@ -153,6 +153,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({ @@ -320,6 +321,20 @@ 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), + ('res_partner_id', 'in', mail.mapped('recipient_ids').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: diff --git a/addons/point_of_sale/static/src/js/gui.js b/addons/point_of_sale/static/src/js/gui.js index b5cff2f233a..0d8c67bc3d9 100644 --- a/addons/point_of_sale/static/src/js/gui.js +++ b/addons/point_of_sale/static/src/js/gui.js @@ -458,7 +458,7 @@ var Gui = core.Class.extend({ } } else if (input === '-') { if (options.firstinput) { - newbuf = '-'; + newbuf = '-0'; } else if ( newbuf[0] === '-' ) { newbuf = newbuf.substring(1,newbuf.length); } else { @@ -473,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) { diff --git a/addons/sale/models/sale.py b/addons/sale/models/sale.py index 4b92f183fd6..9fca947f6f1 100644 --- a/addons/sale/models/sale.py +++ b/addons/sale/models/sale.py @@ -1269,7 +1269,7 @@ class SaleOrderLine(models.Model): value_to_write.setdefault(so_line, 0.0) uom = self.env['product.uom'].browse(item['product_uom_id'][0]) if so_line.product_uom.category_id == uom.category_id: - qty = uom._compute_quantity(item['unit_amount'], so_line.product_uom) + qty = uom._compute_quantity(item['unit_amount'], so_line.product_uom, rounding_method='HALF-UP') else: qty = item['unit_amount'] value_to_write[so_line] += qty 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 96346a3814d..bdc28c2fa25 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 5674f3d3440..ccd0732839d 100644 --- a/addons/website_customer/views/website_customer_templates.xml +++ b/addons/website_customer/views/website_customer_templates.xml @@ -239,4 +239,13 @@ + + diff --git a/odoo/addons/base/res/res_users.py b/odoo/addons/base/res/res_users.py index 30eba4f3c0d..8032dc0e15c 100644 --- a/odoo/addons/base/res/res_users.py +++ b/odoo/addons/base/res/res_users.py @@ -875,9 +875,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 @@ -888,9 +888,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): 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 97fb2be7330..222c847ba52 100644 --- a/odoo/tools/convert.py +++ b/odoo/tools/convert.py @@ -180,15 +180,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, {})