Zeiterfassung

* Feature Implementation Arbeitszeitänderungen mit History für alle möglichen historischen Berechnungen und Auswertung
 * neue Migration
This commit is contained in:
Daniel Spitzer
2025-02-07 12:27:19 +01:00
parent 3c4cba8562
commit c38a9919b6
6 changed files with 603 additions and 2 deletions

View File

@@ -4,6 +4,7 @@ foreach ($days as $key => $day) {
$daysSelect .= '<option value="' . $key . '">' . $day . '</option>'; $daysSelect .= '<option value="' . $key . '">' . $day . '</option>';
} }
$daysSelect .= "</select>"; $daysSelect .= "</select>";
?> ?>
<script> <script>
var daysselect = `<?= $daysSelect ?>`; var daysselect = `<?= $daysSelect ?>`;
@@ -29,6 +30,15 @@ $daysSelect .= "</select>";
margin-left: 7px; margin-left: 7px;
margin-top: 8px; margin-top: 8px;
} }
.remove-wh-history {
cursor: pointer;
color: #ff0606;
font-size: 17px;
float: right;
margin-left: 7px;
}
</style> </style>
<!-- start page title --> <!-- start page title -->
<div class="row"> <div class="row">
@@ -214,6 +224,12 @@ $daysSelect .= "</select>";
<label class="col-form-label" for="workinghours"><span <label class="col-form-label" for="workinghours"><span
id="workinghours-text">0 Stunden 0 Minuten</span></label> id="workinghours-text">0 Stunden 0 Minuten</span></label>
</div> </div>
<div class="col-lg-2 text-center">
<button type="button" id="change-workinghours" data-toggle="modal"
data-target="#closeWorkingHoursModal" class="btn btn-danger">
Arbeitszeit Abschluss
</button>
</div>
</div> </div>
<?php <?php
@@ -251,25 +267,113 @@ $daysSelect .= "</select>";
?> ?>
</div> </div>
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
<label class="col-lg-2"></label> <label class="col-lg-2"></label>
<div class="col-lg-10"> <div class="col-lg-10">
<button type="submit" class="btn btn-primary">Speichern</button>
<a href="<?= self::getUrl("TimerecordingEmployee") ?>"> <a href="<?= self::getUrl("TimerecordingEmployee") ?>">
<button type="button" class="btn btn-secondary">Abbrechen</button> <button type="button" class="btn btn-secondary">Abbrechen</button>
</a> </a>
<button type="submit" class="btn btn-primary">Speichern</button>
</div> </div>
</div> </div>
</form> </form>
</div> </div>
<?php if ($timerecordingworkinghourshistory) : ?>
<div class="card-body">
<div class="card no-shadow">
<div class="card-body">
<div class="row">
<div class="col-lg-6">
<h4 class="header-title mb-2">Arbeitszeiten Historie</h4>
<table id="datatable" class="table table-sm ">
<thead>
<tr>
<th class="text-center">Arbeitszeiten</th>
<th class="text-center">Wochenstunden</th>
<th class="text-center">Bis</th>
<th></th>
</tr>
</thead>
<tbody>
<?php
foreach ($timerecordingworkinghourshistory as $key => $timerecordingworkinghours):
if (!$timerecordingworkinghours['workinghours']) {
$historyworkingkours = '0 Stunden';
$historyworkingkoursText = "keine";
} else {
$historyworkingkoursText = $timerecordingworkinghours['workinghours'];
$hours = $timerecordingworkinghours['workingtime'] / 3600;
$historyworkingkours = $hours . ' Stunden';
}
?>
<tr>
<td class="text-center"><?= $historyworkingkoursText ?></td>
<td class="text-center"><?= $historyworkingkours ?></td>
<td class="text-center"><?= date('d.m.Y', $key) ?></td>
<td><i class="fa-regular fa-circle-minus remove-wh-history" data-id="<?= $timerecordingworkinghours['id'] ?>"></i>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<?php endif; ?>
</div> </div>
</div> </div>
</div> </div>
<div class="modal fade" id="closeWorkingHoursModal" tabindex="-1" role="dialog"
aria-labelledby="closeWorkingHoursModalTitle" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLongTitle">Arbeitszeit abschließen</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="form-group row justify-content-center ">
<label class="col-lg-5 col-form-label" for="user_id">Letzter Tag</label>
<div class="col-lg-5">
<input type="date" id="workingHoursCloseDate" class="form-control required">
</div>
</div>
<div class="form-group row justify-content-center ">
<div class="col-lg-12 row justify-content-center"><h4 class="p-0 m-1">Arbeitszeiten</h4></div>
<div class="col-lg-12 row justify-content-center"><h5 class="p-0 m-1"
id="workingHoursTime"></h5></div>
<div id="old-workingHours" class="col-lg-10">
<table class="table table-sm">
</table>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-success" id="save-close-workingHours">Abschließen</button>
<button type="button" class="btn btn-secondary" data-dismiss="modal">Abbrechen</button>
</div>
</div>
</div>
</div>
<script type="text/javascript"> <script type="text/javascript">
function checktime() { function checktime() {
var sum = 0; var sum = 0;
@@ -320,6 +424,82 @@ $daysSelect .= "</select>";
}) })
$(document).ready(function () { $(document).ready(function () {
checktime(); checktime();
$('#closeWorkingHoursModal').on('shown.bs.modal', function () {
$('#old-workingHours table').empty();
$('#workingHoursCloseDate').val('');
$('#workingHoursTime').text($('#workinghours-text').text());
if ($(".wtime").length > 0) {
$(".wtime").each(function (index) {
var day = $(this).find('select').val();
var start = $(this).find('.timestart').val();
var end = $(this).find('.timeend').val();
var dayname = $(this).find('select option:selected').text();
var html = '<tr><td>' + dayname + '</td><td>' + start + '</td><td>' + end + '</td></tr>';
$('#old-workingHours table').append(html);
});
} else {
$('#old-workingHours table').append('<tr><td class="text-center" colspan="3">Keine Arbeitszeiten</td></tr>');
}
$(".wtime").each(function (index) {
});
})
//old-workingHours
$('body').on('click', '#save-close-workingHours', function () {
let reqworkingHoursSaveUrl = '<?= self::getUrl("TimerecordingEmployee", "api", ['do' => 'saveWorkingHours']) ?>';
let workingHours = [];
$(".wtime").each(function (index) {
var day = $(this).find('select').val();
var start = $(this).find('.timestart').val();
var end = $(this).find('.timeend').val();
workingHours.push({day: day, start: start, end: end});
});
let required = true;
$('.required').each(function () {
if ($(this).val() == '') {
required = false;
$(this).addClass('is-invalid');
} else {
$(this).removeClass('is-invalid');
}
});
if (!required) {
return;
}
$.ajax({
url: reqworkingHoursSaveUrl,
type: 'POST',
data: {
workingHours: workingHours,
enddate: $('#workingHoursCloseDate').val(),
userid: <?= $timerecordinguser[0]->id ?>
},
success: function (data) {
$('#closeWorkingHoursModal').modal('hide');
location.reload();
}
});
});
$('body').on('click', '.remove-wh-history', function () {
let reqworkingHoursSaveUrl = '<?= self::getUrl("TimerecordingEmployee", "api", ['do' => 'deleteWorkingHours']) ?>';
let id = $(this).data('id');
if (!confirm('Arbeitszeitänderung löschen?')) {
return;
}
$.ajax({
url: reqworkingHoursSaveUrl,
type: 'POST',
data: {
id: id,
userid: <?= $timerecordinguser[0]->id ?>
},
success: function (data) {
location.reload();
}
});
});
}); });
</script> </script>

