Uses own parent, name for work
This is to simplify the tree structure of the works. issue4938 review17431002
This commit is contained in:
1 parent
f494ad599f
commit
15733cff51
7 files changed
+120
-140
No files matched your search
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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())
|
||||
|
||||
|
||||
Reference in new issue
Block a user