Use TimeDelta field
issue4026 review13821002
This commit is contained in:
1 parent
d04a2edabb
commit
749094ea8a
11 files changed
+77
-32
No files matched your search
@@ -1,3 +1,5 @@
|
||||
* Use TimeDelta field
|
||||
|
||||
Version 3.4.0 - 2014-10-20
|
||||
* Bug fixes (see mercurial logs for details)
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
# This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
# this repository contains the full copyright notices and license terms.
|
||||
from __future__ import division
|
||||
import datetime
|
||||
|
||||
from sql import Literal
|
||||
from sql.aggregate import Max, Sum
|
||||
from sql.conditionals import Coalesce
|
||||
from sql.functions import Extract
|
||||
|
||||
from trytond.model import ModelView, ModelSQL, fields
|
||||
@@ -28,7 +30,7 @@ class Line(ModelSQL, ModelView):
|
||||
],
|
||||
depends=['company'])
|
||||
date = fields.Date('Date', required=True, select=True)
|
||||
hours = fields.Float('Hours', digits=(16, 2), required=True)
|
||||
duration = fields.TimeDelta('Duration', 'company_work_time', required=True)
|
||||
work = fields.Many2One('timesheet.work', 'Work',
|
||||
required=True, select=True, domain=[
|
||||
('company', '=', Eval('company', -1)),
|
||||
@@ -49,10 +51,10 @@ class Line(ModelSQL, ModelView):
|
||||
def __setup__(cls):
|
||||
super(Line, cls).__setup__()
|
||||
cls._order.insert(0, ('date', 'DESC'))
|
||||
cls._sql_constraints += [
|
||||
('check_move_hours_pos',
|
||||
'CHECK(hours >= 0.0)', 'Hours field must be positive'),
|
||||
]
|
||||
cls._error_messages.update({
|
||||
'duration_positive': (
|
||||
'Duration of line "%(line)s" must be positive.'),
|
||||
})
|
||||
|
||||
@classmethod
|
||||
def __register__(cls, module_name):
|
||||
@@ -68,12 +70,26 @@ class Line(ModelSQL, ModelView):
|
||||
|
||||
super(Line, cls).__register__(module_name)
|
||||
|
||||
table = TableHandler(cursor, cls, module_name)
|
||||
|
||||
# Migration from 3.4: new company field
|
||||
if created_company:
|
||||
# Don't use FROM because SQLite nor MySQL support it.
|
||||
cursor.execute(*sql_table.update(
|
||||
[sql_table.company], [work.select(work.company,
|
||||
where=work.id == sql_table.work)]))
|
||||
# Migration from 3.4: change hours into timedelta duration
|
||||
if table.column_exist('hours'):
|
||||
table.drop_constraint('check_move_hours_pos')
|
||||
cursor.execute(*sql_table.select(
|
||||
sql_table.id, sql_table.hours))
|
||||
for id_, hours in cursor.fetchall():
|
||||
duration = datetime.timedelta(hours=hours)
|
||||
cursor.execute(*sql_table.update(
|
||||
[sql_table.duration],
|
||||
[duration],
|
||||
where=sql_table.id == id_))
|
||||
table.drop_column('hours')
|
||||
|
||||
@staticmethod
|
||||
def default_company():
|
||||
@@ -105,6 +121,22 @@ class Line(ModelSQL, ModelView):
|
||||
employee = Employee(Transaction().context['employee'])
|
||||
return value + " (" + employee.rec_name + ")"
|
||||
|
||||
@classmethod
|
||||
def validate(cls, lines):
|
||||
super(Line, cls).validate(lines)
|
||||
for line in lines:
|
||||
line.check_duration()
|
||||
|
||||
def check_duration(self):
|
||||
if self.duration < datetime.timedelta():
|
||||
self.raise_user_error('duration_positive', {
|
||||
'line': self.rec_name,
|
||||
})
|
||||
|
||||
@property
|
||||
def hours(self):
|
||||
return self.duration.total_seconds() / 60 / 60
|
||||
|
||||
|
||||
class EnterLinesStart(ModelView):
|
||||
'Enter Lines'
|
||||
@@ -156,7 +188,7 @@ class HoursEmployee(ModelSQL, ModelView):
|
||||
'Hours per Employee'
|
||||
__name__ = 'timesheet.hours_employee'
|
||||
employee = fields.Many2One('company.employee', 'Employee', select=True)
|
||||
hours = fields.Float('Hours', digits=(16, 2))
|
||||
duration = fields.TimeDelta('Duration', 'company_work_time')
|
||||
|
||||
@staticmethod
|
||||
def table_query():
|
||||
@@ -175,7 +207,7 @@ class HoursEmployee(ModelSQL, ModelView):
|
||||
Max(line.write_uid).as_('write_uid'),
|
||||
Max(line.write_date).as_('write_date'),
|
||||
line.employee,
|
||||
Sum(Coalesce(line.hours, 0)).as_('hours'),
|
||||
Sum(line.duration).as_('duration'),
|
||||
where=where,
|
||||
group_by=line.employee)
|
||||
|
||||
@@ -214,7 +246,7 @@ class HoursEmployeeWeekly(ModelSQL, ModelView):
|
||||
year = fields.Char('Year', select=True)
|
||||
week = fields.Integer('Week', select=True)
|
||||
employee = fields.Many2One('company.employee', 'Employee', select=True)
|
||||
hours = fields.Float('Hours', digits=(16, 2), select=True)
|
||||
duration = fields.TimeDelta('Duration', 'company_work_time')
|
||||
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
@@ -242,7 +274,7 @@ class HoursEmployeeWeekly(ModelSQL, ModelView):
|
||||
year_column,
|
||||
week_column,
|
||||
line.employee,
|
||||
Sum(Coalesce(line.hours, 0)).as_('hours'),
|
||||
Sum(line.duration).as_('duration'),
|
||||
group_by=(year_column, week_column, line.employee))
|
||||
|
||||
|
||||
@@ -252,7 +284,7 @@ class HoursEmployeeMonthly(ModelSQL, ModelView):
|
||||
year = fields.Char('Year', select=True)
|
||||
month = fields.Integer('Month', select=True)
|
||||
employee = fields.Many2One('company.employee', 'Employee', select=True)
|
||||
hours = fields.Float('Hours', digits=(16, 2), select=True)
|
||||
duration = fields.TimeDelta('Duration', 'company_work_time')
|
||||
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
@@ -280,5 +312,5 @@ class HoursEmployeeMonthly(ModelSQL, ModelView):
|
||||
year_column,
|
||||
month_column,
|
||||
line.employee,
|
||||
Sum(Coalesce(line.hours, 0)).as_('hours'),
|
||||
Sum(line.duration).as_('duration'),
|
||||
group_by=(year_column, month_column, line.employee))
|
||||
@@ -6,7 +6,6 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<field name="employee"/>
|
||||
</x>
|
||||
<y>
|
||||
<field name="hours" widget="float_time"
|
||||
float_time="company_work_time"/>
|
||||
<field name="duration" timedelta="company_work_time"/>
|
||||
</y>
|
||||
</graph>
|
||||
@@ -5,5 +5,5 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<field name="year"/>
|
||||
<field name="month"/>
|
||||
<field name="employee"/>
|
||||
<field name="hours" widget="float_time" float_time="company_work_time"/>
|
||||
<field name="duration"/>
|
||||
</tree>
|
||||
@@ -3,6 +3,5 @@
|
||||
this repository contains the full copyright notices and license terms. -->
|
||||
<tree string="Hours per Employee">
|
||||
<field name="employee"/>
|
||||
<field name="hours" widget="float_time" float_time="company_work_time"
|
||||
sum="Hours"/>
|
||||
<field name="duration" sum="Duration"/>
|
||||
</tree>
|
||||
@@ -5,5 +5,5 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<field name="year"/>
|
||||
<field name="week"/>
|
||||
<field name="employee"/>
|
||||
<field name="hours" widget="float_time" float_time="company_work_time"/>
|
||||
<field name="duration"/>
|
||||
</tree>
|
||||
+2
-2
@@ -8,8 +8,8 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<field name="company"/>
|
||||
<label name="date"/>
|
||||
<field name="date"/>
|
||||
<label name="hours"/>
|
||||
<field name="hours" widget="float_time" float_time="company_work_time"/>
|
||||
<label name="duration"/>
|
||||
<field name="duration"/>
|
||||
<label name="work"/>
|
||||
<field name="work"/>
|
||||
<label name="description"/>
|
||||
|
||||
+1
-2
@@ -5,8 +5,7 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<field name="company"/>
|
||||
<field name="employee"/>
|
||||
<field name="date"/>
|
||||
<field name="hours" widget="float_time" float_time="company_work_time"
|
||||
sum="Hours"/>
|
||||
<field name="duration" sum="Duration"/>
|
||||
<field name="work" width="300"/>
|
||||
<field name="description"/>
|
||||
</tree>
|
||||
+1
-2
@@ -6,7 +6,6 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<field name="name"/>
|
||||
</x>
|
||||
<y>
|
||||
<field name="hours" widget="float_time"
|
||||
float_time="company_work_time"/>
|
||||
<field name="duration" timedelta="company_work_time"/>
|
||||
</y>
|
||||
</graph>
|
||||
+1
-1
@@ -3,5 +3,5 @@
|
||||
this repository contains the full copyright notices and license terms. -->
|
||||
<tree string="Works">
|
||||
<field name="name"/>
|
||||
<field name="hours" widget="float_time" float_time="company_work_time"/>
|
||||
<field name="duration"/>
|
||||
</tree>
|
||||
@@ -1,5 +1,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.
|
||||
from __future__ import division
|
||||
|
||||
import datetime
|
||||
|
||||
from sql import Literal
|
||||
from sql.aggregate import Sum
|
||||
|
||||
@@ -31,8 +35,9 @@ class Work(ModelSQL, ModelView):
|
||||
('company', '=', Eval('company', -1)),
|
||||
],
|
||||
depends=['company'])
|
||||
hours = fields.Function(fields.Float('Timesheet Hours', digits=(16, 2),
|
||||
help="Total time spent on this work"), 'get_hours')
|
||||
duration = fields.Function(fields.TimeDelta('Timesheet Duration',
|
||||
'company_work_time', help="Total time spent on this work"),
|
||||
'get_duration')
|
||||
timesheet_available = fields.Boolean('Available on timesheets',
|
||||
help="Allow to fill in timesheets with this work")
|
||||
timesheet_start_date = fields.Date('Timesheet Start',
|
||||
@@ -90,7 +95,7 @@ class Work(ModelSQL, ModelView):
|
||||
cls.check_recursion(works, rec_name='name')
|
||||
|
||||
@classmethod
|
||||
def get_hours(cls, works, name):
|
||||
def get_duration(cls, works, name):
|
||||
pool = Pool()
|
||||
Line = pool.get('timesheet.line')
|
||||
transaction = Transaction()
|
||||
@@ -101,7 +106,7 @@ class Work(ModelSQL, ModelView):
|
||||
table_c = cls.__table__()
|
||||
line = Line.__table__()
|
||||
ids = [w.id for w in works]
|
||||
hours = dict.fromkeys(ids, 0)
|
||||
durations = dict.fromkeys(ids, None)
|
||||
where = Literal(True)
|
||||
if context.get('from_date'):
|
||||
where &= line.date >= context['from_date']
|
||||
@@ -115,11 +120,15 @@ class Work(ModelSQL, ModelView):
|
||||
condition=(table_c.left >= table_w.left)
|
||||
& (table_c.right <= table_w.right)
|
||||
).join(line, 'LEFT', condition=line.work == table_c.id
|
||||
).select(table_w.id, Sum(line.hours),
|
||||
).select(table_w.id, Sum(line.duration),
|
||||
where=red_sql & where,
|
||||
group_by=table_w.id))
|
||||
hours.update(dict(cursor.fetchall()))
|
||||
return hours
|
||||
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
|
||||
|
||||
def get_rec_name(self, name):
|
||||
if self.parent:
|
||||
@@ -185,6 +194,12 @@ class Work(ModelSQL, ModelView):
|
||||
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 OpenWorkStart(ModelView):
|
||||
'Open Work'
|
||||
|
||||
Reference in new issue
Block a user