View File

@@ -16,6 +16,47 @@ class TimerecordingEmployeeController extends mfBaseController
} }
} }
protected function apiAction()
{
$do = $this->request->do;
$r = $this->request;
$data = [];
switch ($do) {
case "saveWorkingHours":
$userid = $r->userid;
$enddate = $r->enddate;
$workinghours = $r->workingHours;
$data = [];
$data['user_id'] = $userid;
$data['enddate'] = strtotime($enddate) + 10800;
$data['workinghours'] = json_encode($workinghours);
$timerecordingemployeesworkinghoursHistory = TimerecordingEmployeeWorkingHourHistoryModel::create($data);
$timerecordingemployeesworkinghoursHistory->save();
break;
case "deleteWorkingHours":
$id = $r->id;
$timerecordingemployeesworkinghoursHistory = new TimerecordingEmployeeWorkingHourHistory($id);
if (!$timerecordingemployeesworkinghoursHistory->id || $timerecordingemployeesworkinghoursHistory->id != $id) {
$data = ["status" => "error"];
$this->returnJson($data);
}
$timerecordingemployeesworkinghoursHistory->delete();
break;
default:
$data = ["status" => "error"];
$this->returnJson($data);
}
if (!is_array($return) || !count($return)) {
$data = ["status" => "error"];
$this->returnJson($data);
}
$data['status'] = "OK";
$data['result'] = $return;
$this->returnJson($data);
}
protected function indexAction() protected function indexAction()
{ {
@@ -56,6 +97,8 @@ class TimerecordingEmployeeController extends mfBaseController
$this->layout()->set("timerecordingemployees", $timerecordingemployees); $this->layout()->set("timerecordingemployees", $timerecordingemployees);
} }
$timerecordingworkinghourshistory = $this->generateWorkingHoursHistory($userid);
$this->layout()->set("timerecordingworkinghourshistory", $timerecordingworkinghourshistory);
$timerecordinguser = UserModel::search(['worker_id' => $userid]); $timerecordinguser = UserModel::search(['worker_id' => $userid]);
$this->layout()->set("timerecordinguser", $timerecordinguser); $this->layout()->set("timerecordinguser", $timerecordinguser);
return $this->addAction(); return $this->addAction();
@@ -219,4 +262,26 @@ class TimerecordingEmployeeController extends mfBaseController
$this->redirect("TimerecordingEmployee"); $this->redirect("TimerecordingEmployee");
} }
protected function generateWorkingHoursHistory($userid)
{
$days_short = TimerecordingEmployeeWorkingHourModel::$days_short;
$TimerecordingEmployeeWorkingHourHistory = TimerecordingEmployeeWorkingHourHistoryModel::search(['user_id' => $userid]);
foreach ($TimerecordingEmployeeWorkingHourHistory as $key => $value) {
$workinghours = json_decode($value->workinghours, true);
$datetimetext = "";
$secondcounter = "";
foreach ($workinghours as $key2 => $data) {
$secondcounter = $secondcounter + strtotime(date("Y-m-d " . $data['end'] . ":00")) - strtotime(date("Y-m-d " . $data['start'] . ":00"));
$datetimetext .= $days_short[$data['day']] . " " . $data['start'] . " - " . $data['end'] . "<br>";
$datetimetext = TimerecordingEmployeeWorkingHourModel::cleardays($datetimetext);
}
$result[$value->enddate]['workinghours'] = $datetimetext;
$result[$value->enddate]['workingtime'] = $secondcounter;
$result[$value->enddate]['id'] = $value->id;
}
return $result;
}
} }

