Add basic ability to edit employee schedule

This commit is contained in:
Lance Edgar 2016-10-12 14:16:06 -05:00
parent 788f3ad386
commit 048951153d
6 changed files with 419 additions and 57 deletions

View file

@ -59,6 +59,11 @@ class ScheduledShiftsView(MasterView):
url_prefix = '/shifts/scheduled'
def configure_grid(self, g):
g.joiners['employee'] = lambda q: q.join(model.Employee).join(model.Person)
g.filters['employee'] = g.make_filter('employee', model.Person.display_name,
default_active=True, default_verb='contains',
label="Employee Name")
g.default_sortkey = 'start_time'
g.default_sortdir = 'desc'
g.append(ShiftLengthField('length'))

View file

@ -34,6 +34,7 @@ from rattail.time import localtime, make_utc, get_sunday
import formencode as fe
from pyramid_simpleform import Form
from webhelpers.html import HTML
from tailbone import forms
from tailbone.db import Session
@ -71,14 +72,60 @@ class TimeSheetView(View):
def get_title(cls):
return cls.title or cls.key.capitalize()
def full(self):
def get_timesheet_context(self):
"""
Determine date/store/dept context from user's session and/or defaults.
"""
date = None
date_key = 'timesheet.{}.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()
store = None
department = None
store_key = 'timesheet.{}.store'.format(self.key)
department_key = 'timesheet.{}.department'.format(self.key)
if store_key in self.request.session or department_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)
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)
employees = Session.query(model.Employee)\
.filter(model.Employee.status == enum.EMPLOYEE_STATUS_CURRENT)
if store:
employees = employees.join(model.EmployeeStore)\
.filter(model.EmployeeStore.store == store)
if department:
employees = employees.join(model.EmployeeDepartment)\
.filter(model.EmployeeDepartment.department == department)
form = Form(self.request, schema=ShiftFilter)
return {
'date': date,
'store': store,
'department': department,
'employees': employees.all(),
}
def process_filter_form(self, form):
"""
Process a "shift filter" form if one was in fact POST'ed. If it was
then we store new context in session and redirect to display as normal.
"""
if self.request.method == 'POST':
if form.validate():
store = form.data['store']
@ -87,45 +134,18 @@ class TimeSheetView(View):
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())
raise self.redirect(self.request.current_route_url())
else:
store_key = 'timesheet.{}.store'.format(self.key)
department_key = 'timesheet.{}.department'.format(self.key)
if store_key in self.request.session or department_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)
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)
if date_value:
try:
date = datetime.datetime.strptime(date_value, '%m/%d/%Y').date()
except ValueError:
pass
if store:
employees = employees.join(model.EmployeeStore)\
.filter(model.EmployeeStore.store == store)
if department:
employees = employees.join(model.EmployeeDepartment)\
.filter(model.EmployeeDepartment.department == department)
if not date:
date = localtime(self.rattail_config).date()
return self.render_full(date, employees.all(), store=store, department=department, form=form)
def full(self):
"""
View a "full" timesheet/schedule, i.e. all employees but filterable by
store and/or department.
"""
form = Form(self.request, schema=ShiftFilter)
self.process_filter_form(form)
context = self.get_timesheet_context()
context['form'] = form
return self.render_full(**context)
def employee(self):
"""
@ -233,7 +253,7 @@ class TimeSheetView(View):
options.insert(0, ('', "(all)"))
return options
def render_full(self, date, employees, store=None, department=None, form=None):
def render_full(self, date=None, employees=None, store=None, department=None, form=None, **kwargs):
"""
Render a time sheet for one or more employees, for the week which
includes the specified date.
@ -257,7 +277,7 @@ class TimeSheetView(View):
departments = self.get_departments()
department_options = self.get_department_options(departments)
return {
context = {
'page_title': "Full {}".format(self.get_title()),
'form': forms.FormRenderer(form) if form else None,
'employees': employees,
@ -275,9 +295,11 @@ class TimeSheetView(View):
'permission_prefix': self.key,
'render_shift': self.render_shift,
}
context.update(kwargs)
return context
def render_shift(self, shift):
return shift.get_display(self.rattail_config)
return HTML.tag('span', c=shift.get_display(self.rattail_config))
def render_single(self, date, employee, form=None):
"""
@ -371,7 +393,7 @@ class TimeSheetView(View):
"""
title = cls.get_title()
config.add_tailbone_permission_group(cls.key, title)
config.add_tailbone_permission(cls.key, '{}.view'.format(cls.key), "View employee {}".format(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

View file

@ -26,9 +26,15 @@ Views for employee schedules
from __future__ import unicode_literals, absolute_import
from rattail.db import model
import datetime
from tailbone.views.shifts.lib import TimeSheetView
from rattail.db import model
from rattail.time import localtime, make_utc
from pyramid_simpleform import Form
from tailbone.db import Session
from tailbone.views.shifts.lib import TimeSheetView, ShiftFilter
class ScheduleView(TimeSheetView):
@ -38,6 +44,76 @@ class ScheduleView(TimeSheetView):
key = 'schedule'
model_class = model.ScheduledShift
def edit(self):
"""
View for editing (full) schedule.
"""
if self.request.method == 'POST':
# organize form data by uuid / field
fields = ['employee_uuid', 'store_uuid', 'start_time', 'end_time', 'delete']
data = dict([(f, {}) for f in fields])
for key in self.request.POST:
for field in fields:
if key.startswith('{}-'.format(field)):
uuid = key[len('{}-'.format(field)):]
if uuid:
data[field][uuid] = self.request.POST[key]
# apply delete operations
deleted = []
for uuid, value in data['delete'].iteritems():
assert value == 'delete'
shift = Session.query(model.ScheduledShift).get(uuid)
assert shift
Session.delete(shift)
deleted.append(uuid)
# apply create / update operations
created = {}
updated = {}
time_format = '%a %d %b %Y %I:%M %p'
for uuid, employee_uuid in data['start_time'].iteritems():
if uuid in deleted:
continue
if uuid.startswith('new-'):
shift = model.ScheduledShift()
shift.employee_uuid = data['employee_uuid'][uuid]
shift.store_uuid = data['store_uuid'][uuid]
Session.add(shift)
created[uuid] = shift
else:
shift = Session.query(model.ScheduledShift).get(uuid)
assert shift
updated[uuid] = shift
start_time = datetime.datetime.strptime(data['start_time'][uuid], time_format)
shift.start_time = make_utc(localtime(self.rattail_config, start_time))
end_time = datetime.datetime.strptime(data['end_time'][uuid], time_format)
shift.end_time = make_utc(localtime(self.rattail_config, end_time))
self.request.session.flash("Changes were applied: created {}, updated {}, "
"deleted {} Scheduled Shifts".format(
len(created), len(updated), len(deleted)))
return self.redirect(self.request.route_url('schedule.edit'))
form = Form(self.request, schema=ShiftFilter)
self.process_filter_form(form)
context = self.get_timesheet_context()
context['form'] = form
context['page_title'] = "Edit Schedule"
return self.render_full(**context)
@classmethod
def defaults(cls, config):
cls._defaults(config)
# edit schedule
config.add_route('schedule.edit', '/schedule/edit')
config.add_view(cls, attr='edit', route_name='schedule.edit',
renderer='/shifts/schedule_edit.mako',
permission='schedule.edit')
config.add_tailbone_permission('schedule', 'schedule.edit', "Edit full schedule")
def includeme(config):
ScheduleView.defaults(config)