Merge branch 'spidev' into 'master'

Zeiterfassung Update

See merge request fronk/thetool!1858
This commit is contained in:
Daniel Spitzer
2025-10-28 16:55:22 +00:00
12 changed files with 435 additions and 10 deletions

View File

@@ -149,7 +149,7 @@ $mindate = date("Y-m-d", strtotime("+ 1 Month", $closedmonth));
data-comment="<?= $timerecordingCategories->require_comment ?>"
data-hourday="<?= $timerecordingCategories->hourday ?>"
data-businesstrip="<?= $timerecordingCategories->businesstrip ?>"
data-homeoffice="<?= ($timerecordingCategories->hourday == 1) ? 1 : 0 ?>"><?= $timerecordingCategories->name ?></option>
data-homeoffice="<?= ($timerecordingCategories->hourday == 1 && $timerecordingCategories->approval_fibu == 0) ? 1 : 0 ?>"><?= $timerecordingCategories->name ?></option>
</option>
<?php
endif;

View File

@@ -66,12 +66,22 @@
</div>
</div>
<div class="form-group row">
<label class="col-lg-2 col-form-label" for="approval">Genehmigungspflichtig</label>
<label class="col-lg-2 col-form-label" for="approval">Genehmigungspf. GF</label>
<div class="col-lg-3">
<div class="form-check">
<input id="approval"
class="form-check-input" <?php if ($timerecordingcategoriess->approval) echo 'checked="checked"'; ?>
type="checkbox" name="approval" value="1" id="olt">
type="checkbox" name="approval" value="1">
</div>
</div>
</div>
<div class="form-group row">
<label class="col-lg-2 col-form-label" for="approval_fibu">Genehmigungspfl. Buchhaltung</label>
<div class="col-lg-3">
<div class="form-check">
<input id="approval_fibu"
class="form-check-input" <?php if ($timerecordingcategoriess->approval_fibu) echo 'checked="checked"'; ?>
type="checkbox" name="approval_fibu" value="1" >
</div>
</div>
</div>

View File

@@ -41,7 +41,7 @@
<th class="text-center">Beizeichnung</th>
<th class="text-center">BMD KZ</th>
<th class="text-center">Buchungszeitraum</th>
<th class="text-center">Genehmigungspflichtig</th>
<th class="text-center">Genehmigungspf. GF/BH</th>
<th class="text-center">Dienstreisemöglichkeit</th>
<th class="text-center">Anmerkung Pflichtfeld</th>
<th class="text-center">Nur Buchhaltung</th>
@@ -66,7 +66,7 @@
<td class="text-center"><?= $timerecordingcategories->name ?></td>
<td class="text-center"><?= $timerecordingcategories->short ?></td>
<td class="text-center"><?= $timerecordingcategorieshourday[$timerecordingcategories->hourday] ?></td>
<td class="text-center"><?= $timerecordingcategoriesapproval[$timerecordingcategories->approval] ?></td>
<td class="text-center"><?= $timerecordingcategoriesapproval[$timerecordingcategories->approval] ." / ".$timerecordingcategoriesapproval[$timerecordingcategories->approval_fibu] ?></td>
<td class="text-center"><?= $timerecordingcategoriesbusinesstrip[$timerecordingcategories->businesstrip] ?></td>
<td class="text-center"><?= $timerecordingcategoriesrequire_comment[$timerecordingcategories->require_comment] ?></td>
<td class="text-center"><?= $timerecordingcategoriesrequire_only_admin[$timerecordingcategories->only_admin] ?></td>

View File