View File

@@ -0,0 +1,60 @@
<?php
class TimerecordingEmployeeWorkingHourHistory extends mfBaseModel
{
private $editor;
private $creator;
private $user;
public function getProperty($name)
{
if ($this->$name == null) {
if (!$this->id) {
return null;
}
if ($name == "creator") {
$this->creator = mfValuecache::singleton()->get("Worker-id-" . $this->create_by);
if ($this->creator === null) {
$this->creator = new User($this->create_by);
if ($this->creator->id) {
mfValuecache::singleton()->set("Worker-id-" . $this->create_by, $this->creator);
}
}
return $this->creator;
}
if ($name == "editor") {
$this->editor = mfValuecache::singleton()->get("Worker-id-" . $this->edit_by);
if ($this->editor === null) {
$this->editor = new User($this->edit_by);
if ($this->editor->id) {
mfValuecache::singleton()->set("Worker-id-" . $this->edit_by, $this->editor);
}
}
return $this->editor;
}
$classname = ucfirst($name);
$idfield = $name . "_id";
$this->$name = mfValuecache::singleton()->get("mfObjectmodel-$name-" . $this->$idfield);
if (!$this->$name) {
$this->$name = new $classname($this->$idfield);
}
if ($this->$name->id) {
mfValuecache::singleton()->set("mfObjectmodel-$name-" . $this->$name->id, $this->$name);
return $this->$name;
} else {
return null;
}
}
return $this->$name;
}
}

