[MERGE] forward port branch saas-14 up to 82effcca83
This commit is contained in:
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)]"/>
|
||||
|
||||
@@ -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'):
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user