@@ -0,0 +1,211 @@
<?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?<?= $git_merge_ts ?>" rel="stylesheet"
type="text/css"/>
<link href="<?= self::getResourcePath() ?>assets/css/datatables-std.css?<?= $git_merge_ts ?>" rel="stylesheet"
type="text/css"/>
<link href="<?= self::getResourcePath() ?>datatables/DataTables-2x/datatables.min.css?<?= $git_merge_ts ?>" rel="stylesheet"
type="text/css"/>
<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">Buchungen</li>
</ol>
</div>
<h4 class="page-title">Buchungen</h4>
</div>
</div>
</div>
<!-- end page title -->
<div class="card">
<div class="card-body mb-3">
<div class="row">
<div class="col-12">
<div class="float-left">
<h4 class="header-title">Liste aller Buchungen</h4>
</div>
</div>
</div>
<table id="datatable" class="table table-hover table-sm">
<thead>
<tr class="bg-white">
<th style="width: 200px;" class="text-center text-nowrap ">Datum</th>
<th style="width: 200px;" class="text-nowrap">Mitarbeiter</th>
<th class="text-nowrap text-center edit-width">Von</th>
<th class="text-nowrap text-center edit-width">Bis</th>
<th class="text-nowrap edit-width text-center">Summe</th>
<th class="text-center text-nowrap">Buchungsart</th>
<th class="text-center">Anmerkung</th>
<th class="text-center edit-width-large">Freigabe</th>
<th class="edit-width-w70 text-center"></th>
</tr>
<tr id="filterrow">
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<?php foreach ($timerecordings as $timerecording):
$state = "";
$enddate = "";
$sum = "-";
$day = "";
$orderdate = $timerecording->start;
if ($timerecording->timerecordingCategory->hourday == 1) {
$date = date("d.m.Y", $timerecording->start);
$datadate = date("Y-m-d", $timerecording->start);
$start = date("H:i", $timerecording->start);
$end = date("H:i", $timerecording->end);
$seconds = $timerecording->end - $timerecording->start;
$minutes = floor(($seconds % 3600) / 60);
$hours = floor($seconds / 3600);
$sum = sprintf("%02d", $hours) . ":" . sprintf("%02d", $minutes);
$day = $daysgerm[date("w", $timerecording->start)];
} else if ($timerecording->timerecordingCategory->hourday == 2) {
$date = date("d.m.", $timerecording->start) . " - " . $daysgerm[date("w", $timerecording->end)] . " " . date("d.m.Y", $timerecording->end);
$datadate = date("Y-m-d", $timerecording->start);
$enddate = date("Y-m-d", $timerecording->end);
$start = "-";
$end = "-";
$day = $daysgerm[date("w", $timerecording->start)];
} else if ($timerecording->timerecordingCategory->hourday == 3 || $timerecording->timerecordingCategory->hourday == 4) {
$date = date("d.m.Y", $timerecording->start);
$datadate = date("Y-m-d", $timerecording->start);
$start = "-";
$end = "-";
$day = $daysgerm[date("w", $timerecording->start)];
} else if ($timerecording->timerecordingCategory->hourday == 6) {
$date = date("d.m.Y", $timerecording->start);
$datadate = date("Y-m-d", $timerecording->start);
$start = date("H:i", $timerecording->start);
$end = date("H:i", $timerecording->end);
$seconds = ($timerecording->end - $timerecording->start);
$minutes = floor(($seconds % 3600) / 60);
$hours = floor($seconds / 3600);
$sum = sprintf("%02d", $hours) . ":" . sprintf("%02d", $minutes);
$day = $daysgerm[date("w", $timerecording->start)];
$isSeconds = $isSeconds + $seconds;
}
if ($timerecording->timerecordingCategory->approval_fibu == 1 && $timerecording->approved == 0) {
$state = '<i class="fa-regular fa-clock mr-1"></i>';
} else if ($timerecording->timerecordingCategory->approval_fibu == 1 && $timerecording->approved == 1) {
$state = '<i class="fa-regular fa-circle-check mr-1"></i>';
}
$approved = 'Offen';
if ($timerecording->approved == 1) $approved = 'Genehmigt';
$completed = 'Genehmigt';
// if ($timerecording->completed == 1) $completed = 'Genehmigt';
?>
<tr class="">
<td data-order="<?= $orderdate ?>"
class="text-nowrap text-left "><?= $state ?><?= $day . " " . $date ?></td>
<td class="text-nowrap "><?= $timerecording->user->name ?></td>
<td class="text-nowrap text-center"><?= $start ?></td>
<td class="text-nowrap text-center"><?= $end ?></td>
<td class="text-nowrap text-center"><?= $sum ?></td>
<td class="text-nowrap"><?= $timerecording->timerecordingCategory->name ?></td>
<td><?= $timerecording->comment ?></td>
<td class="text-center"><?= $approved ?></td>
<td style="text-align: left; letter-spacing: 4px; font-size: 1.1em;">
<?php if ($timerecording->completed == 0):
if ($timerecording->approved == 0) : ?>
<a href="<?= self::getUrl("TimerecordingPermitFibu", "approve", ["id" => $timerecording->id]) ?>"
onclick="if(!confirm('Buchung genehmigen?')) return false;"> <i
class="fa-regular fa-square-check permit-button" title="genehmigen"
data-id="<?= $timerecording->id ?>"></i></a>
<a href="<?= self::getUrl("TimerecordingPermitFibu", "deny", ["id" => $timerecording->id]) ?>"
onclick="if(!confirm('Buchung wirklich ablehnen?')) return false;"> <i
class="fas fa-ban deny-button" title="genehmigen"
data-id="<?= $timerecording->id ?>"></i></a>
<?php else : ?>
<div class="edit-placeholder"></div>
<?php endif; ?>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
<script type="text/javascript"
src="<?= self::getResourcePath() ?>datatables/DataTables-2x/datatables.min.js?<?= $git_merge_ts ?>"></script>
<script type="text/javascript">
var hidesearch = [2, 3, 4, 8];
var pageLength = 100;
var columnfilter = [7];
var columnoptions = '<option value=""></option><option selected="selected" value="Offen">Offen</option><option value="Genehmigt">Genehmigt</option><option value="Abgelehnt">Abgelehnt</option>';
$(document).ready(function () {
$('#selectsearch').change();
});
</script>
<script type="text/javascript"
src="<?= self::getResourcePath() ?>assets/js/datatables-std2.js?<?= $git_merge_ts ?>"></script>
<?php include(realpath(dirname(__FILE__) . "/../../$mfLayoutPackage") . "/footer.php"); ?>

