diff --git a/addons/account_invoicing/__init__.py b/addons/account_invoicing/__init__.py index 67dee8c60db..91206cf6094 100644 --- a/addons/account_invoicing/__init__.py +++ b/addons/account_invoicing/__init__.py @@ -1,2 +1,13 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo.api import Environment, SUPERUSER_ID + + +def uninstall_hook(cr, registry): + env = Environment(cr, SUPERUSER_ID, {}) + res_ids = env['ir.model.data'].search([ + ('model', '=', 'ir.ui.menu'), + ('module', '=', 'account') + ]).mapped('res_id') + env['ir.ui.menu'].browse(res_ids).update({'active': False}) diff --git a/addons/account_invoicing/__manifest__.py b/addons/account_invoicing/__manifest__.py index 8630df721f6..faa54037c69 100644 --- a/addons/account_invoicing/__manifest__.py +++ b/addons/account_invoicing/__manifest__.py @@ -27,4 +27,5 @@ You could use this simplified accounting in case you work with an (external) acc 'qweb': [ ], 'application': True, + 'uninstall_hook': 'uninstall_hook', } diff --git a/addons/base_import/static/src/js/import_action.js b/addons/base_import/static/src/js/import_action.js index ee9e95fa5a0..5d5ec22e8aa 100644 --- a/addons/base_import/static/src/js/import_action.js +++ b/addons/base_import/static/src/js/import_action.js @@ -138,6 +138,7 @@ var DataImport = Widget.extend(ControlPanelMixin, { model: 'base_import.import', method: 'create', args: [{res_model: this.res_model}], + kwargs: {context: session.user_context}, }); }, renderButtons: function() { @@ -287,6 +288,7 @@ var DataImport = Widget.extend(ControlPanelMixin, { model: 'base_import.import', method: 'parse_preview', args: [this.id, this.import_options()], + kwargs: {context: session.user_context}, }).done(function (result) { var signal = result.error ? 'preview_failed' : 'preview_succeeded'; self[signal](result); diff --git a/addons/calendar/views/calendar_views.xml b/addons/calendar/views/calendar_views.xml index c9bca3b52cf..001fcb9c1e6 100644 --- a/addons/calendar/views/calendar_views.xml +++ b/addons/calendar/views/calendar_views.xml @@ -75,7 +75,7 @@ - + diff --git a/addons/delivery/models/stock_picking.py b/addons/delivery/models/stock_picking.py index 234a037588c..c85b7a96e3f 100644 --- a/addons/delivery/models/stock_picking.py +++ b/addons/delivery/models/stock_picking.py @@ -107,16 +107,12 @@ class StockPicking(models.Model): @api.multi def action_done(self): - # TDE FIXME: should work in batch - self.ensure_one() res = super(StockPicking, self).action_done() - - if self.carrier_id and self.carrier_id.integration_level == 'rate_and_ship': - self.send_to_shipper() - - if self.carrier_id: - self._add_delivery_cost_to_so() - + for pick in self: + if pick.carrier_id: + if pick.carrier_id.integration_level == 'rate_and_ship': + pick.send_to_shipper() + pick._add_delivery_cost_to_so() return res @api.multi diff --git a/addons/hr/models/hr.py b/addons/hr/models/hr.py index 6a87d1f74f4..4fa89aa4e5c 100644 --- a/addons/hr/models/hr.py +++ b/addons/hr/models/hr.py @@ -5,7 +5,7 @@ import logging from odoo import api, fields, models from odoo import tools, _ -from odoo.exceptions import ValidationError +from odoo.exceptions import ValidationError, AccessError from odoo.modules.module import get_module_resource _logger = logging.getLogger(__name__) @@ -116,7 +116,6 @@ class Employee(models.Model): is_address_home_a_company = fields.Boolean( 'The employee adress has a company linked', compute='_compute_is_address_home_a_company', - groups="hr.group_hr_user", ) country_id = fields.Many2one( 'res.country', 'Nationality (Country)', groups="hr.group_hr_user") @@ -271,7 +270,11 @@ class Employee(models.Model): """Checks that choosen address (res.partner) is not linked to a company. """ for employee in self: - employee.is_address_home_a_company = employee.address_home_id.parent_id.id is not False + try: + employee.is_address_home_a_company = employee.address_home_id.parent_id.id is not False + except AccessError: + employee.is_address_home_a_company = False + class Department(models.Model): _name = "hr.department" diff --git a/addons/hr/views/hr_views.xml b/addons/hr/views/hr_views.xml index d3351e1b5f2..981ee3ba2fd 100644 --- a/addons/hr/views/hr_views.xml +++ b/addons/hr/views/hr_views.xml @@ -87,8 +87,9 @@ - -
+ +
Use here the home address of the employee. This private address is used in the expense report reimbursement document. It should be different from the work address. diff --git a/addons/mail/static/src/js/activity.js b/addons/mail/static/src/js/activity.js index 4d3d7adee50..59b32f32023 100644 --- a/addons/mail/static/src/js/activity.js +++ b/addons/mail/static/src/js/activity.js @@ -140,7 +140,8 @@ var Activity = AbstractActivityField.extend({ nbPlannedActivities: nbActivities.planned, nbTodayActivities: nbActivities.today, nbOverdueActivities: nbActivities.overdue, - date_format: time.getLangDatetimeFormat(), + date_format: time.getLangDateFormat(), + datetime_format: time.getLangDatetimeFormat(), })); } else { self.$el.empty(); diff --git a/addons/mail/static/src/xml/activity.xml b/addons/mail/static/src/xml/activity.xml index da3e63772a6..fadb93dfa7d 100644 --- a/addons/mail/static/src/xml/activity.xml +++ b/addons/mail/static/src/xml/activity.xml @@ -40,7 +40,7 @@
Created on
- +
Assigned to
diff --git a/addons/pad/models/pad.py b/addons/pad/models/pad.py index aebcff24918..a1e31bf485a 100644 --- a/addons/pad/models/pad.py +++ b/addons/pad/models/pad.py @@ -10,7 +10,6 @@ import requests from odoo import api, models, _ from odoo.exceptions import UserError -from odoo.tools import html2plaintext from ..py_etherpad import EtherpadLiteClient @@ -65,9 +64,7 @@ class PadCommon(models.AbstractModel): # get content of the real field for record in model.browse([self.env.context["object_id"]]): if record[real_field]: - myPad.setText(path, (html2plaintext(record[real_field]).encode('utf-8'))) - # Etherpad for html not functional - # myPad.setHTML(path, record[real_field]) + myPad.setHtmlFallbackText(path, record[real_field]) return { "server": pad["server"], @@ -77,17 +74,25 @@ class PadCommon(models.AbstractModel): @api.model def pad_get_content(self, url): + company = self.env.user.sudo().company_id + myPad = EtherpadLiteClient(company.pad_key, company.pad_server + '/api') content = '' if url: + split_url = url.split('/p/') + path = len(split_url) == 2 and split_url[1] try: - r = requests.get('%s/export/html' % url) - r.raise_for_status() - except: - _logger.warning("No url found '%s'.", url) - else: - mo = re.search('(.*)', r.content.decode(), re.DOTALL) - if mo: - content = mo.group(1) + content = myPad.getHtml(path).get('html', '') + except IOError: + _logger.warning('Http Error: the credentials might be absent for url: "%s". Falling back.' % url) + try: + r = requests.get('%s/export/html' % url) + r.raise_for_status() + except Exception: + _logger.warning("No pad found with url '%s'.", url) + else: + mo = re.search('(.*)', r.content.decode(), re.DOTALL) + if mo: + content = mo.group(1) return content @@ -128,7 +133,7 @@ class PadCommon(models.AbstractModel): company = self.env.user.sudo().company_id myPad = EtherpadLiteClient(company.pad_key, company.pad_server + '/api') path = self[k].split('/p/')[1] - myPad.setText(path, (html2plaintext(vals[field.pad_content_field]).encode('utf-8'))) + myPad.setHtmlFallbackText(path, vals[field.pad_content_field]) # Update the `pad_content_field` if the pad is modified for k, v in list(vals.items()): diff --git a/addons/pad/py_etherpad/__init__.py b/addons/pad/py_etherpad/__init__.py index fc9721eb9c8..2d4872f3bb9 100644 --- a/addons/pad/py_etherpad/__init__.py +++ b/addons/pad/py_etherpad/__init__.py @@ -1,5 +1,10 @@ """Module to talk to EtherpadLite API.""" import requests +import logging + +from odoo.tools import html2plaintext + +_logger = logging.getLogger(__name__) class EtherpadLiteClient: @@ -171,6 +176,15 @@ class EtherpadLiteClient: "text": text }) + def setHtmlFallbackText(self, padID, html): + try: + # Prevents malformed HTML errors + html_wellformed = '' + html + '' + return self.setHtml(padID, html_wellformed) + except Exception: + _logger.exception('Falling back to setText. SetHtml failed with message:') + return self.setText(padID, html2plaintext(html).encode('UTF-8')) + def setHtml(self, padID, html): """sets the text of a pad from html""" return self.call("setHTML", { diff --git a/addons/payment_stripe/models/payment.py b/addons/payment_stripe/models/payment.py index 888cc021b83..e097f4b2e89 100644 --- a/addons/payment_stripe/models/payment.py +++ b/addons/payment_stripe/models/payment.py @@ -270,6 +270,10 @@ class PaymentTokenStripe(models.Model): def _stripe_create_customer(self, token, description=None, acquirer_id=None): + if token.get('error'): + _logger.error('payment.token.stripe_create_customer: Token error:\n%s', pprint.pformat(token['error'])) + raise Exception(token['error']['message']) + if token['object'] != 'token': _logger.error('payment.token.stripe_create_customer: Cannot create a customer for object type "%s"', token.get('object')) raise Exception('We are unable to process your credit card information.') @@ -278,10 +282,6 @@ class PaymentTokenStripe(models.Model): _logger.error('payment.token.stripe_create_customer: Cannot create a customer for token type "%s"', token.get('type')) raise Exception('We are unable to process your credit card information.') - if token.get('error'): - _logger.error('payment.token.stripe_create_customer: Token error:\n%s', pprint.pformat(token['error'])) - raise Exception(token['error']['message']) - payment_acquirer = self.env['payment.acquirer'].browse(acquirer_id or self.acquirer_id.id) url_customer = 'https://%s/customers' % payment_acquirer._get_stripe_api_url() diff --git a/addons/payment_stripe/static/src/js/stripe.js b/addons/payment_stripe/static/src/js/stripe.js index 13f8f91bfcd..2c22dcfa0f4 100644 --- a/addons/payment_stripe/static/src/js/stripe.js +++ b/addons/payment_stripe/static/src/js/stripe.js @@ -97,7 +97,10 @@ odoo.define('payment_stripe.stripe', function(require) { if (so_id) { so_id = parseInt(so_id[1]); } - + var invoice_id = $("input[name='return_url']").val().match(/invoices\/([0-9]+)/) || undefined; + if (invoice_id) { + invoice_id = parseInt(invoice_id[1]); + } var currency = $("input[name='currency']").val(); var currency_id = $("input[name='currency_id']").val(); @@ -121,8 +124,14 @@ odoo.define('payment_stripe.stripe', function(require) { try { provider_form[0].innerHTML = data; } catch (e) {}; }); } else if (window.location.href.includes("/my/orders/")) { - var create_tx = ajax.jsonRpc('/pay/sale/' + acquirer_id + '/form_tx/', 'call', { - so_id: so_id, + var create_tx = ajax.jsonRpc('/pay/sale/' + so_id + '/form_tx/', 'call', { + access_token: access_token, + acquirer_id: acquirer_id + }).then(function (data) { + try { provider_form.innerHTML = data; } catch (e) {}; + }); + } else if (window.location.href.includes("/my/invoices/")) { + var create_tx = ajax.jsonRpc('/invoice/pay/' + invoice_id + '/form_tx/', 'call', { access_token: access_token, acquirer_id: acquirer_id }).then(function (data) { diff --git a/addons/product/models/product_attribute.py b/addons/product/models/product_attribute.py index cffefbbda13..e47297da52f 100644 --- a/addons/product/models/product_attribute.py +++ b/addons/product/models/product_attribute.py @@ -107,7 +107,6 @@ class ProductAttributeLine(models.Model): # search on a m2o and one on a m2m, probably this will quickly become # difficult to compute - check if performance optimization is required if name and operator in ('=', 'ilike', '=ilike', 'like', '=like'): - new_args = ['|', ('attribute_id', operator, name), ('value_ids', operator, name)] - else: - new_args = args - return super(ProductAttributeLine, self).name_search(name=name, args=new_args, operator=operator, limit=limit) + args = ['|', ('attribute_id', operator, name), ('value_ids', operator, name)] + return self.search(args, limit=limit).name_get() + return super(ProductAttributeLine, self).name_search(name=name, args=args, operator=operator, limit=limit) diff --git a/addons/product/tests/test_variants.py b/addons/product/tests/test_variants.py index fbf3073377d..813f5f9e28d 100644 --- a/addons/product/tests/test_variants.py +++ b/addons/product/tests/test_variants.py @@ -2,6 +2,43 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. from . import common +from odoo.tests.common import TransactionCase + +class TestVariants(TransactionCase): + + def setUp(self): + res = super(TestVariants, self).setUp() + self.size_attr = self.env['product.attribute'].create({'name': 'Size'}) + self.size_attr_value_s = self.env['product.attribute.value'].create({'name': 'S', 'attribute_id': self.size_attr.id}) + self.size_attr_value_m = self.env['product.attribute.value'].create({'name': 'M', 'attribute_id': self.size_attr.id}) + self.size_attr_value_l = self.env['product.attribute.value'].create({'name': 'L', 'attribute_id': self.size_attr.id}) + self.product_shirt_template = self.env['product.template'].create({ + 'name': 'Shirt', + 'attribute_line_ids': [(0, 0, { + 'attribute_id': self.size_attr.id, + 'value_ids': [(6, 0, [self.size_attr_value_l.id])], + })] + }) + return res + + def test_attribute_line_search(self): + search_not_to_be_found = self.env['product.template'].search( + [('attribute_line_ids', '=', 'M')] + ) + self.assertNotIn(self.product_shirt_template, search_not_to_be_found, + 'Shirt should not be found searching M') + + search_attribute = self.env['product.template'].search( + [('attribute_line_ids', '=', 'Size')] + ) + self.assertIn(self.product_shirt_template, search_attribute, + 'Shirt should be found searching Size') + + search_value = self.env['product.template'].search( + [('attribute_line_ids', '=', 'L')] + ) + self.assertIn(self.product_shirt_template, search_value, + 'Shirt should be found searching L') class TestVariants(common.TestProductCommon): diff --git a/addons/project_timesheet_holidays/tests/test_timesheet_holidays.py b/addons/project_timesheet_holidays/tests/test_timesheet_holidays.py index 9d358c14114..75c868c9d09 100644 --- a/addons/project_timesheet_holidays/tests/test_timesheet_holidays.py +++ b/addons/project_timesheet_holidays/tests/test_timesheet_holidays.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -from datetime import datetime +from datetime import datetime, time from dateutil.relativedelta import relativedelta from odoo import fields @@ -32,7 +32,9 @@ class TestTimesheetHolidays(TestTimesheet): self.employee_working_calendar = self.empl_employee.resource_calendar_id # leave dates : from next monday to next wednesday (to avoid crashing tests on weekend, when # there is no work days in working calendar) - self.leave_start_datetime = datetime.today().replace(hour=7, minute=0) + relativedelta(weeks=0, days=1, weekday=0) + # NOTE: second and millisecond can add a working days + today = datetime.combine(datetime.today(), time.min) + self.leave_start_datetime = today.replace(hour=7) + relativedelta(weeks=0, days=1, weekday=0) self.leave_end_datetime = self.leave_start_datetime + relativedelta(days=3) # all company have those internal project/task (created by default) @@ -75,7 +77,7 @@ class TestTimesheetHolidays(TestTimesheet): def test_validate_with_timesheet(self): # employee creates a leave request - number_of_days = (self.leave_end_datetime - self.leave_start_datetime).days+1 + number_of_days = (self.leave_end_datetime - self.leave_start_datetime).days holiday = self.Holidays.sudo(self.user_employee.id).create({ 'name': 'Leave 1', 'employee_id': self.empl_employee.id, diff --git a/addons/sale_management/__init__.py b/addons/sale_management/__init__.py index 67dee8c60db..1b2e5977577 100644 --- a/addons/sale_management/__init__.py +++ b/addons/sale_management/__init__.py @@ -1,2 +1,13 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo.api import Environment, SUPERUSER_ID + + +def uninstall_hook(cr, registry): + env = Environment(cr, SUPERUSER_ID, {}) + res_ids = env['ir.model.data'].search([ + ('model', '=', 'ir.ui.menu'), + ('module', '=', 'sale') + ]).mapped('res_id') + env['ir.ui.menu'].browse(res_ids).update({'active': False}) diff --git a/addons/sale_management/__manifest__.py b/addons/sale_management/__manifest__.py index b498649e819..4202ac31f78 100644 --- a/addons/sale_management/__manifest__.py +++ b/addons/sale_management/__manifest__.py @@ -47,4 +47,5 @@ The Dashboard for the Sales Manager will include 'views/sale_management_templates.xml', ], 'application': True, + 'uninstall_hook': 'uninstall_hook', } diff --git a/addons/stock/models/stock_quant.py b/addons/stock/models/stock_quant.py index 225708c9d4a..3b1c27d9eb2 100644 --- a/addons/stock/models/stock_quant.py +++ b/addons/stock/models/stock_quant.py @@ -420,7 +420,7 @@ class QuantPackage(models.Model): def unpack(self): for package in self: - move_lines_to_remove = self.move_line_ids.filtered(lambda move_line: move_line.state != 'done') + move_lines_to_remove = package.move_line_ids.filtered(lambda move_line: move_line.state != 'done') if move_lines_to_remove: move_lines_to_remove.write({'result_package_id': False}) else: diff --git a/addons/website_forum/views/website_forum.xml b/addons/website_forum/views/website_forum.xml index 3d2315db252..049d5dd1835 100644 --- a/addons/website_forum/views/website_forum.xml +++ b/addons/website_forum/views/website_forum.xml @@ -685,7 +685,7 @@