Refactor schedule / timesheet views for better separation of concerns

This was needed to support a "late clock-ins" view which included both
scheduled *and* worked shift data..
This commit is contained in:
Lance Edgar 2017-01-30 23:03:05 -06:00
parent bd6d2d2e11
commit 1e4612bcbe
8 changed files with 258 additions and 193 deletions

View file

@ -63,6 +63,7 @@ class TimeSheetView(View):
key = None
title = None
model_class = None
expose_employee_views = True
# Set this to False to avoid the default behavior of auto-filtering by
# current store.
@ -72,6 +73,10 @@ class TimeSheetView(View):
def get_title(cls):
return cls.title or cls.key.capitalize()
@classmethod
def get_url_prefix(cls):
return getattr(cls, 'url_prefix', cls.key).rstrip('/')
def get_timesheet_context(self):
"""
Determine date/store/dept context from user's session and/or defaults.
@ -272,7 +277,7 @@ class TimeSheetView(View):
department_options = self.get_department_options(departments)
context = {
'page_title': "Full {}".format(self.get_title()),
'page_title': self.get_title_full(),
'form': forms.FormRenderer(form) if form else None,
'employees': employees,
'stores': stores,
@ -292,6 +297,9 @@ class TimeSheetView(View):
context.update(kwargs)
return context
def get_title_full(self):
return "Full {}".format(self.get_title())
def render_shift(self, shift):
return HTML.tag('span', c=shift.get_display(self.rattail_config))
@ -330,26 +338,35 @@ class TimeSheetView(View):
return context
def modify_employees(self, employees, weekdays):
self.fetch_shift_data(self.model_class, employees, weekdays)
def fetch_shift_data(self, cls, employees, weekdays):
"""
Fetch all shift data of the given model class (``cls``), according to
the given params. The cached shift data is attached to each employee.
"""
shift_type = 'scheduled' if cls is model.ScheduledShift else 'worked'
min_time = localtime(self.rattail_config, datetime.datetime.combine(weekdays[0], datetime.time(0)))
max_time = localtime(self.rattail_config, datetime.datetime.combine(weekdays[-1] + datetime.timedelta(days=1), datetime.time(0)))
shifts = Session.query(self.model_class)\
.filter(self.model_class.employee_uuid.in_([e.uuid for e in employees]))\
.filter(self.model_class.start_time >= make_utc(min_time))\
.filter(self.model_class.start_time < make_utc(max_time))\
shifts = Session.query(cls)\
.filter(cls.employee_uuid.in_([e.uuid for e in employees]))\
.filter(cls.start_time >= make_utc(min_time))\
.filter(cls.start_time < make_utc(max_time))\
.all()
for employee in employees:
employee_shifts = sorted([s for s in shifts if s.employee_uuid == employee.uuid],
key=lambda s: (s.start_time, s.end_time))
employee.weekdays = []
employee.hours = datetime.timedelta(0)
employee.hours_display = '0'
if not hasattr(employee, 'weekdays'):
employee.weekdays = [{} for day in weekdays]
setattr(employee, '{}_hours'.format(shift_type), datetime.timedelta(0))
setattr(employee, '{}_hours_display'.format(shift_type), '0')
for day in weekdays:
for i, day in enumerate(weekdays):
empday = {
'shifts': [],
'hours': datetime.timedelta(0),
'hours_display': '',
'{}_shifts'.format(shift_type): [],
'{}_hours'.format(shift_type): datetime.timedelta(0),
'{}_hours_display'.format(shift_type): '',
}
while employee_shifts:
@ -357,23 +374,27 @@ class TimeSheetView(View):
if shift.employee_uuid != employee.uuid:
break
elif shift.get_date(self.rattail_config) == day:
empday['shifts'].append(shift)
empday['{}_shifts'.format(shift_type)].append(shift)
length = shift.length
if length is not None:
empday['hours'] += shift.length
employee.hours += shift.length
empday['{}_hours'.format(shift_type)] += shift.length
setattr(employee, '{}_hours'.format(shift_type),
getattr(employee, '{}_hours'.format(shift_type)) + shift.length)
del employee_shifts[0]
else:
break
if empday['hours']:
minutes = (empday['hours'].days * 1440) + (empday['hours'].seconds / 60)
empday['hours_display'] = '{}:{:02d}'.format(minutes // 60, minutes % 60)
employee.weekdays.append(empday)
hours = empday['{}_hours'.format(shift_type)]
if hours:
minutes = (hours.days * 1440) + (hours.seconds / 60)
empday['{}_hours_display'.format(shift_type)] = '{}:{:02d}'.format(minutes // 60, minutes % 60)
employee.weekdays[i].update(empday)
if employee.hours:
minutes = (employee.hours.days * 1440) + (employee.hours.seconds / 60)
employee.hours_display = '{}:{:02d}'.format(minutes // 60, minutes % 60)
hours = getattr(employee, '{}_hours'.format(shift_type))
if hours:
minutes = (hours.days * 1440) + (hours.seconds / 60)
setattr(employee, '{}_hours_display'.format(shift_type),
'{}:{:02d}'.format(minutes // 60, minutes % 60))
@classmethod
def defaults(cls, config):
@ -388,24 +409,26 @@ class TimeSheetView(View):
Provide default configuration for a time sheet view.
"""
title = cls.get_title()
url_prefix = cls.get_url_prefix()
config.add_tailbone_permission_group(cls.key, title)
config.add_tailbone_permission(cls.key, '{}.view'.format(cls.key), "View single employee {}".format(title))
config.add_tailbone_permission(cls.key, '{}.viewall'.format(cls.key), "View full {}".format(title))
# full time sheet
config.add_route(cls.key, '/{}/'.format(cls.key))
config.add_route(cls.key, '{}/'.format(url_prefix))
config.add_view(cls, attr='full', route_name=cls.key,
renderer='/shifts/{}.mako'.format(cls.key),
permission='{}.viewall'.format(cls.key))
# single employee time sheet
config.add_route('{}.employee'.format(cls.key), '/{}/employee/'.format(cls.key))
config.add_view(cls, attr='employee', route_name='{}.employee'.format(cls.key),
renderer='/shifts/{}.mako'.format(cls.key),
permission='{}.view'.format(cls.key))
if cls.expose_employee_views:
config.add_tailbone_permission(cls.key, '{}.view'.format(cls.key), "View single employee {}".format(title))
config.add_route('{}.employee'.format(cls.key), '{}/employee/'.format(url_prefix))
config.add_view(cls, attr='employee', route_name='{}.employee'.format(cls.key),
renderer='/shifts/{}.mako'.format(cls.key),
permission='{}.view'.format(cls.key))
# goto cross-view (view 'timesheet' as 'schedule' or vice-versa)
other_key = 'timesheet' if cls.key == 'schedule' else 'schedule'
config.add_route('{}.goto.{}'.format(cls.key, other_key), '/{}/goto-{}'.format(cls.key, other_key))
config.add_route('{}.goto.{}'.format(cls.key, other_key), '{}/goto-{}'.format(url_prefix, other_key))
config.add_view(cls, attr='crossview', route_name='{}.goto.{}'.format(cls.key, other_key),
permission='{}.view'.format(other_key))