View File

@@ -0,0 +1,131 @@
<?php
class TimerecordingEmployeeWorkingHourHistoryController extends mfBaseController
{
protected function init()
{
$this->needlogin = true;
$me = new User();
$me->loadMe();
$this->me = $me;
$this->layout()->set("me", $me);
if (!$me->is(["Admin"])) {
$this->redirect("Dashboard");
}
}
protected function indexAction()
{
$this->layout()->setTemplate("TimerecordingEmployeeWorkingHourHistory/Index");
$timerecordingemployeeworkinghourhistorys = TimerecordingEmployeeWorkingHourHistoryModel::getAll();
$this->layout()->set("timerecordingemployeeworkinghourhistorys", $timerecordingemployeeworkinghourhistorys);
}
protected function addAction()
{
$users=UserModel::getAll();
$this->layout()->set("users", $users);
$this->layout()->setTemplate("TimerecordingEmployeeWorkingHourHistory/Form");
}
protected function editAction()
{
$id = $this->request->id;
if (!is_numeric($id) || !$id) {
$this->layout()->setFlash("tt History nicht gefunden", "error");
$this->redirect("TimerecordingEmployeeWorkingHourHistory");
}
$timerecordingemployeeworkinghourhistorys = new TimerecordingEmployeeWorkingHourHistory($id);
if ($timerecordingemployeeworkinghourhistorys->id != $id) {
$this->layout()->setFlash("tt History nicht gefunden", "error");
$this->redirect("TimerecordingEmployeeWorkingHourHistory");
}
$this->layout()->set("timerecordingemployeeworkinghourhistorys", $timerecordingemployeeworkinghourhistorys);
return $this->addAction();
}
protected function saveAction()
{
$r = $this->request;
$id = $r->id;
//var_dump($r->get());exit;
if (is_numeric($id) && $id > 0) {
$mode = "edit";
$timerecordingemployeeworkinghourhistorys = new TimerecordingEmployeeWorkingHourHistory($id);
if (!$timerecordingemployeeworkinghourhistorys->id) {
$this->layout()->setFlash("tt History nicht gefunden", "error");
$this->redirect("TimerecordingEmployeeWorkingHourHistory");
}
} else {
$mode = "add";
}
$data = [];
$data['user_id'] = trim($r->user_id);
$data['end'] = trim($r->end);
$data['times'] = trim($r->times);
if (!$data['user_id']) {
$data['user_id']=NULL;
}
if (!$data['end']) {
$data['end']=NULL;
}
if (!$data['times']) {
$data['times']=NULL;
}
// var_dump($_FILES);
// var_dump($upload);
// exit;
if ($mode == "edit") {
$timerecordingemployeeworkinghourhistorys->update($data);
} else {
$timerecordingemployeeworkinghourhistorys = TimerecordingEmployeeWorkingHourHistoryModel::create($data);
}
// var_dump($filestore);
// exit;
$id = $timerecordingemployeeworkinghourhistorys->save();
if (!$id) {
$this->layout()->setFlash("tt History konnte nicht angelegt werden", "error");
$this->redirect("TimerecordingEmployeeWorkingHourHistory");
}
if ($mode == "edit") {
$this->layout()->setFlash("tt History erfolgreich geändert", "success");
} else if ($mode = "add") {
$this->layout()->setFlash("tt History erfolgreich angelegt", "success");
}
$this->redirect("TimerecordingEmployeeWorkingHourHistory");
}
protected function deleteAction()
{
$id = $this->request->id;
$timerecordingemployeeworkinghourhistorys = new TimerecordingEmployeeWorkingHourHistory($id);
if (!$timerecordingemployeeworkinghourhistorys->id || $timerecordingemployeeworkinghourhistorys->id != $id) {
$this->layout()->setFlash("tt History nicht gefunden.", "error");
$this->redirect("TimerecordingEmployeeWorkingHourHistory");
}
$timerecordingemployeeworkinghourhistorys->delete();
$this->redirect("TimerecordingEmployeeWorkingHourHistory");
}
}

