Zeiterfassung update

* Kalender Implementation
* Dienstreise Funktionalitäten
This commit is contained in:
Spitzer Daniel
2024-02-18 11:36:16 +01:00
parent 36b3f1e0f8
commit d03e31134b
11 changed files with 334 additions and 7 deletions

View File

@@ -184,9 +184,9 @@ $years[time() - 31536000] = date('Y', time() - 31536000);
<div class="col-lg-2 mb-2">
<div class="form-check text-center mt-1">
<input class="form-check-input" type="checkbox" name="businesstrip" value=""
id="businesstrip">
<label class="form-check-label" for="businesstrip" value="1">
<input class="form-check-input" type="checkbox" name="businesstrip"
id="businesstrip" value="1">
<label class="form-check-label" for="businesstrip" >
Dienstreise > 12KM
</label>
</div>

View File

@@ -0,0 +1,244 @@
<?php include(realpath(dirname(__FILE__) . "/../../$mfLayoutPackage") . "/header.php");
$daysgerm = array("So", "Mo", "Di", "Mi", "Do", "Fr", "Sa");
?>
<link href="<?= self::getResourcePath() ?>assets/css/select2-cstm.css?<?= date('U') ?>" rel="stylesheet"
type="text/css"/>
<style>
.fc-toolbar {
@media (max-width: 767px) {
flex-direction: column;
.fc-toolbar-chunk {
margin-bottom: 12px;
}
}
}
.fc .fc-button-primary:focus {
box-shadow: none;
}
.fc .fc-button:focus {
box-shadow: none;
outline: 0px;
}
.fc .fc-button-primary:not(:disabled).fc-button-active:focus, .fc .fc-button-primary:not(:disabled):active:focus {
box-shadow: none;
}
</style>
<script type="text/javascript"
src="<?= self::getResourcePath() ?>assets/js/calendar/moment/moment.min.js?<?= date('U') ?>"></script>
<script type="text/javascript"
src="<?= self::getResourcePath() ?>assets/js/calendar/index.global.min.js?<?= date('U') ?>"></script>
<script type="text/javascript"
src="<?= self::getResourcePath() ?>assets/js/calendar/moment/index.global.min.js?<?= date('U') ?>"></script>
<script type="text/javascript"
src="<?= self::getResourcePath() ?>assets/js/calendar/locales-all.global.min.js?<?= date('U') ?>"></script>
<script>
document.addEventListener('DOMContentLoaded', function () {
let requestUrl = "<?= self::getUrl("TimerecordingReport", "api", ['do' => 'getTimerecordings', 'datatype' => '3', 'datayear' => time()]) ?>";
var cindex = 1;
var holiDays = [];
<?php
$counter = 1;
foreach ($timerecordingholidays as $timerecordingholiday) :?>
holiDays.push({
id: <?= $counter ?>,
start: '<?= date("Y-m-d", $timerecordingholiday->timestamp) ?>',
end: '<?= date("Y-m-d", $timerecordingholiday->timestamp) ?>',
title: '<?= $timerecordingholiday->description ?>'
});
<?php
$counter++;
endforeach;
?>
cindex = <?= $counter ?>;
console.log(holiDays);
$.getJSON(requestUrl, function (data) {
}).done(function (json) {
var bgcolors = ['rgba(251, 71, 71,0.2)', 'rgba(253, 126, 20, 0.25)', 'rgba(91, 71, 251, 0.2)', 'rgba(71, 251, 151, 0.2)', 'rgba(251, 71, 194, 0.2)', 'rgba(20, 251, 253, 0.25)'];
//read data from json
var json = json.data;
var holidays = [];
var colorcount = 0;
var oldname = "";
$.each(json, function (index, value) {
if (oldname != value.user.user) {
colorcount++;
if (colorcount > 5) {
colorcount = 0;
}
oldname = value.user.user;
}
holidays.push({
id: cindex,
backgroundColor: bgcolors[colorcount],
start: value.cstart.cstart,
end: value.cend.cend,
title: value.category.category + " " + value.user.user,
description: value.category.category + " " + value.user.user
});
oldname = value.user.user;
cindex++;
});
console.log(holidays);
var initialLocaleCode = 'en';
var calendarEl = document.getElementById('fullcalendar');
var containerEl = document.getElementById('external-events');
var localeSelectorEl = document.getElementById('locale-selector');
var curYear = '2024';
var curMonth = '02';
// Calendar Event Source
// Birthday Events Source
var birthdayEvents = {
id: 1,
backgroundColor: 'rgba(255, 0, 0 , 1)',
borderColor: 'rgba(255, 0, 0 , 1)',
textColor: '#fff',
events: holiDays
};
var holidayEvents = {
id: 5,
backgroundColor: 'rgba(0,204,204,.25)',
borderColor: 'rgb(192 0 255)',
textColor: '#000',
events: holidays
};
var initialLocaleCode = 'en';
var calendarEl = document.getElementById('calendar');
var calendar = new FullCalendar.Calendar(calendarEl, {
locale: 'de',
headerToolbar: {
left: "prev,today,next",
center: 'title',
right: 'dayGridMonth,timeGridWeek,timeGridDay,listMonth'
},
editable: true,
droppable: true, // this allows things to be dropped onto the calendar
fixedWeekCount: true,
// height: 300,
initialView: 'dayGridMonth',
timeZone: 'UTC',
hiddenDays: [],
navLinks: 'true',
events: [],
height: 800,
eventSources: [holidayEvents, birthdayEvents]
});
calendar.render();
});
});
</script>
<style>
.edit-button {
color: #007bff;
cursor: pointer;
}
.approved-open {
background-color: #fdb751 !important;
color: #000;
border-radius: 5px;
}
.approved-closed {
background-color: #96ff68 !important;
color: #000;
border-radius: 5px;
padding-top: 2px;
}
.fa-clock {
color: #ff9b00;
font-size: 15px;
}
.edit-placeholder {
height: 15px;
width: 22px;
display: inline-block;
}
.fa-square-check {
color: #23b900;
font-size: 17px;
vertical-align: middle;
margin-bottom: 2px;
margin-right: 3px;
}
</style>
<link href="<?= self::getResourcePath() ?>assets/css/datatables-std.css?<?= date('U') ?>" rel="stylesheet"
type="text/css"/>
<!-- start page title -->
<div class="row">
<div class="col-12">
<div class="page-title-box">
<div class="page-title-right">
<ol class="breadcrumb m-0">
<li class="breadcrumb-item"><a href="<?= self::getUrl("Dashboard") ?>"><?= MFAPPNAME_SLUG ?></a>
</li>
<li class="breadcrumb-item active">Abwesenheitskalender</li>
</ol>
</div>
<h4 class="page-title">Abwesenheitskalender</h4>
</div>
</div>
</div>
<!-- end page title -->
<div class="card text-center">
<div class="card-body mb-3 ">
<div class="row ">
<div class="col-12">
<div class="float-left">
<h4 class="header-title">Abwesenheitskalender</h4>
</div>
</div>
</div>
<div class="row justify-content-center">
<div class="col-12 col-lg-10">
<div id='calendar'></div>
</div>
</div>
</div>
</div>
</div>
</div>
<script type="text/javascript">
var hidesearch = [2, 3, 4, 8];
var columnfilter = [7];
var columnoptions = '<option value=""></option><option value="Offen">Offen</option><option value="Genehmigt">Genehmigt</option><option value="Abgelehnt">Abgelehnt</option>';
$(document).ready(function () {
});
</script>
<script type="text/javascript"
src="<?= self::getResourcePath() ?>assets/js/datatables-std.js?<?= date('U') ?>"></script>
<?php include(realpath(dirname(__FILE__) . "/../../$mfLayoutPackage") . "/footer.php"); ?>

