Zeiterfassung Feature Updates
* Reports wurde um Soll/Ist Detail Auswertung erweitert * Nachtzulage wird nun auch in der Verrechnungsübersicht angezeigt * M25,Ü50,Ü100 werden nun in Stichtagsauswertung mit berechnet * Bei der offenen Arbeitstage Auswertung wird nun die Arbeitszeitverschiebung rausgerechnet
This commit is contained in:
@@ -1167,6 +1167,9 @@ class TimerecordingController extends mfBaseController
|
||||
$hours = floor($seconds / 3600);
|
||||
$sum = "-" .sprintf("%02d", $hours) . ":" . sprintf("%02d", $minutes);
|
||||
$day = $daysgerm[date("w", $timerecording->start)];
|
||||
if ($ajax == 0) {
|
||||
$workdaycheck[] = $datadate;
|
||||
}
|
||||
}
|
||||
|
||||
if (($timerecording->timerecordingCategory->approval == 1 && $timerecording->approved == 0) || ($timerecording->timerecordingCategory->approval_fibu == 1 && $timerecording->approved == 0)) {
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
<?php
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
|
||||
|
||||
class TimerecordingBillingController extends mfBaseController
|
||||
{
|
||||
|
||||
@@ -121,6 +124,9 @@ class TimerecordingBillingController extends mfBaseController
|
||||
case "generateopenworkdays":
|
||||
$return = $this->generateopenworkdays($month);
|
||||
break;
|
||||
case "generateworkdaysdetail":
|
||||
$return = $this->generateworkdaysdetail($month);
|
||||
break;
|
||||
case "generatebmdexportclosed":
|
||||
$return = $this->generateBmdExportClosed($month);
|
||||
break;
|
||||
@@ -436,6 +442,162 @@ class TimerecordingBillingController extends mfBaseController
|
||||
exit;
|
||||
}
|
||||
|
||||
protected function generateworkdaysdetail($month)
|
||||
{
|
||||
// Aktiviert das 1904-Datumssystem global für dieses Script, um negative Zeiten zu unterstützen
|
||||
\PhpOffice\PhpSpreadsheet\Shared\Date::setExcelCalendar(\PhpOffice\PhpSpreadsheet\Shared\Date::CALENDAR_MAC_1904);
|
||||
|
||||
$spreadsheet = new Spreadsheet();
|
||||
$sheet = $spreadsheet->getActiveSheet();
|
||||
|
||||
$headerarray = ["Monat", "Mitarbeiter", "Datum", "Soll", "Ist", "Differenz"];
|
||||
$column = 'A';
|
||||
foreach ($headerarray as $header) {
|
||||
$sheet->setCellValue($column . '1', $header);
|
||||
$column++;
|
||||
}
|
||||
|
||||
$month_start = strtotime("01." . $month);
|
||||
$month_end = strtotime("last day of this month", $month_start);
|
||||
$month_end = strtotime("23:59:59", $month_end);
|
||||
$daycount = date("t", $month_start);
|
||||
|
||||
$holidays = TimerecordingHolidayModel::getAll();
|
||||
$holiDay = [];
|
||||
foreach ($holidays as $holiday) {
|
||||
$holiDay[date('Y-m-d', $holiday->timestamp)] = $holiday->timestamp;
|
||||
}
|
||||
|
||||
$rowIdx = 2;
|
||||
$timerecordingsEmployees = TimerecordingEmployeeModel::getAll();
|
||||
foreach ($timerecordingsEmployees as $timerecordingsEmployee) {
|
||||
if ($timerecordingsEmployee->bmd_active == 0 || $timerecordingsEmployee->startdate > $month_end || ($timerecordingsEmployee->enddate && $timerecordingsEmployee->enddate < $month_start)) continue;
|
||||
|
||||
$user = new User($timerecordingsEmployee->user_id);
|
||||
$user_id = $user->id;
|
||||
|
||||
// Schedule
|
||||
$workinghours = TimerecordingEmployeeWorkingHourModel::search(['user_id' => $user_id]);
|
||||
$userWorkingHours = [];
|
||||
foreach ($workinghours as $wh) {
|
||||
if (!isset($userWorkingHours[$wh->day])) $userWorkingHours[$wh->day] = 0;
|
||||
$userWorkingHours[$wh->day] += (strtotime("2000-01-01 " . $wh->end . ":00") - strtotime("2000-01-01 " . $wh->start . ":00"));
|
||||
}
|
||||
|
||||
$workinghourshistory = TimerecordingEmployeeWorkingHourHistoryModel::search(['user_id' => $user_id]);
|
||||
$userWorkingHoursHistory = [];
|
||||
if ($workinghourshistory) {
|
||||
$userWorkingHoursHistory[9732489200] = $userWorkingHours;
|
||||
foreach ($workinghourshistory as $whh) {
|
||||
$histWH = [];
|
||||
$whh_hours = json_decode($whh->workinghours, true);
|
||||
foreach ($whh_hours as $h) {
|
||||
if (!isset($histWH[$h['day']])) $histWH[$h['day']] = 0;
|
||||
$histWH[$h['day']] += (strtotime("2000-01-01 " . $h['end'] . ":00") - strtotime("2000-01-01 " . $h['start'] . ":00"));
|
||||
}
|
||||
$userWorkingHoursHistory[$whh->enddate] = $histWH;
|
||||
}
|
||||
ksort($userWorkingHoursHistory);
|
||||
}
|
||||
|
||||
// Ist
|
||||
$timerecordings = TimerecordingModel::search(['user_id' => $user_id, 'start' => $month_start, 'end' => $month_end]);
|
||||
$istPerDay = [];
|
||||
foreach ($timerecordings as $tr) {
|
||||
$cat = $tr->timerecordingCategory;
|
||||
if ($cat->hourday == 1 || $cat->hourday == 7) {
|
||||
$day = date('Y-m-d', $tr->start);
|
||||
if (!isset($istPerDay[$day])) $istPerDay[$day] = 0;
|
||||
$seconds = $tr->end - $tr->start;
|
||||
$istPerDay[$day] += $seconds;
|
||||
} else if ($cat->hourday == 6 || $cat->hourday == 10) {
|
||||
$day = date('Y-m-d', $tr->start);
|
||||
if (!isset($istPerDay[$day])) $istPerDay[$day] = 0;
|
||||
// Zeitausgleich/Abzug wird hier POSITIV angerechnet um das Soll zu füllen
|
||||
$seconds = $tr->end - $tr->start;
|
||||
$istPerDay[$day] += $seconds;
|
||||
} else if ($cat->hourday == 2 || $cat->hourday == 3) {
|
||||
$calcstart = max($tr->start, $month_start);
|
||||
$calcend = $tr->end ? min($tr->end, $month_end) : min(time(), $month_end);
|
||||
for ($t = $calcstart; $t <= $calcend; $t += 86400) {
|
||||
$dDate = date('Y-m-d', $t);
|
||||
if (isset($holiDay[$dDate])) continue;
|
||||
$dDay = date('w', $t);
|
||||
$activeWH = $userWorkingHours;
|
||||
if ($userWorkingHoursHistory) {
|
||||
foreach ($userWorkingHoursHistory as $whkey => $whdata) {
|
||||
if (strtotime(date('Y-m-d 23:59:59', $whkey)) >= $t) {
|
||||
$activeWH = $whdata;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isset($activeWH[$dDay])) {
|
||||
if (!isset($istPerDay[$dDate])) $istPerDay[$dDate] = 0;
|
||||
$istPerDay[$dDate] += $activeWH[$dDay];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Loop Days
|
||||
for ($i = 0; $i < $daycount; $i++) {
|
||||
$t = strtotime("+$i days", $month_start);
|
||||
$dDate = date('Y-m-d', $t);
|
||||
$dDay = date('w', $t);
|
||||
|
||||
$activeWH = $userWorkingHours;
|
||||
if ($userWorkingHoursHistory) {
|
||||
foreach ($userWorkingHoursHistory as $whkey => $whdata) {
|
||||
if (strtotime(date('Y-m-d 23:59:59', $whkey)) >= $t) {
|
||||
$activeWH = $whdata;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$soll = 0;
|
||||
if (!isset($holiDay[$dDate]) && $t >= $timerecordingsEmployee->startdate && (!$timerecordingsEmployee->enddate || $t <= $timerecordingsEmployee->enddate)) {
|
||||
$soll = $activeWH[$dDay] ?? 0;
|
||||
}
|
||||
$ist = $istPerDay[$dDate] ?? 0;
|
||||
|
||||
if ($soll > 0 || $ist > 0) {
|
||||
$diff = $ist - $soll;
|
||||
|
||||
$sheet->setCellValue('A' . $rowIdx, date("m-Y", $month_start));
|
||||
$sheet->setCellValue('B' . $rowIdx, $user->name);
|
||||
$sheet->setCellValue('C' . $rowIdx, date("d.m.Y", $t));
|
||||
|
||||
// Zeitwerte als Excel-Zeit (Sekunden / 86400)
|
||||
$sheet->setCellValue('D' . $rowIdx, $soll / 86400);
|
||||
$sheet->setCellValue('E' . $rowIdx, $ist / 86400);
|
||||
$sheet->setCellValue('F' . $rowIdx, $diff / 86400);
|
||||
|
||||
$rowIdx++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Styling & Formate
|
||||
$sheet->getStyle('A1:F1')->getFont()->setBold(true);
|
||||
// Zeitformat [hh]:mm für die Spalten D, E und F
|
||||
$sheet->getStyle('D2:F' . ($rowIdx - 1))->getNumberFormat()->setFormatCode('[hh]:mm');
|
||||
|
||||
foreach (range('A', 'F') as $col) {
|
||||
$sheet->getColumnDimension($col)->setAutoSize(true);
|
||||
}
|
||||
|
||||
$filename = "workdays_detail_" . $month . ".xlsx";
|
||||
header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
|
||||
header('Content-Disposition: attachment;filename="' . $filename . '"');
|
||||
header('Cache-Control: max-age=0');
|
||||
|
||||
$writer = new Xlsx($spreadsheet);
|
||||
$writer->save('php://output');
|
||||
exit;
|
||||
}
|
||||
|
||||
protected function generateBmdExport($month, $nlz = 0, $export = 1)
|
||||
{
|
||||
//create and download csv file
|
||||
|
||||
@@ -26,6 +26,172 @@ class TimerecordingReportExportController extends mfBaseController
|
||||
$this->layout()->setTemplate("TimerecordingReportExport/Index");
|
||||
}
|
||||
|
||||
protected function generateworkdaysdetailAction()
|
||||
{
|
||||
$r = $this->request;
|
||||
$month = $r->month; // can be m.Y or YYYY-MM
|
||||
if (!$month) {
|
||||
$month = date("m.Y");
|
||||
}
|
||||
|
||||
// Handle YYYY-MM from <input type="month">
|
||||
if (strpos($month, '-') !== false) {
|
||||
$month = date("m.Y", strtotime($month . "-01"));
|
||||
}
|
||||
|
||||
// Aktiviert das 1904-Datumssystem global für dieses Script, um negative Zeiten zu unterstützen
|
||||
\PhpOffice\PhpSpreadsheet\Shared\Date::setExcelCalendar(\PhpOffice\PhpSpreadsheet\Shared\Date::CALENDAR_MAC_1904);
|
||||
|
||||
$spreadsheet = new Spreadsheet();
|
||||
$sheet = $spreadsheet->getActiveSheet();
|
||||
|
||||
$headerarray = ["Monat", "Mitarbeiter", "Datum", "Soll", "Ist", "Differenz"];
|
||||
$column = 'A';
|
||||
foreach ($headerarray as $header) {
|
||||
$sheet->setCellValue($column . '1', $header);
|
||||
$column++;
|
||||
}
|
||||
|
||||
$month_start = strtotime("01." . $month);
|
||||
$month_end = strtotime("last day of this month", $month_start);
|
||||
$month_end = strtotime("23:59:59", $month_end);
|
||||
$daycount = date("t", $month_start);
|
||||
|
||||
$holidays = TimerecordingHolidayModel::getAll();
|
||||
$holiDay = [];
|
||||
foreach ($holidays as $holiday) {
|
||||
$holiDay[date('Y-m-d', $holiday->timestamp)] = $holiday->timestamp;
|
||||
}
|
||||
|
||||
$rowIdx = 2;
|
||||
$timerecordingsEmployees = TimerecordingEmployeeModel::getAll();
|
||||
foreach ($timerecordingsEmployees as $timerecordingsEmployee) {
|
||||
if ($timerecordingsEmployee->bmd_active == 0 || $timerecordingsEmployee->startdate > $month_end || ($timerecordingsEmployee->enddate && $timerecordingsEmployee->enddate < $month_start)) continue;
|
||||
|
||||
$user = new User($timerecordingsEmployee->user_id);
|
||||
$user_id = $user->id;
|
||||
|
||||
// Schedule
|
||||
$workinghours = TimerecordingEmployeeWorkingHourModel::search(['user_id' => $user_id]);
|
||||
$userWorkingHours = [];
|
||||
foreach ($workinghours as $wh) {
|
||||
if (!isset($userWorkingHours[$wh->day])) $userWorkingHours[$wh->day] = 0;
|
||||
$userWorkingHours[$wh->day] += (strtotime("2000-01-01 " . $wh->end . ":00") - strtotime("2000-01-01 " . $wh->start . ":00"));
|
||||
}
|
||||
|
||||
$workinghourshistory = TimerecordingEmployeeWorkingHourHistoryModel::search(['user_id' => $user_id]);
|
||||
$userWorkingHoursHistory = [];
|
||||
if ($workinghourshistory) {
|
||||
$userWorkingHoursHistory[9732489200] = $userWorkingHours;
|
||||
foreach ($workinghourshistory as $whh) {
|
||||
$histWH = [];
|
||||
$whh_hours = json_decode($whh->workinghours, true);
|
||||
foreach ($whh_hours as $h) {
|
||||
if (!isset($histWH[$h['day']])) $histWH[$h['day']] = 0;
|
||||
$histWH[$h['day']] += (strtotime("2000-01-01 " . $h['end'] . ":00") - strtotime("2000-01-01 " . $h['start'] . ":00"));
|
||||
}
|
||||
$userWorkingHoursHistory[$whh->enddate] = $histWH;
|
||||
}
|
||||
ksort($userWorkingHoursHistory);
|
||||
}
|
||||
|
||||
// Ist
|
||||
$timerecordings = TimerecordingModel::search(['user_id' => $user_id, 'start' => $month_start, 'end' => $month_end]);
|
||||
$istPerDay = [];
|
||||
foreach ($timerecordings as $tr) {
|
||||
$cat = $tr->timerecordingCategory;
|
||||
if ($cat->hourday == 1 || $cat->hourday == 7) {
|
||||
$day = date('Y-m-d', $tr->start);
|
||||
if (!isset($istPerDay[$day])) $istPerDay[$day] = 0;
|
||||
$seconds = $tr->end - $tr->start;
|
||||
$istPerDay[$day] += $seconds;
|
||||
} else if ($cat->hourday == 6 || $cat->hourday == 10) {
|
||||
$day = date('Y-m-d', $tr->start);
|
||||
if (!isset($istPerDay[$day])) $istPerDay[$day] = 0;
|
||||
$seconds = $tr->end - $tr->start;
|
||||
$istPerDay[$day] += $seconds;
|
||||
} else if ($cat->hourday == 2 || $cat->hourday == 3) {
|
||||
$calcstart = max($tr->start, $month_start);
|
||||
$calcend = $tr->end ? min($tr->end, $month_end) : min(time(), $month_end);
|
||||
for ($t = $calcstart; $t <= $calcend; $t += 86400) {
|
||||
$dDate = date('Y-m-d', $t);
|
||||
if (isset($holiDay[$dDate])) continue;
|
||||
$dDay = date('w', $t);
|
||||
$activeWH = $userWorkingHours;
|
||||
if ($userWorkingHoursHistory) {
|
||||
foreach ($userWorkingHoursHistory as $whkey => $whdata) {
|
||||
if (strtotime(date('Y-m-d 23:59:59', $whkey)) >= $t) {
|
||||
$activeWH = $whdata;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isset($activeWH[$dDay])) {
|
||||
if (!isset($istPerDay[$dDate])) $istPerDay[$dDate] = 0;
|
||||
$istPerDay[$dDate] += $activeWH[$dDay];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Loop Days
|
||||
for ($i = 0; $i < $daycount; $i++) {
|
||||
$t = strtotime("+$i days", $month_start);
|
||||
$dDate = date('Y-m-d', $t);
|
||||
$dDay = date('w', $t);
|
||||
|
||||
$activeWH = $userWorkingHours;
|
||||
if ($userWorkingHoursHistory) {
|
||||
foreach ($userWorkingHoursHistory as $whkey => $whdata) {
|
||||
if (strtotime(date('Y-m-d 23:59:59', $whkey)) >= $t) {
|
||||
$activeWH = $whdata;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$soll = 0;
|
||||
if (!isset($holiDay[$dDate]) && $t >= $timerecordingsEmployee->startdate && (!$timerecordingsEmployee->enddate || $t <= $timerecordingsEmployee->enddate)) {
|
||||
$soll = $activeWH[$dDay] ?? 0;
|
||||
}
|
||||
$ist = $istPerDay[$dDate] ?? 0;
|
||||
|
||||
if ($soll > 0 || $ist > 0) {
|
||||
$diff = $ist - $soll;
|
||||
|
||||
$sheet->setCellValue('A' . $rowIdx, date("m-Y", $month_start));
|
||||
$sheet->setCellValue('B' . $rowIdx, $user->name);
|
||||
$sheet->setCellValue('C' . $rowIdx, date("d.m.Y", $t));
|
||||
|
||||
// Zeitwerte als Excel-Zeit (Sekunden / 86400)
|
||||
$sheet->setCellValue('D' . $rowIdx, $soll / 86400);
|
||||
$sheet->setCellValue('E' . $rowIdx, $ist / 86400);
|
||||
$sheet->setCellValue('F' . $rowIdx, $diff / 86400);
|
||||
|
||||
$rowIdx++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Styling & Formate
|
||||
$sheet->getStyle('A1:F1')->getFont()->setBold(true);
|
||||
// Zeitformat [hh]:mm für die Spalten D, E und F
|
||||
$sheet->getStyle('D2:F' . ($rowIdx - 1))->getNumberFormat()->setFormatCode('[hh]:mm');
|
||||
|
||||
foreach (range('A', 'F') as $col) {
|
||||
$sheet->getColumnDimension($col)->setAutoSize(true);
|
||||
}
|
||||
|
||||
$filename = "workdays_detail_" . $month . ".xlsx";
|
||||
header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
|
||||
header('Content-Disposition: attachment;filename="' . $filename . '"');
|
||||
header('Cache-Control: max-age=0');
|
||||
|
||||
$writer = new Xlsx($spreadsheet);
|
||||
$writer->save('php://output');
|
||||
exit;
|
||||
}
|
||||
|
||||
protected function exportdeadlineAction()
|
||||
{
|
||||
$r = $this->request;
|
||||
@@ -62,7 +228,7 @@ class TimerecordingReportExportController extends mfBaseController
|
||||
|
||||
|
||||
$headers = [
|
||||
'Stichtag', 'Pers. Nr.', 'Mitarbeiter', 'Urlaub', 'Mehrstunden', 'Gutstunden'
|
||||
'Stichtag', 'Pers. Nr.', 'Mitarbeiter', 'Urlaub', 'Mehrstunden', 'Gutstunden', 'M25', 'Ü50', 'Ü100', 'NZ'
|
||||
];
|
||||
if ($this->me->superexpertEnabled()) {
|
||||
$headers[] = 'Black P. St.';
|
||||
@@ -106,6 +272,14 @@ class TimerecordingReportExportController extends mfBaseController
|
||||
$sheet->setCellValue('D' . $row, $holidays[$timerecordingbillingsemployee->timerecordingEmployee->user_id]);
|
||||
$sheet->setCellValue('E' . $row, round(($timerecordingbillingsemployee->plushours_all + $timerecordingbillingsemployee->transfer_plushours) / 3600, 2));
|
||||
$sheet->setCellValue('F' . $row, round(($timerecordingbillingsemployee->timerecordingEmployee->overtime_now + $overtimediff) / 3600, 2));
|
||||
$sheet->setCellValue('G' . $row, round($timerecordingbillingsemployee->plushours25 / 3600, 2));
|
||||
$sheet->setCellValue('H' . $row, round($timerecordingbillingsemployee->overtime50free / 3600, 2));
|
||||
$sheet->setCellValue('I' . $row, round($timerecordingbillingsemployee->overtime100free / 3600, 2));
|
||||
$sheet->setCellValue('J' . $row, round($timerecordingbillingsemployee->night_allowance / 3600, 2));
|
||||
|
||||
if ($this->me->superexpertEnabled()) {
|
||||
$sheet->setCellValue('K' . $row, round(($timerecordingbillingsemployee->timerecordingEmployee->bpahours + $bpadiff) / 3600, 2));
|
||||
}
|
||||
|
||||
$oldEmployee_id = $timerecordingbillingsemployee->timerecordingEmployee_id;
|
||||
$row++;
|
||||
@@ -115,7 +289,6 @@ class TimerecordingReportExportController extends mfBaseController
|
||||
|
||||
|
||||
$sheet->getStyle('A2:A' . $row)->getNumberFormat()->setFormatCode('@');
|
||||
$sheet->getStyle('C2:C' . $row)->getNumberFormat()->setFormatCode($number_format_code);
|
||||
$sheet->getStyle('E2:E' . $row)->getNumberFormat()->setFormatCode($number_format_code);
|
||||
$sheet->getStyle('F2:F' . $row)->getNumberFormat()->setFormatCode($number_format_code);
|
||||
$sheet->getStyle('G2:G' . $row)->getNumberFormat()->setFormatCode($number_format_code);
|
||||
|
||||
Reference in New Issue
Block a user