[MERGE] forward port of branch 8.0 up to c9fce1a

This commit is contained in:
Denis Ledoux
2015-11-17 13:19:14 +01:00
26 changed files with 179 additions and 85 deletions
@@ -1,4 +1,5 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_account_analytic_default,account.analytic.default,model_account_analytic_default,account.group_account_user,1,0,0,0
access_account_analytic_default_analytic,account.analytic.default analytic,model_account_analytic_default,analytic.group_analytic_accounting,1,0,0,0
access_account_analytic_default_invoice,account.analytic.default invoice,model_account_analytic_default,account.group_account_invoice,1,1,1,1
access_account_analytic_default_salesman,account.analytic.default.salesman,model_account_analytic_default,base.group_sale_salesman,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_account_analytic_default account.analytic.default model_account_analytic_default account.group_account_user 1 0 0 0
3 access_account_analytic_default_analytic account.analytic.default analytic model_account_analytic_default analytic.group_analytic_accounting 1 0 0 0
4 access_account_analytic_default_invoice account.analytic.default invoice model_account_analytic_default account.group_account_invoice 1 1 1 1
5 access_account_analytic_default_salesman account.analytic.default.salesman model_account_analytic_default base.group_sale_salesman 1 1 1 1
@@ -36,7 +36,7 @@ class report_print_check(report_sxw.rml_parse):
})
def fill_stars(self, amount):
if len(amount) < 100:
if amount and len(amount) < 100:
stars = 100 - len(amount)
return ' '.join([amount,'*'*stars])
+1 -1
View File
@@ -1290,7 +1290,7 @@ class account_voucher(osv.osv):
'move_id': move_id,
'partner_id': line.voucher_id.partner_id.id,
'currency_id': line.move_line_id.currency_id.id,
'amount_currency': -1 * foreign_currency_diff,
'amount_currency': (-1 if line.type == 'cr' else 1) * foreign_currency_diff,
'quantity': 1,
'credit': 0.0,
'debit': 0.0,
@@ -102,6 +102,7 @@ class view(osv.osv):
self.import_module(cr, uid, mod_name, path, force=force, context=context)
success.append(mod_name)
except Exception, e:
_logger.exception('Error while importing module')
errors[mod_name] = exception_to_unicode(e)
r = ["Successfully imported module '%s'" % mod for mod in success]
for mod, error in errors.items():
@@ -1,5 +1,6 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_hr_resource_manager,hr.employee.resource.manager,resource.model_resource_resource,base.group_hr_manager,1,1,1,1
access_hr_resource_calendar_user,hr.employee.resource.calendar.user,resource.model_resource_calendar,base.group_hr_user,1,1,1,1
access_hr_resource_calendar_attendance_user,hr.employee.resource.calendar.attendance.user,resource.model_resource_calendar_attendance,base.group_hr_user,1,1,1,1
access_hr_contract_type_manager,hr.contract.type.manager,model_hr_contract_type,base.group_hr_manager,1,1,1,1
access_hr_contract_manager,hr.contract.manager,model_hr_contract,base.group_hr_manager,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_hr_resource_manager hr.employee.resource.manager resource.model_resource_resource base.group_hr_manager 1 1 1 1
3 access_hr_resource_calendar_user hr.employee.resource.calendar.user resource.model_resource_calendar base.group_hr_user 1 1 1 1
4 access_hr_resource_calendar_attendance_user hr.employee.resource.calendar.attendance.user resource.model_resource_calendar_attendance base.group_hr_user 1 1 1 1
5 access_hr_contract_type_manager hr.contract.type.manager model_hr_contract_type base.group_hr_manager 1 1 1 1
6 access_hr_contract_manager hr.contract.manager model_hr_contract base.group_hr_manager 1 1 1 1
+17 -11
View File
@@ -48,18 +48,24 @@ class hr_timesheet_sheet(osv.osv):
""" Compute the attendances, analytic lines timesheets and differences between them
for all the days of a timesheet and the current day
"""
res = dict.fromkeys(ids, {
'total_attendance': 0.0,
'total_timesheet': 0.0,
'total_difference': 0.0,
})
cr.execute("""
SELECT sheet_id as id,
sum(total_attendance) as total_attendance,
sum(total_timesheet) as total_timesheet,
sum(total_difference) as total_difference
FROM hr_timesheet_sheet_sheet_day
WHERE sheet_id IN %s
GROUP BY sheet_id
""", (tuple(ids),))
res.update(dict((x.pop('id'), x) for x in cr.dictfetchall()))
res = {}
for sheet in self.browse(cr, uid, ids, context=context or {}):
res.setdefault(sheet.id, {
'total_attendance': 0.0,
'total_timesheet': 0.0,
'total_difference': 0.0,
})
for period in sheet.period_ids:
res[sheet.id]['total_attendance'] += period.total_attendance
res[sheet.id]['total_timesheet'] += period.total_timesheet
res[sheet.id]['total_difference'] += period.total_attendance - period.total_timesheet
return res
def check_employee_attendance_state(self, cr, uid, sheet_id, context=None):
+6 -6
View File
@@ -42,6 +42,8 @@ class report_custom(report_rml):
rml_obj.localcontext.update({'lang':context.get('lang',False)})
company_currency = user_pool.browse(cr, uid, uid).company_id.currency_id
company_currency_symbol = company_currency.symbol or company_currency.name
product_uom_digits = rml_obj.get_digits(dp='Product Unit of Measure')
purchase_price_digits = rml_obj.get_digits(dp='Product Price')
def process_bom(bom, currency_id, factor=1):
xml = '<row>'
sum = 0
@@ -63,7 +65,7 @@ class report_custom(report_rml):
'uom': prod.uom_po_id.id,
'date': time.strftime('%Y-%m-%d'),
})[pricelist.id]
main_sp_price = """<b>"""+rml_obj.formatLang(price)+' '+ (company_currency_symbol)+"""</b>\r\n"""
main_sp_price = """<b>"""+rml_obj.formatLang(price, digits=purchase_price_digits)+' '+ (company_currency_symbol)+"""</b>\r\n"""
sum += prod_qtty*price
std_price = product_uom_pool._compute_price(cr, uid, prod.uom_id.id, prod.standard_price, to_uom_id=product_uom.id)
main_strd_price = str(std_price) + '\r\n'
@@ -78,11 +80,11 @@ class report_custom(report_rml):
'uom': prod.uom_po_id.id,
'date': time.strftime('%Y-%m-%d'),
})[pricelist.id]
sellers_price += """<i>"""+rml_obj.formatLang(price) +' '+ (company_currency_symbol) +"""</i>\r\n"""
sellers_price += """<i>"""+rml_obj.formatLang(price, digits=purchase_price_digits) +' '+ (company_currency_symbol) +"""</i>\r\n"""
xml += """<col para='yes'> """+ prod_name +""" </col>
<col para='yes'> """+ main_sp_name + sellers + """ </col>
<col f='yes'>"""+ rml_obj.formatLang(prod_qtty) +' '+ product_uom_name +"""</col>
<col f='yes'>"""+ rml_obj.formatLang(float(main_strd_price)) +' '+ (company_currency_symbol) +"""</col>
<col f='yes'>"""+ rml_obj.formatLang(prod_qtty, digits=product_uom_digits) +' '+ product_uom_name +"""</col>
<col f='yes'>"""+ rml_obj.formatLang(float(main_strd_price), digits=purchase_price_digits) +' '+ (company_currency_symbol) +"""</col>
<col f='yes'>""" + main_sp_price + sellers_price + """</col>'"""
xml += '</row>'
@@ -140,8 +142,6 @@ class report_custom(report_rml):
</row>
""" % (_('Components'), _('Components suppliers'), _('Quantity'),_('Cost Price per Unit of Measure'), _('Supplier Price per Unit of Measure'))
purchase_price_digits = rml_obj.get_digits(dp='Product Price')
for product in product_pool.browse(cr, uid, ids, context=context):
product_uom_name = to_xml(product.uom_id.name)
bom_id = bom_pool._bom_find(cr, uid, product_id=product.id, context=context)
+2 -2
View File
@@ -41,7 +41,7 @@
% endif
</p>
<% set signup_url = object.get_signup_url() %>
% set signup_url = object.get_signup_url()
% if signup_url and object.amount_total > 0 :
<p>
You can access this document and pay online via our Customer Portal:
@@ -128,7 +128,7 @@
% endif
</p>
<% set signup_url = object.get_signup_url() %>
% set signup_url = object.get_signup_url()
% if signup_url:
<p>
You can access the invoice document and pay online via our Customer Portal:
+8
View File
@@ -424,6 +424,14 @@ class product_attribute_line(osv.osv):
'value_ids': fields.many2many('product.attribute.value', id1='line_id', id2='val_id', string='Product Attribute Value(s)'),
}
def _check_valid_attribute(self, cr, uid, ids, context=None):
obj_pal = self.browse(cr, uid, ids[0], context=context)
return obj_pal.value_ids <= obj_pal.attribute_id.value_ids
_constraints = [
(_check_valid_attribute, 'Error ! You cannot use this attribute with the following value.', ['attribute_id'])
]
#----------------------------------------------------------
# Products
+2 -1
View File
@@ -19,6 +19,7 @@
#
##############################################################################
from openerp import SUPERUSER_ID
from openerp.osv import fields, osv
from openerp.tools.translate import _
@@ -127,7 +128,7 @@ class project_task(osv.osv):
proc_obj = self.pool.get("procurement.order")
for task in self.browse(cr, uid, ids, context=context):
if task.procurement_id:
proc_obj.check(cr, uid, [task.procurement_id.id], context=context)
proc_obj.check(cr, SUPERUSER_ID, [task.procurement_id.id], context=context)
def write(self, cr, uid, ids, values, context=None):
""" When closing tasks, validate subflows. """
+1 -1
View File
@@ -488,7 +488,7 @@ class stock_picking(osv.osv):
def _get_invoice_vals(self, cr, uid, key, inv_type, journal_id, move, context=None):
inv_vals = super(stock_picking, self)._get_invoice_vals(cr, uid, key, inv_type, journal_id, move, context=context)
sale = move.picking_id.sale_id
if sale:
if sale and inv_type in ('out_invoice', 'out_refund'):
inv_vals.update({
'fiscal_position': sale.fiscal_position.id,
'payment_term': sale.payment_term.id,
+2 -1
View File
@@ -1400,7 +1400,8 @@ class stock_picking(osv.osv):
This can be used to provide a button that rereserves taking into account the existing pack operations
"""
for pick in self.browse(cr, uid, ids, context=context):
self.rereserve_quants(cr, uid, pick, move_ids = [x.id for x in pick.move_lines], context=context)
self.rereserve_quants(cr, uid, pick, move_ids = [x.id for x in pick.move_lines
if x.state not in ('done', 'cancel')], context=context)
def rereserve_quants(self, cr, uid, picking, move_ids=[], context=None):
""" Unreserve quants then try to reassign quants."""
+4 -1
View File
@@ -1047,8 +1047,11 @@ instance.web.BufferedDataSet = instance.web.DataSetStatic.extend({
return this._super(id, signal);
},
alter_ids: function(n_ids) {
var dirty = !_.isEqual(this.ids, n_ids);
this._super(n_ids);
this.trigger("dataset_changed", n_ids);
if(dirty) {
this.trigger("dataset_changed", n_ids);
}
},
});
instance.web.BufferedDataSet.virtual_id_regex = /^one2many_v_id_.*$/;
+10 -1
View File
@@ -312,7 +312,16 @@ instance.web.round_precision = function(value, precision){
var epsilon_magnitude = Math.log(Math.abs(normalized_value))/Math.log(2);
var epsilon = Math.pow(2, epsilon_magnitude - 53);
normalized_value += normalized_value >= 0 ? epsilon : -epsilon;
var rounded_value = Math.round(normalized_value);
/**
* Javascript performs strictly the round half up method, which is asymmetric. However, in
* Python, the method is symmetric. For example:
* - In JS, Math.round(-0.5) is equal to -0.
* - In Python, round(-0.5) is equal to -1.
* We want to keep the Python behavior for consistency.
*/
var sign = normalized_value < 0 ? -1.0 : 1.0;
var rounded_value = sign * Math.round(Math.abs(normalized_value));
return rounded_value * precision;
};
-4
View File
@@ -4413,10 +4413,6 @@ instance.web.form.One2ManyListView = instance.web.ListView.extend({
}));
this.on('edit:after', this, this.proxy('_after_edit'));
this.on('save:before cancel:before', this, this.proxy('_before_unedit'));
this.records
.bind('add', this.proxy("changed_records"))
.bind('remove', this.proxy("changed_records"));
this.on('save:after', this, this.proxy("changed_records"));
},
start: function () {
@@ -532,6 +532,9 @@ openerp.web_calendar = function(instance) {
throw new Error("Incomplete data received from dataset for record " + evt.id);
}
}
else if (_.contains(["date", "datetime"], self.fields[fieldname].type)) {
temp_ret[fieldname] = instance.web.format_value(value, self.fields[fieldname]);
}
else {
temp_ret[fieldname] = value;
}
+2 -1
View File
@@ -274,7 +274,8 @@ class ir_http(orm.AbstractModel):
logger.error("500 Internal Server Error:\n\n%s", values['traceback'])
if 'qweb_exception' in values:
view = request.registry.get("ir.ui.view")
views = view._views_get(request.cr, request.uid, exception.qweb['template'], request.context)
views = view._views_get(request.cr, request.uid, exception.qweb['template'],
context=request.context)
to_reset = [v for v in views if v.model_data_id.noupdate is True and not v.page]
values['views'] = to_reset
elif code == 403:
@@ -190,7 +190,7 @@
}'/>
</t>
<t t-if="not message.author_id"><t t-esc="message.email_from"/></t>
- <i class="fa fa-calendar"/> <t t-esc="message.date"/>
- <i class="fa fa-calendar"/> <span t-field="message.date"/>
</small>
<div t-raw="message.body"/>
@@ -262,7 +262,7 @@
}'/>
</t>
<t t-if="not thread.author_id"><t t-esc="thread.email_from"/></t>
- <i class="fa fa-calendar"/> <t t-esc="thread.date"/>
- <i class="fa fa-calendar"/> <span t-field="thread.date"/>
- <i class="fa fa-paperclip"/> <t t-esc="len(thread.attachment_ids)"/>
</small>
<p t-if="thread.child_ids" class="mt8">
@@ -201,7 +201,7 @@
% endif
</p>
<% set signup_url = object.get_signup_url() %>
% set signup_url = object.get_signup_url()
% if signup_url and object.amount_total > 0 :
<p>
You can access this document and pay online via our Customer Portal:
@@ -1,4 +1,12 @@
$(document).ready(function () {
function update_product_image(event_source, product_id) {
var $img = $(event_source).closest('tr.js_product, .oe_website_sale').find('span[data-oe-model^="product."][data-oe-type="image"] img:first, img.product_detail_img');
$img.attr("src", "/website/image/product.product/" + product_id + "/image");
$img.parent().attr('data-oe-model', 'product.product').attr('data-oe-id', product_id)
.data('oe-model', 'product.product').data('oe-id', product_id);
}
$('.oe_website_sale').each(function () {
var oe_website_sale = this;
@@ -157,9 +165,7 @@ $('.oe_website_sale').each(function () {
var $parent = $(this).closest('.js_product');
$parent.find(".oe_default_price:first .oe_currency_value").html( price_to_str(+$(this).data('lst_price')) );
$parent.find(".oe_price:first .oe_currency_value").html(price_to_str(+$(this).data('price')) );
var $img = $(this).closest('tr.js_product, .oe_website_sale').find('span[data-oe-model^="product."][data-oe-type="image"] img:first, img.product_detail_img');
$img.attr("src", "/website/image/product.product/" + $(this).val() + "/image");
update_product_image(this, +$(this).val());
});
$(oe_website_sale).on('change', 'input.js_variant_change, select.js_variant_change, ul[data-attribute_value_ids]', function (ev) {
@@ -195,10 +201,7 @@ $('.oe_website_sale').each(function () {
}
if (product_id) {
var $img = $(this).closest('tr.js_product, .oe_website_sale').find('span[data-oe-model^="product."][data-oe-type="image"] img:first, img.product_detail_img');
$img.attr("src", "/website/image/product.product/" + product_id + "/image");
$img.parent().attr('data-oe-model', 'product.product').attr('data-oe-id', product_id)
.data('oe-model', 'product.product').data('oe-id', product_id);
update_product_image(this, product_id);
}
$parent.find("input.js_variant_change:radio, select.js_variant_change").each(function () {
@@ -141,5 +141,14 @@
</div>
</template>
<template id="products_add_to_cart" inherit_id="website_sale.products_add_to_cart">
<xpath expr="//div[@class='product_price']/t[1]" position="attributes">
<attribute name="t-if">len(product.product_variant_ids) == 1 and not len(product.optional_product_ids) &gt;= 1</attribute>
</xpath>
<xpath expr="//div[@class='product_price']/t[2]" position="attributes">
<attribute name="t-if">len(product.product_variant_ids) &gt; 1 or len(product.optional_product_ids) &gt;= 1</attribute>
</xpath>
</template>
</data>
</openerp>
+1
View File
@@ -12,3 +12,4 @@ List of contributors:
Damián Soriano ds@adhoc.com.ar https://github.com/damiansoriano
Juan José Scarafía jjs@adhoc.com.ar https://github.com/jjscarafia
Nicolás Mac Rouillon nmr@adhoc.com.ar https://github.com/nicomacr
+19
View File
@@ -0,0 +1,19 @@
United States, 2015-09-18
Sodexis, Inc. 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,
Stephan Keller skeller@sodexis.com https://github.com/stephankeller
List of contributors:
Stephan Keller skeller@sodexis.com https://github.com/stephankeller
Suganthi Karunanithi ksuganthi@sodexis.com https://github.com/suganthikarunanithi
Xavier Dass xavier@sodexis.com https://github.com/xavier-dass
Atchuthan atchuthan@sodexis.com https://github.com/atchuthan
Dhinesh dhinesh@sodexis.com https://github.com/dvdhinesh
+4
View File
@@ -26,6 +26,7 @@ import os
import time
import datetime
import dateutil
import pytz
import openerp
from openerp import SUPERUSER_ID
@@ -97,6 +98,9 @@ class actions(osv.osv):
'time': time,
'datetime': datetime,
'dateutil': dateutil,
# NOTE: only `timezone` function. Do not provide the whole `pytz` module as users
# will have access to `pytz.os` and `pytz.sys` to do nasty things...
'timezone': pytz.timezone,
}
class ir_actions_report_xml(osv.osv):
@@ -1,6 +1,7 @@
import unittest2
import openerp
import openerp.osv.expression as expression
from openerp.osv.expression import get_unaccent_wrapper
from openerp.osv.orm import BaseModel
import openerp.tests.common as common
@@ -502,6 +503,25 @@ class test_expression(common.TransactionCase):
norm_domain = ['&', '&', '&'] + domain
assert norm_domain == expression.normalize_domain(domain), "Non-normalized domains should be properly normalized"
def test_40_negating_long_expression(self):
source = ['!','&',('user_id','=',4),('partner_id','in',[1,2])]
expect = ['|',('user_id','!=',4),('partner_id','not in',[1,2])]
self.assertEqual(expression.distribute_not(source), expect,
"distribute_not on expression applied wrongly")
pos_leaves = [[('a', 'in', [])], [('d', '!=', 3)]]
neg_leaves = [[('a', 'not in', [])], [('d', '=', 3)]]
source = expression.OR([expression.AND(pos_leaves)] * 1000)
expect = source
self.assertEqual(expression.distribute_not(source), expect,
"distribute_not on long expression without negation operator should not alter it")
source = ['!'] + source
expect = expression.AND([expression.OR(neg_leaves)] * 1000)
self.assertEqual(expression.distribute_not(source), expect,
"distribute_not on long expression applied wrongly")
def test_translate_search(self):
Country = self.registry('res.country')
be = self.ref('base.be')
+51 -44
View File
@@ -167,6 +167,26 @@ TERM_OPERATORS = ('=', '!=', '<=', '<', '>', '>=', '=?', '=like', '=ilike',
# legal in the processed term.
NEGATIVE_TERM_OPERATORS = ('!=', 'not like', 'not ilike', 'not in')
# Negation of domain expressions
DOMAIN_OPERATORS_NEGATION = {
AND_OPERATOR: OR_OPERATOR,
OR_OPERATOR: AND_OPERATOR,
}
TERM_OPERATORS_NEGATION = {
'<': '>=',
'>': '<=',
'<=': '>',
'>=': '<',
'=': '!=',
'!=': '=',
'in': 'not in',
'like': 'not like',
'ilike': 'not ilike',
'not in': 'in',
'not like': 'like',
'not ilike': 'ilike',
}
TRUE_LEAF = (1, '=', 1)
FALSE_LEAF = (0, '=', 1)
@@ -263,51 +283,36 @@ def distribute_not(domain):
['|',('user_id','!=',4),('partner_id','not in',[1,2])]
"""
def negate(leaf):
"""Negates and returns a single domain leaf term,
using the opposite operator if possible"""
left, operator, right = leaf
mapping = {
'<': '>=',
'>': '<=',
'<=': '>',
'>=': '<',
'=': '!=',
'!=': '=',
}
if operator in ('in', 'like', 'ilike'):
operator = 'not ' + operator
return [(left, operator, right)]
if operator in ('not in', 'not like', 'not ilike'):
operator = operator[4:]
return [(left, operator, right)]
if operator in mapping:
operator = mapping[operator]
return [(left, operator, right)]
return [NOT_OPERATOR, (left, operator, right)]
def distribute_negate(domain):
"""Negate the domain ``subtree`` rooted at domain[0],
leaving the rest of the domain intact, and return
(negated_subtree, untouched_domain_rest)
"""
if is_leaf(domain[0]):
return negate(domain[0]), domain[1:]
if domain[0] == AND_OPERATOR:
done1, todo1 = distribute_negate(domain[1:])
done2, todo2 = distribute_negate(todo1)
return [OR_OPERATOR] + done1 + done2, todo2
if domain[0] == OR_OPERATOR:
done1, todo1 = distribute_negate(domain[1:])
done2, todo2 = distribute_negate(todo1)
return [AND_OPERATOR] + done1 + done2, todo2
if not domain:
return []
if domain[0] != NOT_OPERATOR:
return [domain[0]] + distribute_not(domain[1:])
if domain[0] == NOT_OPERATOR:
done, todo = distribute_negate(domain[1:])
return done + distribute_not(todo)
# This is an iterative version of a recursive function that split domain
# into subdomains, processes them and combine the results. The "stack" below
# represents the recursive calls to be done.
result = []
stack = [False]
for token in domain:
negate = stack.pop()
# negate tells whether the subdomain starting with token must be negated
if is_leaf(token):
if negate:
left, operator, right = token
if operator in TERM_OPERATORS_NEGATION:
result.append((left, TERM_OPERATORS_NEGATION[operator], right))
else:
result.append(NOT_OPERATOR)
result.append(token)
else:
result.append(token)
elif token == NOT_OPERATOR:
stack.append(not negate)
elif token in DOMAIN_OPERATORS_NEGATION:
result.append(DOMAIN_OPERATORS_NEGATION[token] if negate else token)
stack.append(negate)
stack.append(negate)
else:
result.append(token)
return result
# --------------------------------------------------
@@ -724,6 +729,8 @@ class expression(object):
""" Return a domain implementing the child_of operator for [(left,child_of,ids)],
either as a range using the parent_left/right tree lookup fields
(when available), or as an expanded [(left,in,child_ids)] """
if not ids:
return FALSE_DOMAIN
if left_model._parent_store and (not left_model.pool._init):
# TODO: Improve where joins are implemented for many with '.', replace by:
# doms += ['&',(prefix+'.parent_left','<',o.parent_right),(prefix+'.parent_left','>=',o.parent_left)]