View File

@@ -38,7 +38,8 @@
<li><a href="<?=self::getUrl("Timerecording")?>"><i class="far fa-fw fa-calendar text-info"></i> Buchungen</a></li>
<?php if ($me->can('Fibu')): ?>
<li><a href="<?=self::getUrl("TimerecordingPermit")?>"><i class="far fa-fw fa-calendar-check text-info"></i> Freigaben</a></li>
<li><a href="<?=self::getUrl("TimerecordingReport")?>"><i class="far fa-fw fa-chart-pie text-info"></i> Auswertungen</a></li>
<li><a href="<?=self::getUrl("TimerecordingCalendar")?>"><i class="far fa-fw fa-calendar-days text-info"></i> Abwesenheitskalender</a></li>
<li><a href="<?=self::getUrl("TimerecordingReport")?>"><i class="far fa-fw fa-chart-pie text-info"></i> Auswertung/Korrektur</a></li>
<li><a href="<?=self::getUrl("TimerecordingCategory")?>"><i class="far fa-fw fa-list text-info"></i> Buchungsarten</a></li>
<li><a href="<?=self::getUrl("TimerecordingHoliday")?>"><i class="far fa-fw fa-umbrella-beach text-info"></i> Feiertage</a></li>
<li><a href="<?=self::getUrl("TimerecordingEmployee")?>"><i class="far fa-fw fa-user text-info"></i> Personaladministration</a></li>

