diff --git a/addons/account/models/account.py b/addons/account/models/account.py index eb86c2da02f..17ae41f53dc 100644 --- a/addons/account/models/account.py +++ b/addons/account/models/account.py @@ -1366,7 +1366,7 @@ class AccountTax(models.Model): base = recompute_base(base, incl_fixed_amount, incl_percent_amount, incl_division_amount) incl_fixed_amount = incl_percent_amount = incl_division_amount = 0 store_included_tax_total = True - if tax.price_include: + if tax.price_include or self._context.get('force_price_include'): if tax.amount_type == 'percent': incl_percent_amount += tax.amount elif tax.amount_type == 'division': @@ -1395,7 +1395,7 @@ class AccountTax(models.Model): cumulated_tax_included_amount = 0 for tax in taxes: #compute the tax_amount - if tax.price_include and total_included_checkpoints.get(i): + if (self._context.get('force_price_include') or tax.price_include) and total_included_checkpoints.get(i): # We know the total to reach for that tax, so we make a substraction to avoid any rounding issues tax_amount = total_included_checkpoints[i] - (base + cumulated_tax_included_amount) cumulated_tax_included_amount = 0 @@ -1434,7 +1434,7 @@ class AccountTax(models.Model): 'sequence': tax.sequence, 'account_id': tax.cash_basis_transition_account_id.id if tax.tax_exigibility == 'on_payment' else repartition_line.account_id.id, 'analytic': tax.analytic, - 'price_include': tax.price_include, + 'price_include': tax.price_include or self._context.get('force_price_include'), 'tax_exigibility': tax.tax_exigibility, 'tax_repartition_line_id': repartition_line.id, 'tag_ids': [(6, False, (repartition_line.tag_ids + subsequent_tags).ids)], diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py index bf2daeaae28..face60dbc47 100644 --- a/addons/account/models/account_invoice.py +++ b/addons/account/models/account_invoice.py @@ -440,7 +440,7 @@ class AccountInvoice(models.Model): vendor_display_name = _('From: ') + invoice.source_email invoice.invoice_icon = '@' else: - vendor_display_name = ('Created by: ') + invoice.create_uid.name + vendor_display_name = ('Created by: ') + invoice.sudo().create_uid.name invoice.invoice_icon = '#' invoice.vendor_display_name = vendor_display_name diff --git a/addons/account/models/account_reconcile_model.py b/addons/account/models/account_reconcile_model.py index a1db4fc2c5f..94a077f58b8 100644 --- a/addons/account/models/account_reconcile_model.py +++ b/addons/account/models/account_reconcile_model.py @@ -102,7 +102,7 @@ class AccountReconcileModel(models.Model): second_analytic_account_id = fields.Many2one('account.analytic.account', string='Second Analytic Account', ondelete='set null') second_analytic_tag_ids = fields.Many2many('account.analytic.tag', string='Second Analytic Tags', relation='account_reconcile_model_second_analytic_tag_rel') - + number_entries = fields.Integer(string='Number of entries related to this model', compute='_compute_number_entries') @api.multi @@ -113,7 +113,7 @@ class AccountReconcileModel(models.Model): 'help': """

{}

