Files
tryton/work.py
T

333 lines
12 KiB
Python
Raw Normal View History

2008-10-22 20:11:42 +02:00
#This file is part of Tryton. The COPYRIGHT file at the top level of
#this repository contains the full copyright notices and license terms.
2009-02-28 17:02:07 +01:00
from trytond.model import ModelView, ModelSQL, fields
2011-08-21 11:20:48 +02:00
from trytond.pyson import Eval
from trytond.backend import TableHandler
2010-08-19 22:16:58 +02:00
from trytond.transaction import Transaction
2011-07-03 13:19:05 +02:00
from trytond.pool import Pool
2012-09-24 22:18:06 +02:00
__all__ = ['Work']
2009-02-28 17:02:07 +01:00
class Work(ModelSQL, ModelView):
2009-05-26 13:24:03 +02:00
'Work Effort'
2012-09-24 22:18:06 +02:00
__name__ = 'project.work'
2013-03-20 22:50:59 +01:00
_rec_name = 'work'
2009-05-26 13:24:03 +02:00
work = fields.Many2One('timesheet.work', 'Work', required=True,
ondelete='CASCADE')
2013-03-20 22:50:59 +01:00
active = fields.Function(fields.Boolean('Active'),
'get_active', setter='set_active', searcher='search_active')
2009-05-26 13:24:03 +02:00
type = fields.Selection([
('project', 'Project'),
('task', 'Task')
],
'Type', required=True, select=True)
2013-03-20 22:50:59 +01:00
company = fields.Function(fields.Many2One('company.company', 'Company'),
'get_company', searcher='search_comany')
2008-10-22 20:11:42 +02:00
party = fields.Many2One('party.party', 'Party',
2011-08-21 11:20:48 +02:00
states={
'invisible': Eval('type') != 'project',
2009-05-26 13:24:03 +02:00
}, depends=['type'])
2008-10-22 20:11:42 +02:00
party_address = fields.Many2One('party.address', 'Contact Address',
2011-08-21 11:20:48 +02:00
domain=[('party', '=', Eval('party'))],
states={
'invisible': Eval('type') != 'project',
2009-05-26 13:24:03 +02:00
}, depends=['party', 'type'])
2013-03-20 22:50:59 +01:00
timesheet_available = fields.Function(
fields.Boolean('Available on timesheets'), 'get_timesheet_available')
hours = fields.Function(fields.Float('Timesheet Hours', digits=(16, 2),
states={
'invisible': ~Eval('timesheet_available'),
},
depends=['timesheet_available'],
help="Total time spent on this work"), 'get_hours')
2009-05-26 13:24:03 +02:00
effort = fields.Float("Effort",
2011-08-21 11:20:48 +02:00
states={
'invisible': Eval('type') != 'task',
2009-05-29 11:10:17 +02:00
}, depends=['type'], help="Estimated Effort for this work")
2010-03-11 16:52:21 +01:00
total_effort = fields.Function(fields.Float('Total Effort',
help="Estimated total effort for this work and the sub-works"),
'get_total_effort')
2008-06-03 17:46:57 +02:00
comment = fields.Text('Comment')
2010-03-11 16:52:21 +01:00
parent = fields.Function(fields.Many2One('project.work', 'Parent'),
'get_parent', setter='set_parent', searcher='search_parent')
2009-05-26 13:24:03 +02:00
children = fields.One2Many('project.work', 'parent', 'Children')
state = fields.Selection([
('opened', 'Opened'),
('done', 'Done'),
], 'State', required=True, select=True)
2012-05-14 15:36:45 +02:00
sequence = fields.Integer('Sequence',
order_field='(%(table)s.sequence IS NULL) %(order)s, '
'%(table)s.sequence %(order)s')
2009-05-26 13:24:03 +02:00
2012-09-24 22:18:06 +02:00
@staticmethod
def default_type():
2009-05-26 13:24:03 +02:00
return 'task'
2012-09-24 22:18:06 +02:00
@staticmethod
def default_state():
2009-05-26 13:24:03 +02:00
return 'opened'
2012-09-24 22:18:06 +02:00
@staticmethod
def default_effort():
return 0.0
2012-09-24 22:18:06 +02:00
@classmethod
def __register__(cls, module_name):
TimesheetWork = Pool().get('timesheet.work')
cursor = Transaction().cursor
2012-09-24 22:18:06 +02:00
table_project_work = TableHandler(cursor, cls, module_name)
table_timesheet_work = TableHandler(cursor, TimesheetWork, module_name)
migrate_sequence = (not table_project_work.column_exist('sequence')
and table_timesheet_work.column_exist('sequence'))
2012-09-24 22:18:06 +02:00
super(Work, cls).__register__(module_name)
# Migration from 2.0: copy sequence from timesheet to project
if migrate_sequence:
cursor.execute(
'SELECT t.sequence, t.id '
'FROM "%s" AS t '
'JOIN "%s" AS p ON (p.work = t.id)' % (
2012-09-24 22:18:06 +02:00
TimesheetWork._table, cls._table))
for sequence, id_ in cursor.fetchall():
sql = ('UPDATE "%s" '
2013-02-24 15:30:48 +01:00
'SET sequence = %%s '
'WHERE work = %%s' % cls._table)
cursor.execute(sql, (sequence, id_))
2012-05-14 15:36:45 +02:00
# Migration from 2.4: drop required on sequence
table_project_work.not_null_action('sequence', action='remove')
2012-09-24 22:18:06 +02:00
@classmethod
def __setup__(cls):
super(Work, cls).__setup__()
cls._sql_constraints += [
2013-02-24 15:30:48 +01:00
('work_uniq', 'UNIQUE(work)', 'There should be only one '
'timesheet work by task/project.'),
2012-09-24 22:18:06 +02:00
]
cls._order.insert(0, ('sequence', 'ASC'))
cls._error_messages.update({
'invalid_parent_state': ('Work "%(child)s" can not be opened '
'because its parent work "%(parent)s" is already done."'),
'invalid_children_state': ('Work "%(parent)s" can not be '
'done because its child work "%(child)s" is still '
'opened."'),
})
@classmethod
def validate(cls, works):
super(Work, cls).validate(works)
for work in works:
work.check_state()
2012-09-24 22:18:06 +02:00
def check_state(self):
if (self.state == 'opened'
and (self.parent and self.parent.state == 'done')):
self.raise_user_error('invalid_parent_state', {
'child': self.rec_name,
'parent': self.parent.rec_name,
})
if self.state == 'done':
for child in self.children:
if child.state == 'opened':
self.raise_user_error('invalid_children_state', {
'parent': self.rec_name,
'child': child.rec_name,
})
2009-05-26 13:24:03 +02:00
2013-03-20 22:50:59 +01:00
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
@classmethod
def set_active(self, 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 get_company(self, name):
return self.work.company.id
@classmethod
def search_comany(cls, name, clause):
return [('work.company',) + tuple(clause[1:])]
def get_timesheet_available(self, name):
return self.work.timesheet_available
def get_hours(self, name):
return self.work.hours
2012-09-24 22:18:06 +02:00
@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:
2012-09-24 22:18:06 +02:00
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]
2010-08-19 22:16:58 +02:00
with Transaction().set_context(active_test=False):
2012-09-24 22:18:06 +02:00
parent_projects = cls.search([
2010-08-19 22:16:58 +02:00
('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:
2012-09-24 22:18:06 +02:00
parents[child_project] = parent_project.id
2012-09-24 22:18:06 +02:00
return parents
2009-05-26 13:24:03 +02:00
2012-09-24 22:18:06 +02:00
@classmethod
def set_parent(cls, project_works, name, value):
TimesheetWork = Pool().get('timesheet.work')
2009-05-26 13:24:03 +02:00
if value:
2012-09-24 22:18:06 +02:00
project_works.append(cls(value))
child_timesheet_works = [x.work for x in project_works[:-1]]
2010-03-11 16:52:21 +01:00
parent_timesheet_work_id = project_works[-1].work.id
2009-05-26 13:24:03 +02:00
else:
2012-09-24 22:18:06 +02:00
child_timesheet_works = [x.work for x in project_works]
2012-03-27 10:43:50 +02:00
parent_timesheet_work_id = None
2009-05-26 13:24:03 +02:00
2012-09-24 22:18:06 +02:00
TimesheetWork.write(child_timesheet_works, {
2009-05-26 13:24:03 +02:00
'parent': parent_timesheet_work_id
2010-08-19 22:16:58 +02:00
})
2009-05-26 13:24:03 +02:00
2012-09-24 22:18:06 +02:00
@classmethod
def search_parent(cls, name, domain):
TimesheetWork = Pool().get('timesheet.work')
2009-05-26 13:24:03 +02:00
2009-05-29 14:45:47 +02:00
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)
2009-05-26 13:24:03 +02:00
2009-05-29 14:45:47 +02:00
# 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:
2012-04-03 20:07:27 +02:00
if (isinstance(operand, (int, long))
and not isinstance(operand, bool)):
2009-05-29 14:45:47 +02:00
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:
2012-09-24 22:18:06 +02:00
operands = cls.search([
2009-05-29 14:45:47 +02:00
('id', 'in', operands)
2010-08-19 22:16:58 +02:00
])
2009-05-29 14:45:47 +02:00
# create project_work > timesheet_work mapping
2012-09-24 22:18:06 +02:00
for pw in operands:
2009-05-29 14:45:47 +02:00
pw2tw[pw.id] = pw.work.id
2009-05-26 13:24:03 +02:00
2009-05-29 14:45:47 +02:00
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)
2009-05-26 13:24:03 +02:00
2009-05-29 14:45:47 +02:00
if project_work_domain:
2012-09-24 22:18:06 +02:00
project_works = cls.search(project_work_domain)
2009-05-29 14:45:47 +02:00
timesheet_work_domain.append(
('id', 'in', [pw.work.id for pw in project_works]))
2009-05-26 13:24:03 +02:00
2012-09-24 22:18:06 +02:00
tw_ids = [tw.id for tw in TimesheetWork.search(timesheet_work_domain)]
2009-05-26 13:24:03 +02:00
2009-05-29 14:45:47 +02:00
return [('work', 'in', tw_ids)]
2009-05-26 13:24:03 +02:00
2013-03-22 17:46:42 +01:00
@staticmethod
def sum_tree(works, getter):
leafs = set()
result = {}
for work in works:
if not work.children:
leafs.add(work)
result[work.id] = getter(work)
works = set(works)
while leafs:
for work in leafs:
works.remove(work)
if work.parent and work.parent.id in result:
result[work.parent.id] += result[work.id]
next_leafs = set(w for w in works)
for work in works:
if not work.parent:
continue
if work.parent.id in next_leafs and work.parent in works:
next_leafs.remove(work.parent)
leafs = next_leafs
return result
2012-09-24 22:18:06 +02:00
@classmethod
def get_total_effort(cls, works, name):
2009-05-26 13:24:03 +02:00
2012-10-17 16:29:21 +02:00
works = cls.search([
2012-09-24 22:18:06 +02:00
('parent', 'child_of', [w.id for w in works]),
2010-08-19 22:16:58 +02:00
('active', '=', True),
2012-10-17 16:29:21 +02:00
]) + works
2013-03-22 17:46:42 +01:00
return cls.sum_tree(works, lambda w: w.effort or 0)
2009-05-26 13:24:03 +02:00
2012-09-24 22:18:06 +02:00
@classmethod
def copy(cls, project_works, default=None):
TimesheetWork = Pool().get('timesheet.work')
2011-03-29 16:09:59 +02:00
if default is None:
default = {}
timesheet_default = default.copy()
for key in timesheet_default.keys():
2012-09-24 22:18:06 +02:00
if key in cls._fields:
del timesheet_default[key]
2012-09-24 22:18:06 +02:00
new_project_works = []
for project_work in project_works:
timesheet_work, = TimesheetWork.copy([project_work.work],
default=timesheet_default)
2011-03-29 16:09:59 +02:00
pwdefault = default.copy()
2012-09-24 22:18:06 +02:00
pwdefault['work'] = timesheet_work.id
new_project_works.extend(super(Work, cls).copy([project_work],
default=pwdefault))
return new_project_works
2009-05-26 13:24:03 +02:00
2012-09-24 22:18:06 +02:00
@classmethod
def delete(cls, project_works):
TimesheetWork = Pool().get('timesheet.work')
2009-05-26 13:24:03 +02:00
# Get the timesheet works linked to the project works
2012-09-24 22:18:06 +02:00
timesheet_works = [pw.work for pw in project_works]
2009-05-26 13:24:03 +02:00
2012-09-24 22:18:06 +02:00
super(Work, cls).delete(project_works)
2009-05-26 13:24:03 +02:00
2012-09-24 22:18:06 +02:00
TimesheetWork.delete(timesheet_works)