Files
tryton/work.py
T

248 lines
8.3 KiB
Python
Raw Normal View History

2014-12-02 12:56:21 +01: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.
2015-02-09 23:02:56 +01:00
from __future__ import division
import datetime
2013-09-06 22:35:00 +02:00
from sql import Literal
from sql.aggregate import Sum
2009-02-28 17:07:12 +01:00
from trytond.model import ModelView, ModelSQL, fields
2012-01-02 18:10:18 +01:00
from trytond.wizard import Wizard, StateView, StateAction, Button
from trytond.pyson import PYSONEncoder, Not, Bool, Eval
2010-08-10 11:59:04 -04:00
from trytond.transaction import Transaction
2011-07-03 13:19:21 +02:00
from trytond.pool import Pool
2014-07-17 19:02:04 +02:00
from trytond.tools import reduce_ids, grouped_slice
2011-07-03 13:19:21 +02:00
2012-09-24 22:18:28 +02:00
__all__ = ['Work', 'OpenWorkStart', 'OpenWork', 'OpenWork2', 'OpenWorkGraph']
2008-05-29 17:55:36 +02:00
2009-02-28 17:07:12 +01:00
class Work(ModelSQL, ModelView):
2008-06-09 16:54:33 +02:00
'Work'
2012-09-24 22:18:28 +02:00
__name__ = 'timesheet.work'
2008-05-29 17:55:36 +02:00
name = fields.Char('Name', required=True)
2008-06-02 17:18:11 +02:00
active = fields.Boolean('Active')
2009-06-03 16:32:58 +02:00
parent = fields.Many2One('timesheet.work', 'Parent', left="left",
2014-12-03 16:14:26 +01:00
right="right", select=True, ondelete="RESTRICT",
domain=[
('company', '=', Eval('company', -1)),
],
depends=['company'])
2012-03-05 10:53:10 +01:00
left = fields.Integer('Left', required=True, select=True)
right = fields.Integer('Right', required=True, select=True)
2014-12-03 16:14:26 +01:00
children = fields.One2Many('timesheet.work', 'parent', 'Children',
domain=[
('company', '=', Eval('company', -1)),
],
depends=['company'])
2015-02-09 23:02:56 +01:00
duration = fields.Function(fields.TimeDelta('Timesheet Duration',
'company_work_time', help="Total time spent on this work"),
'get_duration')
2009-06-03 16:33:29 +02:00
timesheet_available = fields.Boolean('Available on timesheets',
help="Allow to fill in timesheets with this work")
2013-01-26 15:13:51 +01:00
timesheet_start_date = fields.Date('Timesheet Start',
states={
'invisible': ~Eval('timesheet_available'),
},
depends=['timesheet_available'])
timesheet_end_date = fields.Date('Timesheet End',
states={
'invisible': ~Eval('timesheet_available'),
},
depends=['timesheet_available'])
2012-03-14 09:35:19 +01:00
company = fields.Many2One('company.company', 'Company', required=True,
select=True)
timesheet_lines = fields.One2Many('timesheet.line', 'work',
2011-08-04 16:57:47 +02:00
'Timesheet Lines',
depends=['timesheet_available', 'active'],
states={
'invisible': Not(Bool(Eval('timesheet_available'))),
'readonly': Not(Bool(Eval('active'))),
})
2008-06-02 17:18:11 +02:00
2012-09-24 22:18:28 +02:00
@classmethod
def __setup__(cls):
super(Work, cls).__setup__()
cls._error_messages.update({
'change_timesheet_available': ('You can not unset "Available '
'on timesheets" for work "%s" because it already has '
'timesheets.'),
2012-09-24 22:18:28 +02:00
})
2012-09-24 22:18:28 +02:00
@staticmethod
def default_active():
2008-06-02 17:18:11 +02:00
return True
2012-09-24 22:18:28 +02:00
@staticmethod
def default_left():
2012-03-30 13:05:54 +02:00
return 0
2012-09-24 22:18:28 +02:00
@staticmethod
def default_right():
2012-03-30 13:05:54 +02:00
return 0
2012-09-24 22:18:28 +02:00
@staticmethod
def default_timesheet_available():
2009-05-22 19:11:29 +02:00
return True
2008-06-02 17:18:11 +02:00
2012-09-24 22:18:28 +02:00
@staticmethod
def default_company():
2012-03-27 10:44:13 +02:00
return Transaction().context.get('company')
2008-06-09 19:17:07 +02:00
@classmethod
def validate(cls, works):
super(Work, cls).validate(works)
cls.check_recursion(works, rec_name='name')
2009-05-22 19:11:29 +02:00
2012-09-24 22:18:28 +02:00
@classmethod
2015-02-09 23:02:56 +01:00
def get_duration(cls, works, name):
2013-08-21 18:40:44 +02:00
pool = Pool()
Line = pool.get('timesheet.line')
transaction = Transaction()
cursor = transaction.cursor
context = transaction.context
2013-09-06 22:35:00 +02:00
table_w = cls.__table__()
table_c = cls.__table__()
line = Line.__table__()
2012-09-24 22:18:28 +02:00
ids = [w.id for w in works]
2015-02-09 23:02:56 +01:00
durations = dict.fromkeys(ids, None)
2013-09-06 22:35:00 +02:00
where = Literal(True)
2013-08-21 18:40:44 +02:00
if context.get('from_date'):
2013-09-06 22:35:00 +02:00
where &= line.date >= context['from_date']
2013-08-21 18:40:44 +02:00
if context.get('to_date'):
2013-09-06 22:35:00 +02:00
where &= line.date <= context['to_date']
2013-09-20 17:32:34 +02:00
if context.get('employees'):
where &= line.employee.in_(context['employees'])
2014-07-17 19:02:04 +02:00
for sub_ids in grouped_slice(ids):
2013-09-06 22:35:00 +02:00
red_sql = reduce_ids(table_w.id, sub_ids)
cursor.execute(*table_w.join(table_c,
condition=(table_c.left >= table_w.left)
& (table_c.right <= table_w.right)
).join(line, 'LEFT', condition=line.work == table_c.id
2015-02-09 23:02:56 +01:00
).select(table_w.id, Sum(line.duration),
2013-09-06 22:35:00 +02:00
where=red_sql & where,
group_by=table_w.id))
2015-02-09 23:02:56 +01:00
for work_id, duration in cursor.fetchall():
# SQLite uses float for SUM
if duration and not isinstance(duration, datetime.timedelta):
duration = datetime.timedelta(seconds=duration)
durations[work_id] = duration
return durations
2008-06-03 01:57:48 +02:00
2012-09-24 22:18:28 +02:00
def get_rec_name(self, name):
if self.parent:
return self.parent.get_rec_name(name) + '\\' + self.name
else:
return self.name
2012-04-03 20:07:54 +02:00
2013-04-06 12:29:12 +02:00
@classmethod
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)]
2012-09-24 22:18:28 +02:00
@classmethod
def copy(cls, works, default=None):
if default is None:
default = {}
default = default.copy()
if 'timesheet_lines' not in default:
2012-03-27 10:44:13 +02:00
default['timesheet_lines'] = None
2012-09-24 22:18:28 +02:00
return super(Work, cls).copy(works, default=default)
2012-09-24 22:18:28 +02:00
@classmethod
2013-12-19 14:45:42 +01:00
def write(cls, *args):
pool = Pool()
Lines = pool.get('timesheet.line')
2013-12-19 14:45:42 +01:00
actions = iter(args)
childs = []
for works, values in zip(actions, actions):
if not values.get('timesheet_available', True):
2014-07-17 19:02:04 +02:00
for sub_works in grouped_slice(works):
lines = Lines.search([
('work', 'in', [x.id for x in sub_works]),
], limit=1)
2013-12-19 14:45:42 +01:00
if lines:
cls.raise_user_error('change_timesheet_available',
lines[0].work.rec_name)
if not values.get('active', True):
childs += cls.search([
('parent', 'child_of', [w.id for w in works]),
])
super(Work, cls).write(*args)
2012-09-24 22:18:28 +02:00
if childs:
cls.write(childs, {
'active': False,
})
2008-06-03 01:57:48 +02:00
2014-02-20 23:29:33 +01:00
@classmethod
def search_global(cls, text):
2015-02-04 13:39:00 +01:00
for record, rec_name, icon in super(Work, cls).search_global(text):
2014-02-20 23:29:33 +01:00
icon = icon or 'tryton-clock'
2015-02-04 13:39:00 +01:00
yield record, rec_name, icon
2014-02-20 23:29:33 +01:00
2015-02-09 23:02:56 +01:00
@property
def hours(self):
if not self.duration:
return 0
return self.duration.total_seconds() / 60 / 60
2008-06-03 01:57:48 +02:00
2012-01-02 18:10:18 +01:00
class OpenWorkStart(ModelView):
'Open Work'
2012-09-24 22:18:28 +02:00
__name__ = 'timesheet.work.open.start'
2008-06-03 01:57:48 +02:00
from_date = fields.Date('From Date')
to_date = fields.Date('To Date')
2012-01-02 18:10:18 +01:00
2008-06-03 01:57:48 +02:00
2008-06-09 16:54:33 +02:00
class OpenWork(Wizard):
'Open Work'
2012-09-24 22:18:28 +02:00
__name__ = 'timesheet.work.open'
2012-01-02 18:10:18 +01:00
start = StateView('timesheet.work.open.start',
'timesheet.work_open_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Open', 'open_', 'tryton-ok', default=True),
])
open_ = StateAction('timesheet.act_work_hours_board')
2012-09-24 22:18:28 +02:00
def do_open_(self, action):
2012-01-02 18:10:18 +01:00
action['pyson_context'] = PYSONEncoder().encode({
2012-09-24 22:18:28 +02:00
'from_date': self.start.from_date,
'to_date': self.start.to_date,
2012-01-02 18:10:18 +01:00
})
return action, {}
2012-09-24 22:18:28 +02:00
def transition_open_(self):
2012-01-02 18:10:18 +01:00
return 'end'
2008-06-03 01:57:48 +02:00
2008-06-09 16:54:33 +02:00
class OpenWork2(OpenWork):
2012-09-24 22:18:28 +02:00
__name__ = 'timesheet.work.open2'
2012-01-02 18:10:18 +01:00
open_ = StateAction('timesheet.act_work_form2')
2008-06-09 16:54:33 +02:00
class OpenWorkGraph(Wizard):
2012-09-24 22:18:28 +02:00
__name__ = 'timesheet.work.open_graph'
2012-01-02 18:10:18 +01:00
start_state = 'open_'
open_ = StateAction('timesheet.act_work_form3')
2012-09-24 22:18:28 +02:00
def do_open_(self, action):
Work = Pool().get('timesheet.work')
2010-08-10 11:59:04 -04:00
if 'active_id' in Transaction().context:
2012-09-24 22:18:28 +02:00
work = Work(Transaction().context['active_id'])
2012-01-02 18:10:18 +01:00
action['name'] = action['name'] + ' - ' + work.rec_name
return action, {}