[MERGE] forward port branch 10.0 up to 8c55e57c7f
This commit is contained in:
@@ -361,7 +361,7 @@ class AccountInvoice(models.Model):
|
||||
return res
|
||||
|
||||
@api.model
|
||||
def fields_view_get(self, view_id=None, view_type=False, toolbar=False, submenu=False):
|
||||
def fields_view_get(self, view_id=None, view_type='form', toolbar=False, submenu=False):
|
||||
def get_view_id(xid, name):
|
||||
try:
|
||||
return self.env.ref('account.' + xid)
|
||||
|
||||
@@ -31,6 +31,7 @@ class User(models.Model):
|
||||
return self.env['hr.employee'].with_context(ctx).search([('user_id', '=', self.id)])
|
||||
|
||||
@api.multi
|
||||
@api.returns('self', lambda value: value.id)
|
||||
def message_post(self, **kwargs):
|
||||
""" Redirect the posting of message on res.users to the related employees.
|
||||
This is done because when giving the context of Chatter on the
|
||||
|
||||
@@ -88,9 +88,6 @@ class HrEmployee(models.Model):
|
||||
self.ensure_one()
|
||||
action_message = self.env.ref('hr_attendance.hr_attendance_action_greeting_message').read()[0]
|
||||
action_message['previous_attendance_change_date'] = self.last_attendance_id and (self.last_attendance_id.check_out or self.last_attendance_id.check_in) or False
|
||||
if action_message['previous_attendance_change_date']:
|
||||
action_message['previous_attendance_change_date'] = \
|
||||
fields.Datetime.to_string(fields.Datetime.context_timestamp(self, fields.Datetime.from_string(action_message['previous_attendance_change_date'])))
|
||||
action_message['employee_name'] = self.name
|
||||
action_message['next_action'] = next_action
|
||||
|
||||
|
||||
@@ -185,14 +185,17 @@ class AccountFrFec(models.TransientModel):
|
||||
unaffected_earnings_amount = float(unaffected_earnings_results[11].replace(',', '.')) - float(unaffected_earnings_results[12].replace(',', '.'))
|
||||
listrow_amount = current_amount + unaffected_earnings_amount
|
||||
if listrow_amount > 0:
|
||||
listrow[11] = str(listrow_amount)
|
||||
listrow[12] = '0.00'
|
||||
listrow[11] = str(listrow_amount).replace('.', ',')
|
||||
listrow[12] = '0,00'
|
||||
else:
|
||||
listrow[11] = '0.00'
|
||||
listrow[12] = str(listrow_amount)
|
||||
listrow[11] = '0,00'
|
||||
listrow[12] = str(-listrow_amount).replace('.', ',')
|
||||
w.writerow([s.encode("utf-8") for s in listrow])
|
||||
#if the unaffected earnings account wasn't in the selection yet: add it manually
|
||||
if not unaffected_earnings_line and unaffected_earnings_results and unaffected_earnings_results[11] != '0,00' and unaffected_earnings_results[12] != '0,00':
|
||||
if (not unaffected_earnings_line
|
||||
and unaffected_earnings_results
|
||||
and (unaffected_earnings_results[11] != '0,00'
|
||||
or unaffected_earnings_results[12] != '0,00')):
|
||||
#search an unaffected earnings account
|
||||
unaffected_earnings_account = self.env['account.account'].search([('user_type_id', '=', self.env.ref('account.data_unaffected_earnings').id)], limit=1)
|
||||
if unaffected_earnings_account:
|
||||
|
||||
@@ -77,6 +77,7 @@ class Users(models.Model):
|
||||
return self.partner_id.id
|
||||
|
||||
@api.multi
|
||||
@api.returns('self', lambda value: value.id)
|
||||
def message_post(self, **kwargs):
|
||||
""" Redirect the posting of message on res.users as a private discussion.
|
||||
This is done because when giving the context of Chatter on the
|
||||
|
||||
@@ -151,7 +151,7 @@ class Partner(models.Model):
|
||||
|
||||
@api.one
|
||||
@api.constrains('associate_member')
|
||||
def _check_recursion(self):
|
||||
def _check_recursion_associate_member(self):
|
||||
level = 100
|
||||
while self:
|
||||
self = self.associate_member
|
||||
|
||||
@@ -85,12 +85,9 @@ class ProductProduct(models.Model):
|
||||
date_from = self.env.context.get('date_from', time.strftime('%Y-01-01'))
|
||||
date_to = self.env.context.get('date_to', time.strftime('%Y-12-31'))
|
||||
invoice_state = self.env.context.get('invoice_state', 'open_paid')
|
||||
if 'date_from' in field_names:
|
||||
res[val.id]['date_from'] = date_from
|
||||
if 'date_to' in field_names:
|
||||
res[val.id]['date_to'] = date_to
|
||||
if 'invoice_state' in field_names:
|
||||
res[val.id]['invoice_state'] = invoice_state
|
||||
res[val.id]['date_from'] = date_from
|
||||
res[val.id]['date_to'] = date_to
|
||||
res[val.id]['invoice_state'] = invoice_state
|
||||
invoice_types = ()
|
||||
states = ()
|
||||
if invoice_state == 'paid':
|
||||
@@ -136,14 +133,10 @@ class ProductProduct(models.Model):
|
||||
res[val.id]['normal_cost'] = val.standard_price * res[val.id]['purchase_num_invoiced']
|
||||
res[val.id]['purchase_gap'] = res[val.id]['normal_cost'] - res[val.id]['total_cost']
|
||||
|
||||
if 'total_margin' in field_names:
|
||||
res[val.id]['total_margin'] = res[val.id]['turnover'] - res[val.id]['total_cost']
|
||||
if 'expected_margin' in field_names:
|
||||
res[val.id]['expected_margin'] = res[val.id]['sale_expected'] - res[val.id]['normal_cost']
|
||||
if 'total_margin_rate' in field_names:
|
||||
res[val.id]['total_margin_rate'] = res[val.id]['turnover'] and res[val.id]['total_margin'] * 100 / res[val.id]['turnover'] or 0.0
|
||||
if 'expected_margin_rate' in field_names:
|
||||
res[val.id]['expected_margin_rate'] = res[val.id]['sale_expected'] and res[val.id]['expected_margin'] * 100 / res[val.id]['sale_expected'] or 0.0
|
||||
res[val.id]['total_margin'] = res[val.id]['turnover'] - res[val.id]['total_cost']
|
||||
res[val.id]['expected_margin'] = res[val.id]['sale_expected'] - res[val.id]['normal_cost']
|
||||
res[val.id]['total_margin_rate'] = res[val.id]['turnover'] and res[val.id]['total_margin'] * 100 / res[val.id]['turnover'] or 0.0
|
||||
res[val.id]['expected_margin_rate'] = res[val.id]['sale_expected'] and res[val.id]['expected_margin'] * 100 / res[val.id]['sale_expected'] or 0.0
|
||||
for k, v in res[val.id].items():
|
||||
setattr(val, k, v)
|
||||
return res
|
||||
|
||||
@@ -90,11 +90,8 @@ class AccountAnalyticLine(models.Model):
|
||||
result = dict(vals or {})
|
||||
so_line = result.get('so_line', False) or self.so_line
|
||||
if not so_line and self.account_id and self.product_id and (self.product_id.expense_policy != 'no'):
|
||||
order = self.env['sale.order'].search([('project_id', '=', self.account_id.id)], limit=1)
|
||||
if order and order.state != 'sale':
|
||||
raise UserError(_('The Sales Order %s linked to the Analytic Account must be validated before registering expenses.') % order.name)
|
||||
|
||||
order = self.env['sale.order'].search([('project_id', '=', self.account_id.id), ('state', '=', 'sale')], limit=1)
|
||||
order_in_sale = self.env['sale.order'].search([('project_id', '=', self.account_id.id), ('state', '=', 'sale')], limit=1)
|
||||
order = order_in_sale or self.env['sale.order'].search([('project_id', '=', self.account_id.id)], limit=1)
|
||||
if not order:
|
||||
return result
|
||||
price = self._get_invoice_price(order)
|
||||
@@ -106,6 +103,8 @@ class AccountAnalyticLine(models.Model):
|
||||
if so_lines:
|
||||
result.update({'so_line': so_lines[0].id})
|
||||
else:
|
||||
if order.state != 'sale':
|
||||
raise UserError(_('The Sales Order %s linked to the Analytic Account must be validated before registering expenses.') % order.name)
|
||||
order_line_vals = self._get_sale_order_line_vals(order, price)
|
||||
if order_line_vals:
|
||||
so_line = self.env['sale.order.line'].create(order_line_vals)
|
||||
|
||||
@@ -196,13 +196,13 @@ class StockMove(models.Model):
|
||||
new_std_price = move.get_price_unit()
|
||||
else:
|
||||
# Get the standard price
|
||||
amount_unit = std_price_update.get(move.product_id.id) or move.product_id.standard_price
|
||||
amount_unit = std_price_update.get((move.company_id.id, move.product_id.id)) or move.product_id.standard_price
|
||||
new_std_price = ((amount_unit * product_tot_qty_available) + (move.get_price_unit() * move.product_qty)) / (product_tot_qty_available + move.product_qty)
|
||||
|
||||
tmpl_dict[move.product_id.id] += move.product_qty
|
||||
# Write the standard price, as SUPERUSER_ID because a warehouse manager may not have the right to write on products
|
||||
move.product_id.with_context(force_company=move.company_id.id).write({'standard_price': new_std_price})
|
||||
std_price_update[move.product_id.id] = new_std_price
|
||||
move.product_id.with_context(force_company=move.company_id.id).sudo().write({'standard_price': new_std_price})
|
||||
std_price_update[move.company_id.id, move.product_id.id] = new_std_price
|
||||
|
||||
@api.multi
|
||||
def product_price_update_after_done(self):
|
||||
|
||||
@@ -153,14 +153,15 @@ var QWeb2 = {
|
||||
foreach: function(context, enu, as, old_dict, callback) {
|
||||
if (enu != null) {
|
||||
var index, jlen, cur;
|
||||
var size, new_dict = this.extend({}, old_dict);
|
||||
var new_dict = this.extend({}, old_dict);
|
||||
new_dict[as + "_all"] = enu;
|
||||
var as_value = as + "_value",
|
||||
as_index = as + "_index",
|
||||
as_first = as + "_first",
|
||||
as_last = as + "_last",
|
||||
as_parity = as + "_parity";
|
||||
if (size = enu.length) {
|
||||
if (enu instanceof Array) {
|
||||
var size = enu.length;
|
||||
new_dict[as + "_size"] = size;
|
||||
for (index = 0, jlen = enu.length; index < jlen; index++) {
|
||||
cur = enu[index];
|
||||
|
||||
@@ -168,7 +168,9 @@
|
||||
|
||||
// Flex fields
|
||||
.o_form_field_many2one, .o_form_field_radio, .o_form_field_many2manytags, .o_form_field_percent_pie, .o_form_field_monetary, .o_form_field_binary_file {
|
||||
@media (min-width: @screen-sm-min) {
|
||||
.o-inline-flex-display();
|
||||
}
|
||||
> span, > button {
|
||||
.o-flex(0, 0, auto);
|
||||
}
|
||||
|
||||
@@ -191,9 +191,12 @@ class Website(Home):
|
||||
'name': "/sitemap-%d.xml" % current_website.id,
|
||||
})
|
||||
else:
|
||||
# TODO: in master/saas-15, move current_website_id in template directly
|
||||
pages_with_website = map(lambda p: "%d-%d" % (current_website.id, p), range(1, pages + 1))
|
||||
|
||||
# Sitemaps must be split in several smaller files with a sitemap index
|
||||
content = View.render_template('website.sitemap_index_xml', {
|
||||
'pages': range(1, pages + 1),
|
||||
'pages': pages_with_website,
|
||||
'url_root': request.httprequest.url_root,
|
||||
})
|
||||
create_sitemap('/sitemap-%d.xml' % current_website.id, content)
|
||||
|
||||
@@ -23,6 +23,12 @@ class QWeb(models.AbstractModel):
|
||||
'img': 'src',
|
||||
}
|
||||
|
||||
def _get_asset(self, xmlid, options, css=True, js=True, debug=False, async=False, values=None):
|
||||
website = getattr(request, 'website', None) if request else None
|
||||
if website and website.cdn_activated:
|
||||
values = dict(values, url_for=website.get_cdn_url)
|
||||
return super(QWeb, self)._get_asset(xmlid, options, css, js, debug, async, values)
|
||||
|
||||
def _website_build_attribute(self, tagName, name, value, options, values):
|
||||
""" Compute the value of an attribute while rendering the template. """
|
||||
if name == self.URL_ATTRS.get(tagName) and values.get('url_for'):
|
||||
|
||||
@@ -33,6 +33,7 @@ class Blog(models.Model):
|
||||
return res
|
||||
|
||||
@api.multi
|
||||
@api.returns('self', lambda value: value.id)
|
||||
def message_post(self, parent_id=False, subtype=None, **kwargs):
|
||||
""" Temporary workaround to avoid spam. If someone replies on a channel
|
||||
through the 'Presentation Published' email, it should be considered as a
|
||||
|
||||
@@ -799,6 +799,7 @@ class Post(models.Model):
|
||||
return groups
|
||||
|
||||
@api.multi
|
||||
@api.returns('self', lambda value: value.id)
|
||||
def message_post(self, message_type='notification', subtype=None, **kwargs):
|
||||
question_followers = self.env['res.partner']
|
||||
if self.ids and message_type == 'comment': # user comments have a restriction on karma
|
||||
|
||||
@@ -29,7 +29,7 @@ class website_account(website_account):
|
||||
invoice_count = Invoice.search_count([
|
||||
('type', 'in', ['out_invoice', 'out_refund']),
|
||||
('message_partner_ids', 'child_of', [partner.commercial_partner_id.id]),
|
||||
('state', 'in', ['open', 'paid', 'cancelled'])
|
||||
('state', 'in', ['open', 'paid', 'cancel'])
|
||||
])
|
||||
|
||||
response.qcontext.update({
|
||||
|
||||
@@ -165,6 +165,7 @@ class Channel(models.Model):
|
||||
return res
|
||||
|
||||
@api.multi
|
||||
@api.returns('self', lambda value: value.id)
|
||||
def message_post(self, parent_id=False, subtype=None, **kwargs):
|
||||
""" Temporary workaround to avoid spam. If someone replies on a channel
|
||||
through the 'Presentation Published' email, it should be considered as a
|
||||
|
||||
@@ -17,4 +17,4 @@ Kiril Vangelovski kiril@versada.eu https://github.com/kirca
|
||||
Naglis Jonaitis naglis@versada.eu https://github.com/naglis
|
||||
Paulius Sladkevičius paulius@versada.eu https://github.com/paulius-sladkevicius
|
||||
Tomas Račys tomas@versada.eu https://github.com/taezaz
|
||||
Andrius Preimantas andrius@verasda.eu https://github.com/andrius-preimantas
|
||||
Andrius Preimantas andrius@versada.eu https://github.com/andrius-preimantas
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
Spain, 2017-02-27
|
||||
|
||||
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,
|
||||
|
||||
Isaac Gallart Bochons igallart@puntsistemes.es https://github.com/igallart
|
||||
@@ -152,20 +152,20 @@ class ir_cron(models.Model):
|
||||
"""
|
||||
db = odoo.sql_db.db_connect(db_name)
|
||||
threading.current_thread().dbname = db_name
|
||||
cr = db.cursor()
|
||||
jobs = []
|
||||
try:
|
||||
# Make sure the database we poll has the same version as the code of base
|
||||
cr.execute("SELECT 1 FROM ir_module_module WHERE name=%s AND latest_version=%s", ('base', BASE_VERSION))
|
||||
if cr.fetchone():
|
||||
# Careful to compare timestamps with 'UTC' - everything is UTC as of v6.1.
|
||||
cr.execute("""SELECT * FROM ir_cron
|
||||
WHERE numbercall != 0
|
||||
AND active AND nextcall <= (now() at time zone 'UTC')
|
||||
ORDER BY priority""")
|
||||
jobs = cr.dictfetchall()
|
||||
else:
|
||||
_logger.warning('Skipping database %s as its base version is not %s.', db_name, BASE_VERSION)
|
||||
with db.cursor() as cr:
|
||||
# Make sure the database we poll has the same version as the code of base
|
||||
cr.execute("SELECT 1 FROM ir_module_module WHERE name=%s AND latest_version=%s", ('base', BASE_VERSION))
|
||||
if cr.fetchone():
|
||||
# Careful to compare timestamps with 'UTC' - everything is UTC as of v6.1.
|
||||
cr.execute("""SELECT * FROM ir_cron
|
||||
WHERE numbercall != 0
|
||||
AND active AND nextcall <= (now() at time zone 'UTC')
|
||||
ORDER BY priority""")
|
||||
jobs = cr.dictfetchall()
|
||||
else:
|
||||
_logger.warning('Skipping database %s as its base version is not %s.', db_name, BASE_VERSION)
|
||||
except psycopg2.ProgrammingError, e:
|
||||
if e.pgcode == '42P01':
|
||||
# Class 42 — Syntax Error or Access Rule Violation; 42P01: undefined_table
|
||||
@@ -175,8 +175,6 @@ class ir_cron(models.Model):
|
||||
raise
|
||||
except Exception:
|
||||
_logger.warning('Exception in cron:', exc_info=True)
|
||||
finally:
|
||||
cr.close()
|
||||
|
||||
for job in jobs:
|
||||
lock_cr = db.cursor()
|
||||
|
||||
@@ -175,8 +175,7 @@ class AssetsBundle(object):
|
||||
]
|
||||
|
||||
# force bundle invalidation on other workers
|
||||
if 'xml' not in tools.config['dev_mode']:
|
||||
self.env['ir.qweb']._get_asset.clear_cache(self.env['ir.qweb'])
|
||||
self.env['ir.qweb'].clear_caches()
|
||||
|
||||
return ira.sudo().search(domain).unlink()
|
||||
|
||||
|
||||
@@ -2866,6 +2866,11 @@ class BaseModel(object):
|
||||
if field.compute:
|
||||
cls._field_computed[field] = group = groups[field.compute]
|
||||
group.append(field)
|
||||
for fields in groups.itervalues():
|
||||
compute_sudo = fields[0].compute_sudo
|
||||
if not all(field.compute_sudo == compute_sudo for field in fields):
|
||||
_logger.warning("%s: inconsistent 'compute_sudo' for computed fields: %s",
|
||||
self._name, ", ".join(field.name for field in fields))
|
||||
|
||||
@api.model
|
||||
def _setup_complete(self):
|
||||
|
||||
@@ -217,8 +217,12 @@ class ThreadedServer(CommonServer):
|
||||
_logger.debug('cron%d polling for jobs', number)
|
||||
for db_name, registry in registries.iteritems():
|
||||
while registry.ready:
|
||||
acquired = odoo.addons.base.ir.ir_cron.ir_cron._acquire_job(db_name)
|
||||
if not acquired:
|
||||
try:
|
||||
acquired = odoo.addons.base.ir.ir_cron.ir_cron._acquire_job(db_name)
|
||||
if not acquired:
|
||||
break
|
||||
except Exception:
|
||||
_logger.warning('cron%d encountered an Exception:', number, exc_info=True)
|
||||
break
|
||||
|
||||
def cron_spawn(self):
|
||||
|
||||
Reference in New Issue
Block a user