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:
parent
2e88cdde88
commit
7104e275c3
186
tailbone/static/js/tailbone.edit-shifts.js
Normal file
186
tailbone/static/js/tailbone.edit-shifts.js
Normal file
|
@ -0,0 +1,186 @@
|
||||||
|
|
||||||
|
/************************************************************
|
||||||
|
*
|
||||||
|
* tailbone.edit-shifts.js
|
||||||
|
*
|
||||||
|
* Common logic for editing time sheet / schedule data.
|
||||||
|
*
|
||||||
|
************************************************************/
|
||||||
|
|
||||||
|
|
||||||
|
var editing_day = null;
|
||||||
|
var new_shift_id = 1;
|
||||||
|
|
||||||
|
function add_shift(focus, uuid, start_time, end_time) {
|
||||||
|
var shift = $('#snippets .shift').clone();
|
||||||
|
if (! uuid) {
|
||||||
|
uuid = 'new-' + (new_shift_id++).toString();
|
||||||
|
}
|
||||||
|
shift.attr('data-uuid', uuid);
|
||||||
|
shift.children('input').each(function() {
|
||||||
|
var name = $(this).attr('name') + '-' + uuid;
|
||||||
|
$(this).attr('name', name);
|
||||||
|
$(this).attr('id', name);
|
||||||
|
});
|
||||||
|
shift.children('input[name|="edit_start_time"]').val(start_time || '');
|
||||||
|
shift.children('input[name|="edit_end_time"]').val(end_time || '');
|
||||||
|
$('#day-editor .shifts').append(shift);
|
||||||
|
shift.children('input').timepicker({showPeriod: true});
|
||||||
|
if (focus) {
|
||||||
|
shift.children('input:first').focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function calc_minutes(start_time, end_time) {
|
||||||
|
var start = parseTime(start_time);
|
||||||
|
start = new Date(2000, 0, 1, start.hh, start.mm);
|
||||||
|
var end = parseTime(end_time);
|
||||||
|
end = new Date(2000, 0, 1, end.hh, end.mm);
|
||||||
|
return Math.floor((end - start) / 1000 / 60);
|
||||||
|
}
|
||||||
|
|
||||||
|
function format_minutes(minutes) {
|
||||||
|
var hours = Math.floor(minutes / 60);
|
||||||
|
if (hours) {
|
||||||
|
minutes -= hours * 60;
|
||||||
|
}
|
||||||
|
return hours.toString() + ':' + (minutes < 10 ? '0' : '') + minutes.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// stolen from http://stackoverflow.com/a/1788084
|
||||||
|
function parseTime(s) {
|
||||||
|
var part = s.match(/(\d+):(\d+)(?: )?(am|pm)?/i);
|
||||||
|
var hh = parseInt(part[1], 10);
|
||||||
|
var mm = parseInt(part[2], 10);
|
||||||
|
var ap = part[3] ? part[3].toUpperCase() : null;
|
||||||
|
if (ap == 'AM') {
|
||||||
|
if (hh == 12) {
|
||||||
|
hh = 0;
|
||||||
|
}
|
||||||
|
} else if (ap == 'PM') {
|
||||||
|
if (hh != 12) {
|
||||||
|
hh += 12;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { hh: hh, mm: mm };
|
||||||
|
}
|
||||||
|
|
||||||
|
function time_input(shift, type) {
|
||||||
|
var input = shift.children('input[name|="' + type + '_time"]');
|
||||||
|
if (! input.length) {
|
||||||
|
input = $('<input type="hidden" name="' + type + '_time-' + shift.data('uuid') + '" />');
|
||||||
|
shift.append(input);
|
||||||
|
}
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
function update_row_hours(row) {
|
||||||
|
var minutes = 0;
|
||||||
|
row.find('.day .shift:not(.deleted)').each(function() {
|
||||||
|
var time_range = $.trim($(this).children('span').text()).split(' - ');
|
||||||
|
minutes += calc_minutes(time_range[0], time_range[1]);
|
||||||
|
});
|
||||||
|
row.children('.total').text(minutes ? format_minutes(minutes) : '0');
|
||||||
|
}
|
||||||
|
|
||||||
|
$(function() {
|
||||||
|
|
||||||
|
$('.timesheet').on('click', '.day', function() {
|
||||||
|
editing_day = $(this);
|
||||||
|
var editor = $('#day-editor');
|
||||||
|
var employee = editing_day.siblings('.employee').text();
|
||||||
|
var date = weekdays[editing_day.get(0).cellIndex - 1];
|
||||||
|
var shifts = editor.children('.shifts');
|
||||||
|
shifts.empty();
|
||||||
|
editing_day.children('.shift:not(.deleted)').each(function() {
|
||||||
|
var uuid = $(this).data('uuid');
|
||||||
|
var time_range = $.trim($(this).children('span').text()).split(' - ');
|
||||||
|
add_shift(false, uuid, time_range[0], time_range[1]);
|
||||||
|
});
|
||||||
|
if (! shifts.children('.shift').length) {
|
||||||
|
add_shift();
|
||||||
|
}
|
||||||
|
editor.dialog({
|
||||||
|
modal: true,
|
||||||
|
title: employee + ' - ' + date,
|
||||||
|
position: {my: 'center', at: 'center', of: editing_day},
|
||||||
|
width: 'auto',
|
||||||
|
autoResize: true,
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
text: "Update",
|
||||||
|
click: function() {
|
||||||
|
|
||||||
|
// TODO: need to validate times here...
|
||||||
|
|
||||||
|
// create / update shifts in time table, as needed
|
||||||
|
editor.find('.shifts .shift').each(function() {
|
||||||
|
var uuid = $(this).data('uuid');
|
||||||
|
var start_time = $(this).children('input[name|="edit_start_time"]').val();
|
||||||
|
var end_time = $(this).children('input[name|="edit_end_time"]').val();
|
||||||
|
var shift = editing_day.children('.shift[data-uuid="' + uuid + '"]');
|
||||||
|
if (! shift.length) {
|
||||||
|
shift = $('<p class="shift" data-uuid="' + uuid + '"><span></span></p>');
|
||||||
|
shift.append($('<input type="hidden" name="employee_uuid-' + uuid + '" value="'
|
||||||
|
+ editing_day.parents('tr:first').data('employee-uuid') + '" />'));
|
||||||
|
editing_day.append(shift);
|
||||||
|
}
|
||||||
|
shift.children('span').text(start_time + ' - ' + end_time);
|
||||||
|
time_input(shift, 'start').val(date + ' ' + start_time);
|
||||||
|
time_input(shift, 'end').val(date + ' ' + end_time);
|
||||||
|
});
|
||||||
|
|
||||||
|
// remove shifts from time table, as needed
|
||||||
|
editing_day.children('.shift').each(function() {
|
||||||
|
var uuid = $(this).data('uuid');
|
||||||
|
if (! editor.find('.shifts .shift[data-uuid="' + uuid + '"]').length) {
|
||||||
|
if (uuid.match(/^new-/)) {
|
||||||
|
$(this).remove();
|
||||||
|
} else {
|
||||||
|
$(this).addClass('deleted');
|
||||||
|
$(this).append($('<input type="hidden" name="delete-' + uuid + '" value="delete" />'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// mark day as modified, close dialog
|
||||||
|
editing_day.addClass('modified');
|
||||||
|
$('.save-changes').button('enable');
|
||||||
|
$('.undo-changes').button('enable');
|
||||||
|
update_row_hours(editing_day.parents('tr:first'));
|
||||||
|
editor.dialog('close');
|
||||||
|
data_modified = true;
|
||||||
|
okay_to_leave = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "Cancel",
|
||||||
|
click: function() {
|
||||||
|
editor.dialog('close');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#day-editor #add-shift').click(function() {
|
||||||
|
add_shift(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#day-editor').on('click', '.shifts button', function() {
|
||||||
|
$(this).parents('.shift:first').remove();
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.save-changes').click(function() {
|
||||||
|
$(this).button('disable').button('option', 'label', "Saving Changes...");
|
||||||
|
okay_to_leave = true;
|
||||||
|
$('#timetable-form').submit();
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.undo-changes').click(function() {
|
||||||
|
$(this).button('disable').button('option', 'label', "Refreshing...");
|
||||||
|
okay_to_leave = true;
|
||||||
|
location.href = location.href;
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
|
@ -259,24 +259,6 @@ $(function() {
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/*
|
|
||||||
* Whenever the "change" button is clicked within the context of an
|
|
||||||
* autocomplete field, hide the static display and show the autocomplete
|
|
||||||
* textbox.
|
|
||||||
*/
|
|
||||||
$('div.autocomplete-container button.autocomplete-change').click(function() {
|
|
||||||
var container = $(this).parents('div.autocomplete-container');
|
|
||||||
var textbox = container.find('input.autocomplete-textbox');
|
|
||||||
|
|
||||||
container.find('input[type="hidden"]').val('');
|
|
||||||
container.find('div.autocomplete-display').hide();
|
|
||||||
|
|
||||||
textbox.val('');
|
|
||||||
textbox.show();
|
|
||||||
textbox.select();
|
|
||||||
textbox.focus();
|
|
||||||
});
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Add "check all" functionality to tables with checkboxes.
|
* Add "check all" functionality to tables with checkboxes.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
## -*- coding: utf-8 -*-
|
## -*- coding: utf-8 -*-
|
||||||
## TODO: This function signature is getting out of hand...
|
## TODO: This function signature is getting out of hand...
|
||||||
<%def name="autocomplete(field_name, service_url, field_value=None, field_display=None, width='300px', select=None, selected=None, cleared=None, options={})">
|
<%def name="autocomplete(field_name, service_url, field_value=None, field_display=None, width='300px', select=None, selected=None, cleared=None, change_clicked=None, options={})">
|
||||||
<div id="${field_name}-container" class="autocomplete-container">
|
<div id="${field_name}-container" class="autocomplete-container">
|
||||||
${h.hidden(field_name, id=field_name, value=field_value)}
|
${h.hidden(field_name, id=field_name, value=field_value)}
|
||||||
${h.text(field_name+'-textbox', id=field_name+'-textbox', value=field_display,
|
${h.text(field_name+'-textbox', id=field_name+'-textbox', value=field_display,
|
||||||
|
@ -37,6 +37,11 @@
|
||||||
% endif
|
% endif
|
||||||
});
|
});
|
||||||
$('#${field_name}-change').click(function() {
|
$('#${field_name}-change').click(function() {
|
||||||
|
% if change_clicked:
|
||||||
|
if (! ${change_clicked}()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
% endif
|
||||||
$('#${field_name}').val('');
|
$('#${field_name}').val('');
|
||||||
$('#${field_name}-display').hide();
|
$('#${field_name}-display').hide();
|
||||||
with ($('#${field_name}-textbox')) {
|
with ($('#${field_name}-textbox')) {
|
||||||
|
|
|
@ -3,9 +3,8 @@
|
||||||
|
|
||||||
<%def name="title()">${page_title}</%def>
|
<%def name="title()">${page_title}</%def>
|
||||||
|
|
||||||
<%def name="head_tags()">
|
<%def name="extra_javascript()">
|
||||||
${parent.head_tags()}
|
${parent.extra_javascript()}
|
||||||
${h.stylesheet_link(request.static_url('tailbone:static/css/timesheet.css'))}
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
|
|
||||||
var data_modified = false;
|
var data_modified = false;
|
||||||
|
@ -82,6 +81,55 @@
|
||||||
</script>
|
</script>
|
||||||
</%def>
|
</%def>
|
||||||
|
|
||||||
|
<%def name="extra_styles()">
|
||||||
|
${parent.extra_styles()}
|
||||||
|
${h.stylesheet_link(request.static_url('tailbone:static/css/timesheet.css'))}
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
<%def name="edit_timetable_javascript()">
|
||||||
|
${h.javascript_link(request.static_url('tailbone:static/js/tailbone.edit-shifts.js'))}
|
||||||
|
<script type="text/javascript">
|
||||||
|
|
||||||
|
var weekdays = [
|
||||||
|
% for i, day in enumerate(weekdays, 1):
|
||||||
|
'${day.strftime('%a %d %b %Y')}'${',' if i < len(weekdays) else ''}
|
||||||
|
% endfor
|
||||||
|
];
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
<%def name="edit_timetable_styles()">
|
||||||
|
<style type="text/css">
|
||||||
|
.timesheet .day {
|
||||||
|
cursor: pointer;
|
||||||
|
height: 5em;
|
||||||
|
}
|
||||||
|
.timesheet tr .day.modified {
|
||||||
|
background-color: #fcc;
|
||||||
|
}
|
||||||
|
.timesheet tr:nth-child(odd) .day.modified {
|
||||||
|
background-color: #ebb;
|
||||||
|
}
|
||||||
|
.timesheet .day .shift.deleted {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
#day-editor .shift {
|
||||||
|
margin-bottom: 1em;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
#day-editor .shift input {
|
||||||
|
width: 6em;
|
||||||
|
}
|
||||||
|
#day-editor .shift button {
|
||||||
|
margin-left: 0.5em;
|
||||||
|
}
|
||||||
|
#snippets {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</%def>
|
||||||
|
|
||||||
<%def name="context_menu()"></%def>
|
<%def name="context_menu()"></%def>
|
||||||
|
|
||||||
<%def name="render_day(day)">
|
<%def name="render_day(day)">
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
## -*- coding: utf-8 -*-
|
## -*- coding: utf-8 -*-
|
||||||
<%namespace file="/autocomplete.mako" import="autocomplete" />
|
<%namespace file="/autocomplete.mako" import="autocomplete" />
|
||||||
|
|
||||||
<%def name="timesheet_wrapper(edit_form=None, edit_tools=None, context_menu=None, render_day=None)">
|
<%def name="timesheet_wrapper(edit_form=None, edit_tools=None, context_menu=None, render_day=None, change_employee=None)">
|
||||||
<div class="timesheet-wrapper">
|
<div class="timesheet-wrapper">
|
||||||
|
|
||||||
${form.begin(id='filter-form')}
|
${form.begin(id='filter-form')}
|
||||||
|
@ -21,7 +21,8 @@
|
||||||
${autocomplete('employee', url('employees.autocomplete'),
|
${autocomplete('employee', url('employees.autocomplete'),
|
||||||
field_value=employee.uuid if employee else None,
|
field_value=employee.uuid if employee else None,
|
||||||
field_display=unicode(employee or ''),
|
field_display=unicode(employee or ''),
|
||||||
selected='employee_selected')}
|
selected='employee_selected',
|
||||||
|
change_clicked=change_employee)}
|
||||||
% else:
|
% else:
|
||||||
${form.hidden('employee', value=employee.uuid)}
|
${form.hidden('employee', value=employee.uuid)}
|
||||||
${employee}
|
${employee}
|
||||||
|
@ -111,7 +112,10 @@
|
||||||
<tbody>
|
<tbody>
|
||||||
% for emp in sorted(employees, key=unicode):
|
% for emp in sorted(employees, key=unicode):
|
||||||
<tr data-employee-uuid="${emp.uuid}">
|
<tr data-employee-uuid="${emp.uuid}">
|
||||||
<td class="employee">${emp}</td>
|
<td class="employee">
|
||||||
|
## TODO: add link to single employee schedule / timesheet here...
|
||||||
|
${emp}
|
||||||
|
</td>
|
||||||
% for day in emp.weekdays:
|
% for day in emp.weekdays:
|
||||||
<td class="day">
|
<td class="day">
|
||||||
% if render_day:
|
% if render_day:
|
||||||
|
|
|
@ -2,195 +2,13 @@
|
||||||
<%inherit file="/shifts/base.mako" />
|
<%inherit file="/shifts/base.mako" />
|
||||||
<%namespace file="/shifts/lib.mako" import="timesheet_wrapper" />
|
<%namespace file="/shifts/lib.mako" import="timesheet_wrapper" />
|
||||||
|
|
||||||
<%def name="head_tags()">
|
<%def name="extra_javascript()">
|
||||||
${parent.head_tags()}
|
${parent.extra_javascript()}
|
||||||
|
${self.edit_timetable_javascript()}
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
|
|
||||||
var weekdays = [
|
|
||||||
% for i, day in enumerate(weekdays, 1):
|
|
||||||
'${day.strftime('%a %d %b %Y')}'${',' if i < len(weekdays) else ''}
|
|
||||||
% endfor
|
|
||||||
];
|
|
||||||
|
|
||||||
var editing_day = null;
|
|
||||||
var new_shift_id = 1;
|
|
||||||
|
|
||||||
function add_shift(focus, uuid, start_time, end_time) {
|
|
||||||
var shift = $('#snippets .shift').clone();
|
|
||||||
if (! uuid) {
|
|
||||||
uuid = 'new-' + (new_shift_id++).toString();
|
|
||||||
}
|
|
||||||
shift.attr('data-uuid', uuid);
|
|
||||||
shift.children('input').each(function() {
|
|
||||||
var name = $(this).attr('name') + '-' + uuid;
|
|
||||||
$(this).attr('name', name);
|
|
||||||
$(this).attr('id', name);
|
|
||||||
});
|
|
||||||
shift.children('input[name|="edit_start_time"]').val(start_time || '');
|
|
||||||
shift.children('input[name|="edit_end_time"]').val(end_time || '');
|
|
||||||
$('#day-editor .shifts').append(shift);
|
|
||||||
shift.children('input').timepicker({showPeriod: true});
|
|
||||||
if (focus) {
|
|
||||||
shift.children('input:first').focus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function calc_minutes(start_time, end_time) {
|
|
||||||
var start = parseTime(start_time);
|
|
||||||
start = new Date(2000, 0, 1, start.hh, start.mm);
|
|
||||||
var end = parseTime(end_time);
|
|
||||||
end = new Date(2000, 0, 1, end.hh, end.mm);
|
|
||||||
return Math.floor((end - start) / 1000 / 60);
|
|
||||||
}
|
|
||||||
|
|
||||||
function format_minutes(minutes) {
|
|
||||||
var hours = Math.floor(minutes / 60);
|
|
||||||
if (hours) {
|
|
||||||
minutes -= hours * 60;
|
|
||||||
}
|
|
||||||
return hours.toString() + ':' + (minutes < 10 ? '0' : '') + minutes.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
// stolen from http://stackoverflow.com/a/1788084
|
|
||||||
function parseTime(s) {
|
|
||||||
var part = s.match(/(\d+):(\d+)(?: )?(am|pm)?/i);
|
|
||||||
var hh = parseInt(part[1], 10);
|
|
||||||
var mm = parseInt(part[2], 10);
|
|
||||||
var ap = part[3] ? part[3].toUpperCase() : null;
|
|
||||||
if (ap == 'AM') {
|
|
||||||
if (hh == 12) {
|
|
||||||
hh = 0;
|
|
||||||
}
|
|
||||||
} else if (ap == 'PM') {
|
|
||||||
if (hh != 12) {
|
|
||||||
hh += 12;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return { hh: hh, mm: mm };
|
|
||||||
}
|
|
||||||
|
|
||||||
function time_input(shift, type) {
|
|
||||||
var input = shift.children('input[name|="' + type + '_time"]');
|
|
||||||
if (! input.length) {
|
|
||||||
input = $('<input type="hidden" name="' + type + '_time-' + shift.data('uuid') + '" />');
|
|
||||||
shift.append(input);
|
|
||||||
}
|
|
||||||
return input;
|
|
||||||
}
|
|
||||||
|
|
||||||
function update_row_hours(row) {
|
|
||||||
var minutes = 0;
|
|
||||||
row.find('.day .shift:not(.deleted)').each(function() {
|
|
||||||
var time_range = $.trim($(this).children('span').text()).split(' - ');
|
|
||||||
minutes += calc_minutes(time_range[0], time_range[1]);
|
|
||||||
});
|
|
||||||
row.children('.total').text(minutes ? format_minutes(minutes) : '0');
|
|
||||||
}
|
|
||||||
|
|
||||||
$(function() {
|
$(function() {
|
||||||
|
|
||||||
$('.timesheet').on('click', '.day', function() {
|
|
||||||
editing_day = $(this);
|
|
||||||
var editor = $('#day-editor');
|
|
||||||
var employee = editing_day.siblings('.employee').text();
|
|
||||||
var date = weekdays[editing_day.get(0).cellIndex - 1];
|
|
||||||
var shifts = editor.children('.shifts');
|
|
||||||
shifts.empty();
|
|
||||||
editing_day.children('.shift:not(.deleted)').each(function() {
|
|
||||||
var uuid = $(this).data('uuid');
|
|
||||||
var time_range = $.trim($(this).children('span').text()).split(' - ');
|
|
||||||
add_shift(false, uuid, time_range[0], time_range[1]);
|
|
||||||
});
|
|
||||||
if (! shifts.children('.shift').length) {
|
|
||||||
add_shift();
|
|
||||||
}
|
|
||||||
editor.dialog({
|
|
||||||
modal: true,
|
|
||||||
title: employee + ' - ' + date,
|
|
||||||
position: {my: 'center', at: 'center', of: editing_day},
|
|
||||||
width: 'auto',
|
|
||||||
autoResize: true,
|
|
||||||
buttons: [
|
|
||||||
{
|
|
||||||
text: "Update",
|
|
||||||
click: function() {
|
|
||||||
|
|
||||||
// TODO: need to validate times here...
|
|
||||||
|
|
||||||
// create / update shifts in schedule table, as needed
|
|
||||||
editor.find('.shifts .shift').each(function() {
|
|
||||||
var uuid = $(this).data('uuid');
|
|
||||||
var start_time = $(this).children('input[name|="edit_start_time"]').val();
|
|
||||||
var end_time = $(this).children('input[name|="edit_end_time"]').val();
|
|
||||||
var shift = editing_day.children('.shift[data-uuid="' + uuid + '"]');
|
|
||||||
if (! shift.length) {
|
|
||||||
shift = $('<p class="shift" data-uuid="' + uuid + '"><span></span></p>');
|
|
||||||
shift.append($('<input type="hidden" name="employee_uuid-' + uuid + '" value="'
|
|
||||||
+ editing_day.parents('tr:first').data('employee-uuid') + '" />'));
|
|
||||||
## TODO: how to handle editing schedule w/ no store selected..?
|
|
||||||
% if store:
|
|
||||||
shift.append($('<input type="hidden" name="store_uuid-' + uuid + '" value="${store.uuid}" />'));
|
|
||||||
% endif
|
|
||||||
editing_day.append(shift);
|
|
||||||
}
|
|
||||||
shift.children('span').text(start_time + ' - ' + end_time);
|
|
||||||
time_input(shift, 'start').val(date + ' ' + start_time);
|
|
||||||
time_input(shift, 'end').val(date + ' ' + end_time);
|
|
||||||
});
|
|
||||||
|
|
||||||
// remove shifts from schedule table, as needed
|
|
||||||
editing_day.children('.shift').each(function() {
|
|
||||||
var uuid = $(this).data('uuid');
|
|
||||||
if (! editor.find('.shifts .shift[data-uuid="' + uuid + '"]').length) {
|
|
||||||
if (uuid.match(/^new-/)) {
|
|
||||||
$(this).remove();
|
|
||||||
} else {
|
|
||||||
$(this).addClass('deleted');
|
|
||||||
$(this).append($('<input type="hidden" name="delete-' + uuid + '" value="delete" />'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// mark day as modified, close dialog
|
|
||||||
editing_day.addClass('modified');
|
|
||||||
$('.save-changes').button('enable');
|
|
||||||
$('.undo-changes').button('enable');
|
|
||||||
update_row_hours(editing_day.parents('tr:first'));
|
|
||||||
editor.dialog('close');
|
|
||||||
data_modified = true;
|
|
||||||
okay_to_leave = false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: "Cancel",
|
|
||||||
click: function() {
|
|
||||||
editor.dialog('close');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
$('#day-editor #add-shift').click(function() {
|
|
||||||
add_shift(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
$('#day-editor').on('click', '.shifts button', function() {
|
|
||||||
$(this).parents('.shift:first').remove();
|
|
||||||
});
|
|
||||||
|
|
||||||
$('.save-changes').click(function() {
|
|
||||||
$(this).button('disable').button('option', 'label', "Saving Changes...");
|
|
||||||
okay_to_leave = true;
|
|
||||||
$('#schedule-form').submit();
|
|
||||||
});
|
|
||||||
|
|
||||||
$('.undo-changes').click(function() {
|
|
||||||
$(this).button('disable').button('option', 'label', "Refreshing...");
|
|
||||||
okay_to_leave = true;
|
|
||||||
location.href = '${url('schedule.edit')}';
|
|
||||||
});
|
|
||||||
|
|
||||||
$('.clear-schedule').click(function() {
|
$('.clear-schedule').click(function() {
|
||||||
if (confirm("This will remove all shifts from the schedule you're " +
|
if (confirm("This will remove all shifts from the schedule you're " +
|
||||||
"currently viewing.\n\nAre you sure you wish to do this?")) {
|
"currently viewing.\n\nAre you sure you wish to do this?")) {
|
||||||
|
@ -234,34 +52,11 @@
|
||||||
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<style type="text/css">
|
</%def>
|
||||||
.timesheet .day {
|
|
||||||
cursor: pointer;
|
<%def name="extra_styles()">
|
||||||
height: 5em;
|
${parent.extra_styles()}
|
||||||
}
|
${self.edit_timetable_styles()}
|
||||||
.timesheet tr .day.modified {
|
|
||||||
background-color: #fcc;
|
|
||||||
}
|
|
||||||
.timesheet tr:nth-child(odd) .day.modified {
|
|
||||||
background-color: #ebb;
|
|
||||||
}
|
|
||||||
.timesheet .day .shift.deleted {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
#day-editor .shift {
|
|
||||||
margin-bottom: 1em;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
#day-editor .shift input {
|
|
||||||
width: 6em;
|
|
||||||
}
|
|
||||||
#day-editor .shift button {
|
|
||||||
margin-left: 0.5em;
|
|
||||||
}
|
|
||||||
#snippets {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</%def>
|
</%def>
|
||||||
|
|
||||||
<%def name="context_menu()">
|
<%def name="context_menu()">
|
||||||
|
@ -282,7 +77,7 @@
|
||||||
</%def>
|
</%def>
|
||||||
|
|
||||||
<%def name="edit_form()">
|
<%def name="edit_form()">
|
||||||
${h.form(url('schedule.edit'), id='schedule-form')}
|
${h.form(url('schedule.edit'), id='timetable-form')}
|
||||||
${h.csrf_token(request)}
|
${h.csrf_token(request)}
|
||||||
</%def>
|
</%def>
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,9 @@
|
||||||
<%namespace file="/shifts/lib.mako" import="timesheet_wrapper" />
|
<%namespace file="/shifts/lib.mako" import="timesheet_wrapper" />
|
||||||
|
|
||||||
<%def name="context_menu()">
|
<%def name="context_menu()">
|
||||||
|
% if employee is not Undefined and request.has_perm('timesheet.edit'):
|
||||||
|
<li>${h.link_to("Edit this Time Sheet", url('timesheet.employee.edit'))}</li>
|
||||||
|
% endif
|
||||||
% 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>
|
||||||
% endif
|
% endif
|
||||||
|
|
58
tailbone/templates/shifts/timesheet_edit.mako
Normal file
58
tailbone/templates/shifts/timesheet_edit.mako
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
## -*- coding: utf-8 -*-
|
||||||
|
<%inherit file="/shifts/base.mako" />
|
||||||
|
<%namespace file="/shifts/lib.mako" import="timesheet_wrapper" />
|
||||||
|
|
||||||
|
<%def name="extra_javascript()">
|
||||||
|
${parent.extra_javascript()}
|
||||||
|
${self.edit_timetable_javascript()}
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
<%def name="extra_styles()">
|
||||||
|
${parent.extra_styles()}
|
||||||
|
${self.edit_timetable_styles()}
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
<%def name="context_menu()">
|
||||||
|
% if request.has_perm('timesheet.view'):
|
||||||
|
<li>${h.link_to("View this Time Sheet", url('timesheet.employee'))}</li>
|
||||||
|
% endif
|
||||||
|
% if request.has_perm('schedule.view'):
|
||||||
|
<li>${h.link_to("View this Schedule", url('schedule.employee'))}</li>
|
||||||
|
% endif
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
<%def name="render_day(day)">
|
||||||
|
% for shift in day['shifts']:
|
||||||
|
<p class="shift" data-uuid="${shift.uuid}">
|
||||||
|
${render_shift(shift)}
|
||||||
|
</p>
|
||||||
|
% endfor
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
<%def name="edit_form()">
|
||||||
|
${h.form(url('timesheet.employee.edit'), id='timetable-form')}
|
||||||
|
${h.csrf_token(request)}
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
<%def name="edit_tools()">
|
||||||
|
<div class="buttons">
|
||||||
|
<button type="button" class="save-changes" disabled="disabled">Save Changes</button>
|
||||||
|
<button type="button" class="undo-changes" disabled="disabled">Undo Changes</button>
|
||||||
|
</div>
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
${timesheet_wrapper(edit_form=edit_form, edit_tools=edit_tools, context_menu=context_menu, render_day=render_day, change_employee='confirm_leave')}
|
||||||
|
|
||||||
|
${edit_tools()}
|
||||||
|
|
||||||
|
<div id="day-editor" style="display: none;">
|
||||||
|
<div class="shifts"></div>
|
||||||
|
<button type="button" id="add-shift">Add Shift</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="snippets">
|
||||||
|
<div class="shift" data-uuid="">
|
||||||
|
${h.text('edit_start_time')} thru ${h.text('edit_end_time')}
|
||||||
|
<button type="button"><span class="ui-icon ui-icon-trash"></span></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -2,7 +2,7 @@
|
||||||
################################################################################
|
################################################################################
|
||||||
#
|
#
|
||||||
# Rattail -- Retail Software Framework
|
# Rattail -- Retail Software Framework
|
||||||
# Copyright © 2010-2016 Lance Edgar
|
# Copyright © 2010-2017 Lance Edgar
|
||||||
#
|
#
|
||||||
# This file is part of Rattail.
|
# This file is part of Rattail.
|
||||||
#
|
#
|
||||||
|
@ -121,6 +121,37 @@ class TimeSheetView(View):
|
||||||
'employees': employees.all(),
|
'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):
|
def process_filter_form(self, form):
|
||||||
"""
|
"""
|
||||||
Process a "shift filter" form if one was in fact POST'ed. If it was
|
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
|
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())
|
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):
|
def full(self):
|
||||||
"""
|
"""
|
||||||
View a "full" timesheet/schedule, i.e. all employees but filterable by
|
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.
|
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)
|
form = Form(self.request, schema=EmployeeShiftFilter)
|
||||||
if self.request.method == 'POST':
|
self.process_employee_filter_form(form)
|
||||||
if form.validate():
|
context = self.get_employee_context()
|
||||||
if self.request.has_perm('{}.viewall'.format(self.key)):
|
context['form'] = form
|
||||||
employee = form.data['employee']
|
return self.render_single(**context)
|
||||||
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)
|
|
||||||
|
|
||||||
def crossview(self):
|
def crossview(self):
|
||||||
"""
|
"""
|
||||||
|
@ -216,17 +221,6 @@ class TimeSheetView(View):
|
||||||
self.session_put('date', self.session_get('date'), mainkey=other_key)
|
self.session_put('date', self.session_get('date'), mainkey=other_key)
|
||||||
return self.redirect(self.request.route_url(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):
|
def session_get(self, key, mainkey=None):
|
||||||
if mainkey is None:
|
if mainkey is None:
|
||||||
mainkey = self.key
|
mainkey = self.key
|
||||||
|
@ -301,7 +295,7 @@ class TimeSheetView(View):
|
||||||
def render_shift(self, shift):
|
def render_shift(self, shift):
|
||||||
return HTML.tag('span', c=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):
|
def render_single(self, date=None, employee=None, form=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
Render a time sheet for one employee, for the week which includes the
|
Render a time sheet for one employee, for the week which includes the
|
||||||
specified date.
|
specified date.
|
||||||
|
@ -319,7 +313,7 @@ class TimeSheetView(View):
|
||||||
|
|
||||||
self.modify_employees([employee], weekdays)
|
self.modify_employees([employee], weekdays)
|
||||||
|
|
||||||
return {
|
context = {
|
||||||
'page_title': "Employee {}".format(self.get_title()),
|
'page_title': "Employee {}".format(self.get_title()),
|
||||||
'form': forms.FormRenderer(form) if form else None,
|
'form': forms.FormRenderer(form) if form else None,
|
||||||
'employee': employee,
|
'employee': employee,
|
||||||
|
@ -332,6 +326,8 @@ class TimeSheetView(View):
|
||||||
'permission_prefix': self.key,
|
'permission_prefix': self.key,
|
||||||
'render_shift': self.render_shift,
|
'render_shift': self.render_shift,
|
||||||
}
|
}
|
||||||
|
context.update(kwargs)
|
||||||
|
return context
|
||||||
|
|
||||||
def modify_employees(self, employees, weekdays):
|
def modify_employees(self, employees, weekdays):
|
||||||
min_time = localtime(self.rattail_config, datetime.datetime.combine(weekdays[0], datetime.time(0)))
|
min_time = localtime(self.rattail_config, datetime.datetime.combine(weekdays[0], datetime.time(0)))
|
||||||
|
|
|
@ -64,6 +64,8 @@ class ScheduleView(TimeSheetView):
|
||||||
form = Form(self.request, schema=ShiftFilter)
|
form = Form(self.request, schema=ShiftFilter)
|
||||||
self.process_filter_form(form)
|
self.process_filter_form(form)
|
||||||
|
|
||||||
|
context = self.get_timesheet_context()
|
||||||
|
|
||||||
# okay then, maybe process saved shift data
|
# okay then, maybe process saved shift data
|
||||||
if self.request.method == 'POST':
|
if self.request.method == 'POST':
|
||||||
|
|
||||||
|
@ -97,7 +99,10 @@ class ScheduleView(TimeSheetView):
|
||||||
if uuid.startswith('new-'):
|
if uuid.startswith('new-'):
|
||||||
shift = model.ScheduledShift()
|
shift = model.ScheduledShift()
|
||||||
shift.employee_uuid = data['employee_uuid'][uuid]
|
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)
|
Session.add(shift)
|
||||||
created[uuid] = shift
|
created[uuid] = shift
|
||||||
else:
|
else:
|
||||||
|
@ -114,7 +119,6 @@ class ScheduleView(TimeSheetView):
|
||||||
len(created), len(updated), len(deleted)))
|
len(created), len(updated), len(deleted)))
|
||||||
return self.redirect(self.request.route_url('schedule.edit'))
|
return self.redirect(self.request.route_url('schedule.edit'))
|
||||||
|
|
||||||
context = self.get_timesheet_context()
|
|
||||||
context['form'] = form
|
context['form'] = form
|
||||||
context['page_title'] = "Edit Schedule"
|
context['page_title'] = "Edit Schedule"
|
||||||
return self.render_full(**context)
|
return self.render_full(**context)
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
################################################################################
|
################################################################################
|
||||||
#
|
#
|
||||||
# Rattail -- Retail Software Framework
|
# Rattail -- Retail Software Framework
|
||||||
# Copyright © 2010-2016 Lance Edgar
|
# Copyright © 2010-2017 Lance Edgar
|
||||||
#
|
#
|
||||||
# This file is part of Rattail.
|
# This file is part of Rattail.
|
||||||
#
|
#
|
||||||
|
@ -26,19 +26,103 @@ Views for employee time sheets
|
||||||
|
|
||||||
from __future__ import unicode_literals, absolute_import
|
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):
|
class TimeSheetView(BaseTimeSheetView):
|
||||||
"""
|
"""
|
||||||
Simple view for current user's time sheet.
|
Views for employee time sheets, i.e. worked shift data
|
||||||
"""
|
"""
|
||||||
key = 'timesheet'
|
key = 'timesheet'
|
||||||
title = "Time Sheet"
|
title = "Time Sheet"
|
||||||
model_class = model.WorkedShift
|
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):
|
def includeme(config):
|
||||||
TimeSheetView.defaults(config)
|
TimeSheetView.defaults(config)
|
||||||
|
|
Loading…
Reference in a new issue