View File

@@ -0,0 +1,128 @@
<?php
class TimerecordingEmployeeWorkingHourHistoryModel
{
private $user_id;
private $enddate;
private $workinghours;
public static function find($data)
{
}
public static function create(array $data)
{
$model = new TimerecordingEmployeeWorkingHourHistory();
foreach ($data as $field => $value) {
if (property_exists(get_called_class(), $field)) {
if (substr($field, 0, 5) == "vlan_" && !$value) {
$model->$field = null;
continue;
}
$model->$field = $value;
}
}
$me = mfValuecache::singleton()->get("me");
if (!$me) {
$me = new User();
$me->loadMe();
mfValuecache::singleton()->set("me", $me);
}
if ($model->create_by === null) {
$model->create_by = $me->id;
}
if ($model->edit_by === null) {
$model->edit_by = $me->id;
}
return $model;
}
public static function getOne($id)
{
if (!is_numeric($id) || !$id) {
throw new Exception("Invalid number", 400);
}
$item = [];
$db = FronkDB::singleton();
$res = $db->select("TimerecordingEmployeeWorkingHourHistory", "*", "id=$id LIMIT 1");
if ($db->num_rows($res)) {
$data = $db->fetch_object($res);
$item = new TimerecordingEmployeeWorkingHourHistory($data);
}
return $item;
}
public static function getAll()
{
$items = [];
$db = FronkDB::singleton();
$res = $db->select("TimerecordingEmployeeWorkingHourHistory", "*", "1=1");
if ($db->num_rows($res)) {
while ($data = $db->fetch_object($res)) {
$items[] = new TimerecordingEmployeeWorkingHourHistory($data);
}
}
return $items;
}
public static function getFirst()
{
$db = FronkDB::singleton();
$where = self::getSqlFilter($filter);
$res = $db->select("TimerecordingEmployeeWorkingHourHistory", "*", "$where ");
if ($db->num_rows($res)) {
$data = $db->fetch_object($res);
$item = new TimerecordingEmployeeWorkingHourHistory($data);
if ($item->id) {
return $item;
} else {
return null;
}
}
return null;
}
public static function search($filter)
{
$items = [];
$db = FronkDB::singleton();
$where = self::getSqlFilter($filter);
$res = $db->select("TimerecordingEmployeeWorkingHourHistory", "*", "$where ORDER by enddate DESC");
if ($db->num_rows($res)) {
while ($data = $db->fetch_object($res)) {
$items[] = new TimerecordingEmployeeWorkingHourHistory($data);
}
}
return $items;
}
private static function getSqlFilter($filter)
{
$where = "1=1 ";
//var_dump($filter);exit;
if (array_key_exists("user_id", $filter)) {
$userid = $filter['user_id'];
if (is_numeric($userid)) {
$where .= " AND user_id=$userid";
}
}
//var_dump($filter, $where);exit;
return $where;
}
}

View File

@@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
use Phinx\Migration\AbstractMigration;
final class TimerecordingEmployeeWorkingHourHistory extends AbstractMigration
{
public function up(): void
{
if($this->getEnvironment() == "thetool") {
$table = $this->table("TimerecordingEmployeeWorkingHourHistory", ["signed" => true]);
$table->addColumn("user_id", "integer", ["null" => false]);
$table->addColumn("enddate", "integer", ["null" => false, "limit" => 64]);
$table->addColumn("workinghours", "string", ["null" => true]);
$table->addColumn("create_by", "integer", ["null" => false]);
$table->addColumn("edit_by", "integer", ["null" => false]);
$table->addColumn("create", "integer", ["null" => false]);
$table->addColumn("edit", "integer", ["null" => false]);
$table->save();
}
if($this->getEnvironment() == "addressdb") {
}
}
public function down(): void
{
if($this->getEnvironment() == "thetool") {
$this->table("TimerecordingEmployeeWorkingHourHistory")->drop()->save();
}
if($this->getEnvironment() == "addressdb") {
}
}
}