Use TimeDelta field

issue4026
review13821002
This commit is contained in:
Cédric Krier committed 2015-02-09 23:02:56 +01:00
1 parent d04a2edabb
commit 749094ea8a
11 files changed
+77 -32

No files matched your search

+2
View File
@@ -1,3 +1,5 @@
* Use TimeDelta field
Version 3.4.0 - 2014-10-20
* Bug fixes (see mercurial logs for details)
+44 -12
View File
@@ -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))
+1 -2
View File
@@ -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>
+1 -1
View File
@@ -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>
+1 -2
View File
@@ -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>
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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>
+22 -7
View File
@@ -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'