summaryrefslogtreecommitdiff
path: root/addons
diff options
context:
space:
mode:
authorChristophe Simonis <chs@odoo.com>2018-03-19 18:25:41 +0100
committerChristophe Simonis <chs@odoo.com>2018-03-19 18:25:41 +0100
commit82effcca83ddc7ebb69c68ff5a68ad401d3b9643 (patch)
tree0af576b4ad82cb8d0896c6963abdeb0428b54fce /addons
parent0e898eae3506e1e4a6407e11097b58a60c6d7321 (diff)
parentcaf6b4a7c7909349ab7c8f2e10bf86f5a9ff2a20 (diff)
[MERGE] forward port branch 10.0 up to caf6b4a7c79
Diffstat (limited to 'addons')
-rw-r--r--addons/account/models/account_invoice.py9
-rw-r--r--addons/account/models/account_move.py2
-rw-r--r--addons/account_check_printing/models/account_payment.py4
-rw-r--r--addons/calendar/models/calendar.py2
-rw-r--r--addons/crm/report/crm_opportunity_report_views.xml8
-rw-r--r--addons/hr_expense/views/hr_expense_views.xml2
-rw-r--r--addons/hr_timesheet/hr_timesheet.py7
-rw-r--r--addons/hw_scanner/controllers/main.py4
-rw-r--r--addons/l10n_multilang/models/l10n_multilang.py4
-rw-r--r--addons/payment_authorize/models/payment.py14
-rw-r--r--addons/purchase/data/mail_template_data.xml4
-rw-r--r--addons/stock/data/stock_data.xml1
-rw-r--r--addons/stock/models/stock_inventory.py3
-rw-r--r--addons/stock/models/stock_warehouse.py2
-rw-r--r--addons/web/static/src/js/abstract_web_client.js37
-rw-r--r--addons/web_tour/static/src/js/tour_manager.js2
-rw-r--r--addons/website/static/src/js/website.backendDashboard.js5
-rw-r--r--addons/website/static/src/xml/website.backend.xml1
-rw-r--r--addons/website_sale/models/sale_order.py2
19 files changed, 83 insertions, 30 deletions
diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py
index 2fe9f9536a8..99841241994 100644
--- a/addons/account/models/account_invoice.py
+++ b/addons/account/models/account_invoice.py
@@ -1417,7 +1417,14 @@ class AccountInvoiceTax(models.Model):
currency_id = fields.Many2one('res.currency', related='invoice_id.currency_id', store=True, readonly=True)
base = fields.Monetary(string='Base', compute='_compute_base_amount', store=True)
-
+ # DO NOT FORWARD-PORT!!! ONLY FOR v10
+ def create(self, vals):
+ inv_tax = super(AccountInvoiceTax, self).create(vals)
+ # Workaround to make sure the tax amount is rounded to the currency precision since the ORM
+ # won't round it automatically at creation.
+ if inv_tax.company_id.tax_calculation_rounding_method == 'round_globally':
+ inv_tax.amount = inv_tax.currency_id.round(inv_tax.amount)
+ return inv_tax
class AccountPaymentTerm(models.Model):
diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py
index 1c35f91a295..286a979da56 100644
--- a/addons/account/models/account_move.py
+++ b/addons/account/models/account_move.py
@@ -453,7 +453,7 @@ class AccountMoveLine(models.Model):
raise ValidationError(_("You cannot create journal items with a secondary currency without filling both 'currency' and 'amount currency' field."))
@api.multi
- @api.constrains('amount_currency')
+ @api.constrains('amount_currency', 'debit', 'credit')
def _check_currency_amount(self):
for line in self:
if line.amount_currency:
diff --git a/addons/account_check_printing/models/account_payment.py b/addons/account_check_printing/models/account_payment.py
index ef33dc05ae4..ab6623b3068 100644
--- a/addons/account_check_printing/models/account_payment.py
+++ b/addons/account_check_printing/models/account_payment.py
@@ -4,7 +4,7 @@
import math
from odoo import models, fields, api, _
-from odoo.tools import amount_to_text_en, float_round
+from odoo.tools import amount_to_text_en, float_round, float_is_zero
from odoo.exceptions import UserError, ValidationError
@@ -55,7 +55,7 @@ class AccountPayment(models.Model):
check_amount_in_words = amount_to_text_en.amount_to_text(math.floor(amount), lang='en', currency='')
check_amount_in_words = check_amount_in_words.replace(' and Zero Cent', '') # Ugh
decimals = amount % 1
- if decimals >= 10**-2:
+ if not float_is_zero(decimals, precision_digits=2):
check_amount_in_words += _(' and %s/100') % str(int(round(float_round(decimals*100, precision_rounding=1))))
return check_amount_in_words
diff --git a/addons/calendar/models/calendar.py b/addons/calendar/models/calendar.py
index a55ebc6c7d1..b6245a6793d 100644
--- a/addons/calendar/models/calendar.py
+++ b/addons/calendar/models/calendar.py
@@ -853,7 +853,7 @@ class Meeting(models.Model):
@api.multi
def _compute_color_partner(self):
- for meeting in self:
+ for meeting in self.sudo():
meeting.color_partner_id = meeting.user_id.partner_id.id
@api.constrains('start_datetime', 'stop_datetime', 'start_date', 'stop_date')
diff --git a/addons/crm/report/crm_opportunity_report_views.xml b/addons/crm/report/crm_opportunity_report_views.xml
index 610fb112fc4..bf6bcd83945 100644
--- a/addons/crm/report/crm_opportunity_report_views.xml
+++ b/addons/crm/report/crm_opportunity_report_views.xml
@@ -55,20 +55,20 @@
<field name="model_id">crm.opportunity.report</field>
<field name="domain">[('probability', '=', 100)]</field>
<field name="user_id" eval="False"/>
- <field name="context">{'group_by': ['date_closed:month'],'col_group_by': ['create_date:month'], 'measures': ['__count']}</field>
+ <field name="context">{'group_by': ['date_closed:month'],'col_group_by': ['create_date:month'], 'measures': ['__count__']}</field>
</record>
<record id="filter_opportunity_opportunities_won_per_team" model="ir.filters">
<field name="name">Opportunities Won Per Team</field>
<field name="model_id">crm.opportunity.report</field>
<field name="domain">[('probability', '=', 100)]</field>
<field name="user_id" eval="False"/>
- <field name="context">{'group_by': ['team_id'], 'col_group_by': ['date_last_stage_update:month'], 'measures': ['expected revenue']}</field>
+ <field name="context">{'group_by': ['team_id'], 'col_group_by': ['date_last_stage_update:month'], 'measures': ['expected_revenue']}</field>
</record>
<record id="filter_opportunity_salesperson" model="ir.filters">
<field name="name">Leads By Salespersons</field>
<field name="model_id">crm.opportunity.report</field>
<field name="user_id" eval="False"/>
- <field name="context">{'col_group_by': ['create_date:month'], 'group_by': ['user_id'], 'measures': ['__count']}</field>
+ <field name="context">{'col_group_by': ['create_date:month'], 'group_by': ['user_id'], 'measures': ['__count__']}</field>
</record>
<record id="filter_opportunity_country" model="ir.filters">
<field name="name">Won By Country</field>
@@ -81,7 +81,7 @@
<field name="name">Expected Revenue by Team</field>
<field name="model_id">crm.opportunity.report</field>
<field name="user_id" eval="False"/>
- <field name="context">{'group_by': ['create_date:month', 'team_id'], 'measures': ['expected_revenue', '__count']}</field>
+ <field name="context">{'group_by': ['create_date:month', 'team_id'], 'measures': ['expected_revenue', '__count__']}</field>
</record>
<record id="ir_filters_crm_opportunity_report_next_action" model="ir.filters">
<field name="name">Team Activities</field>
diff --git a/addons/hr_expense/views/hr_expense_views.xml b/addons/hr_expense/views/hr_expense_views.xml
index ef0a090b817..2bd9aa2b26f 100644
--- a/addons/hr_expense/views/hr_expense_views.xml
+++ b/addons/hr_expense/views/hr_expense_views.xml
@@ -164,7 +164,7 @@
<filter domain="[('state', '=', 'draft')]" string="To Submit" name="to_report" help="New Expense"/>
<filter domain="[('state', '=', 'reported')]" string="Reported" name="submitted" help="Confirmed Expenses"/>
<filter domain="[('state', '=', 'refused')]" string="Refused" name="refused" help="Refused Expenses"/>
- <filter domain="[('state', '!=', 'cancel')]" string="Not Refused" name="uncancelled" help="Actual expense sheets, not the refused ones"/>
+ <filter domain="[('state', '!=', 'refused')]" string="Not Refused" name="uncancelled" help="Actual expense sheets, not the refused ones"/>
<separator />
<filter string="My Team Expenses" domain="[('employee_id.parent_id.user_id', '=', uid)]" groups="hr_expense.group_hr_expense_manager" help="Expenses of Your Team Member"/>
<filter string="My Expenses" domain="[('employee_id.user_id', '=', uid)]"/>
diff --git a/addons/hr_timesheet/hr_timesheet.py b/addons/hr_timesheet/hr_timesheet.py
index c8b7241e5c5..9a7d421da88 100644
--- a/addons/hr_timesheet/hr_timesheet.py
+++ b/addons/hr_timesheet/hr_timesheet.py
@@ -9,12 +9,17 @@ class AccountAnalyticLine(models.Model):
task_id = fields.Many2one('project.task', 'Task')
project_id = fields.Many2one('project.project', 'Project', domain=[('allow_timesheets', '=', True)])
- department_id = fields.Many2one('hr.department', "Department", related='user_id.employee_ids.department_id', store=True, readonly=True)
+ department_id = fields.Many2one('hr.department', "Department", compute='_compute_department_id', store=True)
@api.onchange('project_id')
def onchange_project_id(self):
self.task_id = False
+ @api.depends('user_id')
+ def _compute_department_id(self):
+ for line in self:
+ line.department_id = line.user_id.employee_ids[:1].department_id
+
@api.model
def create(self, vals):
if vals.get('project_id'):
diff --git a/addons/hw_scanner/controllers/main.py b/addons/hw_scanner/controllers/main.py
index fc2724dd31d..2ef49b7e6fd 100644
--- a/addons/hw_scanner/controllers/main.py
+++ b/addons/hw_scanner/controllers/main.py
@@ -4,7 +4,7 @@
import logging
import time
from os import listdir
-from os.path import join
+from os.path import join, isdir
from Queue import Queue, Empty
from select import select
from threading import Thread, Lock
@@ -121,7 +121,7 @@ class Scanner(Thread):
if not evdev:
return []
- if not os.path.isdir(self.input_dir):
+ if not isdir(self.input_dir):
return []
new_devices = [device for device in listdir(self.input_dir)
diff --git a/addons/l10n_multilang/models/l10n_multilang.py b/addons/l10n_multilang/models/l10n_multilang.py
index de0ef064bcd..fe2f48a79e2 100644
--- a/addons/l10n_multilang/models/l10n_multilang.py
+++ b/addons/l10n_multilang/models/l10n_multilang.py
@@ -37,11 +37,11 @@ class AccountChartTemplate(models.Model):
'type': 'model',
'res_id': out_ids[counter].id,
'lang': lang,
- 'src': element.name,
+ 'src': element[in_field],
'value': value[element.id],
})
else:
- _logger.info('Language: %s. Translation from template: there is no translation available for %s!' % (lang, element.name))
+ _logger.info('Language: %s. Translation from template: there is no translation available for %s!' % (lang, element[in_field]))
counter += 1
return True
diff --git a/addons/payment_authorize/models/payment.py b/addons/payment_authorize/models/payment.py
index 2f5e8027f91..73c7c044e0a 100644
--- a/addons/payment_authorize/models/payment.py
+++ b/addons/payment_authorize/models/payment.py
@@ -126,8 +126,18 @@ class PaymentAcquirerAuthorize(models.Model):
for field_name in mandatory_fields:
if not data.get(field_name):
error[field_name] = 'missing'
- if data['cc_expiry'] and datetime.now().strftime('%y%m') > datetime.strptime(data['cc_expiry'], '%m / %y').strftime('%y%m'):
- return False
+ if data['cc_expiry']:
+ # FIX we split the date into their components and check if there is two components containing only digits
+ # this fixes multiples crashes, if there was no space between the '/' and the components the code was crashing
+ # the code was also crashing if the customer was proving non digits to the date.
+ cc_expiry = [i.strip() for i in data['cc_expiry'].split('/')]
+ if len(cc_expiry) != 2 or any(not i.isdigit() for i in cc_expiry):
+ return False
+ try:
+ if datetime.now().strftime('%y%m') > datetime.strptime('/'.join(cc_expiry), '%m/%y').strftime('%y%m'):
+ return False
+ except ValueError:
+ return False
return False if error else True
@api.multi
diff --git a/addons/purchase/data/mail_template_data.xml b/addons/purchase/data/mail_template_data.xml
index ecba11ce056..35445697973 100644
--- a/addons/purchase/data/mail_template_data.xml
+++ b/addons/purchase/data/mail_template_data.xml
@@ -15,7 +15,7 @@
<field name="lang">${object.partner_id.lang}</field>
<field name="body_html"><![CDATA[
<p>Dear
-% if object.partner_id.is_company and object.child_ids:
+% if object.partner_id.is_company and object.partner_id.child_ids:
${object.partner_id.child_ids[0].name}
% else :
${object.partner_id.name}
@@ -55,7 +55,7 @@ from ${object.company_id.name}.
<field name="report_name">PO_${(object.name or '').replace('/','_')}</field>
<field name="lang">${object.partner_id.lang}</field>
<field name="body_html"><![CDATA[
-<p>Dear ${object.partner_id.name}
+<p>Dear ${object.partner_id.name}
% if object.partner_id.parent_id:
(<i>${object.partner_id.parent_id.name}</i>)
% endif
diff --git a/addons/stock/data/stock_data.xml b/addons/stock/data/stock_data.xml
index 078273fb32d..35f2c008729 100644
--- a/addons/stock/data/stock_data.xml
+++ b/addons/stock/data/stock_data.xml
@@ -145,7 +145,6 @@ watch your stock valuation, and track production lots (based on serial numbers).
<record id="warehouse0" model="stock.warehouse">
- <field model="res.company" name="name" search="[]" use="name"/>
<field name="partner_id" ref="base.main_partner"/>
<field name="code">WH</field>
</record>
diff --git a/addons/stock/models/stock_inventory.py b/addons/stock/models/stock_inventory.py
index 0f1c5f15419..283dc12ee5f 100644
--- a/addons/stock/models/stock_inventory.py
+++ b/addons/stock/models/stock_inventory.py
@@ -395,7 +395,8 @@ class InventoryLine(models.Model):
if existings:
raise UserError(_("You cannot have two inventory adjustements in state 'in Progess' with the same product"
"(%s), same location(%s), same package, same owner and same lot. Please first validate"
- "the first inventory adjustement with this product before creating another one.") % (res.product_id.name, res.location_id.name))
+ "the first inventory adjustement with this product before creating another one.") %
+ (res.product_id.display_name, res.location_id.display_name))
return res
def _get_quants(self):
diff --git a/addons/stock/models/stock_warehouse.py b/addons/stock/models/stock_warehouse.py
index ad1f0ea07e5..48dcf54d24f 100644
--- a/addons/stock/models/stock_warehouse.py
+++ b/addons/stock/models/stock_warehouse.py
@@ -21,7 +21,7 @@ class Warehouse(models.Model):
# namedtuple used in helper methods generating values for routes
Routing = namedtuple('Routing', ['from_loc', 'dest_loc', 'picking_type'])
- name = fields.Char('Warehouse Name', index=True, required=True)
+ name = fields.Char('Warehouse Name', index=True, required=True, default=lambda self: self.env['res.company']._company_default_get('stock.inventory').name)
active = fields.Boolean('Active', default=True)
company_id = fields.Many2one(
'res.company', 'Company', default=lambda self: self.env['res.company']._company_default_get('stock.inventory'),
diff --git a/addons/web/static/src/js/abstract_web_client.js b/addons/web/static/src/js/abstract_web_client.js
index 1d9c270b6c3..8c82abf846c 100644
--- a/addons/web/static/src/js/abstract_web_client.js
+++ b/addons/web/static/src/js/abstract_web_client.js
@@ -120,12 +120,37 @@ var WebClient = Widget.extend({
// crash manager integration
session.on('error', crash_manager, crash_manager.rpc_error);
window.onerror = function (message, file, line, col, error) {
- var traceback = error ? error.stack : '';
- crash_manager.show_error({
- type: _t("Odoo Client Error"),
- message: message,
- data: {debug: file + ':' + line + "\n" + _t('Traceback:') + "\n" + traceback}
- });
+ // Scripts injected in DOM (eg: google API's js files) won't return a clean error on window.onerror.
+ // The browser will just give you a 'Script error.' as message and nothing else for security issue.
+ // To enable onerror to work properly with CORS file, you should:
+ // 1. add crossorigin="anonymous" to your <script> tag loading the file
+ // 2. enabling 'Access-Control-Allow-Origin' on the server serving the file.
+ // Since in some case it wont be possible to to this, this handle should have the possibility to be
+ // handled by the script manipulating the injected file. For this, you will use window.onOriginError
+ // If it is not handled, we should display something clearer than the common crash_manager error dialog
+ // since it won't show anything except "Script error."
+ // This link will probably explain it better: https://blog.sentry.io/2016/05/17/what-is-script-error.html
+ if (!file && !line && !col) {
+ // Chrome and Opera set "Script error." on the `message` and hide the `error`
+ // Firefox handles the "Script error." directly. It sets the error thrown by the CORS file into `error`
+ if (window.onOriginError) {
+ window.onOriginError();
+ delete window.onOriginError;
+ } else {
+ crash_manager.show_error({
+ type: _t("Odoo Client Error"),
+ message: _t("Unknown CORS error"),
+ data: {debug: _t("An unknown CORS error occured. The error probably originates from a JavaScript file served from a different origin. (Opening your browser console might give you a hint on the error.)")},
+ });
+ }
+ } else {
+ var traceback = error ? error.stack : '';
+ crash_manager.show_error({
+ type: _t("Odoo Client Error"),
+ message: message,
+ data: {debug: file + ':' + line + "\n" + _t('Traceback:') + "\n" + traceback},
+ });
+ }
};
},
set_action_manager: function() {
diff --git a/addons/web_tour/static/src/js/tour_manager.js b/addons/web_tour/static/src/js/tour_manager.js
index ba7adfc5443..654efc01a76 100644
--- a/addons/web_tour/static/src/js/tour_manager.js
+++ b/addons/web_tour/static/src/js/tour_manager.js
@@ -457,7 +457,7 @@ return core.Class.extend({
this._stop_running_tour_timeout();
this.running_tour_timeout = setTimeout((function() {
this._consume_tour(tour_name, _.str.sprintf("Tour %s failed at step %s", tour_name, step.trigger));
- }).bind(this), RUNNING_TOUR_TIMEOUT + this.running_step_delay);
+ }).bind(this), (step.timeout || RUNNING_TOUR_TIMEOUT) + this.running_step_delay);
},
_stop_running_tour_timeout: function () {
clearTimeout(this.running_tour_timeout);
diff --git a/addons/website/static/src/js/website.backendDashboard.js b/addons/website/static/src/js/website.backendDashboard.js
index 18307bba466..c3f2ef2ebfb 100644
--- a/addons/website/static/src/js/website.backendDashboard.js
+++ b/addons/website/static/src/js/website.backendDashboard.js
@@ -174,6 +174,10 @@ var Dashboard = Widget.extend(ControlPanelMixin, {
$analytics_components.empty();
// 1. Authorize component
var $analytics_auth = $('<div>').addClass('col-md-12');
+ window.onOriginError = function () {
+ $analytics_components.find('.js_unauthorized_message').remove();
+ self.display_unauthorized_message($analytics_components, 'not_initialized');
+ };
gapi.analytics.auth.authorize({
container: $analytics_auth[0],
clientid: client_id
@@ -182,6 +186,7 @@ var Dashboard = Widget.extend(ControlPanelMixin, {
self.handle_analytics_auth($analytics_components);
gapi.analytics.auth.on('signIn', function() {
+ delete window.onOriginError;
self.handle_analytics_auth($analytics_components);
});
diff --git a/addons/website/static/src/xml/website.backend.xml b/addons/website/static/src/xml/website.backend.xml
index ad7c4715378..d8db38684b7 100644
--- a/addons/website/static/src/xml/website.backend.xml
+++ b/addons/website/static/src/xml/website.backend.xml
@@ -38,6 +38,7 @@
<div t-name="website.unauthorized_analytics" class="col-xs-12 js_unauthorized_message mb16">
<span t-if="reason === 'not_connected'">You need to log in to your Google Account before: </span>
<span t-if="reason === 'no_right'">You do not seem to have access to this Analytics Account.</span>
+ <span t-if="reason === 'not_initialized'">Google Analytics initialization failed. Maybe this domain is not whitelisted in your Google Analytics project for this client ID.</span>
</div>
<div t-name="website.ga_dialog_content">
diff --git a/addons/website_sale/models/sale_order.py b/addons/website_sale/models/sale_order.py
index 95e1528756e..a828a612f0e 100644
--- a/addons/website_sale/models/sale_order.py
+++ b/addons/website_sale/models/sale_order.py
@@ -193,7 +193,7 @@ class Website(models.Model):
_inherit = 'website'
pricelist_id = fields.Many2one('product.pricelist', compute='_compute_pricelist_id', string='Default Pricelist')
- currency_id = fields.Many2one('res.currency', related='pricelist_id.currency_id', string='Default Currency')
+ currency_id = fields.Many2one('res.currency', related='pricelist_id.currency_id', related_sudo=False, string='Default Currency')
salesperson_id = fields.Many2one('res.users', string='Salesperson')
salesteam_id = fields.Many2one('crm.team', string='Sales Channel')
pricelist_ids = fields.One2many('product.pricelist', compute="_compute_pricelist_ids",