View File

@@ -41,7 +41,8 @@
<li><a href="<?=self::getUrl("Timerecording")?>"><i class="far fa-fw fa-calendar text-info"></i> Buchungen</a></li>
<li><a href="<?=self::getUrl("TimerecordingCalendar")?>"><i class="far fa-fw fa-calendar-days text-info"></i> Abwesenheitskalender</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("TimerecordingPermit")?>"><i class="far fa-fw fa-calendar-check text-info"></i> Freigaben Urlaub/ZA</a></li>
<li><a href="<?=self::getUrl("TimerecordingPermitFibu")?>"><i class="far fa-fw fa-calendar-check text-info"></i> Freigaben Buchhaltung</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("TimerecordingBilling")?>"><i class="far fa-fw fa-money-bill-1-wave text-info"></i> Verrechnung</a></li>
<li><a href="<?=self::getUrl("TimerecordingReportExport")?>"><i class="far fa-fw fa-chart-simple text-info"></i> Reports</a></li>

View File

@@ -474,6 +474,34 @@ class TimerecordingController extends mfBaseController
$email->setTo(TT_TIMERECORDING_EMAIL);
$email->send();
}
else if ($timerecordingCategoriess[0]->approval_fibu == "1" && !$r->user_id)
{
$body = 'Beantrag von: ' . $this->me->name . '
';
$body .= 'Buchungsart: ' . $timerecordingCategoriess[0]->name . '
';
if ($timerecordingCategoriess[0]->hourday == "1") {
$body .= 'von: ' . date("d.m.Y H:i", $data['start']) . ' bis: ' . date("H:i", $data['end']);
} else if ($timerecordingCategoriess[0]->hourday == "2") {
$body .= 'von: ' . date("d.m.Y", $data['start']) . ' bis: ' . date("d.m.Y", $data['end']);
} else if ($timerecordingCategoriess[0]->hourday == "6") {
$body .= 'von: ' . date("d.m.Y H:i", $data['start']) . ' bis: ' . date("H:i", $data['end']);
}
/*
$email = new Emailnotification();
$email->setSubject('Antrag für ' . $timerecordingCategoriess[0]->name . ' erstellt');
$email->setBody($body);
$email->setFrom(TT_TIMERECORDING_EMAIL, TT_TIMERECORDING_EMAIL_NAME);
$email->setTo($this->me->email);
$email->send();
*/
$email = new Emailnotification();
$email->setSubject('Antrag für ' . $timerecordingCategoriess[0]->name . ' erstellt (' . $this->me->name . ')');
$email->setBody($body);
$email->setFrom(TT_TIMERECORDING_EMAIL_FIBU, TT_TIMERECORDING_EMAIL_FIBU_NAME);
$email->setTo(TT_TIMERECORDING_EMAIL_FIBU);
$email->send();
}
if ($data['timerecordingCategory_id'] == "3" || $timerecordingCategoriess[0]->hourday == "5") {
$this->updateHolidays($data['user_id']);
}
@@ -1091,9 +1119,9 @@ class TimerecordingController extends mfBaseController
}
}
if ($timerecording->timerecordingCategory->approval == 1 && $timerecording->approved == 0) {
if (($timerecording->timerecordingCategory->approval == 1 && $timerecording->approved == 0) || ($timerecording->timerecordingCategory->approval_fibu == 1 && $timerecording->approved == 0)) {
$state = '<i class="fa-regular fa-clock mr-1"></i>';
} else if ($timerecording->timerecordingCategory->approval == 1 && $timerecording->approved == 1) {
} else if (($timerecording->timerecordingCategory->approval == 1 && $timerecording->approved == 1) || ($timerecording->timerecordingCategory->approval_fibu == 1 && $timerecording->approved == 1 )) {
$state = '<i class="fa-regular fa-circle-check mr-1"></i>';
}
$edit = "";

View File

@@ -125,6 +125,25 @@ class TimerecordingModel
}
return $items;
}
public static function getAllPermitsFibu()
{
$items = [];
$db = FronkDB::singleton();
$sql = "SELECT Timerecording.* FROM `Timerecording`
INNER JOIN `TimerecordingCategory` ON (`Timerecording`.`timerecordingCategory_id` = `TimerecordingCategory`.`id`)
WHERE `TimerecordingCategory`.`approval_fibu`='1'";
$res = $db->query($sql);
if ($db->num_rows($res)) {
while ($data = $db->fetch_object($res)) {
$items[] = new Timerecording($data);
}
}
return $items;
}
public static function getFirst()

