summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDenis Ledoux <dle@odoo.com>2015-11-06 16:08:30 +0100
committerDenis Ledoux <dle@odoo.com>2015-11-06 16:08:30 +0100
commit8558ecfdf3fdae0c8385639f45da2df364337c55 (patch)
tree443485308d731679d7f0121319c4a3b655f5de1b
parent38868d4991bddad645cb3a0b86ad05b670a81b08 (diff)
parentdd8cbf49ac48c6cb5339e94787b0290a125b8e39 (diff)
[MERGE] forward port of branch saas-3 up to dd8cbf4
-rw-r--r--addons/hr_timesheet_sheet/hr_timesheet_sheet.py28
-rw-r--r--openerp/addons/base/tests/test_expression.py20
-rw-r--r--openerp/osv/expression.py95
3 files changed, 87 insertions, 56 deletions
diff --git a/addons/hr_timesheet_sheet/hr_timesheet_sheet.py b/addons/hr_timesheet_sheet/hr_timesheet_sheet.py
index 730fb656581..8b622dc9b34 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 569d55e30ad..ffa22cc252a 100644
--- a/openerp/addons/base/tests/test_expression.py
+++ b/openerp/addons/base/tests/test_expression.py
@@ -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
@@ -467,6 +468,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')
diff --git a/openerp/osv/expression.py b/openerp/osv/expression.py
index dbad00d6e80..417d03c4bb0 100644
--- a/openerp/osv/expression.py
+++ b/openerp/osv/expression.py
@@ -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
# --------------------------------------------------