[MERGE] forward port branch saas-14 up to 82effcca83

This commit is contained in:
Christophe Simonis
2018-03-19 18:26:25 +01:00
24 changed files with 121 additions and 36 deletions
+8 -1
View File
@@ -1425,7 +1425,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):
+1 -1
View File
@@ -470,7 +470,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:
@@ -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
+1 -1
View File
@@ -854,7 +854,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')
@@ -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>
+1 -1
View File
@@ -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)]"/>
+6 -1
View File
@@ -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'):
+2 -2
View File
@@ -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)
@@ -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
+12 -2
View File
@@ -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
+2 -2
View File
@@ -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
-1
View File
@@ -134,7 +134,6 @@
<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>
+2 -1
View File
@@ -396,7 +396,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):
+1 -1
View File
@@ -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'),
@@ -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() {
@@ -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);
@@ -180,6 +180,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
@@ -188,6 +192,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);
});
@@ -89,6 +89,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">
+1 -1
View File
@@ -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",
+9 -5
View File
@@ -290,29 +290,33 @@
<div class="col-xs-6 col-sm-4">
<span class="menu_title">Services</span>
<ul>
<li><a href="http://www.odoo.com/pricing">Editions</a></li>
<li><a href="http://www.odoo.com/pricing-online">Cloud Pricing</a></li>
<li><a href="http://www.odoo.com/page/upgrade">Upgrade</a></li>
<li><a href="https://www.odoo.sh">Odoo Cloud Platform</a></li>
<li class="divider"></li>
<li><a href="http://www.odoo.com/help">Support</a></li>
<li><a href="https://upgrade.odoo.com">Upgrade</a></li>
<li class="divider"></li>
<li><a href="http://www.odoo.com/partners">Find a partner</a></li>
<li><a href="http://www.odoo.com/page/become-a-partner">Become a partner</a></li>
<li class="divider"></li>
<li><a href="http://training.odoo.com/courses/odoo-functional">Training Center</a></li>
<li><a href="http://www.odoo.com/page/education-program">Education</a></li>
<li class="divider"></li>
<li><a href="http://www.odoo.com/page/security">Security</a></li>
</ul>
</div>
<div class="col-sm-4 mb64">
<div class="col-xs-12 col-sm-4 mb64">
<span class="menu_title">About us</span>
<ul>
<li><a href="http://www.odoo.com/page/about-us">Our company</a></li>
<li><a href="http://www.odoo.com/page/contactus">Contact</a></li>
<li class="divider" />
<li><a href="http://www.odoo.com/event">Events</a></li>
<li><a href="http://www.odoo.com/blog/">Blog</a></li>
<li><a href="http://www.odoo.com/blog">Blog</a></li>
<li><a href="http://www.odoo.com/blog/6">Customers</a></li>
<li class="divider" />
<li><a href="http://www.odoo.com/jobs">Jobs</a></li>
<li class="divider" />
<li><a href="http://www.odoo.com/page/legal">Legal</a> | <a href="http://www.odoo.com/privacy">Privacy</a></li>
</ul>
</div>
</div>
+15
View File
@@ -0,0 +1,15 @@
Croatia, 2017-10-31
DAJ MI 5 agrees to the terms of the Odoo Corporate Contributor License
Agreement v1.0.
I declare that I am authorized and able to make this agreement and sign this
declaration.
Signed,
Davor Bojkić bole@dajmi5.com https://github.com/dajmi5
List of contributors:
Davor Bojkić bole@dajmi5.com https://github.com/badbole
+10
View File
@@ -0,0 +1,10 @@
Ukraine, 2018-03-16
I hereby agree to the terms of the Odoo Individual Contributor License Agreement v1.0.
I declare that I am authorized and able to make this agreement and sign this declaration.
Signed,
Panarin Mykhailo Ihorevich m.panarin@mobilunity.com https://github.com/mpanarin
+3
View File
@@ -630,6 +630,9 @@ Model Reference
If set to ``False``, override :meth:`.init` to create the database
table
.. tip:: To create a model without any table, inherit
from ``odoo.models.AbstractModel``
.. attribute:: _table
+1 -1
View File
@@ -172,7 +172,7 @@
<record id="JPY" model="res.currency">
<field name="name">JPY</field>
<field name="symbol">¥</field>
<field name="rounding">0.01</field>
<field name="rounding">1.00</field>
<field name="active" eval="False"/>
</record>