first commit
This commit is contained in:
180
modules/timesheet/work.py
Normal file
180
modules/timesheet/work.py
Normal file
@@ -0,0 +1,180 @@
|
||||
# This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
# this repository contains the full copyright notices and license terms.
|
||||
|
||||
import datetime
|
||||
|
||||
from sql import Literal
|
||||
from sql.aggregate import Sum
|
||||
|
||||
from trytond.i18n import gettext
|
||||
from trytond.model import ModelSQL, ModelStorage, ModelView, Unique, fields
|
||||
from trytond.pool import Pool
|
||||
from trytond.pyson import Bool, Eval, If
|
||||
from trytond.tools import grouped_slice, reduce_ids
|
||||
from trytond.transaction import Transaction
|
||||
|
||||
from .exceptions import CompanyValidationError
|
||||
|
||||
|
||||
class Work(ModelSQL, ModelView):
|
||||
__name__ = 'timesheet.work'
|
||||
name = fields.Char('Name',
|
||||
states={
|
||||
'invisible': Bool(Eval('origin')),
|
||||
'required': ~Eval('origin'),
|
||||
},
|
||||
help="The main identifier of the work.")
|
||||
origin = fields.Reference('Origin', selection='get_origin',
|
||||
states={
|
||||
'invisible': Bool(Eval('name')),
|
||||
'required': ~Eval('name'),
|
||||
},
|
||||
help="Use to relate the time spent to other records.")
|
||||
duration = fields.Function(fields.TimeDelta('Timesheet Duration',
|
||||
'company_work_time', help="Total time spent on this work."),
|
||||
'get_duration')
|
||||
timesheet_start_date = fields.Date('Timesheet Start',
|
||||
domain=[
|
||||
If(Eval('timesheet_start_date') & Eval('timesheet_end_date'),
|
||||
('timesheet_start_date', '<=', Eval('timesheet_end_date')),
|
||||
()),
|
||||
],
|
||||
help="Restrict adding lines before the date.")
|
||||
timesheet_end_date = fields.Date('Timesheet End',
|
||||
domain=[
|
||||
If(Eval('timesheet_start_date') & Eval('timesheet_end_date'),
|
||||
('timesheet_end_date', '>=', Eval('timesheet_start_date')),
|
||||
()),
|
||||
],
|
||||
help="Restrict adding lines after the date.")
|
||||
company = fields.Many2One(
|
||||
'company.company', "Company", required=True,
|
||||
help="Make the work belong to the company.")
|
||||
timesheet_lines = fields.One2Many('timesheet.line', 'work',
|
||||
'Timesheet Lines',
|
||||
help="Spend time on this work.")
|
||||
# Self referring field to use for aggregation in graph view
|
||||
work = fields.Function(fields.Many2One('timesheet.work', 'Work'),
|
||||
'get_work')
|
||||
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
super().__setup__()
|
||||
t = cls.__table__()
|
||||
cls._sql_constraints += [
|
||||
('origin_unique', Unique(t, t.origin, t.company),
|
||||
'timesheet.msg_work_origin_unique_company'),
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def default_company():
|
||||
return Transaction().context.get('company')
|
||||
|
||||
@classmethod
|
||||
def _get_origin(cls):
|
||||
'Return list of Model names for origin Reference'
|
||||
return []
|
||||
|
||||
@classmethod
|
||||
def get_origin(cls):
|
||||
Model = Pool().get('ir.model')
|
||||
get_name = Model.get_name
|
||||
models = cls._get_origin()
|
||||
return [('', '')] + [(m, get_name(m)) for m in models]
|
||||
|
||||
@classmethod
|
||||
def get_duration(cls, works, name):
|
||||
pool = Pool()
|
||||
Line = pool.get('timesheet.line')
|
||||
transaction = Transaction()
|
||||
cursor = transaction.connection.cursor()
|
||||
context = transaction.context
|
||||
|
||||
table_w = cls.__table__()
|
||||
line = Line.__table__()
|
||||
ids = [w.id for w in works]
|
||||
durations = dict.fromkeys(ids, None)
|
||||
where = Literal(True)
|
||||
if context.get('from_date'):
|
||||
where &= line.date >= context['from_date']
|
||||
if context.get('to_date'):
|
||||
where &= line.date <= context['to_date']
|
||||
if context.get('employees'):
|
||||
where &= line.employee.in_(context['employees'])
|
||||
|
||||
query_table = table_w.join(line, 'LEFT',
|
||||
condition=line.work == table_w.id)
|
||||
|
||||
for sub_ids in grouped_slice(ids):
|
||||
red_sql = reduce_ids(table_w.id, sub_ids)
|
||||
cursor.execute(*query_table.select(table_w.id, Sum(line.duration),
|
||||
where=red_sql & where,
|
||||
group_by=table_w.id))
|
||||
for work_id, duration in cursor:
|
||||
# SQLite uses float for SUM
|
||||
if duration and not isinstance(duration, datetime.timedelta):
|
||||
duration = datetime.timedelta(seconds=duration)
|
||||
durations[work_id] = duration
|
||||
return durations
|
||||
|
||||
def get_work(self, name):
|
||||
return self.id
|
||||
|
||||
def get_rec_name(self, name):
|
||||
if isinstance(self.origin, ModelStorage):
|
||||
return self.origin.rec_name
|
||||
else:
|
||||
return self.name
|
||||
|
||||
@classmethod
|
||||
def search_rec_name(cls, name, clause):
|
||||
if clause[1].startswith('!') or clause[1].startswith('not '):
|
||||
bool_op = 'AND'
|
||||
else:
|
||||
bool_op = 'OR'
|
||||
return [bool_op,
|
||||
('name',) + tuple(clause[1:]),
|
||||
] + [
|
||||
('origin.rec_name',) + tuple(clause[1:]) + (origin,)
|
||||
for origin in cls._get_origin()]
|
||||
|
||||
@classmethod
|
||||
def copy(cls, works, default=None):
|
||||
if default is None:
|
||||
default = {}
|
||||
else:
|
||||
default = default.copy()
|
||||
default.setdefault('timesheet_lines', None)
|
||||
return super().copy(works, default=default)
|
||||
|
||||
@classmethod
|
||||
def validate(cls, works):
|
||||
super().validate(works)
|
||||
for work in works:
|
||||
if work.origin and not work._validate_company():
|
||||
raise CompanyValidationError(
|
||||
gettext('timesheet.msg_work_company_different_origin',
|
||||
work=work.rec_name))
|
||||
|
||||
def _validate_company(self):
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def search_global(cls, text):
|
||||
for record, rec_name, icon in super().search_global(text):
|
||||
icon = icon or 'tryton-clock'
|
||||
yield record, rec_name, icon
|
||||
|
||||
@property
|
||||
def hours(self):
|
||||
if not self.duration:
|
||||
return 0
|
||||
return self.duration.total_seconds() / 60 / 60
|
||||
|
||||
|
||||
class WorkContext(ModelView):
|
||||
__name__ = 'timesheet.work.context'
|
||||
from_date = fields.Date('From Date',
|
||||
help="Do not take into account lines before the date.")
|
||||
to_date = fields.Date('To Date',
|
||||
help="Do not take into account lines after the date.")
|
||||
Reference in New Issue
Block a user