diff --git a/.tx/config b/.tx/config index ba9318f5a58..c9ebc9f6226 100644 --- a/.tx/config +++ b/.tx/config @@ -32,6 +32,11 @@ file_filter = addons/account_budget/i18n/.po source_file = addons/account_budget/i18n/account_budget.pot source_lang = en +[odoo-master.account_cash_basis_base_account] +file_filter = addons/account_cash_basis_base_account/i18n/.po +source_file = addons/account_cash_basis_base_account/i18n/account_cash_basis_base_account.pot +source_lang = en + [odoo-master.account_cancel] file_filter = addons/account_cancel/i18n/.po source_file = addons/account_cancel/i18n/account_cancel.pot diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py index d3aad9da4a9..8e8584d1151 100644 --- a/addons/account/models/account_invoice.py +++ b/addons/account/models/account_invoice.py @@ -1262,6 +1262,8 @@ class AccountInvoice(models.Model): values[name] = line[name] elif name == 'invoice_line_tax_ids': values[name] = [(6, 0, line[name].ids)] + elif name == 'analytic_tag_ids': + values[name] = [(6, 0, line[name].ids)] result.append((0, 0, values)) return result diff --git a/addons/event/data/email_template_data.xml b/addons/event/data/email_template_data.xml index b7788cba9a3..38329a8c6f4 100644 --- a/addons/event/data/email_template_data.xml +++ b/addons/event/data/email_template_data.xml @@ -326,7 +326,6 @@ Add this event to your calendar Google - Outlook Yahoo diff --git a/addons/event/models/event.py b/addons/event/models/event.py index 99f6b35eb52..afeb345b4ee 100644 --- a/addons/event/models/event.py +++ b/addons/event/models/event.py @@ -483,6 +483,17 @@ class EventRegistration(models.Model): ]).write({'partner_id': new_partner.id}) return super(EventRegistration, self)._message_post_after_hook(message, values, notif_layout) + @api.multi + def message_get_default_recipients(self): + # Prioritize registration email over partner_id, which may be shared when a single + # partner booked multiple seats + return { + r.id: {'partner_ids': [], + 'email_to': r.email, + 'email_cc': False} + for r in self + } + @api.multi def action_send_badge_email(self): """ Open a window to compose an email, with the template - 'event_badge' diff --git a/addons/hr_timesheet/report/report_timesheet_templates.xml b/addons/hr_timesheet/report/report_timesheet_templates.xml index fea714a5d7a..dbc0ad4a583 100644 --- a/addons/hr_timesheet/report/report_timesheet_templates.xml +++ b/addons/hr_timesheet/report/report_timesheet_templates.xml @@ -44,7 +44,7 @@ - + @@ -53,7 +53,9 @@ Total - + + + diff --git a/addons/mail/models/mail_template.py b/addons/mail/models/mail_template.py index f5d4b4e3ac6..cb9c4a433a8 100644 --- a/addons/mail/models/mail_template.py +++ b/addons/mail/models/mail_template.py @@ -63,7 +63,7 @@ def format_amount(env, amount, currency): lang = env['res.lang']._lang_get(env.context.get('lang') or 'en_US') formatted_amount = lang.format(fmt, currency.round(amount), grouping=True, monetary=True)\ - .replace(r' ', u'\N{NO-BREAK SPACE}').replace(r'-', u'\u2011') + .replace(r' ', u'\N{NO-BREAK SPACE}').replace(r'-', u'-\N{ZERO WIDTH NO-BREAK SPACE}') pre = post = u'' if currency.position == 'before': diff --git a/addons/point_of_sale/models/pos_config.py b/addons/point_of_sale/models/pos_config.py index 67e4f5a527b..a43246e03bd 100644 --- a/addons/point_of_sale/models/pos_config.py +++ b/addons/point_of_sale/models/pos_config.py @@ -255,6 +255,11 @@ class PosConfig(models.Model): if any(self.journal_ids.mapped(lambda journal: journal.currency_id and journal.currency_id != self.currency_id)): raise ValidationError(_("All payment methods must be in the same currency as the Sales Journal or the company currency if that is not set.")) + @api.constrains('company_id', 'available_pricelist_ids') + def _check_companies(self): + if any(self.available_pricelist_ids.mapped(lambda pl: pl.company_id.id not in (False, self.company_id.id))): + raise ValidationError(_("The selected pricelists must belong to no company or the company of the point of sale.")) + @api.onchange('iface_print_via_proxy') def _onchange_iface_print_via_proxy(self): self.iface_print_auto = self.iface_print_via_proxy diff --git a/addons/web/static/src/js/views/kanban/kanban_column_progressbar.js b/addons/web/static/src/js/views/kanban/kanban_column_progressbar.js index fad822efdb9..b597ccf1c60 100644 --- a/addons/web/static/src/js/views/kanban/kanban_column_progressbar.js +++ b/addons/web/static/src/js/views/kanban/kanban_column_progressbar.js @@ -141,6 +141,7 @@ var KanbanColumnProgressBar = Widget.extend({ } // Display and animate the progress bars + var barNumber = 0; _.each(self.colors, function (val, key) { var $bar = self.$bars[val]; var count = self.subgroupCounts && self.subgroupCounts[key] || 0; @@ -165,7 +166,14 @@ var KanbanColumnProgressBar = Widget.extend({ window.getComputedStyle($bar[0]).getPropertyValue('width'); // Force reflow so that animations work if (count > 0) { $bar.addClass('o_bar_has_records'); + // Make sure every bar that has records has some space + // and that everything adds up to 100% + var minWidth = 6*barNumber; + var maxWidth = 100 - minWidth; + self.$('.progress-bar.o_bar_has_records').css('max-width', maxWidth + '%'); + self.$('.progress-bar.o_bar_has_records').css('min-width', minWidth + '%'); $bar.css('width', (count * 100 / self.groupCount) + '%'); + barNumber++; } else { $bar.css('width', ''); } diff --git a/addons/web/static/src/less/kanban_column_progressbar.less b/addons/web/static/src/less/kanban_column_progressbar.less index f4cffd6c097..233ae5c0d01 100644 --- a/addons/web/static/src/less/kanban_column_progressbar.less +++ b/addons/web/static/src/less/kanban_column_progressbar.less @@ -73,11 +73,6 @@ margin-bottom: 0; box-shadow: none; cursor: pointer; - - // Be sure that a bar with data has a minimun width - &.o_bar_has_records { - min-width: 10px; - } } .o_kanban_counter_label { diff --git a/addons/website_sale_delivery/controllers/main.py b/addons/website_sale_delivery/controllers/main.py index 5a06774fcad..52522fa8117 100644 --- a/addons/website_sale_delivery/controllers/main.py +++ b/addons/website_sale_delivery/controllers/main.py @@ -76,4 +76,4 @@ class WebsiteSaleDelivery(WebsiteSale): lang = request.env['res.lang']._lang_get(request.env.context.get('lang') or 'en_US') return lang.format(fmt, currency.round(amount), grouping=True, monetary=True)\ - .replace(r' ', u'\N{NO-BREAK SPACE}').replace(r'-', u'\u2011') + .replace(r' ', u'\N{NO-BREAK SPACE}').replace(r'-', u'-\N{ZERO WIDTH NO-BREAK SPACE}') diff --git a/odoo/addons/base/models/ir_qweb_fields.py b/odoo/addons/base/models/ir_qweb_fields.py index 19e15ff4621..5a9000598de 100644 --- a/odoo/addons/base/models/ir_qweb_fields.py +++ b/odoo/addons/base/models/ir_qweb_fields.py @@ -7,6 +7,7 @@ from odoo import api, fields, models, _ from PIL import Image import babel from lxml import etree +import math from odoo.tools import html_escape as escape, posix_to_ldml, safe_eval, float_utils, format_date, pycompat @@ -125,7 +126,7 @@ class IntegerConverter(models.AbstractModel): @api.model def value_to_html(self, value, options): - return pycompat.to_text(self.user_lang().format('%d', value, grouping=True).replace(r'-', u'\u2011')) + return pycompat.to_text(self.user_lang().format('%d', value, grouping=True).replace(r'-', u'-\N{ZERO WIDTH NO-BREAK SPACE}')) class FloatConverter(models.AbstractModel): @@ -145,7 +146,7 @@ class FloatConverter(models.AbstractModel): value = float_utils.float_round(value, precision_digits=precision) fmt = '%.{precision}f'.format(precision=precision) - formatted = self.user_lang().format(fmt, value, grouping=True).replace(r'-', u'\u2011') + formatted = self.user_lang().format(fmt, value, grouping=True).replace(r'-', u'-\N{ZERO WIDTH NO-BREAK SPACE}') # %f does not strip trailing zeroes. %g does but its precision causes # it to switch to scientific notation starting at a million *and* to @@ -325,7 +326,7 @@ class MonetaryConverter(models.AbstractModel): lang = self.user_lang() formatted_amount = lang.format(fmt, display_currency.round(value), - grouping=True, monetary=True).replace(r' ', u'\N{NO-BREAK SPACE}').replace(r'-', u'\u2011') + grouping=True, monetary=True).replace(r' ', u'\N{NO-BREAK SPACE}').replace(r'-', u'-\N{ZERO WIDTH NO-BREAK SPACE}') pre = post = u'' if display_currency.position == 'before': @@ -367,8 +368,9 @@ class FloatTimeConverter(models.AbstractModel): @api.model def value_to_html(self, value, options): - hours, minutes = divmod(value * 60, 60) - return '%02d:%02d' % (hours, minutes) + sign = math.copysign(1.0, value) + hours, minutes = divmod(abs(value) * 60, 60) + return '%02d:%02d' % (sign * hours, minutes) class DurationConverter(models.AbstractModel): diff --git a/odoo/addons/test_converter/tests/test_html.py b/odoo/addons/test_converter/tests/test_html.py index e915785e333..d04604a5c4f 100644 --- a/odoo/addons/test_converter/tests/test_html.py +++ b/odoo/addons/test_converter/tests/test_html.py @@ -73,7 +73,7 @@ class TestFloatExport(TestBasicExport): converter = self.get_converter('float') value = converter(-42.0) - self.assertEqual(value, u"\u201142.0") + self.assertEqual(value, u"-\N{ZERO WIDTH NO-BREAK SPACE}42.0") value = converter(42.0100) self.assertEqual(value, "42.01") @@ -120,7 +120,7 @@ class TestCurrencyExport(TestExport): converted = self.convert(obj, dest=currency) self.assertEqual( - converted, u'\u20110.12' + converted, u'-\N{ZERO WIDTH NO-BREAK SPACE}0.12' u'\N{NO-BREAK SPACE}{symbol}'.format( obj=obj, symbol=currency.symbol diff --git a/odoo/service/server.py b/odoo/service/server.py index 8899760ce91..4820b1e3257 100644 --- a/odoo/service/server.py +++ b/odoo/service/server.py @@ -131,7 +131,7 @@ class FSWatcher(object): if isinstance(event, (FileCreatedEvent, FileModifiedEvent, FileMovedEvent)): if not event.is_directory: path = getattr(event, 'dest_path', event.src_path) - if path.endswith('.py'): + if path.endswith('.py') and not os.path.basename(path).startswith('.~'): try: source = open(path, 'rb').read() + b'\n' compile(source, path, 'exec') diff --git a/odoo/tools/safe_eval.py b/odoo/tools/safe_eval.py index 7a5ec03707a..ccb2e3e25b4 100644 --- a/odoo/tools/safe_eval.py +++ b/odoo/tools/safe_eval.py @@ -107,8 +107,8 @@ _SAFE_OPCODES = _EXPR_OPCODES.union(set(opmap[x] for x in [ 'JUMP_FORWARD', 'JUMP_IF_TRUE', 'JUMP_IF_FALSE', 'JUMP_ABSOLUTE', # New in Python 2.7 - http://bugs.python.org/issue4715 : 'JUMP_IF_FALSE_OR_POP', 'JUMP_IF_TRUE_OR_POP', 'POP_JUMP_IF_FALSE', - 'POP_JUMP_IF_TRUE', 'SETUP_EXCEPT', 'END_FINALLY', 'RAISE_VARARGS', - 'LOAD_NAME', 'STORE_NAME', 'DELETE_NAME', 'LOAD_ATTR', + 'POP_JUMP_IF_TRUE', 'SETUP_EXCEPT', 'SETUP_FINALLY', 'END_FINALLY', + 'RAISE_VARARGS', 'LOAD_NAME', 'STORE_NAME', 'DELETE_NAME', 'LOAD_ATTR', 'LOAD_FAST', 'STORE_FAST', 'DELETE_FAST', 'UNPACK_SEQUENCE', 'LOAD_GLOBAL', # Only allows access to restricted globals ] if x in opmap))