Add support for "full" schedule and time sheet views
Temporarily removes support for viewing current user's time sheet; that will be added back in soon.
This commit is contained in:
parent
181123dfaa
commit
123f5ce0c6
|
@ -48,7 +48,18 @@ class ModelValidator(fe.validators.FancyValidator):
|
||||||
obj = Session.query(self.model_class).get(value)
|
obj = Session.query(self.model_class).get(value)
|
||||||
if obj:
|
if obj:
|
||||||
return obj
|
return obj
|
||||||
raise formencode.Invalid("{} not found".format(self.model_name), value, state)
|
raise fe.Invalid("{} not found".format(self.model_name), value, state)
|
||||||
|
|
||||||
|
def _from_python(self, value, state):
|
||||||
|
obj = value
|
||||||
|
if not obj:
|
||||||
|
return ''
|
||||||
|
return obj.uuid
|
||||||
|
|
||||||
|
def validate_python(self, value, state):
|
||||||
|
obj = value
|
||||||
|
if obj is not None and not isinstance(obj, self.model_class):
|
||||||
|
raise fe.Invalid("Value must be a valid {} object".format(self.model_name), value, state)
|
||||||
|
|
||||||
|
|
||||||
class ValidStore(ModelValidator):
|
class ValidStore(ModelValidator):
|
||||||
|
|
|
@ -3,24 +3,50 @@
|
||||||
* styles for time sheets / schedules
|
* styles for time sheets / schedules
|
||||||
**********************************************************************/
|
**********************************************************************/
|
||||||
|
|
||||||
|
/******************************
|
||||||
|
* header table
|
||||||
|
******************************/
|
||||||
|
|
||||||
.timesheet-header {
|
.timesheet-header {
|
||||||
overflow: auto;
|
width: 100%;
|
||||||
position: relative;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.timesheet-header .week-picker {
|
.timesheet-header td.filters {
|
||||||
bottom: 0.5em;
|
vertical-align: bottom;
|
||||||
position: absolute;
|
width: 100%;
|
||||||
right: 0;
|
}
|
||||||
|
|
||||||
|
.timesheet-header td.filters .field {
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timesheet-header td.menu {
|
||||||
|
padding: 0.5em;
|
||||||
|
vertical-align: top;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timesheet-header td.tools {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
text-align: right;
|
||||||
|
vertical-align: bottom;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.timesheet-header .week-picker label {
|
.timesheet-header .week-picker label {
|
||||||
margin-left: 1em;
|
margin-left: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/******************************
|
||||||
|
* timesheet table
|
||||||
|
******************************/
|
||||||
|
|
||||||
.timesheet {
|
.timesheet {
|
||||||
border-bottom: 1px solid black;
|
border-bottom: 1px solid black;
|
||||||
border-right: 1px solid black;
|
border-right: 1px solid black;
|
||||||
|
clear: both;
|
||||||
|
margin-top: 0.3em;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,124 +8,148 @@
|
||||||
|
|
||||||
$(function() {
|
$(function() {
|
||||||
|
|
||||||
$('.timesheet-header select').selectmenu();
|
$('.timesheet-wrapper form').submit(function() {
|
||||||
|
$('.timesheet-header').mask("Fetching data");
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.timesheet-header select').selectmenu({
|
||||||
|
change: function(event, ui) {
|
||||||
|
$(ui.item.element).parents('form').submit();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.timesheet-header a.goto').click(function() {
|
||||||
|
$('.timesheet-header').mask("Fetching data");
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.week-picker button.nav').click(function() {
|
||||||
|
$('.week-picker #date').val($(this).data('date'));
|
||||||
|
});
|
||||||
|
|
||||||
$('.week-picker #date').datepicker({
|
$('.week-picker #date').datepicker({
|
||||||
dateFormat: 'yy-mm-dd',
|
dateFormat: 'mm/dd/yy',
|
||||||
changeYear: true,
|
changeYear: true,
|
||||||
changeMonth: true,
|
changeMonth: true,
|
||||||
showButtonPanel: true,
|
showButtonPanel: true,
|
||||||
onSelect: function(dateText, inst) {
|
onSelect: function(dateText, inst) {
|
||||||
$(this).focus().select();
|
$(this).parents('form').submit();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$('.week-picker form').submit(function() {
|
|
||||||
location.href = '?date=' + $('.week-picker #date').val();
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
</%def>
|
</%def>
|
||||||
|
|
||||||
<%def name="timesheet(employees, employee_column=True)">
|
<%def name="context_menu()"></%def>
|
||||||
|
|
||||||
|
<%def name="timesheet(employee_column=True)">
|
||||||
<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 / float(9 if employee_column else 8))}%;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<div class="timesheet-header">
|
|
||||||
|
|
||||||
## <div class="field-wrapper employee">
|
<div class="timesheet-wrapper">
|
||||||
## <label>Employee</label>
|
|
||||||
## <div class="field">
|
|
||||||
## ${employee}
|
|
||||||
## </div>
|
|
||||||
## </div>
|
|
||||||
|
|
||||||
<div class="fieldset">
|
${form.begin()}
|
||||||
|
|
||||||
<div class="field-wrapper week">
|
<table class="timesheet-header">
|
||||||
<label>Store</label>
|
<tbody>
|
||||||
<div class="field">
|
<tr>
|
||||||
${form.select('store', store_options, selected_value=store.uuid if store else None)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="field-wrapper week">
|
<td class="filters" rowspan="2">
|
||||||
<label>Department</label>
|
|
||||||
<div class="field">
|
|
||||||
${form.select('department', department_options, selected_value=department.uuid if department else None)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="field-wrapper week">
|
## <div class="field-wrapper employee">
|
||||||
<label>Week of</label>
|
## <label>Employee</label>
|
||||||
<div class="field">
|
## <div class="field">
|
||||||
${week_of}
|
## ${employee}
|
||||||
</div>
|
## </div>
|
||||||
</div>
|
## </div>
|
||||||
|
|
||||||
</div>
|
${form.field_div('store', h.select('store', store.uuid if store else None, store_options))}
|
||||||
|
|
||||||
<div class="week-picker">
|
${form.field_div('department', h.select('department', department.uuid if department else None, department_options))}
|
||||||
${h.form(request.current_route_url())}
|
|
||||||
${h.link_to(u"« Previous", '?date=' + prev_sunday.strftime('%Y-%m-%d'), class_='button')}
|
|
||||||
${h.link_to(u"Next »", '?date=' + next_sunday.strftime('%Y-%m-%d'), class_='button')}
|
|
||||||
<label>Jump to week:</label>
|
|
||||||
${h.text('date', value=sunday.strftime('%Y-%m-%d'))}
|
|
||||||
${h.submit('go', "Go")}
|
|
||||||
${h.end_form()}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div><!-- timesheet-header -->
|
<div class="field-wrapper week">
|
||||||
|
<label>Week of</label>
|
||||||
|
<div class="field">
|
||||||
|
${week_of}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<table class="timesheet">
|
</td><!-- filters -->
|
||||||
<thead>
|
|
||||||
<tr>
|
<td class="menu">
|
||||||
% if employee_column:
|
<ul id="context-menu">
|
||||||
<th>Employee</th>
|
${self.context_menu()}
|
||||||
% endif
|
</ul>
|
||||||
% for day in weekdays:
|
</td><!-- menu -->
|
||||||
<th>${day.strftime('%A')}<br />${day.strftime('%b %d')}</th>
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td class="tools">
|
||||||
|
<div class="grid-tools">
|
||||||
|
<div class="week-picker">
|
||||||
|
<button class="nav" data-date="${prev_sunday.strftime('%m/%d/%Y')}">« Previous</button>
|
||||||
|
<button class="nav" data-date="${next_sunday.strftime('%m/%d/%Y')}">Next »</button>
|
||||||
|
<label>Jump to week:</label>
|
||||||
|
${form.text('date', value=sunday.strftime('%m/%d/%Y'))}
|
||||||
|
</div>
|
||||||
|
</div><!-- grid-tools -->
|
||||||
|
</td><!-- tools -->
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
</tbody>
|
||||||
|
</table><!-- timesheet-header -->
|
||||||
|
|
||||||
|
${form.end()}
|
||||||
|
|
||||||
|
<table class="timesheet">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
% if employee_column:
|
||||||
|
<th>Employee</th>
|
||||||
|
% endif
|
||||||
|
% for day in weekdays:
|
||||||
|
<th>${day.strftime('%A')}<br />${day.strftime('%b %d')}</th>
|
||||||
|
% endfor
|
||||||
|
<th>Total<br />Hours</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
% for employee in sorted(employees, key=unicode):
|
||||||
|
<tr>
|
||||||
|
% if employee_column:
|
||||||
|
<td class="employee">${employee}</td>
|
||||||
|
% endif
|
||||||
|
% for day in employee.weekdays:
|
||||||
|
<td>
|
||||||
|
% for shift in day['shifts']:
|
||||||
|
<p class="shift">${shift.get_display(request.rattail_config)}</p>
|
||||||
|
% endfor
|
||||||
|
</td>
|
||||||
|
% endfor
|
||||||
|
<td>${employee.hours_display}</td>
|
||||||
|
</tr>
|
||||||
% endfor
|
% endfor
|
||||||
<th>Total<br />Hours</th>
|
% if employee_column:
|
||||||
</tr>
|
<tr class="total">
|
||||||
</thead>
|
<td class="employee">${len(employees)} employees</td>
|
||||||
<tbody>
|
% for day in weekdays:
|
||||||
% for employee in sorted(employees, key=unicode):
|
<td></td>
|
||||||
<tr>
|
% endfor
|
||||||
% if employee_column:
|
<td></td>
|
||||||
<td class="employee">${employee}</td>
|
</tr>
|
||||||
% endif
|
% else:
|
||||||
% for day in employee.weekdays:
|
<tr>
|
||||||
<td>
|
% for day in employee.weekdays:
|
||||||
% for shift in day['shifts']:
|
<td>${day['hours_display']}</td>
|
||||||
<p class="shift">${shift.get_display(request.rattail_config)}</p>
|
% endfor
|
||||||
% endfor
|
<td>${employee.hours_display}</td>
|
||||||
</td>
|
</tr>
|
||||||
% endfor
|
% endif
|
||||||
<td>${employee.hours_display}</td>
|
</tbody>
|
||||||
</tr>
|
</table>
|
||||||
% endfor
|
</div><!-- timesheet-wrapper -->
|
||||||
% if employee_column:
|
|
||||||
<tr class="total">
|
|
||||||
<td class="employee">${len(employees)} employees</td>
|
|
||||||
% for day in weekdays:
|
|
||||||
<td></td>
|
|
||||||
% endfor
|
|
||||||
<td></td>
|
|
||||||
</tr>
|
|
||||||
% else:
|
|
||||||
<tr>
|
|
||||||
% for day in employee.weekdays:
|
|
||||||
<td>${day['hours_display']}</td>
|
|
||||||
% endfor
|
|
||||||
<td>${employee.hours_display}</td>
|
|
||||||
</tr>
|
|
||||||
% endif
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</%def>
|
</%def>
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
## -*- coding: utf-8 -*-
|
## -*- coding: utf-8 -*-
|
||||||
<%inherit file="/shifts/base.mako" />
|
<%inherit file="/shifts/base.mako" />
|
||||||
|
|
||||||
<%def name="title()">Schedule: ${sunday}</%def>
|
<%def name="title()">Full Schedule</%def>
|
||||||
|
|
||||||
<ul id="context-menu">
|
<%def name="context_menu()">
|
||||||
<li>${h.link_to("Print this Schedule", '#')}</li>
|
% if request.has_perm('timesheet.view'):
|
||||||
<li>${h.link_to("Edit this Schedule", '#')}</li>
|
<li>${h.link_to("View this Time Sheet", url('schedule.goto.timesheet'), class_='goto')}</li>
|
||||||
</ul>
|
% endif
|
||||||
|
## <li>${h.link_to("Print this Schedule", '#')}</li>
|
||||||
|
## <li>${h.link_to("Edit this Schedule", '#')}</li>
|
||||||
|
</%def>
|
||||||
|
|
||||||
${self.timesheet(employees)}
|
${self.timesheet()}
|
||||||
|
|
|
@ -1,6 +1,12 @@
|
||||||
## -*- coding: utf-8 -*-
|
## -*- coding: utf-8 -*-
|
||||||
<%inherit file="/shifts/base.mako" />
|
<%inherit file="/shifts/base.mako" />
|
||||||
|
|
||||||
<%def name="title()">Time Sheet: ${sunday}</%def>
|
<%def name="title()">Full Time Sheet</%def>
|
||||||
|
|
||||||
${self.timesheet(employees, employee_column=False)}
|
<%def name="context_menu()">
|
||||||
|
% if request.has_perm('schedule.view'):
|
||||||
|
<li>${h.link_to("View this Schedule", url('timesheet.goto.schedule'), class_='goto')}</li>
|
||||||
|
% endif
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
${self.timesheet()}
|
||||||
|
|
|
@ -28,6 +28,8 @@ from __future__ import unicode_literals
|
||||||
|
|
||||||
from rattail.db import model
|
from rattail.db import model
|
||||||
|
|
||||||
|
from pyramid import httpexceptions
|
||||||
|
|
||||||
from tailbone.db import Session
|
from tailbone.db import Session
|
||||||
|
|
||||||
|
|
||||||
|
@ -56,6 +58,12 @@ class View(object):
|
||||||
if uuid:
|
if uuid:
|
||||||
return Session.query(model.User).get(uuid)
|
return Session.query(model.User).get(uuid)
|
||||||
|
|
||||||
|
def redirect(self, url):
|
||||||
|
"""
|
||||||
|
Convenience method to return a HTTP 302 response.
|
||||||
|
"""
|
||||||
|
return httpexceptions.HTTPFound(location=url)
|
||||||
|
|
||||||
|
|
||||||
def fake_error(request):
|
def fake_error(request):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -396,12 +396,6 @@ class MasterView(View):
|
||||||
"""
|
"""
|
||||||
return kwargs
|
return kwargs
|
||||||
|
|
||||||
def redirect(self, url):
|
|
||||||
"""
|
|
||||||
Convenience method to return a HTTP 302 response.
|
|
||||||
"""
|
|
||||||
return httpexceptions.HTTPFound(location=url)
|
|
||||||
|
|
||||||
##############################
|
##############################
|
||||||
# Grid Stuff
|
# Grid Stuff
|
||||||
##############################
|
##############################
|
||||||
|
|
|
@ -44,11 +44,11 @@ class ShiftLengthField(formalchemy.Field):
|
||||||
super(ShiftLengthField, self).__init__(name, **kwargs)
|
super(ShiftLengthField, self).__init__(name, **kwargs)
|
||||||
|
|
||||||
def shift_length(self, shift):
|
def shift_length(self, shift):
|
||||||
if not shift.punch_in or not shift.punch_out:
|
if not shift.start_time or not shift.end_time:
|
||||||
return
|
return
|
||||||
if shift.punch_out < shift.punch_in:
|
if shift.end_time < shift.start_time:
|
||||||
return "??"
|
return "??"
|
||||||
return humanize.naturaldelta(shift.punch_out - shift.punch_in)
|
return humanize.naturaldelta(shift.end_time - shift.start_time)
|
||||||
|
|
||||||
|
|
||||||
class ScheduledShiftsView(MasterView):
|
class ScheduledShiftsView(MasterView):
|
||||||
|
@ -61,22 +61,26 @@ class ScheduledShiftsView(MasterView):
|
||||||
def configure_grid(self, g):
|
def configure_grid(self, g):
|
||||||
g.default_sortkey = 'start_time'
|
g.default_sortkey = 'start_time'
|
||||||
g.default_sortdir = 'desc'
|
g.default_sortdir = 'desc'
|
||||||
|
g.append(ShiftLengthField('length'))
|
||||||
g.configure(
|
g.configure(
|
||||||
include=[
|
include=[
|
||||||
g.employee,
|
g.employee,
|
||||||
g.store,
|
g.store,
|
||||||
g.start_time,
|
g.start_time,
|
||||||
g.end_time,
|
g.end_time,
|
||||||
|
g.length,
|
||||||
],
|
],
|
||||||
readonly=True)
|
readonly=True)
|
||||||
|
|
||||||
def configure_fieldset(self, fs):
|
def configure_fieldset(self, fs):
|
||||||
|
fs.append(ShiftLengthField('length'))
|
||||||
fs.configure(
|
fs.configure(
|
||||||
include=[
|
include=[
|
||||||
fs.employee,
|
fs.employee,
|
||||||
fs.store,
|
fs.store,
|
||||||
fs.start_time,
|
fs.start_time,
|
||||||
fs.end_time,
|
fs.end_time,
|
||||||
|
fs.length,
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
||||||
|
@ -88,15 +92,18 @@ class WorkedShiftsView(MasterView):
|
||||||
url_prefix = '/shifts/worked'
|
url_prefix = '/shifts/worked'
|
||||||
|
|
||||||
def configure_grid(self, g):
|
def configure_grid(self, g):
|
||||||
g.default_sortkey = 'punch_in'
|
# TODO: these sorters should be automatic once we fix the schema
|
||||||
|
g.sorters['start_time'] = g.make_sorter(model.WorkedShift.punch_in)
|
||||||
|
g.sorters['end_time'] = g.make_sorter(model.WorkedShift.punch_out)
|
||||||
|
g.default_sortkey = 'start_time'
|
||||||
g.default_sortdir = 'desc'
|
g.default_sortdir = 'desc'
|
||||||
g.append(ShiftLengthField('length'))
|
g.append(ShiftLengthField('length'))
|
||||||
g.configure(
|
g.configure(
|
||||||
include=[
|
include=[
|
||||||
g.employee,
|
g.employee,
|
||||||
g.store,
|
g.store,
|
||||||
g.punch_in,
|
g.start_time,
|
||||||
g.punch_out,
|
g.end_time,
|
||||||
g.length,
|
g.length,
|
||||||
],
|
],
|
||||||
readonly=True)
|
readonly=True)
|
||||||
|
@ -107,8 +114,8 @@ class WorkedShiftsView(MasterView):
|
||||||
include=[
|
include=[
|
||||||
fs.employee,
|
fs.employee,
|
||||||
fs.store,
|
fs.store,
|
||||||
fs.punch_in,
|
fs.start_time,
|
||||||
fs.punch_out,
|
fs.end_time,
|
||||||
fs.length,
|
fs.length,
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
|
@ -45,38 +45,66 @@ class ShiftFilter(fe.Schema):
|
||||||
filter_extra_fields = True
|
filter_extra_fields = True
|
||||||
store = forms.validators.ValidStore()
|
store = forms.validators.ValidStore()
|
||||||
department = forms.validators.ValidDepartment()
|
department = forms.validators.ValidDepartment()
|
||||||
|
date = fe.validators.DateConverter()
|
||||||
|
|
||||||
|
|
||||||
class TimeSheetView(View):
|
class TimeSheetView(View):
|
||||||
"""
|
"""
|
||||||
Base view for time sheets.
|
Base view for time sheets.
|
||||||
"""
|
"""
|
||||||
|
key = None
|
||||||
|
title = None
|
||||||
model_class = None
|
model_class = None
|
||||||
|
|
||||||
# Set this to False to avoid the default behavior of auto-filtering by
|
# Set this to False to avoid the default behavior of auto-filtering by
|
||||||
# current store.
|
# current store.
|
||||||
default_filter_store = True
|
default_filter_store = True
|
||||||
|
|
||||||
def __call__(self):
|
@classmethod
|
||||||
date = self.get_date()
|
def get_title(cls):
|
||||||
|
return cls.title or cls.key.capitalize()
|
||||||
|
|
||||||
|
def full(self):
|
||||||
|
date = None
|
||||||
store = None
|
store = None
|
||||||
department = None
|
department = None
|
||||||
employees = Session.query(model.Employee)\
|
employees = Session.query(model.Employee)\
|
||||||
.filter(model.Employee.status == enum.EMPLOYEE_STATUS_CURRENT)
|
.filter(model.Employee.status == enum.EMPLOYEE_STATUS_CURRENT)
|
||||||
|
|
||||||
form = Form(self.request, schema=ShiftFilter)
|
form = Form(self.request, schema=ShiftFilter)
|
||||||
if form.validate():
|
if self.request.method == 'POST':
|
||||||
store = form.data['store']
|
if form.validate():
|
||||||
department = form.data['department']
|
store = form.data['store']
|
||||||
|
self.request.session['timesheet.{}.store'.format(self.key)] = store.uuid if store else None
|
||||||
|
department = form.data['department']
|
||||||
|
self.request.session['timesheet.{}.department'.format(self.key)] = department.uuid if department else None
|
||||||
|
date = form.data['date']
|
||||||
|
self.request.session['timesheet.{}.date'.format(self.key)] = date.strftime('%m/%d/%Y') if date else None
|
||||||
|
return self.redirect(self.request.current_route_url())
|
||||||
|
|
||||||
elif self.request.method != 'POST' and self.default_filter_store:
|
else:
|
||||||
store = self.rattail_config.get('rattail', 'store')
|
store_key = 'timesheet.{}.store'.format(self.key)
|
||||||
if store:
|
department_key = 'timesheet.{}.department'.format(self.key)
|
||||||
store = api.get_store(Session(), store)
|
date_key = 'timesheet.{}.date'.format(self.key)
|
||||||
|
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)
|
||||||
|
if store_uuid:
|
||||||
|
store = Session.query(model.Store).get(store_uuid) if store_uuid else None
|
||||||
|
department_uuid = self.request.session.get(department_key)
|
||||||
|
if department_uuid:
|
||||||
|
department = Session.query(model.Department).get(department_uuid)
|
||||||
|
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
|
||||||
|
|
||||||
# TODO:
|
else: # nothing stored in session
|
||||||
# store = Session.query(model.Store).filter_by(id='003').one()
|
if self.default_filter_store:
|
||||||
# department = Session.query(model.Department).filter_by(number=6).one()
|
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)\
|
||||||
|
@ -86,25 +114,49 @@ class TimeSheetView(View):
|
||||||
employees = employees.join(model.EmployeeDepartment)\
|
employees = employees.join(model.EmployeeDepartment)\
|
||||||
.filter(model.EmployeeDepartment.department == department)
|
.filter(model.EmployeeDepartment.department == department)
|
||||||
|
|
||||||
return self.render(date, employees.all(), store=store, department=department, form=form)
|
|
||||||
|
|
||||||
def get_date(self):
|
|
||||||
date = None
|
|
||||||
if 'date' in self.request.params:
|
|
||||||
try:
|
|
||||||
date = datetime.datetime.strptime(self.request.params['date'], '%Y-%m-%d').date()
|
|
||||||
except ValueError:
|
|
||||||
self.request.session.flash("The specified date is not valid: {}".format(self.request.params['date']), 'error')
|
|
||||||
if not date:
|
if not date:
|
||||||
date = localtime(self.rattail_config).date()
|
date = localtime(self.rattail_config).date()
|
||||||
return date
|
|
||||||
|
return self.render(date, employees.all(), store=store, department=department, form=form)
|
||||||
|
|
||||||
|
def crossview(self):
|
||||||
|
"""
|
||||||
|
Update session storage to so 'other' view reflects current view
|
||||||
|
filters, then redirect to other view.
|
||||||
|
"""
|
||||||
|
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)
|
||||||
|
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):
|
||||||
|
# if mainkey is None:
|
||||||
|
# mainkey = self.key
|
||||||
|
# return 'timesheet.{}.{}'.format(mainkey, key) in self.request.session
|
||||||
|
|
||||||
|
# def session_has_any(self, *keys, **kwargs):
|
||||||
|
# for key in keys:
|
||||||
|
# if self.session_has(key, **kwargs):
|
||||||
|
# return True
|
||||||
|
# return False
|
||||||
|
|
||||||
|
def session_get(self, key, mainkey=None):
|
||||||
|
if mainkey is None:
|
||||||
|
mainkey = self.key
|
||||||
|
return self.request.session.get('timesheet.{}.{}'.format(mainkey, key))
|
||||||
|
|
||||||
|
def session_put(self, key, value, mainkey=None):
|
||||||
|
if mainkey is None:
|
||||||
|
mainkey = self.key
|
||||||
|
self.request.session['timesheet.{}.{}'.format(mainkey, key)] = value
|
||||||
|
|
||||||
def get_stores(self):
|
def get_stores(self):
|
||||||
return Session.query(model.Store).order_by(model.Store.id).all()
|
return Session.query(model.Store).order_by(model.Store.id).all()
|
||||||
|
|
||||||
def get_store_options(self, stores):
|
def get_store_options(self, stores):
|
||||||
options = [(s.uuid, "{} - {}".format(s.id, s.name)) for s in stores]
|
options = [(s.uuid, "{} - {}".format(s.id, s.name)) for s in stores]
|
||||||
options.insert(0, (None, "(all)"))
|
options.insert(0, ('', "(all)"))
|
||||||
return options
|
return options
|
||||||
|
|
||||||
def get_departments(self):
|
def get_departments(self):
|
||||||
|
@ -112,7 +164,7 @@ class TimeSheetView(View):
|
||||||
|
|
||||||
def get_department_options(self, departments):
|
def get_department_options(self, departments):
|
||||||
options = [(d.uuid, d.name) for d in departments]
|
options = [(d.uuid, d.name) for d in departments]
|
||||||
options.insert(0, (None, "(all)"))
|
options.insert(0, ('', "(all)"))
|
||||||
return options
|
return options
|
||||||
|
|
||||||
def render(self, date, employees, store=None, department=None, form=None):
|
def render(self, date, employees, store=None, department=None, form=None):
|
||||||
|
@ -184,8 +236,10 @@ class TimeSheetView(View):
|
||||||
break
|
break
|
||||||
elif shift.get_date(self.rattail_config) == day:
|
elif shift.get_date(self.rattail_config) == day:
|
||||||
empday['shifts'].append(shift)
|
empday['shifts'].append(shift)
|
||||||
empday['hours'] += shift.length
|
length = shift.length
|
||||||
employee.hours += shift.length
|
if length is not None:
|
||||||
|
empday['hours'] += shift.length
|
||||||
|
employee.hours += shift.length
|
||||||
del employee_shifts[0]
|
del employee_shifts[0]
|
||||||
else:
|
else:
|
||||||
break
|
break
|
||||||
|
@ -198,3 +252,38 @@ class TimeSheetView(View):
|
||||||
if employee.hours:
|
if employee.hours:
|
||||||
minutes = (employee.hours.days * 1440) + (employee.hours.seconds / 60)
|
minutes = (employee.hours.days * 1440) + (employee.hours.seconds / 60)
|
||||||
employee.hours_display = '{}:{:02d}'.format(minutes // 60, minutes % 60)
|
employee.hours_display = '{}:{:02d}'.format(minutes // 60, minutes % 60)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def defaults(cls, config):
|
||||||
|
"""
|
||||||
|
Provide default configuration for a time sheet view.
|
||||||
|
"""
|
||||||
|
cls._defaults(config)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _defaults(cls, config):
|
||||||
|
"""
|
||||||
|
Provide default configuration for a time sheet view.
|
||||||
|
"""
|
||||||
|
title = cls.get_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, '{}.viewall'.format(cls.key), "View full {}".format(title))
|
||||||
|
|
||||||
|
# full time sheet
|
||||||
|
config.add_route(cls.key, '/{}/'.format(cls.key))
|
||||||
|
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))
|
||||||
|
|
||||||
|
# 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_view(cls, attr='crossview', route_name='{}.goto.{}'.format(cls.key, other_key),
|
||||||
|
permission='{}.view'.format(other_key))
|
||||||
|
|
|
@ -26,10 +26,8 @@ Views for employee schedules
|
||||||
|
|
||||||
from __future__ import unicode_literals, absolute_import
|
from __future__ import unicode_literals, absolute_import
|
||||||
|
|
||||||
from rattail import enum
|
|
||||||
from rattail.db import model
|
from rattail.db import model
|
||||||
|
|
||||||
from tailbone.db import Session
|
|
||||||
from tailbone.views.shifts.lib import TimeSheetView
|
from tailbone.views.shifts.lib import TimeSheetView
|
||||||
|
|
||||||
|
|
||||||
|
@ -37,14 +35,9 @@ class ScheduleView(TimeSheetView):
|
||||||
"""
|
"""
|
||||||
Simple view for current user's schedule.
|
Simple view for current user's schedule.
|
||||||
"""
|
"""
|
||||||
|
key = 'schedule'
|
||||||
model_class = model.ScheduledShift
|
model_class = model.ScheduledShift
|
||||||
|
|
||||||
|
|
||||||
def includeme(config):
|
def includeme(config):
|
||||||
|
ScheduleView.defaults(config)
|
||||||
config.add_tailbone_permission('schedule', 'schedule.view', "View Schedule")
|
|
||||||
|
|
||||||
# current user's schedule
|
|
||||||
config.add_route('schedule', '/schedule/')
|
|
||||||
config.add_view(ScheduleView, route_name='schedule',
|
|
||||||
renderer='/shifts/schedule.mako', permission='schedule.view')
|
|
||||||
|
|
|
@ -35,20 +35,21 @@ class TimeSheetView(TimeSheetView):
|
||||||
"""
|
"""
|
||||||
Simple view for current user's time sheet.
|
Simple view for current user's time sheet.
|
||||||
"""
|
"""
|
||||||
|
key = 'timesheet'
|
||||||
|
title = "Time Sheet"
|
||||||
model_class = model.WorkedShift
|
model_class = model.WorkedShift
|
||||||
|
|
||||||
def __call__(self):
|
# def __call__(self):
|
||||||
date = self.get_date()
|
# date = self.get_date()
|
||||||
employee = self.request.user.employee
|
# employee = self.request.user.employee
|
||||||
assert employee
|
# assert employee
|
||||||
return self.render(date, [employee])
|
# return self.render(date, [employee])
|
||||||
|
|
||||||
|
|
||||||
def includeme(config):
|
def includeme(config):
|
||||||
|
TimeSheetView.defaults(config)
|
||||||
config.add_tailbone_permission('timesheet', 'timesheet.view', "View Time Sheet")
|
|
||||||
|
|
||||||
# current user's time sheet
|
# current user's time sheet
|
||||||
config.add_route('timesheet', '/timesheet/')
|
# config.add_route('timesheet', '/timesheet/')
|
||||||
config.add_view(TimeSheetView, route_name='timesheet',
|
# config.add_view(TimeSheetView, route_name='timesheet',
|
||||||
renderer='/shifts/timesheet.mako', permission='timesheet.view')
|
# renderer='/shifts/timesheet.mako', permission='timesheet.view')
|
||||||
|
|
Loading…
Reference in a new issue