first commit
This commit is contained in:
317
modules/timesheet/line.py
Normal file
317
modules/timesheet/line.py
Normal file
@@ -0,0 +1,317 @@
|
||||
# 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 Max, Sum
|
||||
from sql.functions import Extract
|
||||
|
||||
from trytond.i18n import gettext
|
||||
from trytond.model import Index, ModelSQL, ModelView, Unique, fields
|
||||
from trytond.pool import Pool
|
||||
from trytond.pyson import Date, Eval, PYSONEncoder, TimeDelta
|
||||
from trytond.transaction import Transaction
|
||||
from trytond.wizard import Button, StateAction, StateView, Wizard
|
||||
|
||||
from .exceptions import DurationValidationError
|
||||
|
||||
|
||||
class Line(ModelSQL, ModelView):
|
||||
__name__ = 'timesheet.line'
|
||||
company = fields.Many2One('company.company', 'Company', required=True,
|
||||
help="The company on which the time is spent.")
|
||||
employee = fields.Many2One(
|
||||
'company.employee', "Employee", required=True,
|
||||
search_context={
|
||||
'active_test': False,
|
||||
},
|
||||
domain=[
|
||||
('company', '=', Eval('company', -1)),
|
||||
['OR',
|
||||
('start_date', '=', None),
|
||||
('start_date', '<=', Eval('date', None)),
|
||||
],
|
||||
['OR',
|
||||
('end_date', '=', None),
|
||||
('end_date', '>=', Eval('date', None)),
|
||||
],
|
||||
],
|
||||
help="The employee who spends the time.")
|
||||
date = fields.Date(
|
||||
"Date", required=True,
|
||||
help="When the time is spent.")
|
||||
duration = fields.TimeDelta(
|
||||
'Duration', 'company_work_time', required=True,
|
||||
domain=[
|
||||
('duration', '>=', TimeDelta()),
|
||||
])
|
||||
work = fields.Many2One(
|
||||
'timesheet.work', "Work", required=True,
|
||||
domain=[
|
||||
('company', '=', Eval('company', -1)),
|
||||
['OR',
|
||||
('timesheet_start_date', '=', None),
|
||||
('timesheet_start_date', '<=', Eval('date', None)),
|
||||
],
|
||||
['OR',
|
||||
('timesheet_end_date', '=', None),
|
||||
('timesheet_end_date', '>=', Eval('date', None)),
|
||||
],
|
||||
],
|
||||
help="The work on which the time is spent.")
|
||||
description = fields.Char('Description',
|
||||
help="Additional description of the work done.")
|
||||
uuid = fields.Char("UUID", readonly=True, strip=False)
|
||||
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
super().__setup__()
|
||||
cls._order = [
|
||||
('date', 'DESC'),
|
||||
('id', 'DESC'),
|
||||
]
|
||||
t = cls.__table__()
|
||||
cls._sql_constraints = [
|
||||
('uuid_unique', Unique(t, t.uuid),
|
||||
'timesheet.msg_line_uuid_unique'),
|
||||
]
|
||||
cls._sql_indexes.update({
|
||||
Index(
|
||||
t,
|
||||
(t.date, Index.Range()),
|
||||
(t.company, Index.Range()),
|
||||
(t.employee, Index.Range())),
|
||||
Index(
|
||||
t,
|
||||
(t.date, Index.Range()),
|
||||
(t.employee, Index.Range())),
|
||||
Index(
|
||||
t,
|
||||
(Extract('YEAR', t.date), Index.Range()),
|
||||
(Extract('WEEK', t.date), Index.Range()),
|
||||
(t.employee, Index.Range())),
|
||||
Index(
|
||||
t,
|
||||
(Extract('YEAR', t.date), Index.Range()),
|
||||
(Extract('MONTH', t.date), Index.Range()),
|
||||
(t.employee, Index.Range())),
|
||||
})
|
||||
|
||||
@staticmethod
|
||||
def default_company():
|
||||
return Transaction().context.get('company')
|
||||
|
||||
@staticmethod
|
||||
def default_employee():
|
||||
User = Pool().get('res.user')
|
||||
employee_id = None
|
||||
if Transaction().context.get('employee'):
|
||||
employee_id = Transaction().context['employee']
|
||||
else:
|
||||
user = User(Transaction().user)
|
||||
if user.employee:
|
||||
employee_id = user.employee.id
|
||||
if employee_id:
|
||||
return employee_id
|
||||
|
||||
@staticmethod
|
||||
def default_date():
|
||||
Date_ = Pool().get('ir.date')
|
||||
return Transaction().context.get('date') or Date_.today()
|
||||
|
||||
@classmethod
|
||||
def validate_fields(cls, lines, field_names):
|
||||
super().validate_fields(lines, field_names)
|
||||
cls.check_duration(lines, field_names)
|
||||
|
||||
@classmethod
|
||||
def check_duration(cls, lines, field_names=None):
|
||||
if field_names and 'duration' not in field_names:
|
||||
return
|
||||
for line in lines:
|
||||
if line.duration < datetime.timedelta():
|
||||
raise DurationValidationError(
|
||||
gettext('timesheet.msg_line_duration_positive',
|
||||
line=line.rec_name))
|
||||
|
||||
@classmethod
|
||||
def copy(cls, lines, default=None):
|
||||
if default is not None:
|
||||
default = default.copy()
|
||||
else:
|
||||
default = {}
|
||||
default.setdefault('uuid')
|
||||
return super().copy(lines, default=default)
|
||||
|
||||
@property
|
||||
def hours(self):
|
||||
return self.duration.total_seconds() / 60 / 60
|
||||
|
||||
def to_json(self):
|
||||
return {
|
||||
'id': self.id,
|
||||
'work': self.work.id,
|
||||
'work.name': self.work.rec_name,
|
||||
'duration': self.duration.total_seconds(),
|
||||
'description': self.description,
|
||||
'uuid': self.uuid,
|
||||
}
|
||||
|
||||
|
||||
class EnterLinesStart(ModelView):
|
||||
__name__ = 'timesheet.line.enter.start'
|
||||
employee = fields.Many2One('company.employee', 'Employee', required=True,
|
||||
domain=[
|
||||
('company', '=', Eval('context', {}).get('company', -1)),
|
||||
['OR',
|
||||
('start_date', '=', None),
|
||||
('start_date', '<=', Eval('date', None)),
|
||||
],
|
||||
['OR',
|
||||
('end_date', '=', None),
|
||||
('end_date', '>=', Eval('date', None)),
|
||||
],
|
||||
],
|
||||
help="The employee who spends the time.")
|
||||
date = fields.Date('Date', required=True,
|
||||
help="When the time is spent.")
|
||||
|
||||
@staticmethod
|
||||
def default_employee():
|
||||
Line = Pool().get('timesheet.line')
|
||||
return Line.default_employee()
|
||||
|
||||
@staticmethod
|
||||
def default_date():
|
||||
Line = Pool().get('timesheet.line')
|
||||
return Line.default_date()
|
||||
|
||||
|
||||
class EnterLines(Wizard):
|
||||
__name__ = 'timesheet.line.enter'
|
||||
start = StateView('timesheet.line.enter.start',
|
||||
'timesheet.line_enter_start_view_form', [
|
||||
Button('Cancel', 'end', 'tryton-cancel'),
|
||||
Button('Enter', 'enter', 'tryton-ok', default=True),
|
||||
])
|
||||
enter = StateAction('timesheet.act_line_form')
|
||||
|
||||
def do_enter(self, action):
|
||||
pool = Pool()
|
||||
Lang = pool.get('ir.lang')
|
||||
date = self.start.date
|
||||
date = Date(date.year, date.month, date.day)
|
||||
action['pyson_domain'] = PYSONEncoder().encode([
|
||||
('employee', '=', self.start.employee.id),
|
||||
('company', '=', self.start.employee.company.id),
|
||||
('date', '=', date),
|
||||
])
|
||||
action['pyson_context'] = PYSONEncoder().encode({
|
||||
'employee': self.start.employee.id,
|
||||
'company': self.start.employee.company.id,
|
||||
'date': date,
|
||||
})
|
||||
action['name'] += ' @ %(date)s - %(employee)s' % {
|
||||
'date': Lang.get().strftime(self.start.date),
|
||||
'employee': self.start.employee.rec_name,
|
||||
}
|
||||
return action, {}
|
||||
|
||||
def transition_enter(self):
|
||||
return 'end'
|
||||
|
||||
|
||||
class HoursEmployee(ModelSQL, ModelView):
|
||||
__name__ = 'timesheet.hours_employee'
|
||||
employee = fields.Many2One('company.employee', 'Employee')
|
||||
duration = fields.TimeDelta('Duration', 'company_work_time')
|
||||
|
||||
@staticmethod
|
||||
def table_query():
|
||||
pool = Pool()
|
||||
Line = pool.get('timesheet.line')
|
||||
line = Line.__table__()
|
||||
where = Literal(True)
|
||||
if Transaction().context.get('start_date'):
|
||||
where &= line.date >= Transaction().context['start_date']
|
||||
if Transaction().context.get('end_date'):
|
||||
where &= line.date <= Transaction().context['end_date']
|
||||
return line.select(
|
||||
line.employee.as_('id'),
|
||||
line.employee,
|
||||
Sum(line.duration).as_('duration'),
|
||||
where=where,
|
||||
group_by=line.employee)
|
||||
|
||||
|
||||
class HoursEmployeeContext(ModelView):
|
||||
__name__ = 'timesheet.hours_employee.context'
|
||||
start_date = fields.Date('Start Date')
|
||||
end_date = fields.Date('End Date')
|
||||
|
||||
|
||||
class HoursEmployeeWeekly(ModelSQL, ModelView):
|
||||
__name__ = 'timesheet.hours_employee_weekly'
|
||||
year = fields.Integer("Year")
|
||||
week = fields.Integer("Week")
|
||||
employee = fields.Many2One('company.employee', 'Employee')
|
||||
duration = fields.TimeDelta('Duration', 'company_work_time')
|
||||
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
super().__setup__()
|
||||
cls._order.insert(0, ('year', 'DESC'))
|
||||
cls._order.insert(1, ('week', 'DESC'))
|
||||
cls._order.insert(2, ('employee', 'ASC'))
|
||||
|
||||
@classmethod
|
||||
def table_query(cls):
|
||||
pool = Pool()
|
||||
Line = pool.get('timesheet.line')
|
||||
line = Line.__table__()
|
||||
year_column = Extract('YEAR', line.date).as_('year')
|
||||
week_column = Extract('WEEK', line.date).as_('week')
|
||||
return line.select(
|
||||
Max(Extract('WEEK', line.date)
|
||||
+ Extract('YEAR', line.date) * 100
|
||||
+ line.employee * 1000000).as_('id'),
|
||||
year_column,
|
||||
week_column,
|
||||
line.employee,
|
||||
Sum(line.duration).as_('duration'),
|
||||
group_by=(year_column, week_column, line.employee))
|
||||
|
||||
|
||||
class HoursEmployeeMonthly(ModelSQL, ModelView):
|
||||
__name__ = 'timesheet.hours_employee_monthly'
|
||||
year = fields.Integer("Year")
|
||||
month = fields.Many2One('ir.calendar.month', "Month")
|
||||
employee = fields.Many2One('company.employee', 'Employee')
|
||||
duration = fields.TimeDelta('Duration', 'company_work_time')
|
||||
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
super().__setup__()
|
||||
cls._order.insert(0, ('year', 'DESC'))
|
||||
cls._order.insert(1, ('month.index', 'DESC'))
|
||||
cls._order.insert(2, ('employee', 'ASC'))
|
||||
|
||||
@classmethod
|
||||
def table_query(cls):
|
||||
pool = Pool()
|
||||
Line = pool.get('timesheet.line')
|
||||
Month = pool.get('ir.calendar.month')
|
||||
line = Line.__table__()
|
||||
month = Month.__table__()
|
||||
year_column = Extract('YEAR', line.date).as_('year')
|
||||
month_index = Extract('MONTH', line.date)
|
||||
return line.join(month, condition=month_index == month.id).select(
|
||||
Max(Extract('MONTH', line.date)
|
||||
+ Extract('YEAR', line.date) * 100
|
||||
+ line.employee * 1000000).as_('id'),
|
||||
year_column,
|
||||
month.id.as_('month'),
|
||||
line.employee,
|
||||
Sum(line.duration).as_('duration'),
|
||||
group_by=(year_column, month.id, line.employee))
|
||||
Reference in New Issue
Block a user