diff --git a/addons/hr_timesheet_sheet/hr_timesheet_sheet.py b/addons/hr_timesheet_sheet/hr_timesheet_sheet.py index fd085d394eb..de27347866f 100644 --- a/addons/hr_timesheet_sheet/hr_timesheet_sheet.py +++ b/addons/hr_timesheet_sheet/hr_timesheet_sheet.py @@ -40,18 +40,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): diff --git a/openerp/addons/base/tests/test_expression.py b/openerp/addons/base/tests/test_expression.py index bbdaa697bcd..146439f0c7d 100644 --- a/openerp/addons/base/tests/test_expression.py +++ b/openerp/addons/base/tests/test_expression.py @@ -1,5 +1,6 @@ import unittest2 +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 @@ -459,6 +460,25 @@ class test_expression(common.TransactionCase): partner_parent_id_col._auto_join = False state_country_id_col._auto_join = False + 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') diff --git a/openerp/osv/expression.py b/openerp/osv/expression.py index 84989594916..b01f3ad8826 100644 --- a/openerp/osv/expression.py +++ b/openerp/osv/expression.py @@ -164,6 +164,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) @@ -260,51 +280,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 # --------------------------------------------------