View File

@@ -84,6 +84,7 @@ class TimerecordingCategoryController extends mfBaseController
$data['short'] = trim($r->short);
$data['hourday'] = trim($r->hourday);
$data['approval'] = trim($r->approval);
$data['approval_fibu'] = trim($r->approval_fibu);
$data['require_comment'] = trim($r->require_comment);
$data['only_admin'] = trim($r->only_admin);
$data['businesstrip'] = trim($r->businesstrip);

View File

@@ -6,6 +6,7 @@ class TimerecordingCategoryModel
private $short;
private $hourday;
private $approval;
private $approval_fibu;
private $require_comment;
private $only_admin;
private $businesstrip;

View File

@@ -0,0 +1,123 @@
<?php
class TimerecordingPermitFibuController 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()
{
$this->layout()->setTemplate("TimerecordingPermitFibu/Index");
$timerecordingCategoriess = TimerecordingCategoryModel::getAll();
$this->layout()->set("timerecordingCategoriess", $timerecordingCategoriess);
$timerecordings = TimerecordingModel::getAllPermitsFibu();
$this->layout()->set("timerecordings", $timerecordings);
}
protected function addAction()
{
}
protected function editAction()
{
}
protected function saveAction()
{
}
protected function sendMail($timerecordings, $type)
{
if ($type == "deny") {
$sendtext = "abgelehnt";
} else if ($type == "approve") {
$sendtext = "genehmigt";
}
$user = UserModel::getOne($timerecordings->user_id);
$timerecordingCategoriess = TimerecordingCategoryModel::search(['id' => $timerecordings->timerecordingCategory_id]);
$body = 'Beantrag von: ' . $user->name . '
';
$body .= 'Buchungsart: ' . $timerecordingCategoriess[0]->name . '
';
if ($timerecordingCategoriess[0]->hourday == "1") {
$body .= 'von: ' . date("d.m.Y H:i", $timerecordings->start) . ' bis: ' . date("H:i", $timerecordings->end) . '
';
} else if ($timerecordingCategoriess[0]->hourday == "2") {
$body .= 'von: ' . date("d.m.Y", $timerecordings->start) . ' bis: ' . date("d.m.Y", $timerecordings->end) . '
';
}
else if ($timerecordingCategoriess[0]->hourday == "6") {
$body .= 'von: ' . date("d.m.Y H:i", $timerecordings->start) . ' bis: ' . date("H:i", $timerecordings->end) . '
';
}
$body .= ucfirst($sendtext) . ' von: ' . $this->me->name . '
';
$email = new Emailnotification();
$email->setSubject('Antrag für ' . $timerecordingCategoriess[0]->name . ' ' . $sendtext);
$email->setBody($body);
$email->setFrom(TT_TIMERECORDING_EMAIL, TT_TIMERECORDING_EMAIL_NAME);
$email->setTo($user->email);
$email->send();
$email = new Emailnotification();
$email->setSubject('Antrag für ' . $timerecordingCategoriess[0]->name . ' ' . $sendtext . ' (' . $user->name . ')');
$email->setBody($body);
$email->setFrom(TT_TIMERECORDING_EMAIL, TT_TIMERECORDING_EMAIL_NAME);
$email->setTo(TT_TIMERECORDING_EMAIL);
$email->send();
}
protected function approveAction()
{
$id = $this->request->id;
$timerecordings = new Timerecording($id);
if (!$timerecordings->id || $timerecordings->id != $id) {
$this->layout()->setFlash("Buchung nicht gefunden.", "error");
$this->redirect("TimerecordingPermitFibu");
}
$data = [];
$data['approved'] = 1;
$timerecordings->update($data);
$timerecordings->save();
//$this->sendMail($timerecordings, "approve");
$this->redirect("TimerecordingPermitFibu");
}
protected function denyAction()
{
$id = $this->request->id;
$timerecordings = new Timerecording($id);
if (!$timerecordings->id || $timerecordings->id != $id) {
$this->layout()->setFlash("Buchung nicht gefunden.", "error");
$this->redirect("TimerecordingPermitFibu");
}
//$this->sendMail($timerecordings, "deny");
$timerecordings->delete();
if ($this->request->ajax == 1) {
die();
}
$this->redirect("TimerecordingPermitFibu");
}
}

