Uses own parent, name for work

This is to simplify the tree structure of the works.

issue4938
review17431002
This commit is contained in:
Cédric Krier committed 2015-09-07 14:00:31 +02:00
1 parent f494ad599f
commit 15733cff51
7 files changed
+120 -140

No files matched your search

+2
View File
@@ -1,3 +1,5 @@
* Uses own parent, name for work
Version 3.6.0 - 2015-04-20
* Bug fixes (see mercurial logs for details)
* Add support for PyPy
+18 -1
View File
@@ -33,6 +33,8 @@ class ProjectTestCase(ModuleTestCase):
'company': company.id,
}])
p_work_1, = self.project_work.create([{
'name': 'Work 1',
'company': company.id,
'work': t_work_1.id,
'effort_duration': datetime.timedelta(hours=1),
}])
@@ -43,6 +45,9 @@ class ProjectTestCase(ModuleTestCase):
'parent': t_work_1.id,
}])
p_work_1_1, = self.project_work.create([{
'name': 'Work 1 1',
'company': company.id,
'parent': p_work_1.id,
'work': t_work_1_1.id,
'effort_duration': datetime.timedelta(hours=1),
}])
@@ -53,6 +58,9 @@ class ProjectTestCase(ModuleTestCase):
'parent': t_work_1.id,
}])
p_work_1_2, = self.project_work.create([{
'name': 'Work 1 2',
'company': company.id,
'parent': p_work_1.id,
'work': t_work_1_2.id,
'effort_duration': datetime.timedelta(hours=1),
}])
@@ -63,6 +71,9 @@ class ProjectTestCase(ModuleTestCase):
'parent': t_work_1_1.id,
}])
p_work_1_1_1, = self.project_work.create([{
'name': 'Work 1 1 1',
'company': company.id,
'parent': p_work_1_1.id,
'work': t_work_1_1_1.id,
'effort_duration': datetime.timedelta(hours=1),
}])
@@ -73,16 +84,22 @@ class ProjectTestCase(ModuleTestCase):
'parent': t_work_1_1.id,
}])
p_work_1_1_2, = self.project_work.create([{
'name': 'Work 1 1 2',
'company': company.id,
'parent': p_work_1_1.id,
'work': t_work_1_1_2.id,
'effort_duration': datetime.timedelta(hours=1),
}])
t_work_1_1_3, = self.timesheet_work.create([{
'name': 'Work 1 1 2',
'name': 'Work 1 1 3',
'company': company.id,
'parent': t_work_1_1.id,
}])
p_work_1_1_3, = self.project_work.create([{
'name': 'Work 1 1 3',
'company': company.id,
'parent': p_work_1_1.id,
'work': t_work_1_1_3.id,
'effort_duration': datetime.timedelta(hours=1),
}])
+8 -8
View File
@@ -2,24 +2,24 @@
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<form string="Works Efforts" col="6">
<label name="work"/>
<field name="work" colspan="3"/>
<label name="active"/>
<field name="active" xexpand="0"/>
<label name="type"/>
<field name="type"/>
<label name="name"/>
<field name="name" colspan="3"/>
<label name="parent"/>
<field name="parent"/>
<label name="sequence"/>
<field name="sequence"/>
<label name="type"/>
<field name="type"/>
<label name="company"/>
<field name="company"/>
<label name="sequence"/>
<field name="sequence"/>
<label name="party"/>
<field name="party"/>
<label name="party_address"/>
<field name="party_address"/>
<notebook colspan="8">
<page string="General" id="general" col="6">
<label name="work"/>
<field name="work"/>
<label name="timesheet_available"/>
<field name="timesheet_available"/>
<newline/>
+1 -2
View File
@@ -2,10 +2,9 @@
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<tree string="Works Efforts">
<field name="work"/>
<field name="name"/>
<field name="timesheet_duration"/>
<field name="total_effort"/>
<field name="type"/>
<field name="state"/>
<field name="active" tree_invisible="1"/>
</tree>
+1 -2
View File
@@ -2,9 +2,8 @@
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<tree string="Tasks">
<field name="work"/>
<field name="name"/>
<field name="timesheet_duration"/>
<field name="total_effort"/>
<field name="state"/>
<field name="active" tree_invisible="1"/>
</tree>
-1
View File
@@ -7,7 +7,6 @@ this repository contains the full copyright notices and license terms. -->
<field name="total_effort"/>
<field name="type"/>
<field name="state"/>
<field name="active" tree_invisible="1"/>
<field name="parent" tree_invisible="1"/>
<field name="children" tree_invisible="1"/>
</tree>
+90 -126
View File
@@ -17,18 +17,19 @@ __all__ = ['Work']
class Work(ModelSQL, ModelView):
'Work Effort'
__name__ = 'project.work'
_rec_name = 'work'
work = fields.Many2One('timesheet.work', 'Work', required=True,
ondelete='CASCADE')
active = fields.Function(fields.Boolean('Active'),
'get_active', setter='set_active', searcher='search_active')
name = fields.Char('Name', required=True, select=True)
work = fields.Many2One('timesheet.work', 'Work', ondelete='CASCADE',
domain=[
('company', '=', Eval('company', -1)),
],
depends=['company'])
type = fields.Selection([
('project', 'Project'),
('task', 'Task')
],
'Type', required=True, select=True)
company = fields.Function(fields.Many2One('company.company', 'Company'),
'on_change_with_company', searcher='search_comany')
company = fields.Many2One('company.company', 'Company', required=True,
select=True)
party = fields.Many2One('party.party', 'Party',
states={
'invisible': Eval('type') != 'project',
@@ -53,9 +54,19 @@ class Work(ModelSQL, ModelView):
help="Estimated total effort for this work and the sub-works"),
'get_total_effort')
comment = fields.Text('Comment')
parent = fields.Function(fields.Many2One('project.work', 'Parent'),
'get_parent', setter='set_parent', searcher='search_parent')
children = fields.One2Many('project.work', 'parent', 'Children')
parent = fields.Many2One('project.work', 'Parent',
left='left', right='right', ondelete='RESTRICT',
domain=[
('company', '=', Eval('company', -1)),
],
depends=['company'])
left = fields.Integer('Left', required=True, select=True)
right = fields.Integer('Right', required=True, select=True)
children = fields.One2Many('project.work', 'parent', 'Children',
domain=[
('company', '=', Eval('company', -1)),
],
depends=['company'])
state = fields.Selection([
('opened', 'Opened'),
('done', 'Done'),
@@ -71,6 +82,10 @@ class Work(ModelSQL, ModelView):
def default_type():
return 'task'
@classmethod
def default_company(cls):
return Transaction().context.get('company')
@staticmethod
def default_state():
return 'opened'
@@ -79,6 +94,14 @@ class Work(ModelSQL, ModelView):
def default_effort():
return 0.0
@classmethod
def default_left(cls):
return 0
@classmethod
def default_right(cls):
return 0
@classmethod
def __register__(cls, module_name):
TimesheetWork = Pool().get('timesheet.work')
@@ -91,6 +114,9 @@ class Work(ModelSQL, ModelView):
migrate_sequence = (not table_project_work.column_exist('sequence')
and table_timesheet_work.column_exist('sequence'))
add_parent = not table_project_work.column_exist('parent')
add_company = not table_project_work.column_exist('company')
add_name = not table_project_work.column_exist('name')
super(Work, cls).__register__(module_name)
@@ -120,6 +146,42 @@ class Work(ModelSQL, ModelView):
where=project.id == id_))
table_project_work.drop_column('effort')
# Migration from 3.6: add parent, company, drop required on work,
# fill name
if add_parent:
second_project = cls.__table__()
query = project.join(timesheet,
condition=project.work == timesheet.id
).join(second_project,
condition=timesheet.parent == second_project.id
).select(project.id, second_project.id)
cursor.execute(*query)
for id_, parent in cursor.fetchall():
cursor.execute(*project.update(
[project.parent],
[parent],
where=project.id == id_))
cls._rebuild_tree('parent', None, 0)
if add_company:
cursor.execute(*project.join(timesheet,
condition=project.work == timesheet.id
).select(project.id, timesheet.company))
for id_, company in cursor.fetchall():
cursor.execute(*project.update(
[project.company],
[company],
where=project.id == id_))
table_project_work.not_null_action('work', action='remove')
if add_name:
cursor.execute(*project.join(timesheet,
condition=project.work == timesheet.id
).select(project.id, timesheet.name))
for id_, name in cursor.fetchall():
cursor.execute(*project.update(
[project.name],
[name],
where=project.id == id_))
@classmethod
def __setup__(cls):
super(Work, cls).__setup__()
@@ -166,27 +228,25 @@ class Work(ModelSQL, ModelView):
return self.effort_duration.total_seconds() / 60 / 60
def get_rec_name(self, name):
return self.work.name
@staticmethod
def default_active():
return True
def get_active(self, name):
return self.work.active
if self.parent:
return self.parent.get_rec_name(name) + '\\' + self.name
else:
return self.name
@classmethod
def set_active(cls, works, name, value):
pool = Pool()
Work = pool.get('timesheet.work')
Work.write([p.work for p in works], {
'active': value,
})
@classmethod
def search_active(cls, name, clause):
return [('work.active',) + tuple(clause[1:])]
def search_rec_name(cls, name, clause):
if isinstance(clause[2], basestring):
values = clause[2].split('\\')
values.reverse()
domain = []
field = 'name'
for name in values:
domain.append((field, clause[1], name.strip()))
field = 'parent.' + field
else:
domain = [('name',) + tuple(clause[1:])]
ids = [w.id for w in cls.search(domain, order=[])]
return [('parent', 'child_of', ids)]
@fields.depends('work')
def on_change_with_company(self, name=None):
@@ -204,101 +264,6 @@ class Work(ModelSQL, ModelView):
def on_change_with_timesheet_duration(self, name=None):
return self.work.duration if self.work else None
@classmethod
def get_parent(cls, project_works, name):
parents = dict.fromkeys([w.id for w in project_works], None)
# ptw2pw is "parent timesheet work to project works":
ptw2pw = {}
for project_work in project_works:
if not project_work.work.parent:
continue
if project_work.work.parent.id in ptw2pw:
ptw2pw[project_work.work.parent.id].append(project_work.id)
else:
ptw2pw[project_work.work.parent.id] = [project_work.id]
with Transaction().set_context(active_test=False):
parent_projects = cls.search([
('work', 'in', ptw2pw.keys()),
])
for parent_project in parent_projects:
if parent_project.work.id in ptw2pw:
child_projects = ptw2pw[parent_project.work.id]
for child_project in child_projects:
parents[child_project] = parent_project.id
return parents
@classmethod
def set_parent(cls, project_works, name, value):
TimesheetWork = Pool().get('timesheet.work')
if value:
project_works.append(cls(value))
child_timesheet_works = [x.work for x in project_works[:-1]]
parent_timesheet_work_id = project_works[-1].work.id
else:
child_timesheet_works = [x.work for x in project_works]
parent_timesheet_work_id = None
TimesheetWork.write(child_timesheet_works, {
'parent': parent_timesheet_work_id
})
@classmethod
def search_parent(cls, name, domain):
TimesheetWork = Pool().get('timesheet.work')
project_work_domain = []
timesheet_work_domain = []
if domain[0].startswith('parent.'):
project_work_domain.append(
(domain[0].replace('parent.', ''),)
+ domain[1:])
elif domain[0] == 'parent':
timesheet_work_domain.append(domain)
# ids timesheet_work_domain in operand are project_work ids,
# we need to convert them to timesheet_work ids
operands = set()
for _, _, operand in timesheet_work_domain:
if (isinstance(operand, (int, long))
and not isinstance(operand, bool)):
operands.add(operand)
elif isinstance(operand, list):
for o in operand:
if isinstance(o, (int, long)) and not isinstance(o, bool):
operands.add(o)
pw2tw = {}
if operands:
operands = list(operands)
# filter out non-existing ids:
operands = cls.search([
('id', 'in', operands)
])
# create project_work > timesheet_work mapping
for pw in operands:
pw2tw[pw.id] = pw.work.id
for i, d in enumerate(timesheet_work_domain):
if isinstance(d[2], (int, long)):
new_d2 = pw2tw.get(d[2], 0)
elif isinstance(d[2], list):
new_d2 = []
for item in d[2]:
item = pw2tw.get(item, 0)
new_d2.append(item)
timesheet_work_domain[i] = (d[0], d[1], new_d2)
if project_work_domain:
project_works = cls.search(project_work_domain)
timesheet_work_domain.append(
('id', 'in', [pw.work.id for pw in project_works]))
tw_ids = [tw.id for tw in TimesheetWork.search(timesheet_work_domain)]
return [('work', 'in', tw_ids)]
@classmethod
def sum_tree(cls, works, getter):
result = {}
@@ -330,8 +295,7 @@ class Work(ModelSQL, ModelView):
def get_total_effort(cls, works, name):
works = cls.search([
('parent', 'child_of', [w.id for w in works]),
('active', '=', True),
]) + works
])
return cls.sum_tree(works,
lambda w: w.effort_duration or datetime.timedelta())