217 lines
9.7 KiB
Python
217 lines
9.7 KiB
Python
# -*- coding: utf-8; -*-
|
|
################################################################################
|
|
#
|
|
# Rattail -- Retail Software Framework
|
|
# Copyright © 2010-2018 Lance Edgar
|
|
#
|
|
# This file is part of Rattail.
|
|
#
|
|
# Rattail is free software: you can redistribute it and/or modify it under the
|
|
# terms of the GNU General Public License as published by the Free Software
|
|
# Foundation, either version 3 of the License, or (at your option) any later
|
|
# version.
|
|
#
|
|
# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
# details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License along with
|
|
# Rattail. If not, see <http://www.gnu.org/licenses/>.
|
|
#
|
|
################################################################################
|
|
"""
|
|
Views for employee schedules
|
|
"""
|
|
|
|
from __future__ import unicode_literals, absolute_import
|
|
|
|
import datetime
|
|
|
|
from rattail.db import model
|
|
from rattail.time import localtime, make_utc, get_sunday
|
|
|
|
from tailbone.db import Session
|
|
from tailbone.views.shifts.lib import TimeSheetView
|
|
|
|
|
|
class ScheduleView(TimeSheetView):
|
|
"""
|
|
Simple view for current user's schedule.
|
|
"""
|
|
key = 'schedule'
|
|
model_class = model.ScheduledShift
|
|
|
|
def edit(self):
|
|
"""
|
|
View for editing (full) schedule.
|
|
"""
|
|
# first check if we should clear the schedule
|
|
if self.request.method == 'POST' and self.request.POST.get('clear-schedule') == 'clear':
|
|
count = self.clear_schedule()
|
|
self.request.session.flash("Removed {} shifts from current schedule.".format(count))
|
|
return self.redirect(self.request.route_url('schedule.edit'))
|
|
|
|
# okay then, check if we should copy data from another week
|
|
if self.request.method == 'POST' and self.request.POST.get('copy-week'):
|
|
sunday, copied = self.copy_schedule()
|
|
self.request.session.flash("Copied {} shifts from week of {}".format(copied, sunday.strftime('%m/%d/%Y')))
|
|
return self.redirect(self.request.route_url('schedule.edit'))
|
|
|
|
# okay then, process filters; redirect if any were received
|
|
context = self.get_timesheet_context()
|
|
form = self.make_full_filter_form(context)
|
|
self.process_filter_form(form)
|
|
|
|
# okay then, maybe process saved shift data
|
|
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]
|
|
break
|
|
|
|
# apply delete operations
|
|
deleted = []
|
|
for uuid, value in data['delete'].items():
|
|
if value == 'delete':
|
|
shift = Session.query(model.ScheduledShift).get(uuid)
|
|
if 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'].items():
|
|
if uuid in deleted:
|
|
continue
|
|
if uuid.startswith('new-'):
|
|
shift = model.ScheduledShift()
|
|
shift.employee_uuid = data['employee_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:
|
|
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'))
|
|
|
|
context['form'] = form
|
|
context['page_title'] = "Edit Schedule"
|
|
context['allow_clear'] = self.rattail_config.getbool('tailbone', 'schedule.allow_clear',
|
|
default=True)
|
|
return self.render_full(**context)
|
|
|
|
def clear_schedule(self):
|
|
deleted = 0
|
|
context = self.get_timesheet_context()
|
|
if context['employees']:
|
|
sunday = datetime.datetime.combine(context['date'], datetime.time(0))
|
|
start_time = localtime(self.rattail_config, sunday)
|
|
end_time = localtime(self.rattail_config, sunday + datetime.timedelta(days=7))
|
|
shifts = Session.query(model.ScheduledShift)\
|
|
.filter(model.ScheduledShift.employee_uuid.in_([e.uuid for e in context['employees']]))\
|
|
.filter(model.ScheduledShift.start_time >= make_utc(start_time))\
|
|
.filter(model.ScheduledShift.end_time < make_utc(end_time))
|
|
for shift in shifts:
|
|
Session.delete(shift)
|
|
deleted += 1
|
|
return deleted
|
|
|
|
def copy_schedule(self):
|
|
"""
|
|
Clear current schedule, then copy shift data from another week.
|
|
"""
|
|
try:
|
|
sunday = datetime.datetime.strptime(self.request.POST['copy-week'], '%m/%d/%Y').date()
|
|
except ValueError as error:
|
|
self.request.session.flash("Invalid date specified: {}: {}".format(type(error), error), 'error')
|
|
raise self.redirect(self.request.route_url('schedule.edit'))
|
|
sunday = get_sunday(sunday)
|
|
context = self.get_timesheet_context()
|
|
if sunday == context['date']:
|
|
self.request.session.flash("Cannot copy schedule from same week; please specify a different week.", 'error')
|
|
raise self.redirect(self.request.route_url('schedule.edit'))
|
|
|
|
self.clear_schedule()
|
|
|
|
copied = 0
|
|
if context['employees']:
|
|
offset = context['date'] - sunday
|
|
sunday = datetime.datetime.combine(sunday, datetime.time(0))
|
|
start_time = localtime(self.rattail_config, sunday)
|
|
end_time = localtime(self.rattail_config, sunday + datetime.timedelta(days=7))
|
|
shifts = Session.query(model.ScheduledShift)\
|
|
.filter(model.ScheduledShift.employee_uuid.in_([e.uuid for e in context['employees']]))\
|
|
.filter(model.ScheduledShift.start_time >= make_utc(start_time))\
|
|
.filter(model.ScheduledShift.end_time < make_utc(end_time))
|
|
for shift in shifts:
|
|
|
|
# must calculate new times using date as base, b/c of daylight savings
|
|
start_time = localtime(self.rattail_config, shift.start_time, from_utc=True)
|
|
start_time = datetime.datetime.combine(start_time.date() + offset, start_time.time())
|
|
start_time = localtime(self.rattail_config, start_time)
|
|
end_time = localtime(self.rattail_config, shift.end_time, from_utc=True)
|
|
end_time = datetime.datetime.combine(end_time.date() + offset, end_time.time())
|
|
end_time = localtime(self.rattail_config, end_time)
|
|
|
|
Session.add(model.ScheduledShift(
|
|
employee_uuid=shift.employee_uuid,
|
|
store_uuid=shift.store_uuid,
|
|
start_time=make_utc(start_time),
|
|
end_time=make_utc(end_time),
|
|
))
|
|
copied += 1
|
|
|
|
return sunday, copied
|
|
|
|
@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")
|
|
|
|
# printing "any" schedule requires this permission
|
|
config.add_tailbone_permission('schedule', 'schedule.print', "Print schedule")
|
|
|
|
# print full schedule
|
|
config.add_route('schedule.print', '/schedule/print')
|
|
config.add_view(cls, attr='full', route_name='schedule.print',
|
|
renderer='/shifts/schedule_print.mako',
|
|
permission='schedule.print')
|
|
|
|
# print employee schedule
|
|
config.add_route('schedule.employee.print', '/schedule/employee/print')
|
|
config.add_view(cls, attr='employee', route_name='schedule.employee.print',
|
|
renderer='/shifts/schedule_print_employee.mako',
|
|
permission='schedule.print')
|
|
|
|
|
|
def includeme(config):
|
|
ScheduleView.defaults(config)
|