View File

@@ -433,9 +433,9 @@ class TimerecordingReportController extends mfBaseController
$day = $daysgerm[date("w", $timerecording->start)];
}
if ($timerecording->timerecordingCategory->approval == 1 && $timerecording->approved == 0) {
if (($timerecording->timerecordingCategory->approval == 1 && $timerecording->approved == 0 )|| ($timerecording->timerecordingCategory->approval_fibu == 1 && $timerecording->approved == 0)) {
$state = '<i class="fa-regular fa-clock mr-1"></i>';
} else if ($timerecording->timerecordingCategory->approval == 1 && $timerecording->approved == 1) {
} else if (($timerecording->timerecordingCategory->approval == 1 && $timerecording->approved == 1) || ($timerecording->timerecordingCategory->approval_fibu == 1 && $timerecording->approved == 1 )) {
$state = '<i class="fa-regular fa-circle-check mr-1"></i>';
}
$edit = "";

View File

@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
use Phinx\Migration\AbstractMigration;
final class TimerecordingCategoryAddFieldApprovalFibu extends AbstractMigration
{
public function up(): void
{
if($this->getEnvironment() == "thetool") {
$table = $this->table("TimerecordingCategory", ["signed" => true]);
$table->addColumn("approval_fibu", "integer", ["null" => false, "default" => '0', "after" => "approval"]);
$table->update();
}
if($this->getEnvironment() == "addressdb") {
}
}
public function down(): void
{
if($this->getEnvironment() == "thetool") {
$this->table("TimerecordingCategory")->removeColumn("approval_fibu")->save();
}
if($this->getEnvironment() == "addressdb") {
}
}
}