Add support for viewing single employee's schedule / time sheet
A little sloppy perhaps, here and there..but seems to do the job.
This commit is contained in:
parent
c6ab3b80f9
commit
1e0ef53aea
|
@ -1,11 +1,18 @@
|
||||||
## -*- coding: utf-8 -*-
|
## -*- coding: utf-8 -*-
|
||||||
<%inherit file="/base.mako" />
|
<%inherit file="/base.mako" />
|
||||||
|
<%namespace file="/autocomplete.mako" import="autocomplete" />
|
||||||
|
|
||||||
|
<%def name="title()">${page_title}</%def>
|
||||||
|
|
||||||
<%def name="head_tags()">
|
<%def name="head_tags()">
|
||||||
${parent.head_tags()}
|
${parent.head_tags()}
|
||||||
${h.stylesheet_link(request.static_url('tailbone:static/css/timesheet.css'))}
|
${h.stylesheet_link(request.static_url('tailbone:static/css/timesheet.css'))}
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
|
|
||||||
|
function employee_selected(uuid, name) {
|
||||||
|
$('.timesheet-wrapper form').submit();
|
||||||
|
}
|
||||||
|
|
||||||
$(function() {
|
$(function() {
|
||||||
|
|
||||||
$('.timesheet-wrapper form').submit(function() {
|
$('.timesheet-wrapper form').submit(function() {
|
||||||
|
@ -43,10 +50,10 @@
|
||||||
|
|
||||||
<%def name="context_menu()"></%def>
|
<%def name="context_menu()"></%def>
|
||||||
|
|
||||||
<%def name="timesheet(employee_column=True)">
|
<%def name="timesheet()">
|
||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
.timesheet thead th {
|
.timesheet thead th {
|
||||||
width: ${'{:0.2f}'.format(100.0 / float(9 if employee_column else 8))}%;
|
width: ${'{:0.2f}'.format(100.0 / 9)}%;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
@ -60,16 +67,30 @@
|
||||||
|
|
||||||
<td class="filters" rowspan="2">
|
<td class="filters" rowspan="2">
|
||||||
|
|
||||||
## <div class="field-wrapper employee">
|
% if employee is not UNDEFINED:
|
||||||
## <label>Employee</label>
|
<div class="field-wrapper employee">
|
||||||
## <div class="field">
|
<label>Employee</label>
|
||||||
## ${employee}
|
<div class="field">
|
||||||
## </div>
|
% if request.has_perm('{}.viewall'.format(permission_prefix)):
|
||||||
## </div>
|
${autocomplete('employee', url('employees.autocomplete'),
|
||||||
|
field_value=employee.uuid if employee else None,
|
||||||
|
field_display=unicode(employee or ''),
|
||||||
|
selected='employee_selected')}
|
||||||
|
% else:
|
||||||
|
${form.hidden('employee', value=employee.uuid)}
|
||||||
|
${employee}
|
||||||
|
% endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
% endif
|
||||||
|
|
||||||
${form.field_div('store', h.select('store', store.uuid if store else None, store_options))}
|
% if store_options is not UNDEFINED:
|
||||||
|
${form.field_div('store', h.select('store', store.uuid if store else None, store_options))}
|
||||||
|
% endif
|
||||||
|
|
||||||
${form.field_div('department', h.select('department', department.uuid if department else None, department_options))}
|
% if department_options is not UNDEFINED:
|
||||||
|
${form.field_div('department', h.select('department', department.uuid if department else None, department_options))}
|
||||||
|
% endif
|
||||||
|
|
||||||
<div class="field-wrapper week">
|
<div class="field-wrapper week">
|
||||||
<label>Week of</label>
|
<label>Week of</label>
|
||||||
|
@ -108,9 +129,7 @@
|
||||||
<table class="timesheet">
|
<table class="timesheet">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
% if employee_column:
|
<th>Employee</th>
|
||||||
<th>Employee</th>
|
|
||||||
% endif
|
|
||||||
% for day in weekdays:
|
% for day in weekdays:
|
||||||
<th>${day.strftime('%A')}<br />${day.strftime('%b %d')}</th>
|
<th>${day.strftime('%A')}<br />${day.strftime('%b %d')}</th>
|
||||||
% endfor
|
% endfor
|
||||||
|
@ -118,22 +137,20 @@
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
% for employee in sorted(employees, key=unicode):
|
% for emp in sorted(employees, key=unicode):
|
||||||
<tr>
|
<tr>
|
||||||
% if employee_column:
|
<td class="employee">${emp}</td>
|
||||||
<td class="employee">${employee}</td>
|
% for day in emp.weekdays:
|
||||||
% endif
|
|
||||||
% for day in employee.weekdays:
|
|
||||||
<td>
|
<td>
|
||||||
% for shift in day['shifts']:
|
% for shift in day['shifts']:
|
||||||
<p class="shift">${shift.get_display(request.rattail_config)}</p>
|
<p class="shift">${shift.get_display(request.rattail_config)}</p>
|
||||||
% endfor
|
% endfor
|
||||||
</td>
|
</td>
|
||||||
% endfor
|
% endfor
|
||||||
<td>${employee.hours_display}</td>
|
<td>${emp.hours_display}</td>
|
||||||
</tr>
|
</tr>
|
||||||
% endfor
|
% endfor
|
||||||
% if employee_column:
|
% if employee is UNDEFINED:
|
||||||
<tr class="total">
|
<tr class="total">
|
||||||
<td class="employee">${len(employees)} employees</td>
|
<td class="employee">${len(employees)} employees</td>
|
||||||
% for day in weekdays:
|
% for day in weekdays:
|
||||||
|
@ -143,6 +160,7 @@
|
||||||
</tr>
|
</tr>
|
||||||
% else:
|
% else:
|
||||||
<tr>
|
<tr>
|
||||||
|
<td> </td>
|
||||||
% for day in employee.weekdays:
|
% for day in employee.weekdays:
|
||||||
<td>${day['hours_display']}</td>
|
<td>${day['hours_display']}</td>
|
||||||
% endfor
|
% endfor
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
## -*- coding: utf-8 -*-
|
## -*- coding: utf-8 -*-
|
||||||
<%inherit file="/shifts/base.mako" />
|
<%inherit file="/shifts/base.mako" />
|
||||||
|
|
||||||
<%def name="title()">Full Schedule</%def>
|
|
||||||
|
|
||||||
<%def name="context_menu()">
|
<%def name="context_menu()">
|
||||||
% if request.has_perm('timesheet.view'):
|
% if request.has_perm('timesheet.view'):
|
||||||
<li>${h.link_to("View this Time Sheet", url('schedule.goto.timesheet'), class_='goto')}</li>
|
<li>${h.link_to("View this Time Sheet", url('schedule.goto.timesheet'), class_='goto')}</li>
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
## -*- coding: utf-8 -*-
|
## -*- coding: utf-8 -*-
|
||||||
<%inherit file="/shifts/base.mako" />
|
<%inherit file="/shifts/base.mako" />
|
||||||
|
|
||||||
<%def name="title()">Full Time Sheet</%def>
|
|
||||||
|
|
||||||
<%def name="context_menu()">
|
<%def name="context_menu()">
|
||||||
% if request.has_perm('schedule.view'):
|
% if request.has_perm('schedule.view'):
|
||||||
<li>${h.link_to("View this Schedule", url('timesheet.goto.schedule'), class_='goto')}</li>
|
<li>${h.link_to("View this Schedule", url('timesheet.goto.schedule'), class_='goto')}</li>
|
||||||
|
|
|
@ -48,6 +48,13 @@ class ShiftFilter(fe.Schema):
|
||||||
date = fe.validators.DateConverter()
|
date = fe.validators.DateConverter()
|
||||||
|
|
||||||
|
|
||||||
|
class EmployeeShiftFilter(fe.Schema):
|
||||||
|
allow_extra_fields = True
|
||||||
|
filter_extra_fields = True
|
||||||
|
employee = forms.validators.ValidEmployee()
|
||||||
|
date = fe.validators.DateConverter()
|
||||||
|
|
||||||
|
|
||||||
class TimeSheetView(View):
|
class TimeSheetView(View):
|
||||||
"""
|
"""
|
||||||
Base view for time sheets.
|
Base view for time sheets.
|
||||||
|
@ -85,14 +92,21 @@ class TimeSheetView(View):
|
||||||
else:
|
else:
|
||||||
store_key = 'timesheet.{}.store'.format(self.key)
|
store_key = 'timesheet.{}.store'.format(self.key)
|
||||||
department_key = 'timesheet.{}.department'.format(self.key)
|
department_key = 'timesheet.{}.department'.format(self.key)
|
||||||
date_key = 'timesheet.{}.date'.format(self.key)
|
if store_key in self.request.session or department_key in self.request.session:
|
||||||
if store_key in self.request.session or department_key in self.request.session or date_key in self.request.session:
|
|
||||||
store_uuid = self.request.session.get(store_key)
|
store_uuid = self.request.session.get(store_key)
|
||||||
if store_uuid:
|
if store_uuid:
|
||||||
store = Session.query(model.Store).get(store_uuid) if store_uuid else None
|
store = Session.query(model.Store).get(store_uuid) if store_uuid else None
|
||||||
department_uuid = self.request.session.get(department_key)
|
department_uuid = self.request.session.get(department_key)
|
||||||
if department_uuid:
|
if department_uuid:
|
||||||
department = Session.query(model.Department).get(department_uuid)
|
department = Session.query(model.Department).get(department_uuid)
|
||||||
|
else: # no store/department in session
|
||||||
|
if self.default_filter_store:
|
||||||
|
store = self.rattail_config.get('rattail', 'store')
|
||||||
|
if store:
|
||||||
|
store = api.get_store(Session(), store)
|
||||||
|
|
||||||
|
date_key = 'timesheet.{}.date'.format(self.key)
|
||||||
|
if date_key in self.request.session:
|
||||||
date_value = self.request.session.get(date_key)
|
date_value = self.request.session.get(date_key)
|
||||||
if date_value:
|
if date_value:
|
||||||
try:
|
try:
|
||||||
|
@ -100,12 +114,6 @@ class TimeSheetView(View):
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
else: # nothing stored in session
|
|
||||||
if self.default_filter_store:
|
|
||||||
store = self.rattail_config.get('rattail', 'store')
|
|
||||||
if store:
|
|
||||||
store = api.get_store(Session(), store)
|
|
||||||
|
|
||||||
if store:
|
if store:
|
||||||
employees = employees.join(model.EmployeeStore)\
|
employees = employees.join(model.EmployeeStore)\
|
||||||
.filter(model.EmployeeStore.store == store)
|
.filter(model.EmployeeStore.store == store)
|
||||||
|
@ -117,7 +125,51 @@ class TimeSheetView(View):
|
||||||
if not date:
|
if not date:
|
||||||
date = localtime(self.rattail_config).date()
|
date = localtime(self.rattail_config).date()
|
||||||
|
|
||||||
return self.render(date, employees.all(), store=store, department=department, form=form)
|
return self.render_full(date, employees.all(), store=store, department=department, form=form)
|
||||||
|
|
||||||
|
def employee(self):
|
||||||
|
"""
|
||||||
|
View time sheet for single employee.
|
||||||
|
"""
|
||||||
|
date = None
|
||||||
|
employee = None
|
||||||
|
if not self.request.has_perm('{}.viewall'.format(self.key)):
|
||||||
|
# force current user if not allowed to view all data
|
||||||
|
employee = self.request.user.employee
|
||||||
|
assert employee
|
||||||
|
form = Form(self.request, schema=EmployeeShiftFilter)
|
||||||
|
if self.request.method == 'POST':
|
||||||
|
if form.validate():
|
||||||
|
if self.request.has_perm('{}.viewall'.format(self.key)):
|
||||||
|
employee = form.data['employee']
|
||||||
|
self.request.session['timesheet.{}.employee'.format(self.key)] = employee.uuid if employee else None
|
||||||
|
date = form.data['date']
|
||||||
|
self.request.session['timesheet.{}.employee.date'.format(self.key)] = date.strftime('%m/%d/%Y') if date else None
|
||||||
|
return self.redirect(self.request.current_route_url())
|
||||||
|
|
||||||
|
else:
|
||||||
|
if self.request.has_perm('{}.viewall'.format(self.key)):
|
||||||
|
employee_key = 'timesheet.{}.employee'.format(self.key)
|
||||||
|
if employee_key in self.request.session:
|
||||||
|
employee_uuid = self.request.session.get(employee_key)
|
||||||
|
if employee_uuid:
|
||||||
|
employee = Session.query(model.Employee).get(employee_uuid)
|
||||||
|
else: # no employee in session
|
||||||
|
if self.request.user:
|
||||||
|
employee = self.request.user.employee
|
||||||
|
|
||||||
|
date_key = 'timesheet.{}.employee.date'.format(self.key)
|
||||||
|
if date_key in self.request.session:
|
||||||
|
date_value = self.request.session.get(date_key)
|
||||||
|
if date_value:
|
||||||
|
try:
|
||||||
|
date = datetime.datetime.strptime(date_value, '%m/%d/%Y').date()
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if not date:
|
||||||
|
date = localtime(self.rattail_config).date()
|
||||||
|
return self.render_single(date, employee, form=form)
|
||||||
|
|
||||||
def crossview(self):
|
def crossview(self):
|
||||||
"""
|
"""
|
||||||
|
@ -125,10 +177,19 @@ class TimeSheetView(View):
|
||||||
filters, then redirect to other view.
|
filters, then redirect to other view.
|
||||||
"""
|
"""
|
||||||
other_key = 'timesheet' if self.key == 'schedule' else 'schedule'
|
other_key = 'timesheet' if self.key == 'schedule' else 'schedule'
|
||||||
self.session_put('store', self.session_get('store'), mainkey=other_key)
|
|
||||||
self.session_put('department', self.session_get('department'), mainkey=other_key)
|
# TODO: this check is pretty hacky..
|
||||||
self.session_put('date', self.session_get('date'), mainkey=other_key)
|
# employee time sheet
|
||||||
return self.redirect(self.request.route_url(other_key))
|
if 'employee' in self.request.get_referrer():
|
||||||
|
self.session_put('employee', self.session_get('employee'), mainkey=other_key)
|
||||||
|
self.session_put('employee.date', self.session_get('employee.date'), mainkey=other_key)
|
||||||
|
return self.redirect(self.request.route_url('{}.employee'.format(other_key)))
|
||||||
|
|
||||||
|
else: # full time sheet
|
||||||
|
self.session_put('store', self.session_get('store'), mainkey=other_key)
|
||||||
|
self.session_put('department', self.session_get('department'), mainkey=other_key)
|
||||||
|
self.session_put('date', self.session_get('date'), mainkey=other_key)
|
||||||
|
return self.redirect(self.request.route_url(other_key))
|
||||||
|
|
||||||
# def session_has(self, key, mainkey=None):
|
# def session_has(self, key, mainkey=None):
|
||||||
# if mainkey is None:
|
# if mainkey is None:
|
||||||
|
@ -167,7 +228,7 @@ class TimeSheetView(View):
|
||||||
options.insert(0, ('', "(all)"))
|
options.insert(0, ('', "(all)"))
|
||||||
return options
|
return options
|
||||||
|
|
||||||
def render(self, date, employees, store=None, department=None, form=None):
|
def render_full(self, date, employees, store=None, department=None, form=None):
|
||||||
"""
|
"""
|
||||||
Render a time sheet for one or more employees, for the week which
|
Render a time sheet for one or more employees, for the week which
|
||||||
includes the specified date.
|
includes the specified date.
|
||||||
|
@ -192,6 +253,7 @@ class TimeSheetView(View):
|
||||||
department_options = self.get_department_options(departments)
|
department_options = self.get_department_options(departments)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
'page_title': "Full {}".format(self.get_title()),
|
||||||
'form': forms.FormRenderer(form) if form else None,
|
'form': forms.FormRenderer(form) if form else None,
|
||||||
'employees': employees,
|
'employees': employees,
|
||||||
'stores': stores,
|
'stores': stores,
|
||||||
|
@ -205,6 +267,39 @@ class TimeSheetView(View):
|
||||||
'prev_sunday': sunday - datetime.timedelta(days=7),
|
'prev_sunday': sunday - datetime.timedelta(days=7),
|
||||||
'next_sunday': sunday + datetime.timedelta(days=7),
|
'next_sunday': sunday + datetime.timedelta(days=7),
|
||||||
'weekdays': weekdays,
|
'weekdays': weekdays,
|
||||||
|
'permission_prefix': self.key,
|
||||||
|
}
|
||||||
|
|
||||||
|
def render_single(self, date, employee, form=None):
|
||||||
|
"""
|
||||||
|
Render a time sheet for one employee, for the week which includes the
|
||||||
|
specified date.
|
||||||
|
"""
|
||||||
|
sunday = get_sunday(date)
|
||||||
|
weekdays = [sunday]
|
||||||
|
for i in range(1, 7):
|
||||||
|
weekdays.append(sunday + datetime.timedelta(days=i))
|
||||||
|
|
||||||
|
saturday = weekdays[-1]
|
||||||
|
if saturday.year == sunday.year:
|
||||||
|
week_of = '{} - {}'.format(sunday.strftime('%a %b %d'), saturday.strftime('%a %b %d, %Y'))
|
||||||
|
else:
|
||||||
|
week_of = '{} - {}'.format(sunday.strftime('%a %b %d, Y'), saturday.strftime('%a %b %d, %Y'))
|
||||||
|
|
||||||
|
if employee:
|
||||||
|
self.modify_employees([employee], weekdays)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'page_title': "Employee {}".format(self.get_title()),
|
||||||
|
'form': forms.FormRenderer(form) if form else None,
|
||||||
|
'employee': employee,
|
||||||
|
'employees': [employee],
|
||||||
|
'week_of': week_of,
|
||||||
|
'sunday': sunday,
|
||||||
|
'prev_sunday': sunday - datetime.timedelta(days=7),
|
||||||
|
'next_sunday': sunday + datetime.timedelta(days=7),
|
||||||
|
'weekdays': weekdays,
|
||||||
|
'permission_prefix': self.key,
|
||||||
}
|
}
|
||||||
|
|
||||||
def modify_employees(self, employees, weekdays):
|
def modify_employees(self, employees, weekdays):
|
||||||
|
@ -267,7 +362,7 @@ class TimeSheetView(View):
|
||||||
"""
|
"""
|
||||||
title = cls.get_title()
|
title = cls.get_title()
|
||||||
config.add_tailbone_permission_group(cls.key, title)
|
config.add_tailbone_permission_group(cls.key, title)
|
||||||
# config.add_tailbone_permission(cls.key, '{}.view'.format(cls.key), "View personal {}".format(title))
|
config.add_tailbone_permission(cls.key, '{}.view'.format(cls.key), "View employee {}".format(title))
|
||||||
config.add_tailbone_permission(cls.key, '{}.viewall'.format(cls.key), "View full {}".format(title))
|
config.add_tailbone_permission(cls.key, '{}.viewall'.format(cls.key), "View full {}".format(title))
|
||||||
|
|
||||||
# full time sheet
|
# full time sheet
|
||||||
|
@ -276,11 +371,11 @@ class TimeSheetView(View):
|
||||||
renderer='/shifts/{}.mako'.format(cls.key),
|
renderer='/shifts/{}.mako'.format(cls.key),
|
||||||
permission='{}.viewall'.format(cls.key))
|
permission='{}.viewall'.format(cls.key))
|
||||||
|
|
||||||
# # single employee time sheet
|
# single employee time sheet
|
||||||
# config.add_route('{}.employee'.format(cls.key), '/{}/employee/'.format(cls.key))
|
config.add_route('{}.employee'.format(cls.key), '/{}/employee/'.format(cls.key))
|
||||||
# config.add_view(cls, attr='employee', route_name='{}.employee'.format(cls.key),
|
config.add_view(cls, attr='employee', route_name='{}.employee'.format(cls.key),
|
||||||
# renderer='/shifts/{}.mako'.format(cls.key),
|
renderer='/shifts/{}.mako'.format(cls.key),
|
||||||
# permission='{}.view'.format(cls.key))
|
permission='{}.view'.format(cls.key))
|
||||||
|
|
||||||
# goto cross-view (view 'timesheet' as 'schedule' or vice-versa)
|
# goto cross-view (view 'timesheet' as 'schedule' or vice-versa)
|
||||||
other_key = 'timesheet' if cls.key == 'schedule' else 'schedule'
|
other_key = 'timesheet' if cls.key == 'schedule' else 'schedule'
|
||||||
|
|
Loading…
Reference in a new issue