""".format(_('No move from this reconciliation model')), }) return action - + @api.multi def _compute_number_entries(self): data = self.env['account.move.line'].read_group([('reconcile_model_id', 'in', self.ids)], ['reconcile_model_ids'], 'reconcile_model_id') @@ -167,19 +167,23 @@ class AccountReconcileModel(models.Model): tax = self.env['account.tax'].browse(tax_res['id']) new_aml_dicts.append({ - 'account_id': tax.account_id and tax.account_id.id or base_line_dict['account_id'], - 'name': tax.name, + 'account_id': tax_res['account_id'] or base_line_dict['account_id'], + 'name': tax_res['name'], 'partner_id': base_line_dict.get('partner_id'), 'debit': tax_res['amount'] > 0 and tax_res['amount'] or 0, 'credit': tax_res['amount'] < 0 and -tax_res['amount'] or 0, 'analytic_account_id': tax.analytic and base_line_dict['analytic_account_id'], 'analytic_tag_ids': tax.analytic and base_line_dict['analytic_tag_ids'], - 'tax_exigible': tax.tax_exigibility == 'on_payment', + 'tax_exigible': tax_res['tax_exigibility'], + 'tax_repartition_line_id': tax_res['tax_repartition_line_id'], + 'tax_ids': tax_res['tax_ids'], + 'tag_ids': tax_res['tag_ids'] }) # Handle price included taxes. base_line_dict['debit'] = tax_res['base'] > 0 and tax_res['base'] or base_line_dict['debit'] base_line_dict['credit'] = tax_res['base'] < 0 and -tax_res['base'] or base_line_dict['credit'] + base_line_dict['tag_ids'] = [(6, 0, res['base_tags'])] return new_aml_dicts @api.multi @@ -456,7 +460,7 @@ class AccountReconcileModel(models.Model): -- if there is a partner, propose all aml of the partner, otherwise propose only the ones -- matching the statement line communication - AND + AND ( ( line_partner.partner_id != 0 diff --git a/addons/account/models/partner.py b/addons/account/models/partner.py index 3911a07ee6b..db91c34a07c 100644 --- a/addons/account/models/partner.py +++ b/addons/account/models/partner.py @@ -260,7 +260,6 @@ class ResPartner(models.Model): def _invoice_total(self): account_invoice_report = self.env['account.invoice.report'] if not self.ids: - self.total_invoiced = 0.0 return True user_currency_id = self.env.user.company_id.currency_id.id diff --git a/addons/account/tests/test_reconciliation_matching_rules.py b/addons/account/tests/test_reconciliation_matching_rules.py index cb10bee162e..5775eb69b47 100644 --- a/addons/account/tests/test_reconciliation_matching_rules.py +++ b/addons/account/tests/test_reconciliation_matching_rules.py @@ -98,6 +98,12 @@ class TestReconciliationMatchingRules(AccountingTestCase): 'sequence': 1, }) + self.tax21 = self.env['account.tax'].create({ + 'name': '21%', + 'type_tax_use': 'purchase', + 'amount': 21, + }) + def test_matching_fields(self): ''' Test all fields used to restrict the rules's applicability.''' @@ -304,6 +310,34 @@ class TestReconciliationMatchingRules(AccountingTestCase): {'partner_id': self.partner_2.id, 'debit': 1000.0, 'credit': 0.0}, ]) + def test_auto_reconcile_with_tax(self): + ''' Test auto reconciliation with a tax amount included in the bank statement line''' + + self.rule_1.write({ + 'auto_reconcile': True, + 'force_tax_included': True, + 'tax_ids': [(6, 0, self.tax21.ids)], + 'rule_type': 'writeoff_suggestion', + }) + + self.bank_line_2.unlink() + self.bank_line_1.amount = -121 + + self._check_statement_matching( + self.rule_1, + { + self.bank_line_1.id: {'aml_ids': [], 'model': self.rule_1, 'status': 'reconciled'}, + }, + self.bank_st + ) + + # Check first line has been well reconciled. + self.assertRecordValues(self.bank_line_1.journal_entry_ids, [ + {'partner_id': self.partner_1.id, 'debit': 0.0, 'credit': 121.0}, + {'partner_id': self.partner_1.id, 'debit': 21.0, 'credit': 0.0, 'tax_line_id': self.tax21.id}, + {'partner_id': self.partner_1.id, 'debit': 100.0, 'credit': 0.0, 'tax_ids': [self.tax21.id]} + ]) + def test_reverted_move_matching(self): AccountMove = self.env['account.move'] move = AccountMove.create({ diff --git a/addons/account/views/account_move_views.xml b/addons/account/views/account_move_views.xml index 73f6dd8105c..7e588676ce8 100644 --- a/addons/account/views/account_move_views.xml +++ b/addons/account/views/account_move_views.xml @@ -363,6 +363,27 @@ + +
+ + + + + + + + + + + + + + + + + + +
diff --git a/addons/account_voucher/models/account_voucher.py b/addons/account_voucher/models/account_voucher.py index c707330b730..f071b6cd187 100644 --- a/addons/account_voucher/models/account_voucher.py +++ b/addons/account_voucher/models/account_voucher.py @@ -123,14 +123,32 @@ class AccountVoucher(models.Model): @api.multi @api.depends('tax_correction', 'line_ids.price_subtotal') def _compute_total(self): + tax_calculation_rounding_method = self.env.user.company_id.tax_calculation_rounding_method for voucher in self: total = 0 tax_amount = 0 + tax_lines_vals_merged = {} for line in voucher.line_ids: tax_info = line.tax_ids.compute_all(line.price_unit, voucher.currency_id, line.quantity, line.product_id, voucher.partner_id) - total += tax_info.get('total_included', 0.0) - tax_amount += sum([t.get('amount',0.0) for t in tax_info.get('taxes', False)]) - voucher.amount = total + voucher.tax_correction + if tax_calculation_rounding_method == 'round_globally': + total += tax_info.get('total_excluded', 0.0) + for t in tax_info.get('taxes', False): + key = ( + t['id'], + t['account_id'], + ) + if key not in tax_lines_vals_merged: + tax_lines_vals_merged[key] = t.get('amount', 0.0) + else: + tax_lines_vals_merged[key] += t.get('amount', 0.0) + else: + total += tax_info.get('total_included', 0.0) + tax_amount += sum([t.get('amount', 0.0) for t in tax_info.get('taxes', False)]) + if tax_calculation_rounding_method == 'round_globally': + tax_amount = sum([voucher.currency_id.round(t) for t in tax_lines_vals_merged.values()]) + voucher.amount = total + tax_amount + voucher.tax_correction + else: + voucher.amount = total + voucher.tax_correction voucher.tax_amount = tax_amount @api.onchange('date') diff --git a/addons/lunch/models/lunch_supplier.py b/addons/lunch/models/lunch_supplier.py index f0ee44b853f..af1cf46f33a 100644 --- a/addons/lunch/models/lunch_supplier.py +++ b/addons/lunch/models/lunch_supplier.py @@ -50,7 +50,7 @@ class LunchSupplier(models.Model): country_id = fields.Many2one('res.country', related='partner_id.country_id', readonly=False) responsible_id = fields.Many2one('res.users', string="Responsible", domain=lambda self: [('groups_id', 'in', self.env.ref('lunch.group_lunch_manager').id)], - default=lambda self: self.env.user.partner_id, + default=lambda self: self.env.user, help="The responsible is the person that will order lunch for everyone" "It will be used as the 'from' when sending the automatic email") diff --git a/addons/mail/static/src/js/composers/basic_composer.js b/addons/mail/static/src/js/composers/basic_composer.js index 0658c3c6820..7c157bc2002 100644 --- a/addons/mail/static/src/js/composers/basic_composer.js +++ b/addons/mail/static/src/js/composers/basic_composer.js @@ -438,10 +438,27 @@ var BasicComposer = Widget.extend({ * @param {boolean} params.submitForm [optional] */ _processAttachmentChange: function (params) { - var self = this, - attachments = this.get('attachment_ids'), - files = params.files, - submitForm = params.submitForm; + var self = this; + var attachments = this.get('attachment_ids'); + var files = params.files; + var submitForm = params.submitForm; + var $form = this.$('form.o_form_binary_form'); + + /** + * makes a new formData as formData.delete() is not supported by IE or Safari Mobile. + * + * @return {FormData} + */ + function makeFormDataWithoutUfile() { + var newFormData = new window.FormData(); + $form.find('input').each(function (index, input) { + if (input.name !== 'ufile') { + newFormData.append(input.name, input.value); + } + }); + return newFormData; + } + _.each(files, function (file) { var attachment = _.findWhere(attachments, { name: file.name, @@ -453,24 +470,20 @@ var BasicComposer = Widget.extend({ attachments = _.without(attachments, attachment); } }); - var $form = this.$('form.o_form_binary_form'); if (submitForm) { $form.submit(); this._$attachmentButton.prop('disabled', true); } else { - var data = new FormData($form[0]); _.each(files, function (file) { - // removing existing key with blank data and appending again with file info - // In safari, existing key will not be updated when append with new file. - data.delete("ufile"); - data.append("ufile", file, file.name); + var formData = makeFormDataWithoutUfile(); + formData.append("ufile", file, file.name); $.ajax({ url: $form.attr("action"), type: "POST", enctype: 'multipart/form-data', processData: false, contentType: false, - data: data, + data: formData, success: function (result) { var $el = $(result); $.globalEval($el.contents().text()); diff --git a/addons/mail/static/src/js/discuss.js b/addons/mail/static/src/js/discuss.js index 4e88434f86d..0c1a39e7b9f 100644 --- a/addons/mail/static/src/js/discuss.js +++ b/addons/mail/static/src/js/discuss.js @@ -168,9 +168,9 @@ var RenameConversationDialog = Dialog.extend({ kwargs: { name: name, } - }).then(function (updatedName) { + }).then(function () { var channel = self.call('mail_service', 'getThread', self._channelID); - channel.setName(updatedName); + channel.setName(name); self._callback(); }); }, diff --git a/addons/mail/static/src/js/models/messages/message.js b/addons/mail/static/src/js/models/messages/message.js index a118807ac47..1cb2ce2c8de 100644 --- a/addons/mail/static/src/js/models/messages/message.js +++ b/addons/mail/static/src/js/models/messages/message.js @@ -800,6 +800,7 @@ var Message = AbstractMessage.extend(Mixins.EventDispatcherMixin, ServicesMixin _warnMessageModerated: function () { var mailBus = this.call('mail_service', 'getMailBus'); if (this.needsModerationByUser()) { + this._setModeratedByUser(false); var moderationBox = this.call('mail_service', 'getMailbox', 'moderation'); moderationBox.decrementMailboxCounter(); moderationBox.removeMessage(this.getID()); diff --git a/addons/mail/static/src/js/services/mail_manager.js b/addons/mail/static/src/js/services/mail_manager.js index dedeacca013..4a20d24af8d 100644 --- a/addons/mail/static/src/js/services/mail_manager.js +++ b/addons/mail/static/src/js/services/mail_manager.js @@ -94,9 +94,7 @@ var MailManager = AbstractService.extend({ additionalThreadIDs: data.channel_ids }); } - if (options.domain && options.domain !== []) { - this._addMessageToThreads(message, options); - } + this._addMessageToThreads(message, options); } return prom; }, diff --git a/addons/mail/static/tests/discuss_tests.js b/addons/mail/static/tests/discuss_tests.js index f25c7082648..a37de219f7c 100644 --- a/addons/mail/static/tests/discuss_tests.js +++ b/addons/mail/static/tests/discuss_tests.js @@ -1426,7 +1426,7 @@ QUnit.test('rename DM conversation', async function (assert) { assert.step(args.method); assert.strictEqual(args.args[0], 1); assert.strictEqual(args.kwargs.name, "Demo"); - return Promise.resolve("Demo"); + return Promise.resolve(); } return this._super.apply(this, arguments); }, diff --git a/addons/mass_mailing/i18n/mass_mailing.pot b/addons/mass_mailing/i18n/mass_mailing.pot index 365fdf842a9..2aec5c16b20 100644 --- a/addons/mass_mailing/i18n/mass_mailing.pot +++ b/addons/mass_mailing/i18n/mass_mailing.pot @@ -2325,9 +2325,9 @@ msgstr "" #. module: mass_mailing #. openerp-web -#: code:addons/mass_mailing/static/src/js/unsubscribe.js:49 +#: code:addons/mass_mailing/static/src/js/unsubscribe.js:50 #, python-format -msgid "You have been successfully unsubscribed from ." +msgid "You have been successfully unsubscribed from %s." msgstr "" #. module: mass_mailing diff --git a/addons/mass_mailing/models/mail_mail.py b/addons/mass_mailing/models/mail_mail.py index 6d718a9ffbf..34ef0472c7e 100644 --- a/addons/mass_mailing/models/mail_mail.py +++ b/addons/mass_mailing/models/mail_mail.py @@ -95,7 +95,7 @@ class MailMail(models.Model): return res @api.multi - def _postprocess_sent_message(self, failure_type=None, **kwargs): + def _postprocess_sent_message(self, success_pids, failure_reason=False, failure_type=None): mail_sent = not failure_type # we consider that a recipient error is a failure with mass mailling and show them as failed for mail in self: if mail.mailing_id: @@ -103,5 +103,4 @@ class MailMail(models.Model): mail.statistics_ids.write({'sent': fields.Datetime.now(), 'exception': False}) elif mail_sent is False and mail.statistics_ids: mail.statistics_ids.write({'exception': fields.Datetime.now()}) - return super(MailMail, self)._postprocess_sent_message(failure_type=failure_type, **kwargs) - \ No newline at end of file + return super(MailMail, self)._postprocess_sent_message(success_pids, failure_reason=failure_reason, failure_type=failure_type) diff --git a/addons/mass_mailing/static/src/js/unsubscribe.js b/addons/mass_mailing/static/src/js/unsubscribe.js index ae1c34f864b..f43aefcd976 100644 --- a/addons/mass_mailing/static/src/js/unsubscribe.js +++ b/addons/mass_mailing/static/src/js/unsubscribe.js @@ -46,7 +46,10 @@ odoo.define('mass_mailing.unsubscribe', function (require) { var unsubscribed_list = $("input[name='unsubscribed_list']").val(); if (unsubscribed_list){ - $('#subscription_info').html(_t('You have been successfully unsubscribed from ' + unsubscribed_list + ".")); + $('#subscription_info').html(_.str.sprintf( + _t("You have been successfully unsubscribed from %s."), + unsubscribed_list + )); } else{ $('#subscription_info').html(_t('You have been successfully unsubscribed.')); diff --git a/addons/mass_mailing/views/unsubscribe_templates.xml b/addons/mass_mailing/views/unsubscribe_templates.xml index bf7b4b54e84..b45c6f39e7c 100644 --- a/addons/mass_mailing/views/unsubscribe_templates.xml +++ b/addons/mass_mailing/views/unsubscribe_templates.xml @@ -121,7 +121,7 @@
Odoo
- +
diff --git a/addons/mrp/models/mrp_production.py b/addons/mrp/models/mrp_production.py index eb046244c34..34ae0f406c7 100644 --- a/addons/mrp/models/mrp_production.py +++ b/addons/mrp/models/mrp_production.py @@ -817,8 +817,6 @@ class MrpProduction(models.Model): moves_to_do = order.move_raw_ids.filtered(lambda x: x.state not in ('done', 'cancel')) for move in moves_to_do.filtered(lambda m: m.product_qty == 0.0 and m.quantity_done > 0): move.product_uom_qty = move.quantity_done - for move in moves_to_do.filtered(lambda m: float_is_zero(m.quantity_done, precision_rounding=m.product_uom.rounding)): - move._action_cancel() # MRP do not merge move, catch the result of _action_done in order # to get extra moves. moves_to_do = moves_to_do._action_done() @@ -849,6 +847,11 @@ class MrpProduction(models.Model): if wo.time_ids.filtered(lambda x: (not x.date_end) and (x.loss_type in ('productive', 'performance'))): raise UserError(_('Work order %s is still running') % wo.name) self._check_lots() + + # Cancel unfinished move + move_to_cancel = self.move_raw_ids.filtered(lambda m: m.state not in ('done', 'cancel') and float_is_zero(m.quantity_done, precision_rounding=m.product_uom.rounding)) + move_to_cancel._action_cancel() + self.post_inventory() # Moves without quantity done are not posted => set them as done instead of canceling. In # case the user edits the MO later on and sets some consumed quantity on those, we do not diff --git a/addons/payment_authorize/models/payment.py b/addons/payment_authorize/models/payment.py index 946f031484d..6361a211f16 100644 --- a/addons/payment_authorize/models/payment.py +++ b/addons/payment_authorize/models/payment.py @@ -62,7 +62,8 @@ class PaymentAcquirerAuthorize(models.Model): # The signature key is now '128-character hexadecimal format', while the # transaction key was only 16-character. if len(values['x_trans_key']) == 128: - return hmac.new(values['x_trans_key'].decode("hex").encode('utf-8'), data, hashlib.sha512).hexdigest().upper() + key = bytes.fromhex(values['x_trans_key']) + return hmac.new(key, data, hashlib.sha512).hexdigest().upper() else: return hmac.new(values['x_trans_key'].encode('utf-8'), data, hashlib.md5).hexdigest() @@ -315,13 +316,14 @@ class TxAuthorize(models.Model): 'acquirer_reference': tree.get('x_trans_id'), 'date': fields.Datetime.now(), }) - if init_state != 'authorized': - self.execute_callback() if self.payment_token_id: self.payment_token_id.verified = True self._set_transaction_done() + + if init_state != 'authorized': + self.execute_callback() if tree.get('x_type').lower() == 'auth_only': self.write({'acquirer_reference': tree.get('x_trans_id')}) self._set_transaction_authorized() diff --git a/addons/purchase/models/purchase.py b/addons/purchase/models/purchase.py index 773d8962d49..bd9cf07f558 100644 --- a/addons/purchase/models/purchase.py +++ b/addons/purchase/models/purchase.py @@ -608,12 +608,7 @@ class PurchaseOrderLine(models.Model): if product_lang.description_purchase: self.name += '\n' + product_lang.description_purchase - fpos = self.order_id.fiscal_position_id - if self.env.uid == SUPERUSER_ID: - company_id = self.env.user.company_id.id - self.taxes_id = fpos.map_tax(self.product_id.supplier_taxes_id.filtered(lambda r: r.company_id.id == company_id)) - else: - self.taxes_id = fpos.map_tax(self.product_id.supplier_taxes_id) + self._compute_tax_id() self._suggest_quantity() self._onchange_quantity() @@ -685,9 +680,8 @@ class PurchaseOrderLine(models.Model): ''' if not self.product_id: return - seller_min_qty = self.product_id.seller_ids\ - .filtered(lambda r: r.name == self.order_id.partner_id)\ + .filtered(lambda r: r.name == self.order_id.partner_id and (not r.product_id or r.product_id == self.product_id))\ .sorted(key=lambda r: r.min_qty) if seller_min_qty: self.product_qty = seller_min_qty[0].min_qty or 1.0 diff --git a/addons/sale_expense/security/ir.model.access.csv b/addons/sale_expense/security/ir.model.access.csv index f9e8f32a752..228b1262f58 100644 --- a/addons/sale_expense/security/ir.model.access.csv +++ b/addons/sale_expense/security/ir.model.access.csv @@ -1,2 +1,2 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink -access_sale_order_employee,sale.order.employee.expense,sale.model_sale_order,base.group_user,1,0,0,0 \ No newline at end of file +access_sale_order_employee,sale.order.employee.expense,sale.model_sale_order,base.group_user,0,0,0,0 \ No newline at end of file diff --git a/addons/sale_expense/security/sale_expense_security.xml b/addons/sale_expense/security/sale_expense_security.xml index 4b40a737f7a..c8087457e9d 100644 --- a/addons/sale_expense/security/sale_expense_security.xml +++ b/addons/sale_expense/security/sale_expense_security.xml @@ -13,6 +13,7 @@ + diff --git a/addons/sale_expense/views/hr_expense_views.xml b/addons/sale_expense/views/hr_expense_views.xml index c86de76cad0..ca46f6a58f7 100644 --- a/addons/sale_expense/views/hr_expense_views.xml +++ b/addons/sale_expense/views/hr_expense_views.xml @@ -6,7 +6,7 @@ - + @@ -17,7 +17,7 @@ - + diff --git a/addons/sale_timesheet/models/project.py b/addons/sale_timesheet/models/project.py index 7ee2d8b9969..6735431cf3a 100644 --- a/addons/sale_timesheet/models/project.py +++ b/addons/sale_timesheet/models/project.py @@ -184,7 +184,7 @@ class ProjectTask(models.Model): @api.depends('project_id.sale_line_employee_ids') def _compute_is_project_map_empty(self): for task in self: - task.is_project_map_empty = not bool(task.project_id.sale_line_employee_ids) + task.is_project_map_empty = not bool(task.sudo().project_id.sale_line_employee_ids) @api.onchange('project_id') def _onchange_project(self): diff --git a/addons/web/static/lib/jSignature/jSignatureCustom.js b/addons/web/static/lib/jSignature/jSignatureCustom.js index ae51103da3d..6ccd2af79a3 100644 --- a/addons/web/static/lib/jSignature/jSignatureCustom.js +++ b/addons/web/static/lib/jSignature/jSignatureCustom.js @@ -760,7 +760,7 @@ function jSignatureClass(parent, options, instanceExtensions) { // these, when enabled, will hover above the sig area. Hence we append them to DOM before canvas. this.$controlbarUpper = (function(){ var controlbarstyle = 'padding:0 !important; margin:0 !important;'+ - 'width: 100% !important; height: 0 !important; -ms-touch-action: none;'+ + 'width: 100% !important; height: 0 !important; -ms-touch-action: none; touch-action: none;'+ 'margin-top:-1em !important; margin-bottom:1em !important;'; return $('
').appendTo($parent); })(); @@ -771,7 +771,7 @@ function jSignatureClass(parent, options, instanceExtensions) { this.$controlbarLower = (function(){ var controlbarstyle = 'padding:0 !important; margin:0 !important;'+ - 'width: 100% !important; height: 0 !important; -ms-touch-action: none;'+ + 'width: 100% !important; height: 0 !important; -ms-touch-action: none; touch-action: none;'+ 'margin-top:-1.5em !important; margin-bottom:1.5em !important; position: relative;'; return $('
').appendTo($parent); })(); @@ -876,7 +876,7 @@ function jSignatureClass(parent, options, instanceExtensions) { $canvas.bind('mouseup.'+apinamespace, drawEndHandler); $canvas.bind('mousedown.'+apinamespace, drawStartHandler); } else { - canvas.ontouchstart = function(e) { + canvas.addEventListener('touchstart', function(e) { canvas.onmousedown = canvas.onmouseup = canvas.onmousemove = undef; this.fatFingerCompensation = ( @@ -886,19 +886,19 @@ function jSignatureClass(parent, options, instanceExtensions) { drawStartHandler(e); - canvas.ontouchend = drawEndHandler; - canvas.ontouchstart = drawStartHandler; - canvas.ontouchmove = drawMoveHandler; - }; - canvas.onmousedown = function(e) { + canvas.addEventListener('touchend', drawEndHandler); + canvas.addEventListener('touchstart', drawStartHandler); + canvas.addEventListener('touchmove', drawMoveHandler); + }); + canvas.addEventListener('mousedown', function(e) { canvas.ontouchstart = canvas.ontouchend = canvas.ontouchmove = undef; drawStartHandler(e); - canvas.onmousedown = drawStartHandler; - canvas.onmouseup = drawEndHandler; - canvas.onmousemove = drawMoveHandler; - } + canvas.addEventListener('mousedown', drawStartHandler); + canvas.addEventListener('mouseup', drawEndHandler); + canvas.addEventListener('mousemove', drawMoveHandler); + }); if (window.navigator.msPointerEnabled) { canvas.onmspointerdown = drawStartHandler; canvas.onmspointerup = drawEndHandler; @@ -1084,28 +1084,16 @@ jSignatureClass.prototype.initializeCanvas = function(settings) { settings.width = '100%'; } - $canvas.css( - 'margin' - , 0 - ).css( - 'padding' - , 0 - ).css( - 'border' - , 'none' - ).css( - 'height' - , settings.height === 'ratio' || !settings.height ? 1 : settings.height.toString(10) - ).css( - 'width' - , settings.width === 'ratio' || !settings.width ? 1 : settings.width.toString(10) - ).css( - '-ms-touch-action' - , 'none' - ).css( - 'background-color', - settings['background-color'] - ); + $canvas.css({ + 'margin': 0, + 'padding': 0, + 'border': 'none', + 'height': settings.height === 'ratio' || !settings.height ? 1 : settings.height.toString(10), + 'width': settings.width === 'ratio' || !settings.width ? 1 : settings.width.toString(10), + '-ms-touch-action': 'none', + 'touch-action': 'none', + 'background-color': settings['background-color'], + }); $canvas.appendTo(this.$parent); diff --git a/addons/web/views/database_manager.html b/addons/web/views/database_manager.html index d39a77ef4e7..4649366dd61 100644 --- a/addons/web/views/database_manager.html +++ b/addons/web/views/database_manager.html @@ -41,6 +41,10 @@ }); // close modal on submit $('.modal').on('submit', 'form', function (ev) { + var form = $(this).closest('form')[0]; + if (form && form.checkValidity && !form.checkValidity()) { + return; + } var modal = $(this).parentsUntil('body', '.modal'); if (modal.hasClass('o_database_backup')) { $(modal).modal('hide'); diff --git a/addons/website/static/src/js/tours/rte.js b/addons/website/static/src/js/tours/rte.js index d7854d3aea9..cc30790d1b8 100644 --- a/addons/website/static/src/js/tours/rte.js +++ b/addons/website/static/src/js/tours/rte.js @@ -175,7 +175,7 @@ tour.register('rte_translator', { }, { content: "check bis: content is translated", trigger: '#wrap p font:first:contains(translated french text)', - extra_trigger: 'body:not(:has(button[data-action=save]))', + extra_trigger: 'html[lang*="fr"] body:not(:has(button[data-action=save]))', }, { content: "check bis: placeholder translation", trigger: 'input[placeholder="test french placeholder"]', diff --git a/addons/website_slides/static/src/js/slides_course_quiz.js b/addons/website_slides/static/src/js/slides_course_quiz.js index 9565cad9ef4..f5b56297fe8 100644 --- a/addons/website_slides/static/src/js/slides_course_quiz.js +++ b/addons/website_slides/static/src/js/slides_course_quiz.js @@ -327,7 +327,7 @@ odoo.define('website_slides.quiz', function (require) { _extractChannelData: function (slideData){ return { id: slideData.channelId, - channelEnroll: slideData.enroll, + channelEnroll: slideData.channelEnroll, signupAllowed: slideData.signupAllowed }; }, diff --git a/odoo/tests/common.py b/odoo/tests/common.py index 603e02ac75b..b05128c6bcd 100644 --- a/odoo/tests/common.py +++ b/odoo/tests/common.py @@ -1531,6 +1531,12 @@ class O2MForm(Form): else: self._values.update(proxy._records[index]) + def _get_modifier(self, field, modifier, default=False, modmap=None, vals=None): + if vals is None: + vals = {**self._values, '•parent•': self._proxy._parent._values} + + return super()._get_modifier(field, modifier, default=default, modmap=modmap, vals=vals) + def _onchange_values(self): values = super(O2MForm, self)._onchange_values() # computed o2m may not have a relation_field(?)