View File

@@ -150,6 +150,22 @@ class TimerecordingController extends mfBaseController
$data['end'] = $endtime;
$data['timerecordingCategory_id'] = trim($r->timerecordingCategory_id);
$data['comment'] = trim($r->comment);
$data['businesstrip'] = $r->businesstrip;
$data['businesstrip_info'] = $r->businesstrip_info;
if (!$data['businesstrip'] || $data['businesstrip'] == "false") {
$data['businesstrip'] = 0;
}
if ($r->businesstrip == 1 && !$r->businesstrip_info) {
$result['state'] = "error";
$result['error'] = "Geschäftsreiseinformationen darf nicht leer sein";
echo json_encode($result);
die();
}
if (!$data['businesstrip_info']) {
$data['businesstrip_info'] = NULL;
}
if (!$data['user_id']) {
$this->layout()->setFlash("Benutzer darf nicht leer sein", "error");

View File

@@ -6,6 +6,8 @@ class TimerecordingModel
private $start;
private $end;
private $timerecordingCategory_id;
private $businesstrip;
private $businesstrip_info;
private $comment;
private $approved;
private $completed;
@@ -122,7 +124,7 @@ class TimerecordingModel
$items = [];
$db = FronkDB::singleton();
$where = self::getSqlFilter($filter);
$res = $db->select("Timerecording", "*", "$where");
$res = $db->select("Timerecording", "*", "$where ");
if ($db->num_rows($res)) {
while ($data = $db->fetch_object($res)) {
$items[] = new Timerecording($data);
@@ -133,7 +135,7 @@ class TimerecordingModel
private static function getSqlFilter($filter)
{
$where = "1=1 ";
$where = "1=1";
if (array_key_exists("user_id", $filter)) {
$userid = $filter['user_id'];
@@ -145,7 +147,7 @@ class TimerecordingModel
$start = $filter['start'];
$end = $filter['end'];
if (is_numeric($start) && is_numeric($end)) {
$where .= " AND `start` > $start AND `start` < $end";
$where .= " AND `start` > $start AND `start` < $end ORDER by user_id ASC";
}
}
if (array_key_exists("starttime", $filter) && array_key_exists("endtime", $filter)) {

View File

@@ -0,0 +1,26 @@
<?php
class TimerecordingCalendarController extends mfBaseController
{
protected function init()
{
$this->needlogin = true;
$me = new User();
$me->loadMe();
$this->me = $me;
$this->layout()->set("me", $me);
if (!$me->can(["Fibu"])) {
$this->redirect("Dashboard");
}
}
protected function indexAction()
{
$timerecordingholidays = TimerecordingHolidayModel::getAll();
$this->layout()->set("timerecordingholidays", $timerecordingholidays);
$this->layout()->setTemplate("TimerecordingCalendar/Index");
}
}

View File

@@ -226,6 +226,8 @@ class TimerecordingReportController extends mfBaseController
'start' => array('start' => $start, 'order' => $start),
'end' => array('end' => $end, 'order' => $end),
'sum' => array('sum' => $sum, 'order' => $sum),
'cstart' => array('cstart' => $datadate, 'order' => $datadate),
'cend' => array('cend' => $enddate, 'order' => $enddate),
'category' => array('category' => $timerecording->timerecordingCategory->name, 'order' => $timerecording->timerecordingCategory->name),
'comment' => array('comment' => $timerecording->comment, 'order' => $timerecording->comment),
'edit' => array('edit' => $edit, 'order' => $edit),

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,6 @@
/*!
FullCalendar Moment Plugin v6.1.10
Docs & License: https://fullcalendar.io/docs/moment-plugin
(c) 2023 Adam Shaw
*/
FullCalendar.Moment=function(e,t,l,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}var r=a(l);function u(e,t,l,n){let a;return"local"===t?a=r.default(e):"UTC"===t?a=r.default.utc(e):r.default.tz?a=r.default.tz(e,t):(a=r.default.utc(e),null!=l&&a.utcOffset(l)),a.locale(n),a}function o(e){return t=>t?e.format(t):""}var d=t.createPlugin({name:"@fullcalendar/moment",cmdFormatter:function(e,t){let l=function e(t){let l=t.match(/^(.*?)\{(.*)\}(.*)$/);if(l){let t=e(l[2]);return{head:l[1],middle:t,tail:l[3],whole:l[1]+t.whole+l[3]}}return{head:null,middle:null,tail:null,whole:t}}(e);if(t.end){let e=u(t.start.array,t.timeZone,t.start.timeZoneOffset,t.localeCodes[0]),n=u(t.end.array,t.timeZone,t.end.timeZoneOffset,t.localeCodes[0]);return function e(t,l,n,a){if(t.middle){let r=l(t.head),u=e(t.middle,l,n,a),o=l(t.tail),d=n(t.head),i=e(t.middle,l,n,a),f=n(t.tail);if(r===d&&o===f)return r+(u===i?u:u+a+i)+o}let r=l(t.whole),u=n(t.whole);if(r===u)return r;return r+a+u}(l,o(e),o(n),t.defaultSeparator)}return u(t.date.array,t.timeZone,t.date.timeZoneOffset,t.localeCodes[0]).format(l.whole)}});return t.globalPlugins.push(d),e.default=d,e.toMoment=function(e,t){if(!(t instanceof n.CalendarImpl))throw new Error("must supply a CalendarApi instance");let{dateEnv:l}=t.getCurrentData();return u(e,l.timeZone,null,l.locale.codes[0])},e.toMomentDuration=function(e){return r.default.duration(e)},Object.defineProperty(e,"__esModule",{value:!0}),e}({},FullCalendar,moment,FullCalendar.Internal);

File diff suppressed because one or more lines are too long

View File

@@ -214,6 +214,18 @@ $(document).ready(function () {
} else {
$('#comment').prop("required", false);
}
if (parseInt($(this).find(':selected').data('businesstrip')) === 1) {
$('#businesstrip-div').show();
$('#businesstrip').prop("checked", false);
$('#businesstrip_info').val('');
$('#businesstrip_info').hide();
} else {
$('#businesstrip-div').hide();
$('#businesstrip').prop("checked", false);
$('#businesstrip_info').val('');
$('#businesstrip_info').hide();
}
});
$("body").on("change", "#date", function () {
if ($('#enddate-div').css('display') === "block") {
@@ -309,6 +321,10 @@ $(document).ready(function () {
$('form').submit(function (e) {
e.preventDefault();
$('#alert-box').remove();
var businesstrip = false;
if ($('#businesstrip').prop('checked') == true) {
businesstrip = 1;
}
$.post(insertUrl, {
id: $.trim($('#id').val()),
timerecordingCategory_id: $.trim($('#timerecordingCategory_id').val()),
@@ -317,6 +333,8 @@ $(document).ready(function () {
start: $.trim($('#start').val()),
end: $.trim($('#end').val()),
comment: $.trim($('#comment').val()),
businesstrip: businesstrip,
businesstrip_info: $.trim($('#businesstrip_info').val()),
ajax: 1
}).done(function (data) {
var result = $.parseJSON(data);
@@ -328,6 +346,10 @@ $(document).ready(function () {
<h5><i class="icon fas fa-check"></i> Erfolgreich</h5>
` + result.message + `</div>
</div>`);
$('#businesstrip').prop('checked', false);
$('#businesstrip_info').hide();
$('#businesstrip_info').val('');
$('#businesstrip_info').prop('required', false);
}
if (result.state === "error") {
$('.wrapper .container-fluid').prepend(`<div id="alert-box" class="row">