Add ability to edit employee time sheet

Also disable some unwanted autocomplete logic, plus add ability to
prevent autocomplete "change click" event
This commit is contained in:
Lance Edgar 2017-01-29 18:53:52 -06:00
parent 2e88cdde88
commit 7104e275c3
11 changed files with 467 additions and 302 deletions

View file

@ -2,7 +2,7 @@
################################################################################
#
# Rattail -- Retail Software Framework
# Copyright © 2010-2016 Lance Edgar
# Copyright © 2010-2017 Lance Edgar
#
# This file is part of Rattail.
#
@ -121,6 +121,37 @@ class TimeSheetView(View):
'employees': employees.all(),
}
def get_employee_context(self):
"""
Determine employee/date context from user's session and/or defaults
"""
date = None
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()
employee = None
employee_key = 'timesheet.{}.employee'.format(self.key)
if employee_key in self.request.session:
employee_uuid = self.request.session[employee_key]
employee = Session.query(model.Employee).get(employee_uuid) if employee_uuid else None
if not employee:
employee = self.request.user.employee
# force current user if not allowed to view all data
if not self.request.has_perm('{}.viewall'.format(self.key)):
employee = self.request.user.employee
assert employee
return {'date': date, 'employee': employee}
def process_filter_form(self, form):
"""
Process a "shift filter" form if one was in fact POST'ed. If it was
@ -136,6 +167,19 @@ class TimeSheetView(View):
self.request.session['timesheet.{}.date'.format(self.key)] = date.strftime('%m/%d/%Y') if date else None
raise self.redirect(self.request.current_route_url())
def process_employee_filter_form(self, form):
"""
Process an "employee 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():
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
raise self.redirect(self.request.current_route_url())
def full(self):
"""
View a "full" timesheet/schedule, i.e. all employees but filterable by
@ -151,50 +195,11 @@ class TimeSheetView(View):
"""
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
# default to current user; force unless allowed to view all data
if not employee or not self.request.has_perm('{}.viewall'.format(self.key)):
employee = self.request.user.employee
assert employee
if not date:
date = localtime(self.rattail_config).date()
return self.render_single(date, employee, form=form)
self.process_employee_filter_form(form)
context = self.get_employee_context()
context['form'] = form
return self.render_single(**context)
def crossview(self):
"""
@ -216,17 +221,6 @@ class TimeSheetView(View):
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
@ -301,7 +295,7 @@ class TimeSheetView(View):
def render_shift(self, shift):
return HTML.tag('span', c=shift.get_display(self.rattail_config))
def render_single(self, date, employee, form=None):
def render_single(self, date=None, employee=None, form=None, **kwargs):
"""
Render a time sheet for one employee, for the week which includes the
specified date.
@ -319,7 +313,7 @@ class TimeSheetView(View):
self.modify_employees([employee], weekdays)
return {
context = {
'page_title': "Employee {}".format(self.get_title()),
'form': forms.FormRenderer(form) if form else None,
'employee': employee,
@ -332,6 +326,8 @@ class TimeSheetView(View):
'permission_prefix': self.key,
'render_shift': self.render_shift,
}
context.update(kwargs)
return context
def modify_employees(self, employees, weekdays):
min_time = localtime(self.rattail_config, datetime.datetime.combine(weekdays[0], datetime.time(0)))

View file

@ -64,6 +64,8 @@ class ScheduleView(TimeSheetView):
form = Form(self.request, schema=ShiftFilter)
self.process_filter_form(form)
context = self.get_timesheet_context()
# okay then, maybe process saved shift data
if self.request.method == 'POST':
@ -97,7 +99,10 @@ class ScheduleView(TimeSheetView):
if uuid.startswith('new-'):
shift = model.ScheduledShift()
shift.employee_uuid = data['employee_uuid'][uuid]
shift.store_uuid = data['store_uuid'][uuid]
if 'store_uuid' in data and uuid in data['store_uuid']:
shift.store_uuid = data['store_uuid'][uuid]
else:
shift.store_uuid = context['store'].uuid if context['store'] else None
Session.add(shift)
created[uuid] = shift
else:
@ -114,7 +119,6 @@ class ScheduleView(TimeSheetView):
len(created), len(updated), len(deleted)))
return self.redirect(self.request.route_url('schedule.edit'))
context = self.get_timesheet_context()
context['form'] = form
context['page_title'] = "Edit Schedule"
return self.render_full(**context)

View file

@ -2,7 +2,7 @@
################################################################################
#
# Rattail -- Retail Software Framework
# Copyright © 2010-2016 Lance Edgar
# Copyright © 2010-2017 Lance Edgar
#
# This file is part of Rattail.
#
@ -26,19 +26,103 @@ Views for employee time sheets
from __future__ import unicode_literals, absolute_import
from rattail.db import model
import datetime
from tailbone.views.shifts.lib import TimeSheetView as BaseTimeSheetView
from rattail.db import model
from rattail.time import make_utc, localtime
from pyramid_simpleform import Form
from tailbone.db import Session
from tailbone.views.shifts.lib import TimeSheetView as BaseTimeSheetView, EmployeeShiftFilter
class TimeSheetView(BaseTimeSheetView):
"""
Simple view for current user's time sheet.
Views for employee time sheets, i.e. worked shift data
"""
key = 'timesheet'
title = "Time Sheet"
model_class = model.WorkedShift
def edit_employee(self):
"""
View for editing single employee's timesheet
"""
# process filters; redirect if any were received
form = Form(self.request, schema=EmployeeShiftFilter)
self.process_employee_filter_form(form)
context = self.get_employee_context()
# okay then, maybe process saved shift data
if self.request.method == 'POST':
# TODO: most of this is copied from 'schedule.edit' view, should merge...
# organize form data by uuid / field
fields = ['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]
break
# apply delete operations
deleted = []
for uuid, value in list(data['delete'].items()):
assert value == 'delete'
shift = Session.query(model.WorkedShift).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, time in data['start_time'].iteritems():
if uuid in deleted:
continue
if uuid.startswith('new-'):
shift = model.WorkedShift()
shift.employee_uuid = context['employee'].uuid
# TODO: add support for setting store here...
Session.add(shift)
created[uuid] = shift
else:
shift = Session.query(model.WorkedShift).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 {} Worked Shifts".format(
len(created), len(updated), len(deleted)))
return self.redirect(self.request.route_url('timesheet.employee.edit'))
context['form'] = form
context['page_title'] = "Edit Employee Time Sheet"
return self.render_single(**context)
@classmethod
def defaults(cls, config):
cls._defaults(config)
# edit employee time sheet
config.add_tailbone_permission('timesheet', 'timesheet.edit',
"Edit time sheet (for *any* employee!)")
config.add_route('timesheet.employee.edit', '/timesheeet/employee/edit')
config.add_view(cls, attr='edit_employee', route_name='timesheet.employee.edit',
renderer='/shifts/timesheet_edit.mako',
permission='timesheet.edit')
def includeme(config):
TimeSheetView.defaults(config)