Compare commits
163
Commits
0.1.0
..
0e1244c02f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0e1244c02f | ||
|
|
b4d30066b9 | ||
|
|
ab9bccf532 | ||
|
|
4acf1275a0 | ||
|
|
6fb8f87c1c | ||
|
|
1d53db71e7 | ||
|
|
dbca8ad570 | ||
|
|
4d91a04430 | ||
|
|
2dcbb6fdbd | ||
|
|
9f53b627c0 | ||
|
|
532c0d60e8 | ||
|
|
cf6eff6ab0 | ||
|
|
a964663f51 | ||
|
|
a9c455381d | ||
|
|
7b5ce23342 | ||
|
|
a3b123f466 | ||
|
|
d52a9d77ab | ||
|
|
a09edcae25 | ||
|
|
91849375c7 | ||
|
|
63ded6da57 | ||
|
|
9a65c7f78b | ||
|
|
b89a3c744f | ||
|
|
85d22d5c06 | ||
|
|
6fa6b28af6 | ||
|
|
41a3c79c91 | ||
|
|
82aad9766e | ||
|
|
b094cf8180 | ||
|
|
dff3c7f34d | ||
|
|
aa66d1f53d | ||
|
|
af61eb2334 | ||
|
|
616ed6383e | ||
|
|
e187bdeab7 | ||
|
|
c35b3a86f3 | ||
|
|
4b12eb3897 | ||
|
|
3452aa9999 | ||
|
|
072e99c6fb | ||
|
|
608d96ac4e | ||
|
|
51b5eb423c | ||
|
|
6e9ec73db4 | ||
|
|
1e48a5b608 | ||
|
|
dc7d84e35f | ||
|
|
5bbcc961e6 | ||
|
|
94b22c1d26 | ||
|
|
99cee223de | ||
|
|
fbbb563aad | ||
|
|
d7e99cbf30 | ||
|
|
3e291171a8 | ||
|
|
7ef5c43901 | ||
|
|
9932f33c34 | ||
|
|
06128f72fc | ||
|
|
18c5c45c26 | ||
|
|
0a21c6ed35 | ||
|
|
47ed408185 | ||
|
|
0813fd637b | ||
|
|
094b50d33d | ||
|
|
3bd3e6837a | ||
|
|
4da284a0a6 | ||
|
|
084abbf42c | ||
|
|
bcc6448fa1 | ||
|
|
1fc54f4b89 | ||
|
|
d4ddd4f32c | ||
|
|
90c6ef0c53 | ||
|
|
1ebf57ac3b | ||
|
|
13554d09e0 | ||
|
|
b46f5634c8 | ||
|
|
77cfcc72b4 | ||
|
|
ea93400135 | ||
|
|
646537e19c | ||
|
|
efadeb501b | ||
|
|
cf50607f43 | ||
|
|
d133cb017b | ||
|
|
ad253a82f5 | ||
|
|
fb91d1121e | ||
|
|
e10b72e086 | ||
|
|
aae4764f5e | ||
|
|
371425f3c5 | ||
|
|
c3a1953cc9 | ||
|
|
66022e42df | ||
|
|
907ce88ed1 | ||
|
|
45650a0f9a | ||
|
|
d13fce569b | ||
|
|
fafc2a0ad5 | ||
|
|
f3ff303b93 | ||
|
|
3dd0f451fc | ||
|
|
46313cc290 | ||
|
|
65195e702e | ||
|
|
ec22f42623 | ||
|
|
b43b6382d7 | ||
|
|
12ed91a6da | ||
|
|
cc9941d1d3 | ||
|
|
2b2753e009 | ||
|
|
735b195c06 | ||
|
|
0bd8932fa1 | ||
|
|
114e31029c | ||
|
|
de0535be32 | ||
|
|
b74e9bbd69 | ||
|
|
338c3ce5e4 | ||
|
|
1b4983a06f | ||
|
|
74193f3b46 | ||
|
|
2e0e6db55a | ||
|
|
c528944746 | ||
|
|
05d46ec407 | ||
|
|
7b48e7e9e2 | ||
|
|
343f12b605 | ||
|
|
6203f98e93 | ||
|
|
87f3bb3f0e | ||
|
|
8d00e0c104 | ||
|
|
3fe73f3b10 | ||
|
|
b5af0c5a54 | ||
|
|
e29a6dcab9 | ||
|
|
8ee9666250 | ||
|
|
edcb80421b | ||
|
|
16e05ce819 | ||
|
|
5c15580738 | ||
|
|
0a5e40d7cb | ||
|
|
ae7491f002 | ||
|
|
8296a2a281 | ||
|
|
c711e0ae24 | ||
|
|
74fd96cf25 | ||
|
|
9686a8505e | ||
|
|
bc83fc8dd1 | ||
|
|
9d5af9a0fb | ||
|
|
7f8a50799f | ||
|
|
365c2f041c | ||
|
|
89ad394773 | ||
|
|
881e7362f8 | ||
|
|
c8d9bc040e | ||
|
|
e3ef53b119 | ||
|
|
f78fdfbbd5 | ||
|
|
02771828c9 | ||
|
|
5a88ff3c6f | ||
|
|
f36324bfbd | ||
|
|
9b99c6a4dc | ||
|
|
fe3a4f69bf | ||
|
|
31a3f4cc01 | ||
|
|
0a59cb60db | ||
|
|
9430f3c74c | ||
|
|
2b31e17e14 | ||
|
|
9e35ab3b07 | ||
|
|
056bf82c7b | ||
|
|
be54a659c9 | ||
|
|
8086b86eb2 | ||
|
|
3e2865dbe7 | ||
|
|
60eb1f92e8 | ||
|
|
9a542c9108 | ||
|
|
a035ea9648 | ||
|
|
46f3a6491d | ||
|
|
e070db18b5 | ||
|
|
a6675605e4 | ||
|
|
520db45d6d | ||
|
|
2d3a1074f6 | ||
|
|
a1e8b1aa51 | ||
|
|
f25c9d8994 | ||
|
|
3854737622 | ||
|
|
d16a4e427d | ||
|
|
4871ff14dc | ||
|
|
ac99e079ee | ||
|
|
09ffadc596 | ||
|
|
4da5c1242c | ||
|
|
e0b8deae8b | ||
|
|
d3df8bdc7e | ||
|
|
b53b1cdd3c | ||
|
|
0054ab5afc |
@@ -0,0 +1,10 @@
|
||||
# http://editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_size = 4
|
||||
indent_style = space
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = false
|
||||
+8
-1
@@ -2,10 +2,13 @@
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
|
||||
/.idea/*
|
||||
|
||||
# User-specific stuff
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/**/usage.statistics.xml
|
||||
.idea/**/markdown.xml
|
||||
.idea/**/dictionaries
|
||||
.idea/**/shelf
|
||||
|
||||
@@ -105,4 +108,8 @@ Temporary Items
|
||||
/api/vendor/
|
||||
/api/tests/_*
|
||||
/api/tests/*.suite.yml
|
||||
/api/html/swagger/
|
||||
/api/.env
|
||||
/ui/static/print.css*
|
||||
/ui/static/global.css*
|
||||
/ui/static/bundle.css*
|
||||
/ui/.svelte-kit/
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
## Datenbank-Einstellungen
|
||||
DATABASES_HOST=psql.domain.tld
|
||||
DATABASES_NAME=db
|
||||
DATABASES_USER=user
|
||||
DATABASES_PASS=1234
|
||||
|
||||
## CORS Einstellungen
|
||||
CORS_ALLOW_ORIGIN_REGEX=/https?:\/\/localhost:\d+/
|
||||
+4
-1
@@ -1,4 +1,7 @@
|
||||
FROM php:8.0-apache
|
||||
FROM php:8.4-apache-bookworm
|
||||
|
||||
RUN apt update && apt install -y libpq-dev
|
||||
RUN docker-php-ext-install -j$(nproc) pdo pdo_pgsql
|
||||
|
||||
WORKDIR /var/www
|
||||
|
||||
|
||||
+15
-9
@@ -16,18 +16,22 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "8.4.*",
|
||||
"slim/slim": "^4.7.1",
|
||||
"vlucas/phpdotenv": "^4.2",
|
||||
"slim/psr7": "^1.3",
|
||||
"vlucas/phpdotenv": "^v5.6.1",
|
||||
"slim/psr7": "^1.7.0",
|
||||
"php-di/slim-bridge": "^3.1.0",
|
||||
"jetbrains/phpstorm-attributes": "^1.0.0",
|
||||
"myclabs/php-enum": "^1.8.0"
|
||||
"myclabs/php-enum": "^1.8.0",
|
||||
"ext-pdo": "*"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/phpstan": "^0.12.80",
|
||||
"codeception/codeception": "^4.1.18",
|
||||
"codeception/module-phpbrowser": "^1.0.0",
|
||||
"codeception/module-asserts": "^1.0.0",
|
||||
"codeception/module-db": "^1.1.0"
|
||||
"phpstan/phpstan": "^2.1.12",
|
||||
"codeception/codeception": "^5.2.1",
|
||||
"codeception/module-phpbrowser": "^3.0.1",
|
||||
"codeception/module-asserts": "^3.1.0",
|
||||
"codeception/module-db": "^3.2.2",
|
||||
"codeception/module-rest": "^3.4.1"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
@@ -36,7 +40,9 @@
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"TorstenHettstedt\\TimekeepingApi\\Tests\\Unit\\": "tests/unit"
|
||||
"TorstenHettstedt\\TimekeepingApi\\Tests\\Unit\\": "tests/unit",
|
||||
"TorstenHettstedt\\TimekeepingApi\\Tests\\Api\\": "tests/api",
|
||||
"Helper\\": "tests/_support/Helper"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
# absolute physical path to the directory that contains this htaccess file.
|
||||
# RewriteBase /
|
||||
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteRule ^ index.php [QSA,L]
|
||||
</IfModule>
|
||||
+44
-2
@@ -1,12 +1,54 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
$dotenv = Dotenv\Dotenv::createImmutable(dirname(__DIR__));
|
||||
$dotenv->load();
|
||||
|
||||
use DI\Container;
|
||||
use Slim\Factory\AppFactory;
|
||||
use Slim\Routing\RouteCollectorProxy;
|
||||
use TorstenHettstedt\TimekeepingApi\Controller\PreflightController;
|
||||
use TorstenHettstedt\TimekeepingApi\Controller\WorkingHoursController;
|
||||
use TorstenHettstedt\TimekeepingApi\Controller\WorkingHoursViewController;
|
||||
use TorstenHettstedt\TimekeepingApi\Middleware\ErrorHandler;
|
||||
use TorstenHettstedt\TimekeepingApi\Middleware\JsonBodyParserMiddleware;
|
||||
use TorstenHettstedt\TimekeepingApi\Middleware\OriginAccessControlHandler;
|
||||
|
||||
$container = new Container();
|
||||
|
||||
$container->set('databases', function () {
|
||||
$dsn = "pgsql:host={$_ENV['DATABASES_HOST']};port=5432;dbname={$_ENV['DATABASES_NAME']}";
|
||||
return new PDO($dsn, $_ENV['DATABASES_USER'], $_ENV['DATABASES_PASS']);
|
||||
});
|
||||
|
||||
AppFactory::setContainer($container);
|
||||
$app = AppFactory::create();
|
||||
$app->add(new JsonBodyParserMiddleware());
|
||||
$app->addBodyParsingMiddleware();
|
||||
$app->add(new OriginAccessControlHandler());
|
||||
$app->addRoutingMiddleware();
|
||||
|
||||
$app->get('/', 'TorstenHettstedt\TimekeepingApi\Controller\HelloWorldController:read');
|
||||
$app->group('/views/working-hours', function (RouteCollectorProxy $group) {
|
||||
$group->options('/weekly', PreflightController::class . ':preflight');
|
||||
$group->get('/weekly', WorkingHoursViewController::class . ':browseWeekly');
|
||||
$group->options('/monthly', PreflightController::class . ':preflight');
|
||||
$group->get('/monthly', WorkingHoursViewController::class . ':browseMonthly');
|
||||
$group->options('/yearly', PreflightController::class . ':preflight');
|
||||
$group->get('/yearly', WorkingHoursViewController::class . ':browseYearly');
|
||||
});
|
||||
|
||||
$app->addErrorMiddleware(true, true, true);
|
||||
$app->group('/working-hours', function (RouteCollectorProxy $group) {
|
||||
$group->options('', PreflightController::class . ':preflight');
|
||||
$group->get('', WorkingHoursController::class . ':browse');
|
||||
$group->post('', WorkingHoursController::class . ':creat');
|
||||
$group->group('/{id:\d\d\d\d-\d\d-\d\d}', function (RouteCollectorProxy $group) {
|
||||
$group->options('', PreflightController::class . ':preflight');
|
||||
$group->get('', WorkingHoursController::class . ':read');
|
||||
$group->put('', WorkingHoursController::class . ':update');
|
||||
});
|
||||
});
|
||||
|
||||
$errorMiddleware = $app->addErrorMiddleware(true, true, true);
|
||||
$errorMiddleware->setDefaultErrorHandler(new ErrorHandler($app));
|
||||
|
||||
$app->run();
|
||||
+46
-5
@@ -4,7 +4,7 @@
|
||||
"title": "Zeiterfassung App",
|
||||
"contact": {
|
||||
"name": "Torsten Lücke",
|
||||
"url": "http://torsten-hettstedt.de",
|
||||
"url": "https://torsten-hettstedt.de",
|
||||
"email": "arbeit@torsten-hettstedt.de"
|
||||
},
|
||||
"license": {
|
||||
@@ -18,6 +18,15 @@
|
||||
"tags": [
|
||||
"Eintragungen Arbeitszeiten"
|
||||
],
|
||||
"description": "Liefert eine Liste der Einträge. Kann gefiltert werden und dadurch eine leere Liste zurückgeben",
|
||||
"parameters": [
|
||||
{
|
||||
"$ref": "#/components/parameters/start-date"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/parameters/end-date"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"$ref": "#/components/responses/ListEntities"
|
||||
@@ -82,6 +91,7 @@
|
||||
"tags": [
|
||||
"Eintragungen Arbeitszeiten"
|
||||
],
|
||||
"description": "Angabe des Datum im Request-Body werden ignoriert",
|
||||
"parameters": [
|
||||
{
|
||||
"$ref": "#/components/parameters/date"
|
||||
@@ -167,14 +177,21 @@
|
||||
"description": "Eintrag der Arbeitszeit für einen Tag",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"date": {
|
||||
"workingDay": {
|
||||
"description": "Datum eines Arbeitstages",
|
||||
"type": "string",
|
||||
"format": "date"
|
||||
"format": "date",
|
||||
"required": false
|
||||
},
|
||||
"workingHours": {
|
||||
"workingTime": {
|
||||
"description": "Arbeitszeit des Tages",
|
||||
"type": "integer"
|
||||
"type": "string",
|
||||
"pattern": "[0-5]\\d:[0-5]\\d:[0-5]\\d",
|
||||
"example": "08:01:00"
|
||||
},
|
||||
"isHomeOffice": {
|
||||
"description": "Wurde an diesem Tag zuhause gearbeitet?",
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -204,6 +221,10 @@
|
||||
"description": "Arbeitstage im Zeitraum",
|
||||
"type": "integer"
|
||||
},
|
||||
"homeOfficeDays": {
|
||||
"description": "Anzahl der Tage, an denen zuhause gearbeitet wurde.",
|
||||
"type": "number"
|
||||
},
|
||||
"overtime": {
|
||||
"description": "Überstunden im Zeitraum",
|
||||
"type": "string",
|
||||
@@ -255,6 +276,26 @@
|
||||
"type": "string",
|
||||
"format": "date"
|
||||
}
|
||||
},
|
||||
"start-date" : {
|
||||
"description": "Filtert Liste ab Datum",
|
||||
"name": "start-date",
|
||||
"in" : "query",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "date"
|
||||
}
|
||||
},
|
||||
"end-date" : {
|
||||
"description": "Filtert Liste bis Datum",
|
||||
"name": "end-date",
|
||||
"in" : "query",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "date"
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 665 B |
Binary file not shown.
|
After Width: | Height: | Size: 628 B |
@@ -0,0 +1,60 @@
|
||||
<!-- HTML for static distribution bundle build -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Swagger UI</title>
|
||||
<link rel="stylesheet" type="text/css" href="swagger-ui.css" >
|
||||
<link rel="icon" type="image/png" href="favicon-32x32.png" sizes="32x32" />
|
||||
<link rel="icon" type="image/png" href="favicon-16x16.png" sizes="16x16" />
|
||||
<style>
|
||||
html
|
||||
{
|
||||
box-sizing: border-box;
|
||||
overflow: -moz-scrollbars-vertical;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
*,
|
||||
*:before,
|
||||
*:after
|
||||
{
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
body
|
||||
{
|
||||
margin:0;
|
||||
background: #fafafa;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="swagger-ui"></div>
|
||||
|
||||
<script src="swagger-ui-bundle.js"> </script>
|
||||
<script src="swagger-ui-standalone-preset.js"> </script>
|
||||
<script>
|
||||
window.onload = function() {
|
||||
// Begin Swagger UI call region
|
||||
const ui = SwaggerUIBundle({
|
||||
url: "../openapi.json",
|
||||
dom_id: '#swagger-ui',
|
||||
deepLinking: true,
|
||||
presets: [
|
||||
SwaggerUIBundle.presets.apis,
|
||||
SwaggerUIStandalonePreset
|
||||
],
|
||||
plugins: [
|
||||
SwaggerUIBundle.plugins.DownloadUrl
|
||||
],
|
||||
layout: "StandaloneLayout"
|
||||
})
|
||||
// End Swagger UI call region
|
||||
|
||||
window.ui = ui
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,68 @@
|
||||
<!doctype html>
|
||||
<html lang="en-US">
|
||||
<title>Swagger UI: OAuth2 Redirect</title>
|
||||
<body onload="run()">
|
||||
</body>
|
||||
</html>
|
||||
<script>
|
||||
'use strict';
|
||||
function run () {
|
||||
var oauth2 = window.opener.swaggerUIRedirectOauth2;
|
||||
var sentState = oauth2.state;
|
||||
var redirectUrl = oauth2.redirectUrl;
|
||||
var isValid, qp, arr;
|
||||
|
||||
if (/code|token|error/.test(window.location.hash)) {
|
||||
qp = window.location.hash.substring(1);
|
||||
} else {
|
||||
qp = location.search.substring(1);
|
||||
}
|
||||
|
||||
arr = qp.split("&")
|
||||
arr.forEach(function (v,i,_arr) { _arr[i] = '"' + v.replace('=', '":"') + '"';})
|
||||
qp = qp ? JSON.parse('{' + arr.join() + '}',
|
||||
function (key, value) {
|
||||
return key === "" ? value : decodeURIComponent(value)
|
||||
}
|
||||
) : {}
|
||||
|
||||
isValid = qp.state === sentState
|
||||
|
||||
if ((
|
||||
oauth2.auth.schema.get("flow") === "accessCode"||
|
||||
oauth2.auth.schema.get("flow") === "authorizationCode"
|
||||
) && !oauth2.auth.code) {
|
||||
if (!isValid) {
|
||||
oauth2.errCb({
|
||||
authId: oauth2.auth.name,
|
||||
source: "auth",
|
||||
level: "warning",
|
||||
message: "Authorization may be unsafe, passed state was changed in server Passed state wasn't returned from auth server"
|
||||
});
|
||||
}
|
||||
|
||||
if (qp.code) {
|
||||
delete oauth2.state;
|
||||
oauth2.auth.code = qp.code;
|
||||
oauth2.callback({auth: oauth2.auth, redirectUrl: redirectUrl});
|
||||
} else {
|
||||
let oauthErrorMsg
|
||||
if (qp.error) {
|
||||
oauthErrorMsg = "["+qp.error+"]: " +
|
||||
(qp.error_description ? qp.error_description+ ". " : "no accessCode received from the server. ") +
|
||||
(qp.error_uri ? "More info: "+qp.error_uri : "");
|
||||
}
|
||||
|
||||
oauth2.errCb({
|
||||
authId: oauth2.auth.name,
|
||||
source: "auth",
|
||||
level: "error",
|
||||
message: oauthErrorMsg || "[Authorization failed]: no accessCode received from the server"
|
||||
});
|
||||
}
|
||||
} else {
|
||||
oauth2.callback({auth: oauth2.auth, token: qp, isValid: isValid, redirectUrl: redirectUrl});
|
||||
}
|
||||
window.close();
|
||||
}
|
||||
</script>
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace TorstenHettstedt\TimekeepingApi\Controller;
|
||||
|
||||
use JsonSerializable;
|
||||
use PDO;
|
||||
use Psr\Container\ContainerExceptionInterface;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Psr\Container\NotFoundExceptionInterface;
|
||||
use Slim\Psr7\Response;
|
||||
|
||||
abstract class AbstractController
|
||||
{
|
||||
|
||||
protected PDO $databases;
|
||||
|
||||
/**
|
||||
* WorkingHoursController constructor.
|
||||
*
|
||||
* @param ContainerInterface $container
|
||||
*
|
||||
* @throws NotDatabasesException
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
*/
|
||||
public function __construct(ContainerInterface $container)
|
||||
{
|
||||
if ($container->has('databases') === false) {
|
||||
throw new NotDatabasesException('Datenbank ist nicht Vorhanden');
|
||||
}
|
||||
$this->databases = $container->get('databases');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param JsonSerializable|JsonSerializable[] $data
|
||||
* @param int $status_code
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
protected function printResponse(Response $response, mixed $data, int $status_code): Response
|
||||
{
|
||||
$payload = json_encode($data);
|
||||
|
||||
$response->getBody()->write($payload);
|
||||
|
||||
return $response
|
||||
->withHeader('Content-Type', 'application/json')
|
||||
->withStatus($status_code);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
<?php /** @noinspection PhpUnused */
|
||||
|
||||
|
||||
namespace TorstenHettstedt\TimekeepingApi\Controller;
|
||||
|
||||
use Slim\Psr7\Response;
|
||||
use Slim\Psr7\Request;
|
||||
|
||||
class HelloWorldController
|
||||
{
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param mixed[] $args
|
||||
*
|
||||
* @return Response
|
||||
*
|
||||
* @noinspection PhpUnusedParameterInspection
|
||||
*/
|
||||
public function read(Request $request, Response $response, array $args): Response
|
||||
{
|
||||
$payload = json_encode('Hallo World');
|
||||
|
||||
$response->getBody()->write($payload);
|
||||
|
||||
return $response
|
||||
->withHeader('Content-Type', 'application/json')
|
||||
->withStatus(200);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php /** @noinspection PhpMissingFieldTypeInspection */
|
||||
|
||||
namespace TorstenHettstedt\TimekeepingApi\Controller;
|
||||
|
||||
use Slim\Exception\HttpSpecializedException;
|
||||
|
||||
class HttpConflictRequestException extends HttpSpecializedException
|
||||
{
|
||||
/** @var int */
|
||||
protected $code = 409;
|
||||
/** @var string */
|
||||
protected $message = 'Conflict.';
|
||||
protected string $title = '409 Conflict';
|
||||
protected string $description = 'The 409 (Conflict) status code indicates that the request could not be completed due to a conflict with the current state of the target resource.';
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace TorstenHettstedt\TimekeepingApi\Controller;
|
||||
|
||||
use Exception;
|
||||
|
||||
class NotDatabasesException extends Exception
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace TorstenHettstedt\TimekeepingApi\Controller;
|
||||
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Slim\Psr7\Request;
|
||||
use Slim\Psr7\Response;
|
||||
use TorstenHettstedt\TimekeepingApi\Middleware\OriginAccessControlHandler;
|
||||
|
||||
class PreflightController
|
||||
{
|
||||
/** @noinspection PhpUnused */
|
||||
public function preflight(Request $request, Response $response): ResponseInterface
|
||||
{
|
||||
return (new OriginAccessControlHandler())->originAccessControl($request, $response);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
<?php
|
||||
|
||||
namespace TorstenHettstedt\TimekeepingApi\Controller;
|
||||
|
||||
use DateInterval;
|
||||
use DateTime;
|
||||
use Exception;
|
||||
use Fig\Http\Message\StatusCodeInterface;
|
||||
use Slim\Exception\HttpBadRequestException;
|
||||
use Slim\Exception\HttpInternalServerErrorException;
|
||||
use Slim\Exception\HttpNotFoundException;
|
||||
use Slim\Psr7\Request;
|
||||
use Slim\Psr7\Response;
|
||||
use TorstenHettstedt\TimekeepingApi\Models\WorkingHours;
|
||||
use TorstenHettstedt\TimekeepingApi\Repositories\RepositoryBadWhereDataException;
|
||||
use TorstenHettstedt\TimekeepingApi\Repositories\RepositoryRecordAlreadyExistException;
|
||||
use TorstenHettstedt\TimekeepingApi\Repositories\RepositoryRecordNotFoundException;
|
||||
use TorstenHettstedt\TimekeepingApi\Repositories\WorkingHoursRepository;
|
||||
|
||||
class WorkingHoursController extends AbstractController
|
||||
{
|
||||
|
||||
/**
|
||||
* @throws HttpBadRequestException
|
||||
* @throws HttpNotFoundException
|
||||
*/
|
||||
public function update(Request $request, Response $response, array $args): Response
|
||||
{
|
||||
$body = $request->getParsedBody();
|
||||
$repository = new WorkingHoursRepository($this->databases);
|
||||
$model = $this->buildModel($request, $args['id'], $body['workingTime'], $body['isHomeOffice'] ?? false);
|
||||
try {
|
||||
$repository->update($model);
|
||||
} catch (RepositoryRecordNotFoundException $exception) {
|
||||
throw new HttpNotFoundException($request, 'Der Eintrag ist nicht vorhanden.', $exception);
|
||||
}
|
||||
return $this->printResponse($response, $model, StatusCodeInterface::STATUS_OK);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws HttpInternalServerErrorException
|
||||
* @throws HttpBadRequestException
|
||||
* @noinspection PhpUnusedParameterInspection
|
||||
*/
|
||||
public function browse(Request $request, Response $response, array $args): Response
|
||||
{
|
||||
$repository = new WorkingHoursRepository($this->databases);
|
||||
$queryParams = $request->getQueryParams();
|
||||
try {
|
||||
return $this->printResponse(
|
||||
$response,
|
||||
$repository->findFiltered(
|
||||
$queryParams['start-date'] ?? null,
|
||||
$queryParams['end-date'] ?? null
|
||||
),
|
||||
StatusCodeInterface::STATUS_OK
|
||||
);
|
||||
} catch (RepositoryBadWhereDataException $exception) {
|
||||
throw new HttpBadRequestException($request, 'Ein Wert für das Datum im Query ist ungültig', $exception);
|
||||
} catch (Exception $exception) {
|
||||
throw new HttpInternalServerErrorException(
|
||||
$request,
|
||||
'Der Wert für das Datum oder die Zeit ist falsch in der Datenbank',
|
||||
$exception
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws HttpNotFoundException
|
||||
* @throws HttpInternalServerErrorException
|
||||
*/
|
||||
public function read(Request $request, Response $response, array $args): Response
|
||||
{
|
||||
$repository = new WorkingHoursRepository($this->databases);
|
||||
try {
|
||||
return $this->printResponse($response, $repository->findByKey($args['id']), StatusCodeInterface::STATUS_OK);
|
||||
} catch (RepositoryRecordNotFoundException $exception) {
|
||||
throw new HttpNotFoundException($request, 'Der Eintrag ist nicht vorhanden.', $exception);
|
||||
} catch (Exception $exception) {
|
||||
throw new HttpInternalServerErrorException(
|
||||
$request,
|
||||
'Der Wert für das Datum oder die Zeit ist falsch in der Datenbank',
|
||||
$exception
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws HttpBadRequestException
|
||||
* @throws HttpConflictRequestException
|
||||
* @throws HttpInternalServerErrorException
|
||||
* @noinspection PhpUnusedParameterInspection
|
||||
*/
|
||||
public function creat(Request $request, Response $response, array $args): Response
|
||||
{
|
||||
$body = $request->getParsedBody();
|
||||
$repository = new WorkingHoursRepository($this->databases);
|
||||
$model = $this->buildModel(
|
||||
$request,
|
||||
$body['workingDay'],
|
||||
$body['workingTime'],
|
||||
$body['isHomeOffice'] ?? false
|
||||
);
|
||||
try {
|
||||
$repository->insert($model);
|
||||
} catch (RepositoryRecordAlreadyExistException $exception) {
|
||||
throw new HttpConflictRequestException($request, 'Der Eintrag ist schon vorhanden.', $exception);
|
||||
} catch (Exception $exception) {
|
||||
throw new HttpInternalServerErrorException(
|
||||
$request,
|
||||
'Der Wert für das Datum oder die Zeit ist falsch in der Datenbank',
|
||||
$exception
|
||||
);
|
||||
}
|
||||
return $this->printResponse($response, $model, StatusCodeInterface::STATUS_CREATED);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws HttpBadRequestException
|
||||
*/
|
||||
protected function buildModel(Request $request, string $date, string $time, bool $isHomeOffice): WorkingHours
|
||||
{
|
||||
try {
|
||||
$workingDay = new DateTime($date);
|
||||
} catch (Exception $exception) {
|
||||
throw new HttpBadRequestException($request, 'Der Wert für das Datum ist falsch', $exception);
|
||||
}
|
||||
try {
|
||||
$workingTime = new DateInterval('P0000-00-00T' . $time);
|
||||
} catch (Exception $exception) {
|
||||
throw new HttpBadRequestException($request, 'Der Wert für die Zeit ist falsch', $exception);
|
||||
}
|
||||
return (new WorkingHours())
|
||||
->setWorkingDay($workingDay)
|
||||
->setWorkingTime($workingTime)
|
||||
->setIsHomeOffice($isHomeOffice);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
namespace TorstenHettstedt\TimekeepingApi\Controller;
|
||||
|
||||
|
||||
use Exception;
|
||||
use Fig\Http\Message\StatusCodeInterface;
|
||||
use Slim\Exception\HttpInternalServerErrorException;
|
||||
use Slim\Psr7\Request;
|
||||
use Slim\Psr7\Response;
|
||||
use TorstenHettstedt\TimekeepingApi\Repositories\WorkingHoursMonthlyViewRepository;
|
||||
use TorstenHettstedt\TimekeepingApi\Repositories\WorkingHoursWeeklyViewRepository;
|
||||
use TorstenHettstedt\TimekeepingApi\Repositories\WorkingHoursYearlyViewRepository;
|
||||
|
||||
class WorkingHoursViewController extends AbstractController
|
||||
{
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param array<string, mixed> $args
|
||||
*
|
||||
* @return Response
|
||||
*
|
||||
* @throws HttpInternalServerErrorException
|
||||
* @noinspection PhpUnusedParameterInspection
|
||||
*/
|
||||
public function browseWeekly(Request $request, Response $response, array $args): Response
|
||||
{
|
||||
$repository = new WorkingHoursWeeklyViewRepository($this->databases);
|
||||
try {
|
||||
return $this->printResponse($response, $repository->findAll(), StatusCodeInterface::STATUS_OK);
|
||||
} catch (Exception $exception) {
|
||||
throw new HttpInternalServerErrorException(
|
||||
$request,
|
||||
'Der Wert für das Datum oder die Zeit ist falsch in der Datenbank', $exception
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param array<string, mixed> $args
|
||||
*
|
||||
* @return Response
|
||||
*
|
||||
* @throws HttpInternalServerErrorException
|
||||
* @noinspection PhpUnusedParameterInspection
|
||||
*/
|
||||
public function browseMonthly(Request $request, Response $response, array $args): Response
|
||||
{
|
||||
$repository = new WorkingHoursMonthlyViewRepository($this->databases);
|
||||
try {
|
||||
return $this->printResponse($response, $repository->findAll(), StatusCodeInterface::STATUS_OK);
|
||||
} catch (Exception $exception) {
|
||||
throw new HttpInternalServerErrorException(
|
||||
$request,
|
||||
'Der Wert für das Datum oder die Zeit ist falsch in der Datenbank', $exception
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param array<string, mixed> $args
|
||||
*
|
||||
* @return Response
|
||||
*
|
||||
* @throws HttpInternalServerErrorException
|
||||
* @noinspection PhpUnusedParameterInspection
|
||||
*/
|
||||
public function browseYearly(Request $request, Response $response, array $args): Response
|
||||
{
|
||||
$repository = new WorkingHoursYearlyViewRepository($this->databases);
|
||||
try {
|
||||
return $this->printResponse($response, $repository->findAll(), StatusCodeInterface::STATUS_OK);
|
||||
} catch (Exception $exception) {
|
||||
throw new HttpInternalServerErrorException(
|
||||
$request,
|
||||
'Der Wert für das Datum oder die Zeit ist falsch in der Datenbank', $exception
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
/** @noinspection PhpUndefinedClassInspection */
|
||||
|
||||
|
||||
namespace TorstenHettstedt\TimekeepingApi\Middleware;
|
||||
|
||||
|
||||
use JetBrains\PhpStorm\ArrayShape;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Slim\App;
|
||||
use Slim\Exception\HttpException;
|
||||
use Throwable;
|
||||
|
||||
class ErrorHandler
|
||||
{
|
||||
/**
|
||||
* @var App<ContainerInterface>
|
||||
*/
|
||||
protected App $app;
|
||||
|
||||
/**
|
||||
* ErrorHandler constructor.
|
||||
*
|
||||
* @param App<ContainerInterface> $app
|
||||
*/
|
||||
public function __construct(App $app)
|
||||
{
|
||||
$this->app = $app;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Throwable $exception
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
#[ArrayShape([
|
||||
'timestamp' => "false|string",
|
||||
'status' => "int",
|
||||
"error" => "string",
|
||||
'message' => "string",
|
||||
'path' => "string"
|
||||
])] protected function buildJsonArray(Throwable $exception): array
|
||||
{
|
||||
$payload = [
|
||||
'timestamp' => date('Y-m-d\TH:m:sP'),
|
||||
'status' => $exception->getCode(),
|
||||
"error" => '',
|
||||
'message' => $exception->getMessage(),
|
||||
'path' => $exception->getFile()
|
||||
];
|
||||
|
||||
if ($exception instanceof HttpException) {
|
||||
$payload['error'] = $exception->getTitle();
|
||||
}
|
||||
|
||||
return $payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ServerRequestInterface $request
|
||||
* @param Throwable $exception
|
||||
* @param bool $displayErrorDetails
|
||||
* @param bool $logErrors
|
||||
* @param bool $logErrorDetails
|
||||
* @param LoggerInterface|null $logger
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function __invoke(
|
||||
ServerRequestInterface $request,
|
||||
Throwable $exception,
|
||||
bool $displayErrorDetails,
|
||||
bool $logErrors,
|
||||
bool $logErrorDetails,
|
||||
?LoggerInterface $logger = null
|
||||
): ResponseInterface {
|
||||
if ($logger !== null && $logErrors === true) {
|
||||
$logger->error($exception->getMessage());
|
||||
}
|
||||
|
||||
$payload = $this->buildJsonArray($exception);
|
||||
|
||||
$response = $this->app->getResponseFactory()->createResponse($exception->getCode());
|
||||
$response->getBody()->write(
|
||||
json_encode($payload, JSON_UNESCAPED_UNICODE)
|
||||
);
|
||||
|
||||
$originAccessControlHandler = new OriginAccessControlHandler();
|
||||
|
||||
return $originAccessControlHandler->originAccessControl($request, $response);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace TorstenHettstedt\TimekeepingApi\Middleware;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Psr\Http\Server\MiddlewareInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
|
||||
|
||||
class JsonBodyParserMiddleware implements MiddlewareInterface
|
||||
{
|
||||
public function process(Request $request, RequestHandler $handler): Response
|
||||
{
|
||||
$contentType = $request->getHeaderLine('Content-Type');
|
||||
|
||||
if (str_contains($contentType, 'application/json')) {
|
||||
$contents = json_decode(file_get_contents('php://input'), true);
|
||||
if (json_last_error() === JSON_ERROR_NONE) {
|
||||
$request = $request->withParsedBody($contents);
|
||||
}
|
||||
}
|
||||
|
||||
return $handler->handle($request);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace TorstenHettstedt\TimekeepingApi\Middleware;
|
||||
|
||||
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Psr\Http\Server\MiddlewareInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
|
||||
use Slim\Routing\RouteContext;
|
||||
|
||||
class OriginAccessControlHandler implements MiddlewareInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* Process an incoming server request.
|
||||
*
|
||||
* Processes an incoming server request in order to produce a response.
|
||||
* If unable to produce the response itself, it may delegate to the provided
|
||||
* request handler to do so.
|
||||
*/
|
||||
public function process(Request $request, RequestHandler $handler): Response
|
||||
{
|
||||
$response = $handler->handle($request);
|
||||
return $this->originAccessControl($request, $response);
|
||||
}
|
||||
|
||||
public function originAccessControl(Request $request, Response $response): Response
|
||||
{
|
||||
$routeContext = RouteContext::fromRequest($request);
|
||||
$routingResults = $routeContext->getRoutingResults();
|
||||
$methods = $routingResults->getAllowedMethods();
|
||||
$requestHeaders = $request->getHeaderLine('Access-Control-Request-Headers');
|
||||
|
||||
if ($this->testRoute($request)) {
|
||||
// Stunden Minuten Sekunden
|
||||
$accessControlMaxAge = 24 * 60 * 60;
|
||||
$response = $response->withHeader('Access-Control-Allow-Origin', $this->buildOrigin($request));
|
||||
$response = $response->withHeader('Access-Control-Allow-Methods', implode(',', $methods));
|
||||
$response = $response->withHeader('Access-Control-Allow-Headers', $requestHeaders);
|
||||
$response = $response->withHeader('Access-Control-Max-Age', (string)$accessControlMaxAge);
|
||||
}
|
||||
return $response;
|
||||
}
|
||||
|
||||
protected function testRoute(Request $request): bool
|
||||
{
|
||||
$corsAllowOriginRegex = $_ENV['CORS_ALLOW_ORIGIN_REGEX'] ?? '/https?:\/\/.*:.*/';
|
||||
return preg_match($corsAllowOriginRegex, $this->buildOrigin($request));
|
||||
}
|
||||
|
||||
protected function buildOrigin(Request $request): string
|
||||
{
|
||||
return $request->getHeaderLine('Origin');
|
||||
}
|
||||
}
|
||||
@@ -1,23 +1,16 @@
|
||||
<?php /** @noinspection PhpUnusedPrivateFieldInspection */
|
||||
<?php
|
||||
/** @noinspection PhpUnusedPrivateFieldInspection */
|
||||
|
||||
|
||||
namespace TorstenHettstedt\TimekeepingApi\Models;
|
||||
|
||||
use MyCLabs\Enum\Enum;
|
||||
|
||||
/**
|
||||
* Enum-Klasse für die unterstützten Periode-Definitionen.
|
||||
* Der Wert der Periode ist ein Format-String für {@link https://secure.php.net/manual/en/datetime.format.php}.
|
||||
*
|
||||
* @extends Enum<string>
|
||||
*
|
||||
* @method static PeriodDesignationEnum WEEKLY()
|
||||
* @method static PeriodDesignationEnum MONTHLY()
|
||||
* @method static PeriodDesignationEnum YEARLY()
|
||||
*/
|
||||
class PeriodDesignationEnum extends Enum
|
||||
enum PeriodDesignationEnum: string
|
||||
{
|
||||
private const WEEKLY = 'Y#W';
|
||||
private const MONTHLY = 'Y-m';
|
||||
private const YEARLY = 'Y';
|
||||
case WEEKLY = 'Y#W';
|
||||
case MONTHLY = 'Y-m';
|
||||
case YEARLY = 'Y';
|
||||
}
|
||||
@@ -12,54 +12,51 @@ class WorkingHours implements ModelInterface
|
||||
{
|
||||
protected ?DateTime $workingDay = null;
|
||||
protected ?DateInterval $workingTime = null;
|
||||
protected bool $isHomeOffice = false;
|
||||
|
||||
/**
|
||||
* @return DateTime|null
|
||||
*/
|
||||
public function getWorkingDay(): ?DateTime
|
||||
{
|
||||
return $this->workingDay;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DateTime $workingDay
|
||||
*
|
||||
* @return WorkingHours
|
||||
*/
|
||||
public function setWorkingDay(DateTime $workingDay): WorkingHours
|
||||
public function setWorkingDay(DateTime $workingDay): static
|
||||
{
|
||||
$this->workingDay = $workingDay;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return DateInterval|null
|
||||
*/
|
||||
public function getWorkingTime(): ?DateInterval
|
||||
{
|
||||
return $this->workingTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DateInterval $workingTime
|
||||
*
|
||||
* @return WorkingHours
|
||||
*/
|
||||
public function setWorkingTime(DateInterval $workingTime): WorkingHours
|
||||
public function setWorkingTime(DateInterval $workingTime): static
|
||||
{
|
||||
$this->workingTime = $workingTime;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function isHomeOffice(): bool
|
||||
{
|
||||
return $this->isHomeOffice;
|
||||
}
|
||||
|
||||
public function setIsHomeOffice(bool $isHomeOffice): WorkingHours
|
||||
{
|
||||
$this->isHomeOffice = $isHomeOffice;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
#[ArrayShape(['workingDay' => "string", 'workingTime' => "string"])]
|
||||
#[ArrayShape(['workingDay' => "string", 'workingTime' => "string", 'isHomeOffice' => "boolean"])]
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return [
|
||||
'workingDay' => $this->getWorkingDay()->format('Y-m-d'),
|
||||
'workingTime' => $this->getWorkingTime()->format('%H:%I:%S')
|
||||
'workingTime' => $this->getWorkingTime()->format('%H:%I:%S'),
|
||||
'isHomeOffice' => $this->isHomeOffice(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -10,93 +10,63 @@ use JetBrains\PhpStorm\ArrayShape;
|
||||
|
||||
class WorkingHoursView implements ModelInterface
|
||||
{
|
||||
protected DateTimeInterface $period;
|
||||
protected PeriodDesignationEnum $periodDesignation;
|
||||
protected int $workingDays;
|
||||
protected DateInterval $totalHours;
|
||||
protected DateInterval $overtime;
|
||||
|
||||
/**
|
||||
* WorkingHoursView constructor.
|
||||
*
|
||||
* @param DateTimeInterface $period
|
||||
* @param PeriodDesignationEnum $periodDesignation
|
||||
* @param int $workingDays
|
||||
* @param DateInterval $totalHours
|
||||
* @param DateInterval $overtime
|
||||
*/
|
||||
public function __construct(
|
||||
DateTimeInterface $period,
|
||||
PeriodDesignationEnum $periodDesignation,
|
||||
int $workingDays,
|
||||
DateInterval $totalHours,
|
||||
DateInterval $overtime
|
||||
protected DateTimeInterface $period,
|
||||
protected PeriodDesignationEnum $periodDesignation,
|
||||
protected int $workingDays,
|
||||
protected DateInterval $totalHours,
|
||||
private int $homeOfficeDays,
|
||||
protected DateInterval $overtime
|
||||
) {
|
||||
$this->period = $period;
|
||||
$this->periodDesignation = $periodDesignation;
|
||||
$this->workingDays = $workingDays;
|
||||
$this->totalHours = $totalHours;
|
||||
$this->overtime = $overtime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return DateTimeInterface
|
||||
*/
|
||||
public function getPeriod(): DateTimeInterface
|
||||
{
|
||||
return $this->period;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return PeriodDesignationEnum
|
||||
*/
|
||||
public function getPeriodDesignation(): PeriodDesignationEnum
|
||||
{
|
||||
return $this->periodDesignation;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getWorkingDays(): int
|
||||
{
|
||||
return $this->workingDays;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return DateInterval
|
||||
*/
|
||||
public function getHomeOfficeDays(): int
|
||||
{
|
||||
return $this->homeOfficeDays;
|
||||
}
|
||||
|
||||
public function getTotalHours(): DateInterval
|
||||
{
|
||||
return $this->totalHours;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return DateInterval
|
||||
*/
|
||||
public function getOvertime(): DateInterval
|
||||
{
|
||||
return $this->overtime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
#[ArrayShape(['period' => "string",
|
||||
'periodDesignation' => "string",
|
||||
'totalHours' => "string",
|
||||
'workingDays' => "int",
|
||||
'homeOfficeDays' => "int",
|
||||
'overtime' => "string"
|
||||
])] public function jsonSerialize(): array
|
||||
{
|
||||
$formatOvertime = (($this->getOvertime()->invert === 1) ? '-' : '') . '%H:%I:%S';
|
||||
return [
|
||||
'period' => $this->getPeriod()->format($this->getPeriodDesignation()->getValue()),
|
||||
'periodDesignation' => strtolower($this->getPeriodDesignation()->getKey()),
|
||||
'period' => $this->getPeriod()->format($this->getPeriodDesignation()->value),
|
||||
'periodDesignation' => strtolower($this->getPeriodDesignation()->name),
|
||||
'workingDays' => $this->getWorkingDays(),
|
||||
'totalHours' => $this->getTotalHours()->format('%H:%I:%S'),
|
||||
'overtime' => $this->getOvertime()->format('%H:%I:%S')
|
||||
'homeOfficeDays' => $this->getHomeOfficeDays(),
|
||||
'overtime' => $this->getOvertime()->format($formatOvertime)
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -17,10 +17,9 @@ use TorstenHettstedt\TimekeepingApi\Models\WorkingHoursView;
|
||||
abstract class AbstractWorkingHoursViewRepository implements RepositoryReaderInterface
|
||||
{
|
||||
|
||||
protected const SQL_SELECT = '';
|
||||
protected const SQL_WHERE = '';
|
||||
protected const string SQL_SELECT = '';
|
||||
protected const string SQL_WHERE = '';
|
||||
|
||||
protected PDO $database;
|
||||
protected PeriodDesignationEnum $periodDesignation;
|
||||
|
||||
/**
|
||||
@@ -28,9 +27,8 @@ abstract class AbstractWorkingHoursViewRepository implements RepositoryReaderInt
|
||||
*
|
||||
* @param PDO $database
|
||||
*/
|
||||
public function __construct(PDO $database)
|
||||
public function __construct(protected PDO $database)
|
||||
{
|
||||
$this->database = $database;
|
||||
$this->database->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
}
|
||||
|
||||
@@ -53,6 +51,7 @@ abstract class AbstractWorkingHoursViewRepository implements RepositoryReaderInt
|
||||
$this->periodDesignation,
|
||||
(int) $row['workingDays'],
|
||||
$this->buildDateInterval($row['totalHours']),
|
||||
(int) $row['homeOfficeDays'],
|
||||
$this->buildDateInterval($row['overtime'])
|
||||
);
|
||||
$models[] = $model;
|
||||
@@ -82,8 +81,9 @@ abstract class AbstractWorkingHoursViewRepository implements RepositoryReaderInt
|
||||
return new WorkingHoursView(
|
||||
$this->buildDateFromPeriod($row['period']),
|
||||
$this->periodDesignation,
|
||||
(int) $row['workingDays'],
|
||||
(int)$row['workingDays'],
|
||||
$this->buildDateInterval($row['totalHours']),
|
||||
(int)$row['homeOfficeDays'],
|
||||
$this->buildDateInterval($row['overtime'])
|
||||
);
|
||||
}
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
namespace TorstenHettstedt\TimekeepingApi\Repositories;
|
||||
|
||||
|
||||
class RepositoryModelAlreadyExists extends RepositoryException
|
||||
class RepositoryBadWhereDataException extends RepositoryException
|
||||
{
|
||||
|
||||
}
|
||||
@@ -3,8 +3,10 @@
|
||||
|
||||
namespace TorstenHettstedt\TimekeepingApi\Repositories;
|
||||
|
||||
use TorstenHettstedt\TimekeepingApi\Models\ModelInterface;
|
||||
|
||||
/**
|
||||
* @template T of \TorstenHettstedt\TimekeepingApi\Models\ModelInterface
|
||||
* @template T of ModelInterface
|
||||
*/
|
||||
interface RepositoryReaderInterface
|
||||
{
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace TorstenHettstedt\TimekeepingApi\Repositories;
|
||||
|
||||
|
||||
class RepositoryRecordAlreadyExistException extends RepositoryException
|
||||
{
|
||||
|
||||
}
|
||||
@@ -3,15 +3,17 @@
|
||||
|
||||
namespace TorstenHettstedt\TimekeepingApi\Repositories;
|
||||
|
||||
use TorstenHettstedt\TimekeepingApi\Models\ModelInterface;
|
||||
|
||||
/**
|
||||
* @template T of \TorstenHettstedt\TimekeepingApi\Models\ModelInterface
|
||||
* @template T of ModelInterface
|
||||
*/
|
||||
interface RepositoryWriterInterface
|
||||
{
|
||||
/**
|
||||
* @param T $model
|
||||
*
|
||||
* @throws RepositoryModelAlreadyExists
|
||||
* @throws RepositoryRecordAlreadyExistException
|
||||
*/
|
||||
public function insert(mixed $model): void;
|
||||
|
||||
|
||||
@@ -12,23 +12,21 @@ use TorstenHettstedt\TimekeepingApi\Models\PeriodDesignationEnum;
|
||||
class WorkingHoursMonthlyViewRepository extends AbstractWorkingHoursViewRepository
|
||||
{
|
||||
|
||||
protected const SQL_SELECT = <<<SQL
|
||||
protected const string SQL_SELECT = <<<SQL
|
||||
select
|
||||
"Monat" as "period",
|
||||
"Gesamtarbeitszeit" as "totalHours",
|
||||
"Arbeitstage" as "workingDays",
|
||||
"Home-Office-Tage" as "homeOfficeDays",
|
||||
"Überstunden" as "overtime"
|
||||
from "Arbeitszeiten - Monat"
|
||||
SQL;
|
||||
protected const SQL_WHERE = ' where "Monat" = ?';
|
||||
|
||||
protected PDO $database;
|
||||
protected PeriodDesignationEnum $periodDesignation;
|
||||
protected const string SQL_WHERE = ' where "Monat" = ?';
|
||||
|
||||
public function __construct(PDO $database)
|
||||
{
|
||||
parent::__construct($database);
|
||||
$this->periodDesignation = PeriodDesignationEnum::MONTHLY();
|
||||
$this->periodDesignation = PeriodDesignationEnum::MONTHLY;
|
||||
}
|
||||
|
||||
protected function buildDateFromPeriod(string $period): DateTimeInterface
|
||||
|
||||
@@ -10,6 +10,7 @@ use Exception;
|
||||
use InvalidArgumentException;
|
||||
use PDO;
|
||||
use PDOException;
|
||||
use PDOStatement;
|
||||
use TorstenHettstedt\TimekeepingApi\Models\ModelInterface;
|
||||
use TorstenHettstedt\TimekeepingApi\Models\WorkingHours;
|
||||
|
||||
@@ -19,17 +20,17 @@ use TorstenHettstedt\TimekeepingApi\Models\WorkingHours;
|
||||
*/
|
||||
class WorkingHoursRepository implements RepositoryReaderInterface, RepositoryWriterInterface
|
||||
{
|
||||
protected const string SQL_SELECT = <<<'SQL'
|
||||
select
|
||||
"Datum" as "workingDay",
|
||||
"Arbeitszeit" as "workingTime",
|
||||
"HomeOffice" as "isHomeOfficeDay"
|
||||
from public."Arbeitszeiten"
|
||||
SQL;
|
||||
|
||||
protected PDO $database;
|
||||
|
||||
/**
|
||||
* WorkingHoursRepository constructor.
|
||||
*
|
||||
* @param PDO $database
|
||||
*/
|
||||
public function __construct(PDO $database)
|
||||
public function __construct(protected PDO $database)
|
||||
{
|
||||
$this->database = $database;
|
||||
$this->database->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
}
|
||||
|
||||
@@ -42,20 +43,73 @@ class WorkingHoursRepository implements RepositoryReaderInterface, RepositoryWri
|
||||
* @throws PDOException
|
||||
*/
|
||||
public function findAll(): array
|
||||
{
|
||||
return $this->findFiltered(null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Gibt alle vorhandenen Einträge zurück
|
||||
*
|
||||
* @param string|null $start
|
||||
* @param string|null $end
|
||||
*
|
||||
* @return WorkingHours[]
|
||||
* @throws Exception
|
||||
*/
|
||||
public function findFiltered(?string $start, ?string $end): array
|
||||
{
|
||||
$models = [];
|
||||
$stmt = $this->database->prepare('select "Datum" as "workingDay", "Arbeitszeit" as "workingTime" from public."Arbeitszeiten"');
|
||||
$stmt->execute();
|
||||
$stmt = $this->buildFindFilteredStatement($start, $end);
|
||||
try {
|
||||
$stmt->execute();
|
||||
} catch (PDOException $exception) {
|
||||
if ($exception->getCode() === '22007' || $exception->getCode() === '22008') {
|
||||
throw new RepositoryBadWhereDataException($exception);
|
||||
}
|
||||
throw $exception;
|
||||
}
|
||||
|
||||
while ($row = $stmt->fetch()) {
|
||||
$model = new WorkingHours();
|
||||
$model
|
||||
->setWorkingDay(new DateTime($row['workingDay']))
|
||||
->setWorkingTime(new DateInterval('P0000-00-00T' . $row['workingTime']));
|
||||
->setWorkingTime(new DateInterval('P0000-00-00T' . $row['workingTime']))
|
||||
->setIsHomeOffice($row['isHomeOfficeDay']);
|
||||
$models[] = $model;
|
||||
}
|
||||
return $models;
|
||||
}
|
||||
|
||||
protected function buildFilterString(?string $start, ?string $end): string
|
||||
{
|
||||
if (is_string($start) && is_string($end)) {
|
||||
return 'where "Datum" >= :start AND "Datum" <= :end ';
|
||||
}
|
||||
if (is_string($start)) {
|
||||
return 'where "Datum" >= :start ';
|
||||
}
|
||||
if (is_string($end)) {
|
||||
return 'where "Datum" <= :end ';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
protected function buildFindFilteredStatement(?string $start, ?string $end): ?PDOStatement
|
||||
{
|
||||
$query = static::SQL_SELECT . ' ';
|
||||
$query .= $this->buildFilterString($start, $end);
|
||||
$query .= 'order by "Datum"';
|
||||
$stmt = $this->database->prepare($query);
|
||||
if (is_string($start)) {
|
||||
$stmt->bindParam(':start', $start);
|
||||
}
|
||||
if (is_string($end)) {
|
||||
$stmt->bindParam(':end', $end);
|
||||
}
|
||||
return $stmt ?: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt den Eintrag zurück, der durch die ID
|
||||
*
|
||||
@@ -68,7 +122,7 @@ class WorkingHoursRepository implements RepositoryReaderInterface, RepositoryWri
|
||||
*/
|
||||
public function findByKey(mixed $primary_key): WorkingHours
|
||||
{
|
||||
$stmt = $this->database->prepare('select "Datum" as "workingDay", "Arbeitszeit" as "workingTime" from public."Arbeitszeiten" where "Datum" = ?');
|
||||
$stmt = $this->database->prepare(static::SQL_SELECT . ' where "Datum" = ?');
|
||||
$stmt->bindParam(1, $primary_key);
|
||||
$stmt->execute();
|
||||
$row = $stmt->fetch();
|
||||
@@ -78,14 +132,15 @@ class WorkingHoursRepository implements RepositoryReaderInterface, RepositoryWri
|
||||
$model = new WorkingHours();
|
||||
$model
|
||||
->setWorkingDay(new DateTime($row['workingDay']))
|
||||
->setWorkingTime(new DateInterval('P0000-00-00T' . $row['workingTime']));
|
||||
->setWorkingTime(new DateInterval('P0000-00-00T' . $row['workingTime']))
|
||||
->setIsHomeOffice($row['isHomeOfficeDay']);
|
||||
return $model;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WorkingHours|ModelInterface $model
|
||||
*
|
||||
* @throws RepositoryModelAlreadyExists
|
||||
* @throws RepositoryRecordAlreadyExistException
|
||||
* @throws Exception
|
||||
*/
|
||||
public function insert(mixed $model): void
|
||||
@@ -93,18 +148,25 @@ class WorkingHoursRepository implements RepositoryReaderInterface, RepositoryWri
|
||||
if ($model instanceof WorkingHours) {
|
||||
$workingDay = $model->getWorkingDay()->format('Y-m-d');
|
||||
$workingTime = $model->getWorkingTime()->format('%H:%I:%S');
|
||||
$isHomeOffice = $model->isHomeOffice();
|
||||
} else {
|
||||
throw new InvalidArgumentException('Es wird ein Modell der Klasse "' . WorkingHours::class . '" verlangt.');
|
||||
}
|
||||
|
||||
try {
|
||||
$this->findByKey($workingDay);
|
||||
throw new RepositoryModelAlreadyExists();
|
||||
throw new RepositoryRecordAlreadyExistException();
|
||||
} /** @noinspection PhpUnusedLocalVariableInspection */
|
||||
catch (RepositoryRecordNotFoundException $exception) {
|
||||
$stmt = $this->database->prepare('insert into public."Arbeitszeiten" ("Datum", "Arbeitszeit") values (?, ?) on conflict do nothing');
|
||||
$stmt = $this->database->prepare(<<<'SQL'
|
||||
insert into public."Arbeitszeiten" ("Datum", "Arbeitszeit", "HomeOffice")
|
||||
values (?, ?, ?)
|
||||
on conflict do nothing
|
||||
SQL
|
||||
);
|
||||
$stmt->bindParam(1, $workingDay);
|
||||
$stmt->bindParam(2, $workingTime);
|
||||
$stmt->bindParam(3, $isHomeOffice, PDO::PARAM_BOOL);
|
||||
$stmt->execute();
|
||||
}
|
||||
}
|
||||
@@ -119,12 +181,19 @@ class WorkingHoursRepository implements RepositoryReaderInterface, RepositoryWri
|
||||
if ($model instanceof WorkingHours) {
|
||||
$workingDay = $model->getWorkingDay()->format('Y-m-d');
|
||||
$workingTime = $model->getWorkingTime()->format('%H:%I:%S');
|
||||
$isHomeOffice = $model->isHomeOffice();
|
||||
} else {
|
||||
throw new InvalidArgumentException('Es wird ein Modell der Klasse "' . WorkingHours::class . '" verlangt.');
|
||||
}
|
||||
$stmt = $this->database->prepare('update public."Arbeitszeiten" set "Arbeitszeit" = ? where "Datum" = ?');
|
||||
$stmt = $this->database->prepare(<<<'SQL'
|
||||
update public."Arbeitszeiten"
|
||||
set "Arbeitszeit" = ?, "HomeOffice" = ?
|
||||
where "Datum" = ?
|
||||
SQL
|
||||
);
|
||||
$stmt->bindParam(1, $workingTime);
|
||||
$stmt->bindParam(2, $workingDay);
|
||||
$stmt->bindParam(2, $isHomeOffice, PDO::PARAM_BOOL);
|
||||
$stmt->bindParam(3, $workingDay);
|
||||
$stmt->execute();
|
||||
if ($stmt->rowCount() < 1) {
|
||||
throw new RepositoryRecordNotFoundException();
|
||||
|
||||
@@ -11,23 +11,21 @@ use TorstenHettstedt\TimekeepingApi\Models\PeriodDesignationEnum;
|
||||
class WorkingHoursWeeklyViewRepository extends AbstractWorkingHoursViewRepository
|
||||
{
|
||||
|
||||
protected const SQL_SELECT = <<<SQL
|
||||
protected const string SQL_SELECT = <<<SQL
|
||||
select
|
||||
"Woche" as "period",
|
||||
"Gesamtarbeitszeit" as "totalHours",
|
||||
"Arbeitstage" as "workingDays",
|
||||
"Home-Office-Tage" as "homeOfficeDays",
|
||||
"Überstunden" as "overtime"
|
||||
from "Arbeitszeiten - Woche"
|
||||
SQL;
|
||||
protected const SQL_WHERE = ' where "Woche" = ?';
|
||||
|
||||
protected PDO $database;
|
||||
protected PeriodDesignationEnum $periodDesignation;
|
||||
protected const string SQL_WHERE = ' where "Woche" = ?';
|
||||
|
||||
public function __construct(PDO $database)
|
||||
{
|
||||
parent::__construct($database);
|
||||
$this->periodDesignation = PeriodDesignationEnum::WEEKLY();
|
||||
$this->periodDesignation = PeriodDesignationEnum::WEEKLY;
|
||||
}
|
||||
|
||||
protected function buildDateFromPeriod(string $period): DateTimeInterface
|
||||
|
||||
@@ -11,23 +11,21 @@ use TorstenHettstedt\TimekeepingApi\Models\PeriodDesignationEnum;
|
||||
class WorkingHoursYearlyViewRepository extends AbstractWorkingHoursViewRepository
|
||||
{
|
||||
|
||||
protected const SQL_SELECT = <<<SQL
|
||||
protected const string SQL_SELECT = <<<SQL
|
||||
select
|
||||
"Jahr" as "period",
|
||||
"Gesamtarbeitszeit" as "totalHours",
|
||||
"Arbeitstage" as "workingDays",
|
||||
"Home-Office-Tage" as "homeOfficeDays",
|
||||
"Überstunden" as "overtime"
|
||||
from "Arbeitszeiten - Jahr"
|
||||
SQL;
|
||||
protected const SQL_WHERE = ' where "Jahr" = ?';
|
||||
|
||||
protected PDO $database;
|
||||
protected PeriodDesignationEnum $periodDesignation;
|
||||
protected const string SQL_WHERE = ' where "Jahr" = ?';
|
||||
|
||||
public function __construct(PDO $database)
|
||||
{
|
||||
parent::__construct($database);
|
||||
$this->periodDesignation = PeriodDesignationEnum::YEARLY();
|
||||
$this->periodDesignation = PeriodDesignationEnum::YEARLY;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+109
-92
@@ -13,136 +13,153 @@ SET xmloption = content;
|
||||
SET client_min_messages = warning;
|
||||
SET row_security = off;
|
||||
|
||||
DROP TABLE IF EXISTS public."Arbeitszeiten";
|
||||
DROP VIEW IF EXISTS public."Arbeitszeiten - Woche";
|
||||
DROP VIEW IF EXISTS public."Arbeitszeiten - Monat";
|
||||
DROP VIEW IF EXISTS public."Arbeitszeiten - Jahr";
|
||||
DROP TABLE IF EXISTS public."Arbeitszeiten";
|
||||
|
||||
--
|
||||
-- Name: Arbeitszeiten; Type: TABLE; Schema: public; Owner: torsten
|
||||
--
|
||||
|
||||
CREATE TABLE public."Arbeitszeiten" (
|
||||
"Datum" date not null,
|
||||
"Arbeitszeit" interval(6) null,
|
||||
CREATE TABLE public."Arbeitszeiten"
|
||||
(
|
||||
"Datum" date not null,
|
||||
"Arbeitszeit" interval(6) null,
|
||||
"HomeOffice" boolean default FALSE not null,
|
||||
constraint "Arbeitszeiten_pkey" primary key ("Datum")
|
||||
);
|
||||
|
||||
|
||||
ALTER TABLE public."Arbeitszeiten" OWNER TO bruce;
|
||||
ALTER TABLE public."Arbeitszeiten"
|
||||
OWNER TO bruce;
|
||||
|
||||
--
|
||||
-- Name: Arbeitszeiten - Jahr; Type: VIEW; Schema: public; Owner: torsten
|
||||
--
|
||||
|
||||
CREATE OR REPLACE VIEW public."Arbeitszeiten - Jahr" AS
|
||||
SELECT (date_part('year'::text, "Arbeitszeiten"."Datum"))::text AS "Jahr",
|
||||
sum("Arbeitszeiten"."Arbeitszeit") AS "Gesamtarbeitszeit",
|
||||
count("Arbeitszeiten"."Datum") AS "Arbeitstage",
|
||||
(sum("Arbeitszeiten"."Arbeitszeit") - ((count("Arbeitszeiten"."Datum"))::double precision * '08:00:00'::interval)) AS "Überstunden"
|
||||
FROM public."Arbeitszeiten"
|
||||
GROUP BY (date_part('year'::text, "Arbeitszeiten"."Datum"))
|
||||
ORDER BY (date_part('year'::text, "Arbeitszeiten"."Datum"));
|
||||
SELECT (date_part('year'::text, "Arbeitszeiten"."Datum"))::text AS "Jahr",
|
||||
sum("Arbeitszeiten"."Arbeitszeit")::interval AS "Gesamtarbeitszeit",
|
||||
count("Arbeitszeiten"."Datum")::bigint AS "Arbeitstage",
|
||||
(sum("Arbeitszeiten"."Arbeitszeit") -
|
||||
((count("Arbeitszeiten"."Datum"))::double precision * '08:00:00'::interval)) AS "Überstunden",
|
||||
(count(*) filter (where "Arbeitszeiten"."HomeOffice"))::int as "Home-Office-Tage"
|
||||
FROM public."Arbeitszeiten"
|
||||
GROUP BY (date_part('year'::text, "Arbeitszeiten"."Datum"))
|
||||
ORDER BY (date_part('year'::text, "Arbeitszeiten"."Datum"));
|
||||
|
||||
|
||||
ALTER TABLE public."Arbeitszeiten - Jahr" OWNER TO bruce;
|
||||
ALTER TABLE public."Arbeitszeiten - Jahr"
|
||||
OWNER TO bruce;
|
||||
|
||||
--
|
||||
-- Name: Arbeitszeiten - Monat; Type: VIEW; Schema: public; Owner: torsten
|
||||
--
|
||||
|
||||
CREATE OR REPLACE VIEW public."Arbeitszeiten - Monat" AS
|
||||
SELECT to_char(date_trunc('month'::text, ("Arbeitszeiten"."Datum")::timestamp with time zone), 'TMYYYY TMMonth'::text) AS "Monat",
|
||||
sum("Arbeitszeiten"."Arbeitszeit") AS "Gesamtarbeitszeit",
|
||||
count("Arbeitszeiten"."Datum") AS "Arbeitstage",
|
||||
(sum("Arbeitszeiten"."Arbeitszeit") - ((count("Arbeitszeiten"."Datum"))::double precision * '08:00:00'::interval)) AS "Überstunden"
|
||||
FROM public."Arbeitszeiten"
|
||||
GROUP BY (date_trunc('month'::text, ("Arbeitszeiten"."Datum")::timestamp with time zone))
|
||||
ORDER BY (date_trunc('month'::text, ("Arbeitszeiten"."Datum")::timestamp with time zone));
|
||||
SELECT to_char(date_trunc('month'::text, ("Arbeitszeiten"."Datum")::timestamp with time zone),
|
||||
'TMYYYY TMMonth'::text) AS "Monat",
|
||||
sum("Arbeitszeiten"."Arbeitszeit")::interval AS "Gesamtarbeitszeit",
|
||||
count("Arbeitszeiten"."Datum")::bigint AS "Arbeitstage",
|
||||
(sum("Arbeitszeiten"."Arbeitszeit") -
|
||||
((count("Arbeitszeiten"."Datum"))::double precision * '08:00:00'::interval)) AS "Überstunden",
|
||||
(count(*) filter (where "Arbeitszeiten"."HomeOffice"))::int as "Home-Office-Tage"
|
||||
FROM public."Arbeitszeiten"
|
||||
GROUP BY (date_trunc('month'::text, ("Arbeitszeiten"."Datum")::timestamp with time zone))
|
||||
ORDER BY (date_trunc('month'::text, ("Arbeitszeiten"."Datum")::timestamp with time zone));
|
||||
|
||||
|
||||
ALTER TABLE public."Arbeitszeiten - Monat" OWNER TO bruce;
|
||||
ALTER TABLE public."Arbeitszeiten - Monat"
|
||||
OWNER TO bruce;
|
||||
|
||||
--
|
||||
-- Name: Arbeitszeiten - Woche; Type: VIEW; Schema: public; Owner: torsten
|
||||
--
|
||||
|
||||
CREATE OR REPLACE VIEW public."Arbeitszeiten - Woche" AS
|
||||
SELECT to_char(date_trunc('week'::text, ("Arbeitszeiten"."Datum")::timestamp with time zone), 'TMYYYY#WW'::text) AS "Woche",
|
||||
sum("Arbeitszeiten"."Arbeitszeit") AS "Gesamtarbeitszeit",
|
||||
count("Arbeitszeiten"."Datum") AS "Arbeitstage",
|
||||
(sum("Arbeitszeiten"."Arbeitszeit") - ((count("Arbeitszeiten"."Datum"))::double precision * '08:00:00'::interval)) AS "Überstunden"
|
||||
FROM public."Arbeitszeiten"
|
||||
GROUP BY (date_trunc('week'::text, ("Arbeitszeiten"."Datum")::timestamp with time zone))
|
||||
ORDER BY (date_trunc('week'::text, ("Arbeitszeiten"."Datum")::timestamp with time zone));
|
||||
SELECT to_char(date_trunc('week'::text, ("Arbeitszeiten"."Datum")::timestamp with time zone),
|
||||
'TMYYYY#WW'::text) AS "Woche",
|
||||
sum("Arbeitszeiten"."Arbeitszeit")::interval AS "Gesamtarbeitszeit",
|
||||
count("Arbeitszeiten"."Datum")::bigint AS "Arbeitstage",
|
||||
(sum("Arbeitszeiten"."Arbeitszeit") -
|
||||
((count("Arbeitszeiten"."Datum"))::double precision * '08:00:00'::interval)) AS "Überstunden",
|
||||
(count(*) filter (where "Arbeitszeiten"."HomeOffice"))::int as "Home-Office-Tage"
|
||||
FROM public."Arbeitszeiten"
|
||||
GROUP BY (date_trunc('week'::text, ("Arbeitszeiten"."Datum")::timestamp with time zone))
|
||||
ORDER BY (date_trunc('week'::text, ("Arbeitszeiten"."Datum")::timestamp with time zone));
|
||||
|
||||
|
||||
ALTER TABLE public."Arbeitszeiten - Woche" OWNER TO bruce;
|
||||
ALTER TABLE public."Arbeitszeiten - Woche"
|
||||
OWNER TO bruce;
|
||||
|
||||
--
|
||||
-- Data for Name: Arbeitszeiten; Type: TABLE DATA; Schema: public; Owner: torsten
|
||||
--
|
||||
|
||||
COPY public."Arbeitszeiten" ("Datum", "Arbeitszeit") FROM stdin;
|
||||
2020-01-07 08:06:00
|
||||
2020-01-08 08:16:00
|
||||
2020-01-09 07:49:00
|
||||
2020-01-10 07:30:00
|
||||
2020-01-13 08:06:00
|
||||
2020-01-14 08:04:00
|
||||
2020-01-15 08:20:00
|
||||
2020-01-16 07:13:00
|
||||
2020-01-17 07:57:00
|
||||
2020-01-20 07:53:00
|
||||
2020-01-21 07:49:00
|
||||
2020-01-22 07:56:00
|
||||
2020-01-23 07:55:00
|
||||
2020-01-24 07:08:00
|
||||
2020-01-27 08:23:00
|
||||
2020-01-28 07:20:00
|
||||
2020-01-29 08:13:00
|
||||
2020-01-30 08:43:00
|
||||
2020-01-31 07:21:00
|
||||
2020-02-03 07:56:00
|
||||
2020-02-04 08:05:00
|
||||
2020-02-05 07:58:00
|
||||
2020-02-06 08:01:00
|
||||
2020-02-07 07:59:00
|
||||
2020-02-10 07:50:00
|
||||
2020-02-11 08:01:00
|
||||
2020-02-12 08:14:00
|
||||
2020-02-13 08:12:00
|
||||
2020-02-14 07:51:00
|
||||
2020-02-17 07:59:00
|
||||
2020-02-18 08:05:00
|
||||
2020-02-19 07:34:00
|
||||
2020-02-20 07:33:00
|
||||
2020-02-21 07:56:00
|
||||
2020-02-24 08:02:00
|
||||
2020-02-25 08:13:00
|
||||
2020-02-26 08:42:00
|
||||
2020-02-27 07:49:00
|
||||
2020-02-28 08:16:00
|
||||
2020-03-02 08:22:00
|
||||
2020-03-03 08:20:00
|
||||
2020-03-04 08:12:00
|
||||
2020-03-05 08:32:00
|
||||
2020-03-06 04:41:00
|
||||
2020-03-09 07:05:00
|
||||
2020-03-10 07:34:00
|
||||
2020-03-11 07:39:00
|
||||
2020-03-12 07:50:00
|
||||
2020-03-13 08:26:00
|
||||
2020-03-16 07:51:00
|
||||
2020-03-17 07:50:00
|
||||
2020-03-18 07:19:00
|
||||
2020-03-19 07:55:00
|
||||
2020-03-20 05:43:00
|
||||
2020-03-23 08:05:00
|
||||
2020-03-24 08:21:00
|
||||
2020-03-25 08:10:00
|
||||
2020-03-26 10:31:00
|
||||
2020-03-27 04:55:00
|
||||
2020-03-30 08:21:00
|
||||
2020-03-31 08:01:00
|
||||
\.
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit")
|
||||
VALUES ('2020-01-07', '08:06:00'),
|
||||
('2020-01-08', '08:16:00'),
|
||||
('2020-01-09', '07:49:00'),
|
||||
('2020-01-10', '07:30:00'),
|
||||
('2020-01-13', '08:06:00'),
|
||||
('2020-01-14', '08:04:00'),
|
||||
('2020-01-15', '08:20:00'),
|
||||
('2020-01-16', '07:13:00'),
|
||||
('2020-01-17', '07:57:00'),
|
||||
('2020-01-20', '07:53:00'),
|
||||
('2020-01-21', '07:49:00'),
|
||||
('2020-01-22', '07:56:00'),
|
||||
('2020-01-23', '07:55:00'),
|
||||
('2020-01-24', '07:08:00'),
|
||||
('2020-01-27', '08:23:00'),
|
||||
('2020-01-28', '07:20:00'),
|
||||
('2020-01-29', '08:13:00'),
|
||||
('2020-01-30', '08:43:00'),
|
||||
('2020-01-31', '07:21:00'),
|
||||
('2020-02-03', '07:56:00'),
|
||||
('2020-02-04', '08:05:00'),
|
||||
('2020-02-05', '07:58:00'),
|
||||
('2020-02-06', '08:01:00')
|
||||
;
|
||||
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit", "HomeOffice")
|
||||
VALUES ('2020-02-07', '07:59:00', FALSE),
|
||||
('2020-02-10', '07:50:00', FALSE),
|
||||
('2020-02-11', '08:01:00', FALSE),
|
||||
('2020-02-12', '08:14:00', FALSE),
|
||||
('2020-02-13', '08:12:00', FALSE),
|
||||
('2020-02-14', '07:51:00', FALSE),
|
||||
('2020-02-17', '07:59:00', FALSE),
|
||||
('2020-02-18', '08:05:00', FALSE),
|
||||
('2020-02-19', '07:34:00', FALSE),
|
||||
('2020-02-20', '07:33:00', FALSE),
|
||||
('2020-02-21', '07:56:00', FALSE),
|
||||
('2020-02-24', '08:02:00', FALSE),
|
||||
('2020-02-25', '08:13:00', FALSE),
|
||||
('2020-02-26', '08:42:00', FALSE),
|
||||
('2020-02-27', '07:49:00', TRUE),
|
||||
('2020-02-28', '08:16:00', FALSE),
|
||||
('2020-03-02', '08:22:00', FALSE),
|
||||
('2020-03-03', '08:20:00', FALSE),
|
||||
('2020-03-04', '08:12:00', FALSE),
|
||||
('2020-03-05', '08:32:00', FALSE),
|
||||
('2020-03-06', '04:41:00', FALSE),
|
||||
('2020-03-09', '07:05:00', FALSE),
|
||||
('2020-03-10', '07:34:00', FALSE),
|
||||
('2020-03-11', '07:39:00', TRUE),
|
||||
('2020-03-12', '07:50:00', FALSE),
|
||||
('2020-03-13', '08:26:00', FALSE),
|
||||
('2020-03-16', '07:51:00', FALSE),
|
||||
('2020-03-17', '07:50:00', FALSE),
|
||||
('2020-03-18', '07:19:00', FALSE),
|
||||
('2020-03-19', '07:55:00', FALSE),
|
||||
('2020-03-20', '05:43:00', TRUE),
|
||||
('2020-03-23', '08:05:00', FALSE),
|
||||
('2020-03-24', '08:21:00', FALSE),
|
||||
('2020-03-25', '08:10:00', FALSE),
|
||||
('2020-03-26', '10:31:00', FALSE),
|
||||
('2020-03-27', '04:55:00', FALSE),
|
||||
('2020-03-30', '08:21:00', FALSE),
|
||||
('2020-03-31', '08:01:00', FALSE)
|
||||
;
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace Helper;
|
||||
|
||||
// here you can define custom actions
|
||||
// all public methods declared in helper class will be available in $I
|
||||
|
||||
use Codeception\Module;
|
||||
|
||||
class Api extends Module
|
||||
{
|
||||
|
||||
const FORMAT_TIME = 'string:regex(/\d+:[0-5]\d:[0-5]\d/)';
|
||||
const FORMAT_DATE = 'string:regex(/\d{4}-\d{2}-\d{2}/)';
|
||||
|
||||
const WORKING_HOURS_JSON_FORMAT = [
|
||||
'workingDay' => self::FORMAT_DATE,
|
||||
'workingTime' => self::FORMAT_TIME,
|
||||
'isHomeOffice' => 'boolean',
|
||||
];
|
||||
|
||||
const WORKING_HOURS_VIEW_JSON_FORMAT = [
|
||||
"period" => "string",
|
||||
"periodDesignation" => "string",
|
||||
"totalHours" => self::FORMAT_TIME,
|
||||
"workingDays" => 'integer:>0',
|
||||
'homeOfficeDays' => 'integer:>=0',
|
||||
"overtime" => self::FORMAT_TIME,
|
||||
];
|
||||
|
||||
const ERROR_JSON_FORMAT = [
|
||||
"timestamp" => "string:date",
|
||||
"status" => "integer",
|
||||
"error" => "string",
|
||||
"message" => "string",
|
||||
"path" => "string",
|
||||
];
|
||||
|
||||
const TEST_ORIGIN = 'http://localhost:1234';
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
actor: ApiTester
|
||||
modules:
|
||||
enabled:
|
||||
- \Helper\Api
|
||||
- REST:
|
||||
url: http://api:80/
|
||||
depends: PhpBrowser
|
||||
part: Json
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php /** @noinspection PhpUnused */
|
||||
|
||||
namespace TorstenHettstedt\TimekeepingApi\Tests\Api\Views;
|
||||
|
||||
use ApiTester;
|
||||
use Codeception\Util\HttpCode;
|
||||
use Helper\Api;
|
||||
|
||||
class BrowseWorkingHoursMonthlyCest
|
||||
{
|
||||
public static function preflight(ApiTester $I): void
|
||||
{
|
||||
$I->haveHttpHeader('Origin', Api::TEST_ORIGIN);
|
||||
$I->haveHttpHeader('Access-Control-Request-Method', 'GET');
|
||||
$I->sendOptions('/views/working-hours/monthly');
|
||||
$I->seeResponseCodeIs(HttpCode::OK);
|
||||
$I->seeHttpHeader('Access-Control-Allow-Origin', Api::TEST_ORIGIN);
|
||||
$I->seeHttpHeader('Access-Control-Allow-Methods');
|
||||
$I->seeHttpHeader('Access-Control-Allow-Headers');
|
||||
$I->seeHttpHeader('Access-Control-Max-Age');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ApiTester $I
|
||||
*/
|
||||
public function browseWorkingHoursView(ApiTester $I): void
|
||||
{
|
||||
$I->haveHttpHeader('Origin', Api::TEST_ORIGIN);
|
||||
$I->sendGet('/views/working-hours/monthly');
|
||||
$I->seeResponseCodeIs(HttpCode::OK);
|
||||
$I->canSeeHttpHeader('Access-Control-Allow-Origin', Api::TEST_ORIGIN);
|
||||
$I->seeResponseIsJson();
|
||||
$I->seeResponseMatchesJsonType(Api::WORKING_HOURS_VIEW_JSON_FORMAT);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php /** @noinspection PhpUnused */
|
||||
|
||||
namespace TorstenHettstedt\TimekeepingApi\Tests\Api\Views;
|
||||
|
||||
use ApiTester;
|
||||
use Codeception\Util\HttpCode;
|
||||
use Helper\Api;
|
||||
|
||||
class BrowseWorkingHoursWeeklyCest
|
||||
{
|
||||
public static function preflight(ApiTester $I): void
|
||||
{
|
||||
$I->haveHttpHeader('Origin', Api::TEST_ORIGIN);
|
||||
$I->haveHttpHeader('Access-Control-Request-Method', 'GET');
|
||||
$I->sendOptions('/views/working-hours/weekly');
|
||||
$I->seeResponseCodeIs(HttpCode::OK);
|
||||
$I->seeHttpHeader('Access-Control-Allow-Origin', Api::TEST_ORIGIN);
|
||||
$I->seeHttpHeader('Access-Control-Allow-Methods');
|
||||
$I->seeHttpHeader('Access-Control-Allow-Headers');
|
||||
$I->seeHttpHeader('Access-Control-Max-Age');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ApiTester $I
|
||||
*/
|
||||
public function browseWorkingHoursView(ApiTester $I): void
|
||||
{
|
||||
$I->haveHttpHeader('Origin', Api::TEST_ORIGIN);
|
||||
$I->sendGet('/views/working-hours/weekly');
|
||||
$I->seeResponseCodeIs(HttpCode::OK);
|
||||
$I->canSeeHttpHeader('Access-Control-Allow-Origin', Api::TEST_ORIGIN);
|
||||
$I->seeResponseIsJson();
|
||||
$I->seeResponseMatchesJsonType(Api::WORKING_HOURS_VIEW_JSON_FORMAT);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php /** @noinspection PhpUnused */
|
||||
|
||||
namespace TorstenHettstedt\TimekeepingApi\Tests\Api\Views;
|
||||
|
||||
use ApiTester;
|
||||
use Codeception\Util\HttpCode;
|
||||
use Helper\Api;
|
||||
|
||||
class BrowseWorkingHoursYearlyCest
|
||||
{
|
||||
public static function preflight(ApiTester $I): void
|
||||
{
|
||||
$I->haveHttpHeader('Origin', Api::TEST_ORIGIN);
|
||||
$I->haveHttpHeader('Access-Control-Request-Method', 'GET');
|
||||
$I->sendOptions('/views/working-hours/yearly');
|
||||
$I->seeResponseCodeIs(HttpCode::OK);
|
||||
$I->seeHttpHeader('Access-Control-Allow-Origin', Api::TEST_ORIGIN);
|
||||
$I->seeHttpHeader('Access-Control-Allow-Methods');
|
||||
$I->seeHttpHeader('Access-Control-Allow-Headers');
|
||||
$I->seeHttpHeader('Access-Control-Max-Age');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ApiTester $I
|
||||
*/
|
||||
public function browseWorkingHoursView(ApiTester $I): void
|
||||
{
|
||||
$I->haveHttpHeader('Origin', Api::TEST_ORIGIN);
|
||||
$I->sendGet('/views/working-hours/yearly');
|
||||
$I->seeResponseCodeIs(HttpCode::OK);
|
||||
$I->canSeeHttpHeader('Access-Control-Allow-Origin', Api::TEST_ORIGIN);
|
||||
$I->seeResponseIsJson();
|
||||
$I->seeResponseMatchesJsonType(Api::WORKING_HOURS_VIEW_JSON_FORMAT);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
<?php /** @noinspection PhpUnused */
|
||||
|
||||
namespace TorstenHettstedt\TimekeepingApi\Tests\Api\WorkingHours;
|
||||
|
||||
use ApiTester;
|
||||
use Codeception\Example;
|
||||
use Codeception\Util\HttpCode;
|
||||
use Helper\Api;
|
||||
|
||||
class BrowseWorkingHoursCest
|
||||
{
|
||||
public static function preflight(ApiTester $I): void
|
||||
{
|
||||
$I->haveHttpHeader('Origin', Api::TEST_ORIGIN);
|
||||
$I->haveHttpHeader('Access-Control-Request-Method', 'GET');
|
||||
$I->sendOptions('/working-hours');
|
||||
$I->seeResponseCodeIs(HttpCode::OK);
|
||||
$I->seeHttpHeader('Access-Control-Allow-Origin', Api::TEST_ORIGIN);
|
||||
$I->seeHttpHeader('Access-Control-Allow-Methods');
|
||||
$I->seeHttpHeader('Access-Control-Allow-Headers');
|
||||
$I->seeHttpHeader('Access-Control-Max-Age');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ApiTester $I
|
||||
*
|
||||
* @dataProvider validWorkingHoursFilterProvider
|
||||
*/
|
||||
public function browseEmptyWorkingHours(ApiTester $I): void
|
||||
{
|
||||
$I->haveHttpHeader('Origin', Api::TEST_ORIGIN);
|
||||
$I->sendGet('/working-hours', ['start-date' => '2020-04-01', 'end-date' => '2020-04-30']);
|
||||
$I->seeResponseCodeIs(HttpCode::OK);
|
||||
$I->canSeeHttpHeader('Access-Control-Allow-Origin', Api::TEST_ORIGIN);
|
||||
$I->seeResponseIsJson();
|
||||
$I->seeResponseEquals('[]');
|
||||
}
|
||||
|
||||
public function validWorkingHoursFilterProvider(): array
|
||||
{
|
||||
return [
|
||||
[[null]],
|
||||
[['start-date' => '2020-01-01', 'end-date' => '2020-01-08']],
|
||||
[['start-date' => '2020-01-01', 'end-date' => '2020-04-30']],
|
||||
[['start-date' => '2020-03-01', 'end-date' => '2020-03-07']],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ApiTester $I
|
||||
* @param Example $example
|
||||
*
|
||||
* @dataProvider validWorkingHoursFilterProvider
|
||||
*/
|
||||
public function browseWorkingHours(ApiTester $I, Example $example): void
|
||||
{
|
||||
$I->haveHttpHeader('Origin', Api::TEST_ORIGIN);
|
||||
$I->sendGet('/working-hours', $example[0]);
|
||||
$I->seeResponseCodeIs(HttpCode::OK);
|
||||
$I->canSeeHttpHeader('Access-Control-Allow-Origin', Api::TEST_ORIGIN);
|
||||
$I->seeResponseIsJson();
|
||||
$I->seeResponseMatchesJsonType(Api::WORKING_HOURS_JSON_FORMAT);
|
||||
}
|
||||
|
||||
public function invalidWorkingHoursFilterProvider(): array
|
||||
{
|
||||
return [
|
||||
['2020-11-31'],
|
||||
['2020-11'],
|
||||
['2020'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ApiTester $I
|
||||
* @param Example $example
|
||||
*
|
||||
* @dataProvider invalidWorkingHoursFilterProvider
|
||||
*/
|
||||
public function browseWorkingHoursWithWrongFilterDate(ApiTester $I, Example $example): void
|
||||
{
|
||||
$I->haveHttpHeader('Origin', Api::TEST_ORIGIN);
|
||||
$I->sendGet('/working-hours', ['start-date' => $example[0]]);
|
||||
$I->seeResponseCodeIs(HttpCode::BAD_REQUEST);
|
||||
$I->canSeeHttpHeader('Access-Control-Allow-Origin', Api::TEST_ORIGIN);
|
||||
$I->seeResponseIsJson();
|
||||
$I->seeResponseMatchesJsonType(Api::ERROR_JSON_FORMAT);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
<?php /** @noinspection PhpUnused */
|
||||
|
||||
namespace TorstenHettstedt\TimekeepingApi\Tests\Api\WorkingHours;
|
||||
|
||||
use ApiTester;
|
||||
use Codeception\Util\HttpCode;
|
||||
use Helper\Api;
|
||||
|
||||
class CreateWorkingHoursCest
|
||||
{
|
||||
public static function preflight(ApiTester $I): void
|
||||
{
|
||||
$I->haveHttpHeader('Origin', Api::TEST_ORIGIN);
|
||||
$I->haveHttpHeader('Access-Control-Request-Method', 'POST');
|
||||
$I->sendOptions('/working-hours');
|
||||
$I->seeResponseCodeIs(HttpCode::OK);
|
||||
$I->seeHttpHeader('Access-Control-Allow-Origin', Api::TEST_ORIGIN);
|
||||
$I->seeHttpHeader('Access-Control-Allow-Methods');
|
||||
$I->seeHttpHeader('Access-Control-Allow-Headers');
|
||||
$I->seeHttpHeader('Access-Control-Max-Age');
|
||||
}
|
||||
|
||||
public function createWorkingHoursWithNewValidRecord(ApiTester $I): void
|
||||
{
|
||||
$I->haveHttpHeader('accept', 'application/json');
|
||||
$I->haveHttpHeader('content-type', 'application/json');
|
||||
$I->haveHttpHeader('Origin', Api::TEST_ORIGIN);
|
||||
$I->sendPost('/working-hours', [
|
||||
'workingDay' => '2020-04-01',
|
||||
'workingTime' => '08:00:00',
|
||||
'isHomeOffice' => false,
|
||||
]);
|
||||
$I->seeResponseCodeIs(HttpCode::CREATED);
|
||||
$I->canSeeHttpHeader('Access-Control-Allow-Origin', Api::TEST_ORIGIN);
|
||||
$I->seeResponseIsJson();
|
||||
$I->seeResponseMatchesJsonType(Api::WORKING_HOURS_JSON_FORMAT);
|
||||
}
|
||||
|
||||
public function createWorkingHoursWithNewValidRecordWithoutHomeOffice(ApiTester $I): void
|
||||
{
|
||||
$I->haveHttpHeader('accept', 'application/json');
|
||||
$I->haveHttpHeader('content-type', 'application/json');
|
||||
$I->haveHttpHeader('Origin', Api::TEST_ORIGIN);
|
||||
$I->sendPost('/working-hours', [
|
||||
'workingDay' => '2020-04-01',
|
||||
'workingTime' => '08:00:00',
|
||||
]);
|
||||
$I->seeResponseCodeIs(HttpCode::CREATED);
|
||||
$I->canSeeHttpHeader('Access-Control-Allow-Origin', Api::TEST_ORIGIN);
|
||||
$I->seeResponseIsJson();
|
||||
$I->seeResponseMatchesJsonType(Api::WORKING_HOURS_JSON_FORMAT);
|
||||
}
|
||||
|
||||
// tests
|
||||
public function createWorkingHoursWithNewInvalidRecord(ApiTester $I): void
|
||||
{
|
||||
$I->haveHttpHeader('accept', 'application/json');
|
||||
$I->haveHttpHeader('content-type', 'application/json');
|
||||
$I->haveHttpHeader('Origin', Api::TEST_ORIGIN);
|
||||
$I->sendPost('/working-hours', [
|
||||
'workingDay' => '2020-04-01',
|
||||
'workingTime' => 8.0,
|
||||
'isHomeOffice' => false,
|
||||
]);
|
||||
$I->seeResponseCodeIs(HttpCode::BAD_REQUEST);
|
||||
$I->canSeeHttpHeader('Access-Control-Allow-Origin', Api::TEST_ORIGIN);
|
||||
$I->seeResponseIsJson();
|
||||
$I->seeResponseMatchesJsonType(Api::ERROR_JSON_FORMAT);
|
||||
}
|
||||
|
||||
// tests
|
||||
public function createWorkingHoursWithExistingValidRecord(ApiTester $I): void
|
||||
{
|
||||
$I->haveHttpHeader('accept', 'application/json');
|
||||
$I->haveHttpHeader('content-type', 'application/json');
|
||||
$I->haveHttpHeader('Origin', Api::TEST_ORIGIN);
|
||||
$I->sendPost('/working-hours', [
|
||||
'workingDay' => '2020-01-15',
|
||||
'workingTime' => '08:00:00',
|
||||
'isHomeOffice' => false,
|
||||
]);
|
||||
$I->seeResponseCodeIs(HttpCode::CONFLICT);
|
||||
$I->canSeeHttpHeader('Access-Control-Allow-Origin', Api::TEST_ORIGIN);
|
||||
$I->seeResponseIsJson();
|
||||
$I->seeResponseMatchesJsonType(Api::ERROR_JSON_FORMAT);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php /** @noinspection PhpUnused */
|
||||
|
||||
namespace TorstenHettstedt\TimekeepingApi\Tests\Api\WorkingHours;
|
||||
|
||||
use ApiTester;
|
||||
use Codeception\Util\HttpCode;
|
||||
use Helper\Api;
|
||||
|
||||
class ReadWorkingHoursCest
|
||||
{
|
||||
public static function preflight(ApiTester $I): void
|
||||
{
|
||||
$I->haveHttpHeader('Origin', Api::TEST_ORIGIN);
|
||||
$I->haveHttpHeader('Access-Control-Request-Method', 'GET');
|
||||
$I->sendOptions('/working-hours/2020-04-15');
|
||||
$I->seeResponseCodeIs(HttpCode::OK);
|
||||
$I->seeHttpHeader('Access-Control-Allow-Origin', Api::TEST_ORIGIN);
|
||||
$I->seeHttpHeader('Access-Control-Allow-Methods');
|
||||
$I->seeHttpHeader('Access-Control-Allow-Headers');
|
||||
$I->seeHttpHeader('Access-Control-Max-Age');
|
||||
}
|
||||
|
||||
public function readExistingWorkingHours(ApiTester $I): void
|
||||
{
|
||||
$I->haveHttpHeader('Origin', Api::TEST_ORIGIN);
|
||||
$I->sendGet('/working-hours/2020-01-15');
|
||||
$I->seeResponseCodeIs(HttpCode::OK);
|
||||
$I->canSeeHttpHeader('Access-Control-Allow-Origin', Api::TEST_ORIGIN);
|
||||
$I->seeResponseIsJson();
|
||||
$I->seeResponseMatchesJsonType(Api::WORKING_HOURS_JSON_FORMAT);
|
||||
}
|
||||
|
||||
public function readNotExistingWorkingHours(ApiTester $I): void
|
||||
{
|
||||
$I->haveHttpHeader('Origin', Api::TEST_ORIGIN);
|
||||
$I->sendGet('/working-hours/2020-04-15');
|
||||
$I->seeResponseCodeIs(HttpCode::NOT_FOUND);
|
||||
$I->canSeeHttpHeader('Access-Control-Allow-Origin', Api::TEST_ORIGIN);
|
||||
$I->seeResponseIsJson();
|
||||
$I->seeResponseMatchesJsonType(Api::ERROR_JSON_FORMAT);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
/** @noinspection PhpUnused */
|
||||
|
||||
namespace TorstenHettstedt\TimekeepingApi\Tests\Api\WorkingHours;
|
||||
|
||||
use ApiTester;
|
||||
use Codeception\Util\HttpCode;
|
||||
use Helper\Api;
|
||||
|
||||
class UpdateWorkingHoursCest
|
||||
{
|
||||
public static function preflight(ApiTester $I): void
|
||||
{
|
||||
$I->haveHttpHeader('Origin', Api::TEST_ORIGIN);
|
||||
$I->haveHttpHeader('Access-Control-Request-Method', 'PUT');
|
||||
$I->sendOptions('/working-hours/2020-01-15');
|
||||
$I->seeResponseCodeIs(HttpCode::OK);
|
||||
$I->seeHttpHeader('Access-Control-Allow-Origin', Api::TEST_ORIGIN);
|
||||
$I->seeHttpHeader('Access-Control-Allow-Methods');
|
||||
$I->seeHttpHeader('Access-Control-Allow-Headers');
|
||||
$I->seeHttpHeader('Access-Control-Max-Age');
|
||||
}
|
||||
|
||||
public function updateWorkingHoursWithExistingValidRecord(ApiTester $I): void
|
||||
{
|
||||
$I->haveHttpHeader('accept', 'application/json');
|
||||
$I->haveHttpHeader('content-type', 'application/json');
|
||||
$I->haveHttpHeader('Origin', Api::TEST_ORIGIN);
|
||||
$I->sendPut('/working-hours/2020-01-15', [
|
||||
'workingDay' => '2020-01-15',
|
||||
'workingTime' => '10:00:00',
|
||||
'isHomeOffice' => false,
|
||||
]);
|
||||
$I->seeResponseCodeIs(HttpCode::OK);
|
||||
$I->canSeeHttpHeader('Access-Control-Allow-Origin', Api::TEST_ORIGIN);
|
||||
$I->seeResponseIsJson();
|
||||
$I->seeResponseMatchesJsonType(Api::WORKING_HOURS_JSON_FORMAT);
|
||||
}
|
||||
|
||||
public function updateWorkingHoursWithNewValidRecord(ApiTester $I): void
|
||||
{
|
||||
$I->haveHttpHeader('accept', 'application/json');
|
||||
$I->haveHttpHeader('content-type', 'application/json');
|
||||
$I->haveHttpHeader('Origin', Api::TEST_ORIGIN);
|
||||
$I->sendPut('/working-hours/2020-04-15', [
|
||||
'workingDay' => '2020-04-15',
|
||||
'workingTime' => '10:00:00',
|
||||
'isHomeOffice' => false,
|
||||
]);
|
||||
$I->seeResponseCodeIs(HttpCode::NOT_FOUND);
|
||||
$I->canSeeHttpHeader('Access-Control-Allow-Origin', Api::TEST_ORIGIN);
|
||||
$I->seeResponseIsJson();
|
||||
$I->seeResponseMatchesJsonType(Api::ERROR_JSON_FORMAT);
|
||||
}
|
||||
|
||||
public function updateWorkingHoursWithNewInvalidRecord(ApiTester $I): void
|
||||
{
|
||||
$I->haveHttpHeader('accept', 'application/json');
|
||||
$I->haveHttpHeader('content-type', 'application/json');
|
||||
$I->haveHttpHeader('Origin', Api::TEST_ORIGIN);
|
||||
$I->sendPut('/working-hours/2020-01-15', [
|
||||
'workingDay' => '2020-01-15',
|
||||
'workingTime' => 8.0,
|
||||
'isHomeOffice' => false,
|
||||
]);
|
||||
$I->seeResponseCodeIs(HttpCode::BAD_REQUEST);
|
||||
$I->canSeeHttpHeader('Access-Control-Allow-Origin', Api::TEST_ORIGIN);
|
||||
$I->seeResponseIsJson();
|
||||
$I->seeResponseMatchesJsonType(Api::ERROR_JSON_FORMAT);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace TorstenHettstedt\TimekeepingApi\Tests\Unit\Controller;
|
||||
|
||||
use Codeception\Test\Unit;
|
||||
use Exception;
|
||||
use PDO;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
use Slim\Psr7\Request;
|
||||
use Slim\Psr7\Response;
|
||||
|
||||
class AbstractControllerTest extends Unit
|
||||
{
|
||||
|
||||
protected const FICTIONAL_STATUS_CODE = 666;
|
||||
|
||||
protected ContainerInterface $container;
|
||||
protected Request $request;
|
||||
protected Response|MockObject $response;
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
* @throws \PHPUnit\Framework\MockObject\Exception
|
||||
*/
|
||||
protected function _before(): void
|
||||
{
|
||||
parent::_before();
|
||||
/** @noinspection SpellCheckingInspection */
|
||||
$this->container = $this->makeEmpty(ContainerInterface::class, [
|
||||
'has' => true,
|
||||
'get' => new PDO(
|
||||
'pgsql:host=psql.torsten-hettstedt.net;port=5432;dbname=testdb;user=bruce;password=mypass'
|
||||
),
|
||||
]);
|
||||
$this->response = $this->createMock(Response::class);
|
||||
$this->response->expects($this->any())->method('getStatusCode')->willReturn(self::FICTIONAL_STATUS_CODE);
|
||||
$this->response->expects($this->any())->method('getBody')->willReturn(
|
||||
$this->makeEmpty(StreamInterface::class, [
|
||||
'write' => function (mixed $data) {
|
||||
$this->assertIsString($data);
|
||||
$this->assertJson($data);
|
||||
return strlen($data);
|
||||
},
|
||||
'getContents' => 'abc',
|
||||
])
|
||||
);
|
||||
$this->response->expects($this->any())->method('withHeader')->willReturn($this->response);
|
||||
$this->response->expects($this->any())->method('withStatus')->willReturn($this->response);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,431 @@
|
||||
<?php
|
||||
|
||||
namespace TorstenHettstedt\TimekeepingApi\Tests\Unit\Controller;
|
||||
|
||||
use Codeception\Attribute\DataProvider;
|
||||
use Exception;
|
||||
use PHPUnit\Framework\MockObject\Exception as MockException;
|
||||
use Psr\Container\ContainerExceptionInterface;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Psr\Container\NotFoundExceptionInterface;
|
||||
use Slim\Exception\HttpBadRequestException;
|
||||
use Slim\Exception\HttpNotFoundException;
|
||||
use Slim\Psr7\Request;
|
||||
use TorstenHettstedt\TimekeepingApi\Controller\HttpConflictRequestException;
|
||||
use TorstenHettstedt\TimekeepingApi\Controller\NotDatabasesException;
|
||||
use TorstenHettstedt\TimekeepingApi\Controller\WorkingHoursController;
|
||||
|
||||
class WorkingHoursControllerTest extends AbstractControllerTest
|
||||
{
|
||||
|
||||
protected const int FICTIONAL_STATUS_CODE = 666;
|
||||
public const string EXISTING_DATE = '2020-01-28';
|
||||
public const string EXISTING_INTERVAL = '07:20:00';
|
||||
public const string NEW_DATE = '2020-04-01';
|
||||
public const string NEW_INTERVAL = '07:59:00';
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
* @throws MockException
|
||||
*/
|
||||
protected function _before(): void
|
||||
{
|
||||
parent::_before();
|
||||
$this->request = $this->makeEmpty(Request::class, [
|
||||
'getParsedBody' => [
|
||||
'workingDay' => self::EXISTING_DATE,
|
||||
'workingTime' => self::EXISTING_INTERVAL,
|
||||
'isHomeOffice' => true,
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws NotDatabasesException
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
* @throws Exception
|
||||
*/
|
||||
public function testConstructWithNonDatabase(): void
|
||||
{
|
||||
$this->container = $this->makeEmpty(ContainerInterface::class, [
|
||||
'has' => false,
|
||||
]);
|
||||
$this->expectException(NotDatabasesException::class);
|
||||
new WorkingHoursController($this->container);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, array<int, array<string, string>>>
|
||||
*/
|
||||
public function valideFilterDataProvider(): array
|
||||
{
|
||||
return [
|
||||
[
|
||||
[],
|
||||
],
|
||||
[
|
||||
[
|
||||
'start-date' => '2020-03-01',
|
||||
'end-date' => '2020-03-31',
|
||||
],
|
||||
],
|
||||
[
|
||||
[
|
||||
'start-date' => '2020-03-01',
|
||||
],
|
||||
],
|
||||
[
|
||||
[
|
||||
'end-date' => '2020-03-31',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, string> $queryParams
|
||||
*
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotDatabasesException
|
||||
* @throws NotFoundExceptionInterface
|
||||
* @throws Exception
|
||||
*/
|
||||
#[DataProvider('valideFilterDataProvider')]
|
||||
public function testBrowseWithValideParameter(array $queryParams): void
|
||||
{
|
||||
$this->request = $this->makeEmpty(Request::class, [
|
||||
'getQueryParams' => $queryParams,
|
||||
]);
|
||||
$controller = new WorkingHoursController($this->container);
|
||||
$response = $controller->browse($this->request, $this->response, []);
|
||||
$this->assertEquals(self::FICTIONAL_STATUS_CODE, $response->getStatusCode());
|
||||
$this->assertNotEmpty($response->getBody()->getContents());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, array<int, array<string, string|null>>>
|
||||
*/
|
||||
public function invalideFilterDataProvider(): array
|
||||
{
|
||||
return [
|
||||
[
|
||||
[
|
||||
'start-date' => '',
|
||||
'end-date' => '',
|
||||
],
|
||||
],
|
||||
[
|
||||
[
|
||||
'start-date' => '',
|
||||
],
|
||||
],
|
||||
[
|
||||
[
|
||||
'end-date' => '',
|
||||
],
|
||||
],
|
||||
[
|
||||
[
|
||||
'start-date' => '2020-03-01',
|
||||
'end-date' => '',
|
||||
],
|
||||
],
|
||||
[
|
||||
[
|
||||
'start-date' => '',
|
||||
'end-date' => '2020-03-31',
|
||||
],
|
||||
],
|
||||
//
|
||||
[
|
||||
[
|
||||
'start-date' => '2020-03-00',
|
||||
'end-date' => '2020-04-31',
|
||||
],
|
||||
],
|
||||
[
|
||||
[
|
||||
'start-date' => '2020-03-00',
|
||||
],
|
||||
],
|
||||
[
|
||||
[
|
||||
'end-date' => '2020-04-31',
|
||||
],
|
||||
],
|
||||
[
|
||||
[
|
||||
'start-date' => '2020-03-01',
|
||||
'end-date' => '2020-04-31',
|
||||
],
|
||||
],
|
||||
[
|
||||
[
|
||||
'start-date' => '2020-03-00',
|
||||
'end-date' => '2020-03-31',
|
||||
],
|
||||
],
|
||||
//
|
||||
[
|
||||
[
|
||||
'start-date' => '2020-03',
|
||||
'end-date' => '2020-03',
|
||||
],
|
||||
],
|
||||
[
|
||||
[
|
||||
'start-date' => '2020-03',
|
||||
],
|
||||
],
|
||||
[
|
||||
[
|
||||
'end-date' => '2020-03',
|
||||
],
|
||||
],
|
||||
[
|
||||
[
|
||||
'start-date' => '2020-03-01',
|
||||
'end-date' => '2020-03',
|
||||
],
|
||||
],
|
||||
[
|
||||
[
|
||||
'start-date' => '2020-03',
|
||||
'end-date' => '2020-03-31',
|
||||
],
|
||||
],
|
||||
//
|
||||
[
|
||||
[
|
||||
'start-date' => '2020',
|
||||
'end-date' => '2020',
|
||||
],
|
||||
],
|
||||
[
|
||||
[
|
||||
'start-date' => '2020',
|
||||
],
|
||||
],
|
||||
[
|
||||
[
|
||||
'end-date' => '2020',
|
||||
],
|
||||
],
|
||||
[
|
||||
[
|
||||
'start-date' => '2020-03-01',
|
||||
'end-date' => '2020',
|
||||
],
|
||||
],
|
||||
[
|
||||
[
|
||||
'start-date' => '2020',
|
||||
'end-date' => '2020-03-31',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, string> $queryParams
|
||||
*
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotDatabasesException
|
||||
* @throws NotFoundExceptionInterface
|
||||
* @throws Exception
|
||||
*/
|
||||
#[DataProvider('invalideFilterDataProvider')]
|
||||
public function testBrowseWithInvalideParameter(array $queryParams): void
|
||||
{
|
||||
$this->request = $this->makeEmpty(Request::class, [
|
||||
'getQueryParams' => $queryParams,
|
||||
]);
|
||||
$controller = new WorkingHoursController($this->container);
|
||||
$this->expectException(HttpBadRequestException::class);
|
||||
$controller->browse($this->request, $this->response, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotDatabasesException
|
||||
* @throws NotFoundExceptionInterface
|
||||
* @throws Exception
|
||||
*/
|
||||
public function testUpdateExistRecord(): void
|
||||
{
|
||||
$controller = new WorkingHoursController($this->container);
|
||||
$response = $controller->update($this->request, $this->response, [
|
||||
'id' => self::EXISTING_DATE,
|
||||
]);
|
||||
$this->assertEquals(self::FICTIONAL_STATUS_CODE, $response->getStatusCode());
|
||||
$this->assertNotEmpty($response->getBody()->getContents());
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotDatabasesException
|
||||
* @throws NotFoundExceptionInterface
|
||||
* @throws Exception
|
||||
*/
|
||||
public function testUpdateExistRecordWithoutHomeOffice(): void
|
||||
{
|
||||
$this->request = $this->makeEmpty(Request::class, [
|
||||
'getParsedBody' => [
|
||||
'workingDay' => self::EXISTING_DATE,
|
||||
'workingTime' => self::EXISTING_INTERVAL,
|
||||
],
|
||||
]);
|
||||
$controller = new WorkingHoursController($this->container);
|
||||
$response = $controller->update($this->request, $this->response, [
|
||||
'id' => self::EXISTING_DATE,
|
||||
]);
|
||||
$this->assertEquals(self::FICTIONAL_STATUS_CODE, $response->getStatusCode());
|
||||
$this->assertNotEmpty($response->getBody()->getContents());
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotDatabasesException
|
||||
* @throws NotFoundExceptionInterface
|
||||
* @throws Exception
|
||||
*/
|
||||
public function testUpdateNotExistRecord(): void
|
||||
{
|
||||
$controller = new WorkingHoursController($this->container);
|
||||
$this->expectException(HttpNotFoundException::class);
|
||||
$controller->update($this->request, $this->response, [
|
||||
'id' => self::NEW_DATE,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[][]
|
||||
*/
|
||||
public function invalidCreatDataProvider(): array
|
||||
{
|
||||
return [
|
||||
[
|
||||
self::NEW_DATE,
|
||||
'07:59',
|
||||
],
|
||||
[
|
||||
'2020-13-33',
|
||||
self::NEW_INTERVAL,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotDatabasesException
|
||||
* @throws NotFoundExceptionInterface
|
||||
* @throws Exception
|
||||
*/
|
||||
#[DataProvider('invalidCreatDataProvider')]
|
||||
public function testCreatInvalidData(string $date, string $time, bool $isHomeOffice = false): void
|
||||
{
|
||||
$this->request = $this->makeEmpty(Request::class, [
|
||||
'getParsedBody' => [
|
||||
'workingDay' => $date,
|
||||
'workingTime' => $time,
|
||||
'isHomeOffice' => $isHomeOffice,
|
||||
],
|
||||
]);
|
||||
$controller = new WorkingHoursController($this->container);
|
||||
$this->expectException(HttpBadRequestException::class);
|
||||
$controller->creat($this->request, $this->response, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotDatabasesException
|
||||
* @throws NotFoundExceptionInterface
|
||||
* @throws Exception
|
||||
*/
|
||||
public function testCreatNewRecord(): void
|
||||
{
|
||||
$this->request = $this->makeEmpty(Request::class, [
|
||||
'getParsedBody' => [
|
||||
'workingDay' => self::NEW_DATE,
|
||||
'workingTime' => self::NEW_INTERVAL,
|
||||
'isHomeOffice' => true,
|
||||
],
|
||||
]);
|
||||
$controller = new WorkingHoursController($this->container);
|
||||
$response = $controller->creat($this->request, $this->response, []);
|
||||
$this->assertEquals(self::FICTIONAL_STATUS_CODE, $response->getStatusCode());
|
||||
$this->assertNotEmpty($response->getBody()->getContents());
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotDatabasesException
|
||||
* @throws NotFoundExceptionInterface
|
||||
* @throws Exception
|
||||
*/
|
||||
public function testCreatNewRecordWithoutHomeOffice(): void
|
||||
{
|
||||
$this->request = $this->makeEmpty(Request::class, [
|
||||
'getParsedBody' => [
|
||||
'workingDay' => self::NEW_DATE,
|
||||
'workingTime' => self::NEW_INTERVAL,
|
||||
],
|
||||
]);
|
||||
$controller = new WorkingHoursController($this->container);
|
||||
$response = $controller->creat($this->request, $this->response, []);
|
||||
$this->assertEquals(self::FICTIONAL_STATUS_CODE, $response->getStatusCode());
|
||||
$this->assertNotEmpty($response->getBody()->getContents());
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotDatabasesException
|
||||
* @throws NotFoundExceptionInterface
|
||||
* @throws Exception
|
||||
*/
|
||||
public function testCreatExistRecord(): void
|
||||
{
|
||||
$this->request = $this->makeEmpty(Request::class, [
|
||||
'getParsedBody' => [
|
||||
'workingDay' => self::EXISTING_DATE,
|
||||
'workingTime' => self::EXISTING_INTERVAL,
|
||||
'isHomeOffice' => true,
|
||||
],
|
||||
]);
|
||||
$controller = new WorkingHoursController($this->container);
|
||||
$this->expectException(HttpConflictRequestException::class);
|
||||
$controller->creat($this->request, $this->response, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotDatabasesException
|
||||
* @throws NotFoundExceptionInterface
|
||||
* @throws Exception
|
||||
*/
|
||||
public function testReadExistRecord(): void
|
||||
{
|
||||
$controller = new WorkingHoursController($this->container);
|
||||
$response = $controller->read($this->request, $this->response, [
|
||||
'id' => self::EXISTING_DATE,
|
||||
]);
|
||||
$this->assertEquals(self::FICTIONAL_STATUS_CODE, $response->getStatusCode());
|
||||
$this->assertNotEmpty($response->getBody()->getContents());
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotDatabasesException
|
||||
* @throws NotFoundExceptionInterface
|
||||
* @throws Exception
|
||||
*/
|
||||
public function testReadNotExistRecord(): void
|
||||
{
|
||||
$controller = new WorkingHoursController($this->container);
|
||||
$this->expectException(HttpNotFoundException::class);
|
||||
$controller->read($this->request, $this->response, [
|
||||
'id' => self::NEW_DATE,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
namespace TorstenHettstedt\TimekeepingApi\Tests\Unit\Controller;
|
||||
|
||||
use Exception;
|
||||
use Psr\Container\ContainerExceptionInterface;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Psr\Container\NotFoundExceptionInterface;
|
||||
use Slim\Psr7\Request;
|
||||
use TorstenHettstedt\TimekeepingApi\Controller\NotDatabasesException;
|
||||
use TorstenHettstedt\TimekeepingApi\Controller\WorkingHoursViewController;
|
||||
|
||||
class WorkingHoursViewControllerTest extends AbstractControllerTest
|
||||
{
|
||||
/**
|
||||
* @throws Exception
|
||||
* @throws \PHPUnit\Framework\MockObject\Exception
|
||||
*/
|
||||
protected function _before(): void
|
||||
{
|
||||
parent::_before();
|
||||
$this->request = $this->makeEmpty(Request::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws NotDatabasesException
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
* @throws Exception
|
||||
*/
|
||||
public function testConstructWithNonDatabase(): void
|
||||
{
|
||||
$this->container = $this->makeEmpty(ContainerInterface::class, [
|
||||
'has' => false
|
||||
]);
|
||||
$this->expectException(NotDatabasesException::class);
|
||||
new WorkingHoursViewController($this->container);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws NotDatabasesException
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
* @throws Exception
|
||||
*/
|
||||
public function testBrowseMonthly(): void
|
||||
{
|
||||
$controller = new WorkingHoursViewController($this->container);
|
||||
$response = $controller->browseMonthly($this->request, $this->response, []);
|
||||
$this->assertEquals(self::FICTIONAL_STATUS_CODE, $response->getStatusCode());
|
||||
$this->assertNotEmpty($response->getBody()->getContents());
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws NotDatabasesException
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
* @throws Exception
|
||||
*/
|
||||
public function testBrowseYearly(): void
|
||||
{
|
||||
$controller = new WorkingHoursViewController($this->container);
|
||||
$response = $controller->browseYearly($this->request, $this->response, []);
|
||||
$this->assertEquals(self::FICTIONAL_STATUS_CODE, $response->getStatusCode());
|
||||
$this->assertNotEmpty($response->getBody()->getContents());
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws NotDatabasesException
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
* @throws Exception
|
||||
*/
|
||||
public function testBrowseWeekly(): void
|
||||
{
|
||||
$controller = new WorkingHoursViewController($this->container);
|
||||
$response = $controller->browseWeekly($this->request, $this->response, []);
|
||||
$this->assertEquals(self::FICTIONAL_STATUS_CODE, $response->getStatusCode());
|
||||
$this->assertNotEmpty($response->getBody()->getContents());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
|
||||
namespace TorstenHettstedt\TimekeepingApi\Tests\Unit\Middleware;
|
||||
|
||||
use Codeception\Stub\Expected;
|
||||
use Codeception\Test\Unit;
|
||||
use Exception;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Psr\Http\Message\ResponseFactoryInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Slim\App;
|
||||
use Slim\Exception\HttpException;
|
||||
use Slim\Interfaces\RouteParserInterface;
|
||||
use Slim\Routing\RouteContext;
|
||||
use Slim\Routing\RoutingResults;
|
||||
use TorstenHettstedt\TimekeepingApi\Middleware\ErrorHandler;
|
||||
|
||||
class ErrorHandlerTest extends Unit
|
||||
{
|
||||
|
||||
/** @var App<ContainerInterface> */
|
||||
protected App $app;
|
||||
protected ServerRequestInterface $request;
|
||||
protected Exception $exception;
|
||||
protected LoggerInterface $logger;
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function _before(): void
|
||||
{
|
||||
parent::_before();
|
||||
$this->app = $this->makeEmpty(App::class, [
|
||||
'getResponseFactory' => $this->makeEmpty(ResponseFactoryInterface::class, [
|
||||
'createResponse' => $this->makeEmpty(ResponseInterface::class, [
|
||||
'getBody' => $this->makeEmpty(StreamInterface::class, [
|
||||
'write' => function (mixed $data) {
|
||||
$this->assertIsString($data);
|
||||
$this->assertJson($data);
|
||||
return strlen($data);
|
||||
},
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
]);
|
||||
$this->request = $this->makeEmpty(ServerRequestInterface::class, [
|
||||
'getAttribute' => function ($name) {
|
||||
/** @noinspection PhpSwitchCanBeReplacedWithMatchExpressionInspection */
|
||||
switch ($name) {
|
||||
case RouteContext::ROUTE_PARSER:
|
||||
return $this->makeEmpty(RouteParserInterface::class);
|
||||
case RouteContext::ROUTING_RESULTS:
|
||||
return $this->makeEmpty(RoutingResults::class);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function testUseWithLogger(): void
|
||||
{
|
||||
$this->exception = $this->make(Exception::class, [
|
||||
'message' => '',
|
||||
'code' => 400,
|
||||
'file' => '/path(to/file',
|
||||
]);
|
||||
$this->logger = $this->makeEmpty(LoggerInterface::class, [
|
||||
'error' => Expected::once(),
|
||||
]);
|
||||
|
||||
$middleWare = new ErrorHandler($this->app);
|
||||
$middleWare($this->request, $this->exception, true, true, true, $this->logger);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function testUseWithHttpException(): void
|
||||
{
|
||||
$this->exception = $this->make(HttpException::class, [
|
||||
'message' => '',
|
||||
'code' => 400,
|
||||
'file' => '/path(to/file',
|
||||
'getTitle' => Expected::once('The Title'),
|
||||
]);
|
||||
$this->logger = $this->makeEmpty(LoggerInterface::class);
|
||||
|
||||
$middleWare = new ErrorHandler($this->app);
|
||||
$middleWare($this->request, $this->exception, true, true, true, $this->logger);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function testUseWithGeneralException(): void
|
||||
{
|
||||
$this->exception = $this->make(Exception::class, [
|
||||
'message' => '',
|
||||
'code' => 400,
|
||||
'file' => '/path(to/file',
|
||||
]);
|
||||
$this->logger = $this->makeEmpty(LoggerInterface::class);
|
||||
|
||||
$middleWare = new ErrorHandler($this->app);
|
||||
$middleWare($this->request, $this->exception, true, true, true, $this->logger);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace TorstenHettstedt\TimekeepingApi\Tests\Unit\Models;
|
||||
|
||||
use Codeception\Attribute\DataProvider;
|
||||
use Codeception\Test\Unit;
|
||||
use DateInterval;
|
||||
use DateTime;
|
||||
@@ -16,13 +17,14 @@ class WorkingHoursTest extends Unit
|
||||
$obj = new WorkingHours();
|
||||
$this->assertNull($obj->getWorkingDay());
|
||||
$this->assertNull($obj->getWorkingTime());
|
||||
$this->assertFalse($obj->isHomeOffice());
|
||||
|
||||
$this->expectException(Error::class);
|
||||
$obj->jsonSerialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array[]
|
||||
* @return array<array<DateTime|DateInterval|array<string,string>>>
|
||||
*/
|
||||
public function workingHoursProvider(): array
|
||||
{
|
||||
@@ -31,17 +33,21 @@ class WorkingHoursTest extends Unit
|
||||
new DateTime('2020-11-30'),
|
||||
new DateInterval("PT8H"),
|
||||
[
|
||||
'workingDay' => '2020-11-30',
|
||||
'workingTime' => '08:00:00',
|
||||
'workingDay' => '2020-11-30',
|
||||
'workingTime' => '08:00:00',
|
||||
'isHomeOffice' => true,
|
||||
],
|
||||
true,
|
||||
],
|
||||
[
|
||||
new DateTime('2021-05-03'),
|
||||
new DateInterval("PT7H45M"),
|
||||
[
|
||||
'workingDay' => '2021-05-03',
|
||||
'workingTime' => '07:45:00',
|
||||
'workingDay' => '2021-05-03',
|
||||
'workingTime' => '07:45:00',
|
||||
'isHomeOffice' => false,
|
||||
],
|
||||
false,
|
||||
],
|
||||
];
|
||||
}
|
||||
@@ -49,17 +55,14 @@ class WorkingHoursTest extends Unit
|
||||
/**
|
||||
* @param DateTime $date
|
||||
* @param DateInterval $interval
|
||||
* @param array<string, string> $should_json
|
||||
*
|
||||
* @dataProvider workingHoursProvider
|
||||
* @param array<string, string> $shouldJson
|
||||
* @param bool $isHomeOffice
|
||||
*/
|
||||
public function testWorkingHoursWithContent(DateTime $date, DateInterval $interval, array $should_json): void
|
||||
#[DataProvider('workingHoursProvider')]
|
||||
public function testWorkingHoursWithContent(DateTime $date, DateInterval $interval, array $shouldJson, bool $isHomeOffice): void
|
||||
{
|
||||
|
||||
$obj = new WorkingHours();
|
||||
$obj
|
||||
->setWorkingDay($date)
|
||||
->setWorkingTime($interval);
|
||||
$obj->setWorkingDay($date)->setWorkingTime($interval)->setIsHomeOffice($isHomeOffice);
|
||||
|
||||
$this->assertNotNull($obj->getWorkingDay());
|
||||
$this->assertInstanceOf(DateTime::class, $obj->getWorkingDay());
|
||||
@@ -69,8 +72,37 @@ class WorkingHoursTest extends Unit
|
||||
$this->assertInstanceOf(DateInterval::class, $obj->getWorkingTime());
|
||||
$this->assertEquals($interval, $obj->getWorkingTime());
|
||||
|
||||
$this->assertIsBool($obj->isHomeOffice());
|
||||
$this->assertEquals($isHomeOffice, $obj->isHomeOffice());
|
||||
|
||||
$json = $obj->jsonSerialize();
|
||||
$this->assertIsArray($json);
|
||||
$this->assertEquals($should_json, $json);
|
||||
$this->assertEquals($shouldJson, $json);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DateTime $date
|
||||
* @param DateInterval $interval
|
||||
* @param array<string, string> $shouldJson
|
||||
*/
|
||||
#[DataProvider('workingHoursProvider')]
|
||||
public function testWorkingHoursWithContentWithoutHomeOfficeInformation(DateTime $date, DateInterval $interval, array $shouldJson): void
|
||||
{
|
||||
$shouldJson['isHomeOffice'] = false;
|
||||
$obj = new WorkingHours();
|
||||
$obj->setWorkingDay($date)->setWorkingTime($interval);
|
||||
|
||||
$this->assertNotNull($obj->getWorkingDay());
|
||||
$this->assertInstanceOf(DateTime::class, $obj->getWorkingDay());
|
||||
$this->assertEquals($date, $obj->getWorkingDay());
|
||||
|
||||
$this->assertNotNull($obj->getWorkingTime());
|
||||
$this->assertInstanceOf(DateInterval::class, $obj->getWorkingTime());
|
||||
$this->assertEquals($interval, $obj->getWorkingTime());
|
||||
|
||||
$this->assertIsBool($obj->isHomeOffice());
|
||||
$this->assertFalse($obj->isHomeOffice());
|
||||
|
||||
$json = $obj->jsonSerialize();
|
||||
$this->assertEquals($shouldJson, $json);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace TorstenHettstedt\TimekeepingApi\Tests\Unit\Models;
|
||||
|
||||
use Codeception\Attribute\DataProvider;
|
||||
use Codeception\Test\Unit;
|
||||
use DateInterval;
|
||||
use DateTime;
|
||||
@@ -13,101 +14,157 @@ class WorkingHoursViewTest extends Unit
|
||||
{
|
||||
|
||||
/**
|
||||
* @return array[]
|
||||
* @return array<array{DateTime,PeriodDesignationEnum,int,DateInterval,DateInterval,array<string,mixed>}>
|
||||
*/
|
||||
public function workingHoursProvider(): array
|
||||
{
|
||||
$date = new DateTime('2020-11-30');
|
||||
$work_days = 15;
|
||||
$home_work_days = 12;
|
||||
$total_hours = new DateInterval("PT32H22M");
|
||||
$overtime = new DateInterval("PT22M");
|
||||
$absence_time = clone $overtime;
|
||||
$absence_time->invert = 1;
|
||||
return [
|
||||
[
|
||||
new DateTime('2020-11-30'),
|
||||
PeriodDesignationEnum::WEEKLY(),
|
||||
15,
|
||||
new DateInterval("PT32H22M"),
|
||||
new DateInterval("PT22M"),
|
||||
$date,
|
||||
PeriodDesignationEnum::WEEKLY,
|
||||
$work_days,
|
||||
$total_hours,
|
||||
$home_work_days,
|
||||
$overtime,
|
||||
[
|
||||
'period' => '2020#49',
|
||||
'periodDesignation' => 'weekly',
|
||||
'workingDays' => 15,
|
||||
'totalHours' => '32:22:00',
|
||||
'homeOfficeDays' => $home_work_days,
|
||||
'overtime' => '00:22:00'
|
||||
],
|
||||
],
|
||||
[
|
||||
new DateTime('2020-11-30'),
|
||||
PeriodDesignationEnum::MONTHLY(),
|
||||
15,
|
||||
new DateInterval("PT32H22M"),
|
||||
new DateInterval("PT22M"),
|
||||
$date,
|
||||
PeriodDesignationEnum::WEEKLY,
|
||||
$work_days,
|
||||
$total_hours,
|
||||
$home_work_days,
|
||||
$absence_time,
|
||||
[
|
||||
'period' => '2020#49',
|
||||
'periodDesignation' => 'weekly',
|
||||
'workingDays' => 15,
|
||||
'totalHours' => '32:22:00',
|
||||
'homeOfficeDays' => $home_work_days,
|
||||
'overtime' => '-00:22:00'
|
||||
],
|
||||
],
|
||||
[
|
||||
$date,
|
||||
PeriodDesignationEnum::MONTHLY,
|
||||
$work_days,
|
||||
$total_hours,
|
||||
$home_work_days,
|
||||
$overtime,
|
||||
[
|
||||
'period' => '2020-11',
|
||||
'periodDesignation' => 'monthly',
|
||||
'workingDays' => 15,
|
||||
'totalHours' => '32:22:00',
|
||||
'homeOfficeDays' => $home_work_days,
|
||||
'overtime' => '00:22:00'
|
||||
],
|
||||
],
|
||||
[
|
||||
new DateTime('2020-11-30'),
|
||||
PeriodDesignationEnum::YEARLY(),
|
||||
15,
|
||||
new DateInterval("PT32H22M"),
|
||||
new DateInterval("PT22M"),
|
||||
$date,
|
||||
PeriodDesignationEnum::MONTHLY,
|
||||
$work_days,
|
||||
$total_hours,
|
||||
$home_work_days,
|
||||
$absence_time,
|
||||
[
|
||||
'period' => '2020-11',
|
||||
'periodDesignation' => 'monthly',
|
||||
'workingDays' => 15,
|
||||
'totalHours' => '32:22:00',
|
||||
'homeOfficeDays' => $home_work_days,
|
||||
'overtime' => '-00:22:00'
|
||||
],
|
||||
],
|
||||
[
|
||||
$date,
|
||||
PeriodDesignationEnum::YEARLY,
|
||||
$work_days,
|
||||
$total_hours,
|
||||
$home_work_days,
|
||||
$overtime,
|
||||
[
|
||||
'period' => '2020',
|
||||
'periodDesignation' => 'yearly',
|
||||
'workingDays' => 15,
|
||||
'totalHours' => '32:22:00',
|
||||
'homeOfficeDays' => $home_work_days,
|
||||
'overtime' => '00:22:00'
|
||||
],
|
||||
],
|
||||
[
|
||||
$date,
|
||||
PeriodDesignationEnum::YEARLY,
|
||||
$work_days,
|
||||
$total_hours,
|
||||
$home_work_days,
|
||||
$absence_time,
|
||||
[
|
||||
'period' => '2020',
|
||||
'periodDesignation' => 'yearly',
|
||||
'workingDays' => 15,
|
||||
'totalHours' => '32:22:00',
|
||||
'homeOfficeDays' => $home_work_days,
|
||||
'overtime' => '-00:22:00'
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DateTimeInterface $period
|
||||
* @param PeriodDesignationEnum $periodDesignation
|
||||
* @param int $workingDays
|
||||
* @param DateInterval $totalHours
|
||||
* @param DateInterval $overtime
|
||||
* @param array<string, string> $should_json
|
||||
*
|
||||
* @dataProvider workingHoursProvider
|
||||
* @param DateTimeInterface $period
|
||||
* @param PeriodDesignationEnum $periodDesignation
|
||||
* @param int $workingDays
|
||||
* @param DateInterval $totalHours
|
||||
* @param int $homeWorkDays ,
|
||||
* @param DateInterval $overtime
|
||||
* @param array<string, string> $shouldJson
|
||||
*/
|
||||
#[DataProvider('workingHoursProvider')]
|
||||
public function testWorkingHoursViewWithContent(
|
||||
DateTimeInterface $period,
|
||||
PeriodDesignationEnum $periodDesignation,
|
||||
int $workingDays,
|
||||
DateInterval $totalHours,
|
||||
int $homeWorkDays,
|
||||
DateInterval $overtime,
|
||||
array $should_json
|
||||
): void
|
||||
{
|
||||
array $shouldJson
|
||||
): void {
|
||||
$obj = new WorkingHoursView($period, $periodDesignation, $workingDays, $totalHours, $homeWorkDays, $overtime);
|
||||
|
||||
$obj = new WorkingHoursView($period, $periodDesignation, $workingDays, $totalHours, $overtime);
|
||||
|
||||
$this->assertNotNull($obj->getPeriod());
|
||||
$this->assertInstanceOf(DateTimeInterface::class, $obj->getPeriod());
|
||||
$this->assertEquals($period, $obj->getPeriod());
|
||||
|
||||
$this->assertNotNull($obj->getPeriodDesignation());
|
||||
$this->assertInstanceOf(PeriodDesignationEnum::class, $obj->getPeriodDesignation());
|
||||
$this->assertEquals($periodDesignation, $obj->getPeriodDesignation());
|
||||
|
||||
$this->assertNotNull($obj->getWorkingDays());
|
||||
$this->assertIsInt($obj->getWorkingDays());
|
||||
$this->assertEquals($workingDays, $obj->getWorkingDays());
|
||||
|
||||
$this->assertNotNull($obj->getTotalHours());
|
||||
$this->assertInstanceOf(DateInterval::class, $obj->getTotalHours());
|
||||
$this->assertEquals($totalHours, $obj->getTotalHours());
|
||||
|
||||
$this->assertNotNull($obj->getOvertime());
|
||||
$this->assertIsInt($obj->getHomeOfficeDays());
|
||||
$this->assertEquals($homeWorkDays, $obj->getHomeOfficeDays());
|
||||
|
||||
$this->assertInstanceOf(DateInterval::class, $obj->getOvertime());
|
||||
$this->assertEquals($overtime, $obj->getOvertime());
|
||||
|
||||
|
||||
$json = $obj->jsonSerialize();
|
||||
$this->assertIsArray($json);
|
||||
$this->assertEquals($should_json, $json);
|
||||
$this->assertEquals($shouldJson, $json);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,49 +2,124 @@
|
||||
|
||||
namespace TorstenHettstedt\TimekeepingApi\Tests\Unit\Repositories;
|
||||
|
||||
use Codeception\Attribute\DataProvider;
|
||||
use Codeception\Test\Unit;
|
||||
use DateInterval;
|
||||
use DateTime;
|
||||
use Exception;
|
||||
use InvalidArgumentException;
|
||||
use PDO;
|
||||
use PDOStatement;
|
||||
use TorstenHettstedt\TimekeepingApi\Models\ModelInterface;
|
||||
use TorstenHettstedt\TimekeepingApi\Models\WorkingHours;
|
||||
use TorstenHettstedt\TimekeepingApi\Repositories\RepositoryModelAlreadyExists;
|
||||
use TorstenHettstedt\TimekeepingApi\Repositories\RepositoryBadWhereDataException;
|
||||
use TorstenHettstedt\TimekeepingApi\Repositories\RepositoryRecordAlreadyExistException;
|
||||
use TorstenHettstedt\TimekeepingApi\Repositories\RepositoryRecordNotFoundException;
|
||||
use TorstenHettstedt\TimekeepingApi\Repositories\WorkingHoursRepository;
|
||||
|
||||
class WorkingHoursRepositoryTest extends Unit
|
||||
{
|
||||
const EXISTING_DATE = '2020-01-28';
|
||||
const EXISTING_INTERVAL = 'PT7H20M';
|
||||
const NEW_DATE = '2020-04-01';
|
||||
const NEW_INTERVAL = 'PT7H59M';
|
||||
const RECORDS_COUNT = 61;
|
||||
public const string EXISTING_DATE = '2020-01-28';
|
||||
public const string EXISTING_INTERVAL = 'PT7H20M';
|
||||
public const string NEW_DATE = '2020-04-01';
|
||||
public const string NEW_INTERVAL = 'PT7H59M';
|
||||
public const int RECORDS_COUNT = 61;
|
||||
|
||||
protected PDO $pdoObject;
|
||||
protected PDOStatement $pdoStatement;
|
||||
protected PDO $pdoObject;
|
||||
|
||||
protected function _before(): void
|
||||
{
|
||||
/** @noinspection SpellCheckingInspection */
|
||||
$this->pdoObject = new PDO('pgsql:host=psql.torsten-hettstedt.net;port=5432;dbname=testdb;user=bruce;password=mypass');
|
||||
$this->pdoObject = new PDO(
|
||||
'pgsql:host=psql.torsten-hettstedt.net;port=5432;dbname=testdb;user=bruce;password=mypass'
|
||||
);
|
||||
parent::_before();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function testFindAll(): void
|
||||
public function testFindAllWithBlankParameter(): void
|
||||
{
|
||||
$repository = new WorkingHoursRepository($this->pdoObject);
|
||||
$models = $repository->findAll();
|
||||
$this->assertIsArray($models);
|
||||
$this->assertContainsOnly(WorkingHours::class, $models);
|
||||
|
||||
$this->assertContainsOnlyInstancesOf(WorkingHours::class, $models);
|
||||
$this->assertCount(self::RECORDS_COUNT, $models);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, array<int|string|null>>
|
||||
*/
|
||||
public function valideFilterParameter(): array
|
||||
{
|
||||
return [
|
||||
[null, null, 61,],
|
||||
['2020-04-01', null, 0,],
|
||||
['2020-04-01', '2020-04-30', 0,],
|
||||
[null, '2020-04-30', 61,],
|
||||
['2020-03-01', null, 22,],
|
||||
['2020-03-01', '2020-03-31', 22,],
|
||||
['2020-03-01', '2020-03-07', 5,],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $start
|
||||
* @param string|null $ende
|
||||
* @param int $count
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
#[DataProvider('valideFilterParameter')]
|
||||
public function testFindAllWithValideParameter(?string $start, ?string $ende, int $count): void
|
||||
{
|
||||
$repository = new WorkingHoursRepository($this->pdoObject);
|
||||
$models = $repository->findFiltered($start, $ende);
|
||||
|
||||
$this->assertContainsOnlyInstancesOf(WorkingHours::class, $models);
|
||||
$this->assertCount($count, $models);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, array<int|string|null>>
|
||||
*/
|
||||
public function invalideFilterParameter(): array
|
||||
{
|
||||
return [
|
||||
['2020-04-31', null],
|
||||
[null, '2020-04-31'],
|
||||
['2020-04-00', '2020-04-31'],
|
||||
['2020-04-01', '2020-04-31'],
|
||||
['2020-04-00', '2020-04-30'],
|
||||
//
|
||||
['2020-04', null],
|
||||
[null, '2020-04'],
|
||||
['2020-04', '2020-04'],
|
||||
['2020-04-01', '2020-04'],
|
||||
['2020-04', '2020-04-30'],
|
||||
//
|
||||
['2020', null],
|
||||
[null, '2020'],
|
||||
['2020', '2020'],
|
||||
['2020-04-01', '2020'],
|
||||
['2020', '2020-04-30'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $start
|
||||
* @param string|null $ende
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
#[DataProvider('invalideFilterParameter')]
|
||||
public function testFindAllWithInvalideParameter(?string $start, ?string $ende): void
|
||||
{
|
||||
$repository = new WorkingHoursRepository($this->pdoObject);
|
||||
$this->expectException(RepositoryBadWhereDataException::class);
|
||||
$repository->findFiltered($start, $ende);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws RepositoryRecordNotFoundException
|
||||
*/
|
||||
@@ -52,7 +127,6 @@ class WorkingHoursRepositoryTest extends Unit
|
||||
{
|
||||
$repository = new WorkingHoursRepository($this->pdoObject);
|
||||
$model = $repository->findByKey(self::EXISTING_DATE);
|
||||
$this->assertInstanceOf(WorkingHours::class, $model);
|
||||
$this->assertEquals(new DateTime(self::EXISTING_DATE), $model->getWorkingDay());
|
||||
$this->assertEquals(new DateInterval(self::EXISTING_INTERVAL), $model->getWorkingTime());
|
||||
}
|
||||
@@ -83,12 +157,12 @@ class WorkingHoursRepositoryTest extends Unit
|
||||
{
|
||||
$repository = new WorkingHoursRepository($this->pdoObject);
|
||||
$model = $this->make(WorkingHours::class, [
|
||||
'workingDay' => new DateTime(self::NEW_DATE),
|
||||
'workingTime' => new DateInterval(self::NEW_INTERVAL),
|
||||
'workingDay' => new DateTime(self::NEW_DATE),
|
||||
'workingTime' => new DateInterval(self::NEW_INTERVAL),
|
||||
'isHomeOffice' => false,
|
||||
]);
|
||||
$repository->insert($model);
|
||||
$model = $repository->findByKey(self::NEW_DATE);
|
||||
$this->assertInstanceOf(WorkingHours::class, $model);
|
||||
$this->assertEquals(new DateTime(self::NEW_DATE), $model->getWorkingDay());
|
||||
$this->assertEquals(new DateInterval(self::NEW_INTERVAL), $model->getWorkingTime());
|
||||
$this->assertCount(self::RECORDS_COUNT + 1, $repository->findAll());
|
||||
@@ -101,10 +175,11 @@ class WorkingHoursRepositoryTest extends Unit
|
||||
{
|
||||
$repository = new WorkingHoursRepository($this->pdoObject);
|
||||
$model = $this->make(WorkingHours::class, [
|
||||
'workingDay' => new DateTime(self::EXISTING_DATE),
|
||||
'workingTime' => new DateInterval(self::NEW_INTERVAL),
|
||||
'workingDay' => new DateTime(self::EXISTING_DATE),
|
||||
'workingTime' => new DateInterval(self::NEW_INTERVAL),
|
||||
'isHomeOffice' => false,
|
||||
]);
|
||||
$this->expectException(RepositoryModelAlreadyExists::class);
|
||||
$this->expectException(RepositoryRecordAlreadyExistException::class);
|
||||
$repository->insert($model);
|
||||
}
|
||||
|
||||
@@ -128,8 +203,9 @@ class WorkingHoursRepositoryTest extends Unit
|
||||
{
|
||||
$repository = new WorkingHoursRepository($this->pdoObject);
|
||||
$model = $this->make(WorkingHours::class, [
|
||||
'workingDay' => new DateTime(self::EXISTING_DATE),
|
||||
'workingTime' => new DateInterval(self::EXISTING_INTERVAL),
|
||||
'workingDay' => new DateTime(self::EXISTING_DATE),
|
||||
'workingTime' => new DateInterval(self::EXISTING_INTERVAL),
|
||||
'isHomeOffice' => false,
|
||||
]);
|
||||
$repository->delete($model);
|
||||
$this->expectException(RepositoryRecordNotFoundException::class);
|
||||
@@ -144,8 +220,9 @@ class WorkingHoursRepositoryTest extends Unit
|
||||
{
|
||||
$repository = new WorkingHoursRepository($this->pdoObject);
|
||||
$model = $this->make(WorkingHours::class, [
|
||||
'workingDay' => new DateTime(self::NEW_DATE),
|
||||
'workingTime' => new DateInterval(self::NEW_INTERVAL),
|
||||
'workingDay' => new DateTime(self::NEW_DATE),
|
||||
'workingTime' => new DateInterval(self::NEW_INTERVAL),
|
||||
'isHomeOffice' => false,
|
||||
]);
|
||||
$this->expectException(RepositoryRecordNotFoundException::class);
|
||||
$repository->delete($model);
|
||||
@@ -171,8 +248,9 @@ class WorkingHoursRepositoryTest extends Unit
|
||||
{
|
||||
$repository = new WorkingHoursRepository($this->pdoObject);
|
||||
$model = $this->make(WorkingHours::class, [
|
||||
'workingDay' => new DateTime(self::EXISTING_DATE),
|
||||
'workingTime' => new DateInterval(self::NEW_INTERVAL),
|
||||
'workingDay' => new DateTime(self::EXISTING_DATE),
|
||||
'workingTime' => new DateInterval(self::NEW_INTERVAL),
|
||||
'isHomeOffice' => false,
|
||||
]);
|
||||
$repository->update($model);
|
||||
$model_db = $repository->findByKey(self::EXISTING_DATE);
|
||||
@@ -186,8 +264,9 @@ class WorkingHoursRepositoryTest extends Unit
|
||||
{
|
||||
$repository = new WorkingHoursRepository($this->pdoObject);
|
||||
$model = $this->make(WorkingHours::class, [
|
||||
'workingDay' => new DateTime(self::NEW_DATE),
|
||||
'workingTime' => new DateInterval(self::NEW_INTERVAL),
|
||||
'workingDay' => new DateTime(self::NEW_DATE),
|
||||
'workingTime' => new DateInterval(self::NEW_INTERVAL),
|
||||
'isHomeOffice' => false,
|
||||
]);
|
||||
$this->expectException(RepositoryRecordNotFoundException::class);
|
||||
$repository->update($model);
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
|
||||
namespace TorstenHettstedt\TimekeepingApi\Tests\Unit\Repositories;
|
||||
|
||||
use Codeception\Attribute\DataProvider;
|
||||
use Codeception\Test\Unit;
|
||||
use DateTime;
|
||||
use DateTimeZone;
|
||||
use Exception;
|
||||
use PDO;
|
||||
use TorstenHettstedt\TimekeepingApi\Models\WorkingHoursView;
|
||||
use TorstenHettstedt\TimekeepingApi\Repositories\RepositoryRecordNotFoundException;
|
||||
@@ -15,95 +15,101 @@ use TorstenHettstedt\TimekeepingApi\Repositories\WorkingHoursYearlyViewRepositor
|
||||
|
||||
class WorkingHoursViewRepositoryTest extends Unit
|
||||
{
|
||||
protected PDO $pdoObject;
|
||||
protected PDO $pdoObject;
|
||||
|
||||
protected function _before(): void
|
||||
{
|
||||
/** @noinspection SpellCheckingInspection */
|
||||
$this->pdoObject = new PDO('pgsql:host=psql.torsten-hettstedt.net;port=5432;dbname=testdb;user=bruce;password=mypass');
|
||||
$this->pdoObject = new PDO(
|
||||
'pgsql:host=psql.torsten-hettstedt.net;port=5432;dbname=testdb;user=bruce;password=mypass'
|
||||
);
|
||||
parent::_before();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array[]
|
||||
* @return array<array{string, int}>
|
||||
*/
|
||||
public function listObjectProvider(): array
|
||||
{
|
||||
return [
|
||||
[WorkingHoursYearlyViewRepository::class, 1],
|
||||
[WorkingHoursYearlyViewRepository::class, 1],
|
||||
[WorkingHoursMonthlyViewRepository::class, 3],
|
||||
[WorkingHoursWeeklyViewRepository::class, 13],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array[]
|
||||
* @throws Exception
|
||||
* @return array<array{string, string, int, DateTime}>
|
||||
*/
|
||||
public function existingObjectProvider(): array
|
||||
{
|
||||
return [
|
||||
[WorkingHoursYearlyViewRepository::class, '2020', 61, new DateTime('2020-01-01', new DateTimeZone('UCT'))],
|
||||
[WorkingHoursMonthlyViewRepository::class, '2020 February', 20, new DateTime('2020-02-01', new DateTimeZone('UCT'))],
|
||||
[WorkingHoursWeeklyViewRepository::class, '2020#03', 5, new DateTime('2020-01-13', new DateTimeZone('UCT'))],
|
||||
[
|
||||
WorkingHoursYearlyViewRepository::class,
|
||||
'2020',
|
||||
61,
|
||||
date_create('2020-01-01', new DateTimeZone('UCT')),
|
||||
3,
|
||||
],
|
||||
[
|
||||
WorkingHoursMonthlyViewRepository::class,
|
||||
'2020 February',
|
||||
20,
|
||||
date_create('2020-02-01', new DateTimeZone('UCT')),
|
||||
1,
|
||||
],
|
||||
[
|
||||
WorkingHoursWeeklyViewRepository::class,
|
||||
'2020#03',
|
||||
5,
|
||||
date_create('2020-01-13', new DateTimeZone('UCT')),
|
||||
0,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array[]
|
||||
* @return array<array{string, string}>
|
||||
*/
|
||||
public function notExistingObjectProvider(): array
|
||||
{
|
||||
return [
|
||||
[WorkingHoursYearlyViewRepository::class, '2022',],
|
||||
[WorkingHoursYearlyViewRepository::class, '2022',],
|
||||
[WorkingHoursMonthlyViewRepository::class, '2020 May',],
|
||||
[WorkingHoursWeeklyViewRepository::class, '2020#30',],
|
||||
[WorkingHoursWeeklyViewRepository::class, '2020#30',],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $period_class
|
||||
* @param int $count_records
|
||||
*
|
||||
* @dataProvider listObjectProvider
|
||||
*/
|
||||
public function testFindAll(string $period_class, int $count_records): void
|
||||
#[DataProvider(('listObjectProvider'))]
|
||||
public function testFindAll(string $periodClass, int $countRecords): void
|
||||
{
|
||||
$repository = new $period_class($this->pdoObject);
|
||||
$repository = new $periodClass($this->pdoObject);
|
||||
$models = $repository->findAll();
|
||||
$this->assertIsArray($models);
|
||||
$this->assertContainsOnly(WorkingHoursView::class, $models);
|
||||
$this->assertCount($count_records, $models);
|
||||
$this->assertContainsOnlyInstancesOf(WorkingHoursView::class, $models);
|
||||
$this->assertCount($countRecords, $models);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @dataProvider existingObjectProvider
|
||||
*
|
||||
* @param string $period_class
|
||||
* @param string $search
|
||||
* @param int $workingDays
|
||||
* @param DateTime $period
|
||||
*/
|
||||
public function testExistingFindByKey(string $period_class, string $search, int $workingDays, DateTime $period): void
|
||||
{
|
||||
$repository = new $period_class($this->pdoObject);
|
||||
#[DataProvider(('existingObjectProvider'))]
|
||||
public function testExistingFindByKey(
|
||||
string $periodClass,
|
||||
string $search,
|
||||
int $workingDays,
|
||||
DateTime $period,
|
||||
int $homeOfficeDays
|
||||
): void {
|
||||
$repository = new $periodClass($this->pdoObject);
|
||||
$model = $repository->findByKey($search);
|
||||
$this->assertInstanceOf(WorkingHoursView::class, $model);
|
||||
$this->assertEquals($workingDays, $model->getWorkingDays());
|
||||
$this->assertEquals($period->format('Ymd'), $model->getPeriod()->format('Ymd'));
|
||||
$this->assertEquals($homeOfficeDays, $model->getHomeOfficeDays());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $period_class
|
||||
* @param string $search
|
||||
*
|
||||
* @dataProvider notExistingObjectProvider
|
||||
*
|
||||
*/
|
||||
public function testNotExistingFindByKey(string $period_class, string $search): void
|
||||
#[DataProvider(('notExistingObjectProvider'))]
|
||||
public function testNotExistingFindByKey(string $periodClass, string $search): void
|
||||
{
|
||||
$repository = new $period_class($this->pdoObject);
|
||||
$repository = new $periodClass($this->pdoObject);
|
||||
$this->expectException(RepositoryRecordNotFoundException::class);
|
||||
$repository->findByKey($search);
|
||||
}
|
||||
|
||||
+33
-11
@@ -1,13 +1,35 @@
|
||||
volumes:
|
||||
logs:
|
||||
driver: local
|
||||
logs:
|
||||
driver: local
|
||||
services:
|
||||
api:
|
||||
build: ./api/
|
||||
environment:
|
||||
docker: "true"
|
||||
ports:
|
||||
- 8090:80
|
||||
volumes:
|
||||
- ./api:/var/www
|
||||
- logs:/var/www/logs
|
||||
api:
|
||||
build: ./api/
|
||||
environment:
|
||||
docker: "true"
|
||||
ports:
|
||||
- "8090:80"
|
||||
volumes:
|
||||
- ./api:/var/www
|
||||
- logs:/var/www/logs
|
||||
test-api:
|
||||
build: ./api/
|
||||
environment:
|
||||
docker: "true"
|
||||
DATABASES_HOST: psql.torsten-hettstedt.net
|
||||
DATABASES_NAME: testdb
|
||||
DATABASES_USER: bruce
|
||||
DATABASES_PASS: mypass
|
||||
ports:
|
||||
- "8091:80"
|
||||
volumes:
|
||||
- ./api:/var/www
|
||||
- logs:/var/www/logs
|
||||
ui:
|
||||
build: ./ui/
|
||||
env_file:
|
||||
- ui/.env
|
||||
ports:
|
||||
- "8080:3000"
|
||||
depends_on:
|
||||
- api
|
||||
- test-api
|
||||
@@ -0,0 +1,54 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
create_css_files() {
|
||||
cd ui || exit 2
|
||||
lessc --source-map less/print.less static/print.css
|
||||
lessc --source-map less/global.less static/global.css
|
||||
cd ../..
|
||||
}
|
||||
|
||||
|
||||
install_ui_dependence() {
|
||||
cd ui/ || exit 2
|
||||
npm install
|
||||
cd ..
|
||||
}
|
||||
|
||||
|
||||
install_api_dependence() {
|
||||
cd api/ || exit 2
|
||||
composer install
|
||||
cd ..
|
||||
}
|
||||
|
||||
build_ui() {
|
||||
cd ui/ || exit 2
|
||||
npm run build
|
||||
cd ..
|
||||
}
|
||||
|
||||
codeception_install() {
|
||||
pwd
|
||||
cd api/ || exit 2
|
||||
test -e ./tests/unit.suite.yml || ./vendor/bin/codecept build
|
||||
cd ..
|
||||
}
|
||||
|
||||
test_unit() {
|
||||
pwd
|
||||
cd api/ || exit 2
|
||||
./vendor/bin/codecept run unit
|
||||
cd ..
|
||||
}
|
||||
|
||||
test_api() {
|
||||
cd api/ || exit 2
|
||||
./vendor/bin/codecept run api
|
||||
cd ..
|
||||
}
|
||||
|
||||
composer_run() {
|
||||
docker compose up -d
|
||||
}
|
||||
|
||||
create_css_files && install_ui_dependence && install_api_dependence && build_ui && codeception_install && test_unit && composer_run && test_api
|
||||
@@ -0,0 +1,12 @@
|
||||
@startuml
|
||||
'https://plantuml.com/salt
|
||||
|
||||
salt
|
||||
{
|
||||
{/ <b>Täglich | Wöchentlich }
|
||||
Datum |"0000-00-00 <&calendar>"
|
||||
Uhrzeit | "00:00:00 <&clock>"
|
||||
[Übernehmen und weiter <&plus>]
|
||||
[Abbrechen <&action-undo>]
|
||||
}
|
||||
@enduml
|
||||
@@ -0,0 +1,22 @@
|
||||
@startuml
|
||||
'https://plantuml.com/salt
|
||||
|
||||
salt
|
||||
{
|
||||
{/ Täglich | <b>Wöchentlich }
|
||||
Woche vom
|
||||
"0000-00-00 <&calendar>"
|
||||
[<&chevron-left> Woche zurück] | [Woche vor <&chevron-right>]
|
||||
{!
|
||||
Montag | "00:00:00 <&clock>"
|
||||
Dienstag | "00:00:00 <&clock>"
|
||||
Mittwoch | "00:00:00 <&clock>"
|
||||
Donnerstag | "00:00:00 <&clock>"
|
||||
Freitag | "00:00:00 <&clock>"
|
||||
<color:red>Samstag | "<color:red>00:00:00 <&clock>"
|
||||
<color:red>Sontag | "<color:red>00:00:00 <&clock>"
|
||||
}
|
||||
[Übernehmen und weiter <&plus>]
|
||||
[Abbrechen <&action-undo>]
|
||||
}
|
||||
@enduml
|
||||
@@ -2,44 +2,48 @@
|
||||
-- DROP TABLE public."Arbeitszeiten";
|
||||
create table public."Arbeitszeiten"
|
||||
(
|
||||
"Datum" date not null,
|
||||
"Arbeitszeit" interval(6) null,
|
||||
"Datum" date not null,
|
||||
"Arbeitszeit" interval(6) null,
|
||||
"HomeOffice" boolean default FALSE not null,
|
||||
constraint "Arbeitszeiten_pkey" primary key ("Datum")
|
||||
);
|
||||
|
||||
|
||||
-- # Definition der Ansichten
|
||||
-- ## Wöchentlich
|
||||
create view "Arbeitszeiten - Woche" ("Woche", "Gesamtarbeitszeit", "Arbeitstage", "Überstunden") as
|
||||
select
|
||||
to_char(date_trunc('week'::text, "Arbeitszeiten"."Datum"::timestamp with time zone),
|
||||
'TMYYYY#WW'::text) as "Woche",
|
||||
sum("Arbeitszeiten"."Arbeitszeit") as "Gesamtarbeitszeit",
|
||||
count("Arbeitszeiten"."Datum") as "Arbeitstage",
|
||||
sum("Arbeitszeiten"."Arbeitszeit") - count("Arbeitszeiten"."Datum") ::double precision * '08:00:00'::interval as "Überstunden"
|
||||
from "Arbeitszeiten"
|
||||
group by (date_trunc('week'::text, "Arbeitszeiten"."Datum"::timestamp with time zone))
|
||||
order by (date_trunc('week'::text, "Arbeitszeiten"."Datum"::timestamp with time zone));
|
||||
create view "Arbeitszeiten - Woche" ("Woche", "Gesamtarbeitszeit", "Arbeitstage", "Überstunden", "Home-Office-Tage") as
|
||||
select to_char(date_trunc('week'::text, "Arbeitszeiten"."Datum"::timestamp with time zone),
|
||||
'TMYYYY#WW'::text) as "Woche",
|
||||
sum("Arbeitszeiten"."Arbeitszeit")::interval AS "Gesamtarbeitszeit",
|
||||
count("Arbeitszeiten"."Datum")::bigint AS "Arbeitstage",
|
||||
(sum("Arbeitszeiten"."Arbeitszeit") -
|
||||
((count("Arbeitszeiten"."Datum"))::double precision * '08:00:00'::interval)) AS "Überstunden",
|
||||
(count(*) filter (where "Arbeitszeiten"."HomeOffice"))::int as "Home-Office-Tage"
|
||||
from "Arbeitszeiten"
|
||||
group by (date_trunc('week'::text, "Arbeitszeiten"."Datum"::timestamp with time zone))
|
||||
order by (date_trunc('week'::text, "Arbeitszeiten"."Datum"::timestamp with time zone));
|
||||
|
||||
-- ## Monatlich
|
||||
create view "Arbeitszeiten - Monat" ("Monat", "Gesamtarbeitszeit", "Arbeitstage", "Überstunden") as
|
||||
select
|
||||
to_char(date_trunc('month'::text, "Arbeitszeiten"."Datum"::timestamp with time zone),
|
||||
'TMYYYY TMMonth'::text) as "Monat",
|
||||
sum("Arbeitszeiten"."Arbeitszeit") as "Gesamtarbeitszeit",
|
||||
count("Arbeitszeiten"."Datum") as "Arbeitstage",
|
||||
sum("Arbeitszeiten"."Arbeitszeit") - count("Arbeitszeiten"."Datum") ::double precision * '08:00:00'::interval as "Überstunden"
|
||||
from "Arbeitszeiten"
|
||||
group by (date_trunc('month'::text, "Arbeitszeiten"."Datum"::timestamp with time zone))
|
||||
order by (date_trunc('month'::text, "Arbeitszeiten"."Datum"::timestamp with time zone));
|
||||
create view "Arbeitszeiten - Monat" ("Monat", "Gesamtarbeitszeit", "Arbeitstage", "Überstunden", "Home-Office-Tage") as
|
||||
select to_char(date_trunc('month'::text, "Arbeitszeiten"."Datum"::timestamp with time zone),
|
||||
'TMYYYY TMMonth'::text) as "Monat",
|
||||
sum("Arbeitszeiten"."Arbeitszeit")::interval AS "Gesamtarbeitszeit",
|
||||
count("Arbeitszeiten"."Datum")::bigint AS "Arbeitstage",
|
||||
(sum("Arbeitszeiten"."Arbeitszeit") -
|
||||
((count("Arbeitszeiten"."Datum"))::double precision * '08:00:00'::interval)) AS "Überstunden",
|
||||
(count(*) filter (where "Arbeitszeiten"."HomeOffice"))::int as "Home-Office-Tage"
|
||||
from "Arbeitszeiten"
|
||||
group by (date_trunc('month'::text, "Arbeitszeiten"."Datum"::timestamp with time zone))
|
||||
order by (date_trunc('month'::text, "Arbeitszeiten"."Datum"::timestamp with time zone));
|
||||
|
||||
-- ## Jährlich
|
||||
create view "Arbeitszeiten - Jahr"("Jahr", "Gesamtarbeitszeit", "Arbeitstage", "Überstunden") as
|
||||
select
|
||||
date_part('year'::text, "Arbeitszeiten"."Datum")::text as "Jahr",
|
||||
sum("Arbeitszeiten"."Arbeitszeit") as "Gesamtarbeitszeit",
|
||||
count("Arbeitszeiten"."Datum") as "Arbeitstage",
|
||||
sum("Arbeitszeiten"."Arbeitszeit") - count("Arbeitszeiten"."Datum") ::double precision * '08:00:00'::interval as "Überstunden"
|
||||
from "Arbeitszeiten"
|
||||
group by (date_part('year'::text, "Arbeitszeiten"."Datum"))
|
||||
order by (date_part('year'::text, "Arbeitszeiten"."Datum"));
|
||||
create view "Arbeitszeiten - Jahr"("Jahr", "Gesamtarbeitszeit", "Arbeitstage", "Überstunden", "Home-Office-Tage") as
|
||||
select date_part('year'::text, "Arbeitszeiten"."Datum")::text as "Jahr",
|
||||
sum("Arbeitszeiten"."Arbeitszeit")::interval AS "Gesamtarbeitszeit",
|
||||
count("Arbeitszeiten"."Datum")::bigint AS "Arbeitstage",
|
||||
(sum("Arbeitszeiten"."Arbeitszeit") -
|
||||
((count("Arbeitszeiten"."Datum"))::double precision * '08:00:00'::interval)) AS "Überstunden",
|
||||
(count(*) filter (where "Arbeitszeiten"."HomeOffice"))::int as "Home-Office-Tage"
|
||||
from "Arbeitszeiten"
|
||||
group by (date_part('year'::text, "Arbeitszeiten"."Datum"))
|
||||
order by (date_part('year'::text, "Arbeitszeiten"."Datum"));
|
||||
@@ -0,0 +1,442 @@
|
||||
truncate table public."Arbeitszeiten";
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-01-09', '0 years 0 mons 0 days 8 hours 32 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-01-10', '0 years 0 mons 0 days 8 hours 23 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-01-11', '0 years 0 mons 0 days 8 hours 9 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-01-14', '0 years 0 mons 0 days 8 hours 21 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-01-15', '0 years 0 mons 0 days 9 hours 11 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-01-16', '0 years 0 mons 0 days 8 hours 38 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-01-17', '0 years 0 mons 0 days 9 hours 7 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-01-18', '0 years 0 mons 0 days 7 hours 31 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-01-21', '0 years 0 mons 0 days 8 hours 44 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-01-22', '0 years 0 mons 0 days 8 hours 31 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-01-23', '0 years 0 mons 0 days 9 hours 7 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-01-24', '0 years 0 mons 0 days 7 hours 17 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-01-25', '0 years 0 mons 0 days 7 hours 22 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-02-04', '0 years 0 mons 0 days 8 hours 14 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-02-05', '0 years 0 mons 0 days 8 hours 6 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-02-06', '0 years 0 mons 0 days 8 hours 6 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-02-07', '0 years 0 mons 0 days 7 hours 53 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-02-08', '0 years 0 mons 0 days 8 hours 13 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-02-11', '0 years 0 mons 0 days 6 hours 53 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-02-12', '0 years 0 mons 0 days 8 hours 48 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-02-13', '0 years 0 mons 0 days 7 hours 59 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-02-14', '0 years 0 mons 0 days 8 hours 20 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-02-15', '0 years 0 mons 0 days 7 hours 57 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-02-18', '0 years 0 mons 0 days 8 hours 10 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-02-19', '0 years 0 mons 0 days 8 hours 8 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-02-21', '0 years 0 mons 0 days 15 hours 18 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-02-22', '0 years 0 mons 0 days 4 hours 26 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-02-25', '0 years 0 mons 0 days 8 hours 2 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-02-26', '0 years 0 mons 0 days 8 hours 10 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-02-27', '0 years 0 mons 0 days 7 hours 37 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-01-28', '0 years 0 mons 0 days 8 hours 0 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-03-01', '0 years 0 mons 0 days 8 hours 8 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-03-04', '0 years 0 mons 0 days 8 hours 16 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-03-05', '0 years 0 mons 0 days 8 hours 10 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-03-06', '0 years 0 mons 0 days 8 hours 44 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-03-07', '0 years 0 mons 0 days 8 hours 9 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-03-08', '0 years 0 mons 0 days 7 hours 53 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-03-11', '0 years 0 mons 0 days 8 hours 14 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-03-12', '0 years 0 mons 0 days 7 hours 34 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-03-13', '0 years 0 mons 0 days 8 hours 14 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-03-14', '0 years 0 mons 0 days 8 hours 10 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-03-15', '0 years 0 mons 0 days 8 hours 25 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-03-18', '0 years 0 mons 0 days 7 hours 21 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-03-19', '0 years 0 mons 0 days 8 hours 9 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-03-20', '0 years 0 mons 0 days 8 hours 37 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-03-21', '0 years 0 mons 0 days 8 hours 34 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-03-22', '0 years 0 mons 0 days 7 hours 42 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-03-25', '0 years 0 mons 0 days 8 hours 6 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-03-26', '0 years 0 mons 0 days 8 hours 16 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-03-27', '0 years 0 mons 0 days 8 hours 28 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-03-28', '0 years 0 mons 0 days 8 hours 21 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-03-29', '0 years 0 mons 0 days 6 hours 46 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-04-01', '0 years 0 mons 0 days 8 hours 9 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-04-02', '0 years 0 mons 0 days 8 hours 36 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-04-03', '0 years 0 mons 0 days 7 hours 57 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-04-04', '0 years 0 mons 0 days 8 hours 6 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-04-05', '0 years 0 mons 0 days 8 hours 23 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-04-08', '0 years 0 mons 0 days 7 hours 52 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-04-09', '0 years 0 mons 0 days 8 hours 36 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-04-10', '0 years 0 mons 0 days 8 hours 36 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-04-11', '0 years 0 mons 0 days 7 hours 55 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-04-12', '0 years 0 mons 0 days 7 hours 10 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-04-15', '0 years 0 mons 0 days 10 hours 23 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-04-16', '0 years 0 mons 0 days 8 hours 20 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-04-17', '0 years 0 mons 0 days 7 hours 58 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-04-18', '0 years 0 mons 0 days 5 hours 58 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-04-23', '0 years 0 mons 0 days 7 hours 46 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-04-24', '0 years 0 mons 0 days 8 hours 10 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-04-25', '0 years 0 mons 0 days 8 hours 46 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-04-26', '0 years 0 mons 0 days 8 hours 12 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-05-06', '0 years 0 mons 0 days 8 hours 0 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-05-07', '0 years 0 mons 0 days 8 hours 20 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-05-08', '0 years 0 mons 0 days 10 hours 2 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-05-09', '0 years 0 mons 0 days 7 hours 21 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-05-10', '0 years 0 mons 0 days 7 hours 56 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-05-13', '0 years 0 mons 0 days 8 hours 26 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-05-14', '0 years 0 mons 0 days 8 hours 19 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-05-15', '0 years 0 mons 0 days 7 hours 38 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-05-16', '0 years 0 mons 0 days 7 hours 39 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-05-17', '0 years 0 mons 0 days 8 hours 8 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-05-20', '0 years 0 mons 0 days 8 hours 4 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-05-21', '0 years 0 mons 0 days 9 hours 25 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-05-22', '0 years 0 mons 0 days 8 hours 31 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-05-23', '0 years 0 mons 0 days 7 hours 41 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-05-24', '0 years 0 mons 0 days 7 hours 36 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-05-27', '0 years 0 mons 0 days 8 hours 13 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-05-28', '0 years 0 mons 0 days 8 hours 3 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-05-29', '0 years 0 mons 0 days 8 hours 5 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-06-03', '0 years 0 mons 0 days 8 hours 37 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-06-04', '0 years 0 mons 0 days 8 hours 37 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-06-05', '0 years 0 mons 0 days 7 hours 58 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-06-06', '0 years 0 mons 0 days 7 hours 9 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-06-07', '0 years 0 mons 0 days 7 hours 35 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-06-11', '0 years 0 mons 0 days 8 hours 0 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-06-12', '0 years 0 mons 0 days 8 hours 25 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-06-13', '0 years 0 mons 0 days 8 hours 30 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-06-14', '0 years 0 mons 0 days 4 hours 49 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-06-17', '0 years 0 mons 0 days 8 hours 6 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-06-18', '0 years 0 mons 0 days 8 hours 13 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-06-19', '0 years 0 mons 0 days 8 hours 34 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-06-20', '0 years 0 mons 0 days 7 hours 59 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-06-21', '0 years 0 mons 0 days 8 hours 8 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-06-24', '0 years 0 mons 0 days 8 hours 51 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-06-25', '0 years 0 mons 0 days 8 hours 7 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-06-26', '0 years 0 mons 0 days 8 hours 8 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-06-27', '0 years 0 mons 0 days 6 hours 13 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-06-28', '0 years 0 mons 0 days 7 hours 18 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-07-01', '0 years 0 mons 0 days 7 hours 38 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-07-02', '0 years 0 mons 0 days 7 hours 55 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-07-03', '0 years 0 mons 0 days 7 hours 55 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-07-04', '0 years 0 mons 0 days 7 hours 46 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-07-05', '0 years 0 mons 0 days 8 hours 3 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-07-08', '0 years 0 mons 0 days 8 hours 0 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-07-09', '0 years 0 mons 0 days 8 hours 24 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-07-10', '0 years 0 mons 0 days 8 hours 4 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-07-11', '0 years 0 mons 0 days 7 hours 59 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-07-12', '0 years 0 mons 0 days 7 hours 15 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-07-15', '0 years 0 mons 0 days 8 hours 4 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-07-16', '0 years 0 mons 0 days 8 hours 19 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-07-17', '0 years 0 mons 0 days 8 hours 16 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-07-18', '0 years 0 mons 0 days 8 hours 14 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-07-19', '0 years 0 mons 0 days 7 hours 32 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-07-22', '0 years 0 mons 0 days 7 hours 55 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-07-23', '0 years 0 mons 0 days 8 hours 11 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-07-24', '0 years 0 mons 0 days 8 hours 25 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-07-25', '0 years 0 mons 0 days 8 hours 30 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-07-26', '0 years 0 mons 0 days 8 hours 17 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-07-29', '0 years 0 mons 0 days 8 hours 32 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-07-30', '0 years 0 mons 0 days 7 hours 44 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-07-31', '0 years 0 mons 0 days 8 hours 21 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-08-01', '0 years 0 mons 0 days 9 hours 17 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-08-02', '0 years 0 mons 0 days 7 hours 49 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-08-19', '0 years 0 mons 0 days 7 hours 39 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-08-20', '0 years 0 mons 0 days 7 hours 56 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-08-21', '0 years 0 mons 0 days 7 hours 59 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-08-22', '0 years 0 mons 0 days 8 hours 14 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-08-23', '0 years 0 mons 0 days 7 hours 18 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-08-26', '0 years 0 mons 0 days 7 hours 47 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-08-27', '0 years 0 mons 0 days 8 hours 15 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-08-28', '0 years 0 mons 0 days 8 hours 6 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-08-29', '0 years 0 mons 0 days 7 hours 52 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-08-30', '0 years 0 mons 0 days 8 hours 6 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-09-02', '0 years 0 mons 0 days 7 hours 59 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-09-03', '0 years 0 mons 0 days 8 hours 30 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-09-04', '0 years 0 mons 0 days 7 hours 42 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-09-05', '0 years 0 mons 0 days 8 hours 7 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-09-06', '0 years 0 mons 0 days 8 hours 1 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-09-09', '0 years 0 mons 0 days 8 hours 8 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-09-10', '0 years 0 mons 0 days 8 hours 5 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-09-11', '0 years 0 mons 0 days 8 hours 0 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-09-12', '0 years 0 mons 0 days 8 hours 26 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-09-13', '0 years 0 mons 0 days 8 hours 24 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-09-16', '0 years 0 mons 0 days 8 hours 19 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-09-17', '0 years 0 mons 0 days 7 hours 48 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-09-18', '0 years 0 mons 0 days 6 hours 56 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-09-19', '0 years 0 mons 0 days 8 hours 6 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-09-20', '0 years 0 mons 0 days 7 hours 54 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-09-23', '0 years 0 mons 0 days 7 hours 44 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-09-24', '0 years 0 mons 0 days 8 hours 10 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-09-25', '0 years 0 mons 0 days 7 hours 51 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-09-26', '0 years 0 mons 0 days 7 hours 49 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-09-27', '0 years 0 mons 0 days 7 hours 54 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-09-30', '0 years 0 mons 0 days 7 hours 56 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-10-01', '0 years 0 mons 0 days 8 hours 38 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-10-02', '0 years 0 mons 0 days 7 hours 26 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-10-07', '0 years 0 mons 0 days 7 hours 32 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-10-08', '0 years 0 mons 0 days 7 hours 52 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-10-09', '0 years 0 mons 0 days 8 hours 40 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-10-10', '0 years 0 mons 0 days 7 hours 54 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-10-14', '0 years 0 mons 0 days 7 hours 43 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-10-15', '0 years 0 mons 0 days 7 hours 45 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-10-21', '0 years 0 mons 0 days 7 hours 57 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-10-22', '0 years 0 mons 0 days 8 hours 25 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-10-23', '0 years 0 mons 0 days 8 hours 38 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-10-24', '0 years 0 mons 0 days 7 hours 37 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-10-25', '0 years 0 mons 0 days 8 hours 2 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-10-28', '0 years 0 mons 0 days 7 hours 27 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-10-29', '0 years 0 mons 0 days 7 hours 49 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-10-30', '0 years 0 mons 0 days 7 hours 29 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-11-01', '0 years 0 mons 0 days 8 hours 48 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-11-04', '0 years 0 mons 0 days 7 hours 52 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-11-05', '0 years 0 mons 0 days 8 hours 16 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-11-06', '0 years 0 mons 0 days 7 hours 59 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-11-07', '0 years 0 mons 0 days 7 hours 24 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-11-08', '0 years 0 mons 0 days 8 hours 12 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-11-11', '0 years 0 mons 0 days 7 hours 30 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-11-12', '0 years 0 mons 0 days 7 hours 42 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-11-13', '0 years 0 mons 0 days 7 hours 46 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-11-14', '0 years 0 mons 0 days 7 hours 40 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-11-15', '0 years 0 mons 0 days 7 hours 34 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-11-18', '0 years 0 mons 0 days 7 hours 51 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-11-19', '0 years 0 mons 0 days 7 hours 49 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-11-20', '0 years 0 mons 0 days 7 hours 7 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-11-21', '0 years 0 mons 0 days 7 hours 39 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-11-22', '0 years 0 mons 0 days 7 hours 31 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-11-25', '0 years 0 mons 0 days 8 hours 8 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-11-26', '0 years 0 mons 0 days 7 hours 37 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-11-27', '0 years 0 mons 0 days 8 hours 29 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-11-28', '0 years 0 mons 0 days 8 hours 0 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-11-29', '0 years 0 mons 0 days 7 hours 54 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-12-02', '0 years 0 mons 0 days 7 hours 49 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-12-03', '0 years 0 mons 0 days 7 hours 46 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-12-04', '0 years 0 mons 0 days 8 hours 27 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-12-05', '0 years 0 mons 0 days 5 hours 51 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-12-06', '0 years 0 mons 0 days 7 hours 35 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-12-09', '0 years 0 mons 0 days 7 hours 42 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-12-10', '0 years 0 mons 0 days 8 hours 27 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-12-11', '0 years 0 mons 0 days 7 hours 47 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-12-12', '0 years 0 mons 0 days 8 hours 13 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-12-13', '0 years 0 mons 0 days 6 hours 47 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-12-16', '0 years 0 mons 0 days 7 hours 51 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-12-17', '0 years 0 mons 0 days 8 hours 33 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-12-18', '0 years 0 mons 0 days 7 hours 55 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-12-19', '0 years 0 mons 0 days 8 hours 5 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2019-12-20', '0 years 0 mons 0 days 6 hours 0 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-01-07', '0 years 0 mons 0 days 8 hours 6 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-01-08', '0 years 0 mons 0 days 8 hours 16 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-01-09', '0 years 0 mons 0 days 7 hours 49 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-01-10', '0 years 0 mons 0 days 7 hours 30 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-01-13', '0 years 0 mons 0 days 8 hours 6 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-01-14', '0 years 0 mons 0 days 8 hours 4 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-01-15', '0 years 0 mons 0 days 8 hours 20 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-01-16', '0 years 0 mons 0 days 7 hours 13 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-01-17', '0 years 0 mons 0 days 7 hours 57 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-01-20', '0 years 0 mons 0 days 7 hours 53 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-01-21', '0 years 0 mons 0 days 7 hours 49 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-01-22', '0 years 0 mons 0 days 7 hours 56 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-01-23', '0 years 0 mons 0 days 7 hours 55 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-01-24', '0 years 0 mons 0 days 7 hours 8 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-01-27', '0 years 0 mons 0 days 8 hours 23 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-01-28', '0 years 0 mons 0 days 7 hours 20 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-01-29', '0 years 0 mons 0 days 8 hours 13 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-01-30', '0 years 0 mons 0 days 8 hours 43 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-01-31', '0 years 0 mons 0 days 7 hours 21 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-02-03', '0 years 0 mons 0 days 7 hours 56 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-02-04', '0 years 0 mons 0 days 8 hours 5 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-02-05', '0 years 0 mons 0 days 7 hours 58 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-02-06', '0 years 0 mons 0 days 8 hours 1 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-02-07', '0 years 0 mons 0 days 7 hours 59 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-02-10', '0 years 0 mons 0 days 7 hours 50 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-02-11', '0 years 0 mons 0 days 8 hours 1 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-02-12', '0 years 0 mons 0 days 8 hours 14 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-02-13', '0 years 0 mons 0 days 8 hours 12 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-02-14', '0 years 0 mons 0 days 7 hours 51 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-02-17', '0 years 0 mons 0 days 7 hours 59 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-02-18', '0 years 0 mons 0 days 8 hours 5 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-02-19', '0 years 0 mons 0 days 7 hours 34 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-02-20', '0 years 0 mons 0 days 7 hours 33 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-02-21', '0 years 0 mons 0 days 7 hours 56 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-02-24', '0 years 0 mons 0 days 8 hours 2 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-02-25', '0 years 0 mons 0 days 8 hours 13 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-02-26', '0 years 0 mons 0 days 8 hours 42 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-02-27', '0 years 0 mons 0 days 7 hours 49 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-02-28', '0 years 0 mons 0 days 8 hours 16 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-03-02', '0 years 0 mons 0 days 8 hours 22 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-03-03', '0 years 0 mons 0 days 8 hours 20 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-03-04', '0 years 0 mons 0 days 8 hours 12 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-03-05', '0 years 0 mons 0 days 8 hours 32 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-03-06', '0 years 0 mons 0 days 4 hours 41 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-03-09', '0 years 0 mons 0 days 7 hours 5 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-03-10', '0 years 0 mons 0 days 7 hours 34 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-03-11', '0 years 0 mons 0 days 7 hours 39 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-03-12', '0 years 0 mons 0 days 7 hours 50 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-03-13', '0 years 0 mons 0 days 8 hours 26 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-03-16', '0 years 0 mons 0 days 7 hours 51 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-03-17', '0 years 0 mons 0 days 7 hours 50 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-03-18', '0 years 0 mons 0 days 7 hours 19 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-03-19', '0 years 0 mons 0 days 7 hours 55 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-03-20', '0 years 0 mons 0 days 5 hours 43 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-03-23', '0 years 0 mons 0 days 8 hours 5 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-03-24', '0 years 0 mons 0 days 8 hours 21 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-03-25', '0 years 0 mons 0 days 8 hours 10 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-03-26', '0 years 0 mons 0 days 10 hours 31 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-03-27', '0 years 0 mons 0 days 4 hours 55 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-03-30', '0 years 0 mons 0 days 8 hours 21 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-03-31', '0 years 0 mons 0 days 8 hours 1 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-04-01', '0 years 0 mons 0 days 7 hours 53 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-04-02', '0 years 0 mons 0 days 8 hours 7 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-04-03', '0 years 0 mons 0 days 7 hours 58 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-04-06', '0 years 0 mons 0 days 7 hours 27 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-04-07', '0 years 0 mons 0 days 8 hours 2 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-04-08', '0 years 0 mons 0 days 8 hours 9 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-04-14', '0 years 0 mons 0 days 7 hours 17 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-04-15', '0 years 0 mons 0 days 7 hours 56 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-04-16', '0 years 0 mons 0 days 8 hours 0 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-04-17', '0 years 0 mons 0 days 7 hours 39 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-04-20', '0 years 0 mons 0 days 8 hours 7 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-04-21', '0 years 0 mons 0 days 8 hours 3 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-04-22', '0 years 0 mons 0 days 8 hours 24 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-04-23', '0 years 0 mons 0 days 8 hours 53 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-04-24', '0 years 0 mons 0 days 8 hours 24 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-04-27', '0 years 0 mons 0 days 8 hours 8 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-04-28', '0 years 0 mons 0 days 8 hours 25 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-04-29', '0 years 0 mons 0 days 8 hours 23 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-04-30', '0 years 0 mons 0 days 7 hours 58 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-05-04', '0 years 0 mons 0 days 8 hours 6 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-05-05', '0 years 0 mons 0 days 6 hours 21 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-05-06', '0 years 0 mons 0 days 9 hours 9 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-05-07', '0 years 0 mons 0 days 8 hours 2 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-05-08', '0 years 0 mons 0 days 8 hours 1 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-05-11', '0 years 0 mons 0 days 8 hours 4 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-05-12', '0 years 0 mons 0 days 8 hours 42 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-05-13', '0 years 0 mons 0 days 8 hours 30 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-05-14', '0 years 0 mons 0 days 8 hours 39 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-05-15', '0 years 0 mons 0 days 6 hours 42 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-05-18', '0 years 0 mons 0 days 8 hours 20 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-05-19', '0 years 0 mons 0 days 7 hours 44 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-05-20', '0 years 0 mons 0 days 8 hours 13 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-05-25', '0 years 0 mons 0 days 7 hours 41 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-05-26', '0 years 0 mons 0 days 8 hours 32 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-05-27', '0 years 0 mons 0 days 8 hours 9 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-05-28', '0 years 0 mons 0 days 8 hours 24 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-10-26', '0 years 0 mons 0 days 7 hours 44 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-05-29', '0 years 0 mons 0 days 8 hours 44 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-06-02', '0 years 0 mons 0 days 7 hours 50 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-06-03', '0 years 0 mons 0 days 6 hours 49 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-06-04', '0 years 0 mons 0 days 8 hours 27 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-06-05', '0 years 0 mons 0 days 7 hours 47 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-06-08', '0 years 0 mons 0 days 7 hours 38 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-06-09', '0 years 0 mons 0 days 7 hours 42 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-06-10', '0 years 0 mons 0 days 7 hours 43 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-06-11', '0 years 0 mons 0 days 7 hours 54 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-06-15', '0 years 0 mons 0 days 7 hours 45 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-06-16', '0 years 0 mons 0 days 8 hours 3 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-06-17', '0 years 0 mons 0 days 8 hours 19 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-06-18', '0 years 0 mons 0 days 8 hours 23 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-06-19', '0 years 0 mons 0 days 8 hours 18 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-06-22', '0 years 0 mons 0 days 7 hours 59 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-06-23', '0 years 0 mons 0 days 7 hours 54 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-06-24', '0 years 0 mons 0 days 8 hours 20 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-06-25', '0 years 0 mons 0 days 7 hours 55 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-06-26', '0 years 0 mons 0 days 9 hours 3 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-06-29', '0 years 0 mons 0 days 7 hours 58 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-06-30', '0 years 0 mons 0 days 8 hours 18 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-07-01', '0 years 0 mons 0 days 8 hours 6 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-07-02', '0 years 0 mons 0 days 7 hours 41 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-07-07', '0 years 0 mons 0 days 8 hours 5 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-07-08', '0 years 0 mons 0 days 7 hours 44 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-07-09', '0 years 0 mons 0 days 8 hours 4 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-07-10', '0 years 0 mons 0 days 6 hours 20 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-07-03', '0 years 0 mons 0 days 7 hours 42 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-07-06', '0 years 0 mons 0 days 8 hours 14 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-07-13', '0 years 0 mons 0 days 7 hours 19 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-07-14', '0 years 0 mons 0 days 7 hours 4 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-07-15', '0 years 0 mons 0 days 8 hours 1 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-07-16', '0 years 0 mons 0 days 7 hours 57 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-07-17', '0 years 0 mons 0 days 8 hours 10 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-08-10', '0 years 0 mons 0 days 7 hours 50 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-08-11', '0 years 0 mons 0 days 8 hours 16 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-08-12', '0 years 0 mons 0 days 7 hours 12 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-08-13', '0 years 0 mons 0 days 7 hours 13 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-08-14', '0 years 0 mons 0 days 8 hours 29 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-08-17', '0 years 0 mons 0 days 8 hours 46 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-08-18', '0 years 0 mons 0 days 8 hours 2 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-08-19', '0 years 0 mons 0 days 8 hours 9 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-08-20', '0 years 0 mons 0 days 8 hours 17 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-08-21', '0 years 0 mons 0 days 7 hours 26 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-08-24', '0 years 0 mons 0 days 7 hours 58 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-08-25', '0 years 0 mons 0 days 8 hours 5 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-08-26', '0 years 0 mons 0 days 7 hours 59 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-08-27', '0 years 0 mons 0 days 7 hours 31 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-08-28', '0 years 0 mons 0 days 8 hours 21 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-08-31', '0 years 0 mons 0 days 8 hours 2 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-09-01', '0 years 0 mons 0 days 8 hours 12 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-09-02', '0 years 0 mons 0 days 7 hours 45 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-09-03', '0 years 0 mons 0 days 8 hours 37 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-09-04', '0 years 0 mons 0 days 7 hours 43 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-09-07', '0 years 0 mons 0 days 7 hours 16 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-09-08', '0 years 0 mons 0 days 8 hours 1 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-09-09', '0 years 0 mons 0 days 8 hours 3 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-09-10', '0 years 0 mons 0 days 6 hours 42 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-09-11', '0 years 0 mons 0 days 7 hours 54 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-09-14', '0 years 0 mons 0 days 7 hours 54 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-09-15', '0 years 0 mons 0 days 7 hours 53 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-09-16', '0 years 0 mons 0 days 7 hours 47 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-09-17', '0 years 0 mons 0 days 7 hours 33 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-09-18', '0 years 0 mons 0 days 7 hours 58 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-09-21', '0 years 0 mons 0 days 7 hours 38 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-09-22', '0 years 0 mons 0 days 7 hours 55 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-09-23', '0 years 0 mons 0 days 8 hours 2 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-09-24', '0 years 0 mons 0 days 8 hours 14 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-09-25', '0 years 0 mons 0 days 8 hours 6 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-09-28', '0 years 0 mons 0 days 7 hours 49 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-09-29', '0 years 0 mons 0 days 7 hours 30 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-09-30', '0 years 0 mons 0 days 8 hours 15 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-10-01', '0 years 0 mons 0 days 7 hours 27 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-10-02', '0 years 0 mons 0 days 7 hours 22 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-10-05', '0 years 0 mons 0 days 8 hours 16 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-10-06', '0 years 0 mons 0 days 8 hours 0 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-10-07', '0 years 0 mons 0 days 8 hours 4 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-10-08', '0 years 0 mons 0 days 7 hours 29 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-10-09', '0 years 0 mons 0 days 9 hours 0 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-10-12', '0 years 0 mons 0 days 8 hours 8 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-10-13', '0 years 0 mons 0 days 8 hours 20 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-10-14', '0 years 0 mons 0 days 8 hours 13 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-10-15', '0 years 0 mons 0 days 7 hours 30 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-10-16', '0 years 0 mons 0 days 8 hours 21 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-10-19', '0 years 0 mons 0 days 8 hours 16 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-10-20', '0 years 0 mons 0 days 7 hours 58 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-10-21', '0 years 0 mons 0 days 7 hours 44 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-10-22', '0 years 0 mons 0 days 7 hours 38 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-10-23', '0 years 0 mons 0 days 7 hours 38 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-10-27', '0 years 0 mons 0 days 9 hours 4 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-10-28', '0 years 0 mons 0 days 7 hours 21 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-10-29', '0 years 0 mons 0 days 7 hours 51 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-10-30', '0 years 0 mons 0 days 8 hours 14 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-11-02', '0 years 0 mons 0 days 8 hours 11 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-11-04', '0 years 0 mons 0 days 8 hours 5 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-11-05', '0 years 0 mons 0 days 7 hours 44 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-11-06', '0 years 0 mons 0 days 8 hours 14 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-11-09', '0 years 0 mons 0 days 8 hours 14 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-11-11', '0 years 0 mons 0 days 8 hours 28 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-11-12', '0 years 0 mons 0 days 7 hours 21 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-11-13', '0 years 0 mons 0 days 9 hours 2 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-11-16', '0 years 0 mons 0 days 7 hours 59 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-11-17', '0 years 0 mons 0 days 8 hours 32 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-11-18', '0 years 0 mons 0 days 8 hours 8 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-11-19', '0 years 0 mons 0 days 8 hours 25 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-11-20', '0 years 0 mons 0 days 8 hours 6 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-11-03', '0 years 0 mons 0 days 8 hours 14 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-11-10', '0 years 0 mons 0 days 8 hours 3 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-11-23', '0 years 0 mons 0 days 8 hours 17 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-11-24', '0 years 0 mons 0 days 7 hours 43 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-11-25', '0 years 0 mons 0 days 7 hours 52 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-11-26', '0 years 0 mons 0 days 7 hours 53 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-11-27', '0 years 0 mons 0 days 8 hours 20 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-11-30', '0 years 0 mons 0 days 8 hours 43 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-12-01', '0 years 0 mons 0 days 8 hours 33 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-12-02', '0 years 0 mons 0 days 6 hours 19 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-12-03', '0 years 0 mons 0 days 7 hours 48 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-12-04', '0 years 0 mons 0 days 8 hours 29 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-12-07', '0 years 0 mons 0 days 8 hours 7 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-12-09', '0 years 0 mons 0 days 7 hours 55 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-12-10', '0 years 0 mons 0 days 7 hours 47 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-12-11', '0 years 0 mons 0 days 7 hours 30 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-12-08', '0 years 0 mons 0 days 8 hours 22 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-12-14', '0 years 0 mons 0 days 7 hours 54 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-12-15', '0 years 0 mons 0 days 8 hours 4 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-12-16', '0 years 0 mons 0 days 7 hours 36 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-12-17', '0 years 0 mons 0 days 7 hours 47 mins 0.00 secs');
|
||||
INSERT INTO public."Arbeitszeiten" ("Datum", "Arbeitszeit") VALUES ('2020-12-18', '0 years 0 mons 0 days 7 hours 33 mins 0.00 secs');
|
||||
Executable
+62
@@ -0,0 +1,62 @@
|
||||
Aktivität;Dauer;Von;Bis;Kommentar
|
||||
Arbeit;04:19;2021-07-30 13:01;2021-07-30 17:20;
|
||||
Mittagspause ;00:45;2021-07-30 12:16;2021-07-30 13:01;
|
||||
Arbeit;04:22;2021-07-30 07:54;2021-07-30 12:16;
|
||||
Arbeit;03:25;2021-07-29 13:25;2021-07-29 16:50;
|
||||
Mittagspause ;01:01;2021-07-29 12:23;2021-07-29 13:25;
|
||||
Arbeit;04:44;2021-07-29 07:39;2021-07-29 12:23;
|
||||
Arbeit;04:47;2021-07-28 12:49;2021-07-28 17:37;
|
||||
Mittagspause ;00:58;2021-07-28 11:51;2021-07-28 12:49;
|
||||
Arbeit;03:48;2021-07-28 08:02;2021-07-28 11:51;
|
||||
Arbeit;03:49;2021-07-27 12:34;2021-07-27 16:24;
|
||||
Mittagspause ;00:47;2021-07-27 11:47;2021-07-27 12:34;
|
||||
Arbeit;03:20;2021-07-27 08:27;2021-07-27 11:47;
|
||||
Arbeit;04:47;2021-07-26 12:59;2021-07-26 17:47;
|
||||
Mittagspause ;00:52;2021-07-26 12:07;2021-07-26 12:59;
|
||||
Arbeit;03:59;2021-07-26 08:08;2021-07-26 12:07;
|
||||
Arbeit;05:53;2021-07-23 12:43;2021-07-23 18:37;
|
||||
Mittagspause ;00:36;2021-07-23 12:07;2021-07-23 12:43;
|
||||
Arbeit;04:10;2021-07-23 07:57;2021-07-23 12:07;
|
||||
Arbeit;04:09;2021-07-22 12:54;2021-07-22 17:04;
|
||||
Mittagspause ;00:42;2021-07-22 12:11;2021-07-22 12:54;
|
||||
Arbeit;04:16;2021-07-22 07:55;2021-07-22 12:11;
|
||||
Arbeit;03:11;2021-07-21 13:37;2021-07-21 16:48;
|
||||
Mittagspause ;01:07;2021-07-21 12:29;2021-07-21 13:37;
|
||||
Arbeit;04:58;2021-07-21 07:31;2021-07-21 12:29;
|
||||
Verkehr;01:17;2021-07-20 18:44;2021-07-20 20:02;
|
||||
Arbeit;03:50;2021-07-20 13:14;2021-07-20 17:04;
|
||||
Mittagspause ;00:41;2021-07-20 12:32;2021-07-20 13:14;
|
||||
Arbeit;04:55;2021-07-20 07:37;2021-07-20 12:32;
|
||||
Arbeit;03:19;2021-07-19 13:12;2021-07-19 16:31;
|
||||
Mittagspause ;00:52;2021-07-19 12:20;2021-07-19 13:12;
|
||||
Arbeit;04:28;2021-07-19 07:51;2021-07-19 12:20;
|
||||
Arbeit;02:48;2021-07-16 12:55;2021-07-16 15:43;
|
||||
Mittagspause ;00:47;2021-07-16 12:07;2021-07-16 12:55;
|
||||
Arbeit;04:01;2021-07-16 08:06;2021-07-16 12:07;
|
||||
Arbeit;03:38;2021-07-15 13:05;2021-07-15 16:43;
|
||||
Mittagspause ;01:05;2021-07-15 11:59;2021-07-15 13:05;
|
||||
Arbeit;03:56;2021-07-15 08:02;2021-07-15 11:59;
|
||||
Arbeit;03:38;2021-07-14 13:07;2021-07-14 16:45;
|
||||
Mittagspause ;01:00;2021-07-14 12:07;2021-07-14 13:07;
|
||||
Arbeit;04:00;2021-07-14 08:06;2021-07-14 12:07;
|
||||
Arbeit;04:22;2021-07-13 12:53;2021-07-13 17:15;
|
||||
Mittagspause ;01:03;2021-07-13 11:50;2021-07-13 12:53;
|
||||
Arbeit;03:47;2021-07-13 08:02;2021-07-13 11:50;
|
||||
Arbeit;03:26;2021-07-12 13:02;2021-07-12 16:28;
|
||||
Mittagspause ;01:03;2021-07-12 11:58;2021-07-12 13:02;
|
||||
Arbeit;04:14;2021-07-12 07:44;2021-07-12 11:58;
|
||||
Arbeit;03:57;2021-07-09 13:09;2021-07-09 17:06;
|
||||
Mittagspause ;01:06;2021-07-09 12:02;2021-07-09 13:09;
|
||||
Arbeit;03:57;2021-07-09 08:04;2021-07-09 12:02;
|
||||
Arbeit;03:17;2021-07-08 13:12;2021-07-08 16:29;
|
||||
Mittagspause ;01:07;2021-07-08 12:04;2021-07-08 13:12;
|
||||
Arbeit;04:00;2021-07-08 08:03;2021-07-08 12:04;
|
||||
Arbeit;03:41;2021-07-07 13:08;2021-07-07 16:49;
|
||||
Mittagspause ;01:00;2021-07-07 12:07;2021-07-07 13:08;
|
||||
Arbeit;04:05;2021-07-07 08:02;2021-07-07 12:07;
|
||||
Arbeit;02:50;2021-07-06 13:05;2021-07-06 15:55;
|
||||
Mittagspause ;01:05;2021-07-06 11:59;2021-07-06 13:05;
|
||||
Arbeit;03:49;2021-07-06 08:10;2021-07-06 11:59;
|
||||
Arbeit;03:32;2021-07-05 13:00;2021-07-05 16:33;
|
||||
Mittagspause ;00:59;2021-07-05 12:00;2021-07-05 13:00;
|
||||
Arbeit;03:48;2021-07-05 08:12;2021-07-05 12:00;
|
||||
|
@@ -0,0 +1,15 @@
|
||||
@startuml
|
||||
'https://plantuml.com/activity-diagram-beta
|
||||
|UI|
|
||||
:Bestimme //Startdatum//;
|
||||
:Bestimme //Enddatum//;
|
||||
:Erstelle //Liste der Wochentage//;
|
||||
|REST|
|
||||
start
|
||||
:Rufe API auf;
|
||||
|UI|
|
||||
:Zeige //Liste der Wochentage// an;
|
||||
:Verarbeite Informationen der Rest-API;
|
||||
stop
|
||||
|
||||
@enduml
|
||||
@@ -0,0 +1,2 @@
|
||||
node_modules
|
||||
dist
|
||||
@@ -0,0 +1,2 @@
|
||||
# Url ohne abschlißenden Slash
|
||||
API_URL= http://localhost:8080
|
||||
@@ -0,0 +1,6 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
public
|
||||
/.env
|
||||
@@ -0,0 +1,16 @@
|
||||
FROM node:22-alpine AS builder
|
||||
WORKDIR /app
|
||||
COPY package*.json ./
|
||||
RUN npm ci
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
RUN npm prune --production
|
||||
|
||||
FROM node:22-alpine
|
||||
WORKDIR /app
|
||||
COPY --from=builder /app/build build/
|
||||
COPY --from=builder /app/node_modules node_modules/
|
||||
COPY package.json .
|
||||
EXPOSE 3000
|
||||
ENV NODE_ENV=production
|
||||
CMD [ "node", "build" ]
|
||||
@@ -0,0 +1,19 @@
|
||||
// Einstellungen auf die Grundlagen zurücksetzen,
|
||||
// beinhaltet auch Einstellungen für die neuen HTML5-Tags
|
||||
//noinspection CssUnknownTarget
|
||||
@import "meyer_resets.css";
|
||||
// Import und Einbindung der Zeichensätze
|
||||
@import "lesslib/fonts.less";
|
||||
// Bestimmung der Farben
|
||||
@import "lesslib/colors.less";
|
||||
// Mixins des Layouts
|
||||
@import "lesslib/mixins.less";
|
||||
// Layout-Unabhängige Mixins
|
||||
@import "lesslib/mixins.framework.less";
|
||||
// Bestimmung des grundsätzlichen Erscheinungsbildes der einzelnen Elemente,
|
||||
// einschließlich des Hauptbereiches
|
||||
@import "lesslib/basics.less";
|
||||
// Darstellung unabhängig der Darstellungsgrößen
|
||||
@import "lesslib/layout.less";
|
||||
// Darstellung in den verschiedenen Ansichtsgrößen
|
||||
@import "lesslib/media_queries.less";
|
||||
@@ -0,0 +1,101 @@
|
||||
html {
|
||||
font-size: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
color: #000;
|
||||
font-size: 1em;
|
||||
font-family: OpenSans, "Lucida Grande", "Lucida Sans Unicode", Verdana, Helvetica, Arial, sans-serif;
|
||||
text-align: left;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
strong, b {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
em, i {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
small {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
section article {
|
||||
font-size: 95%;
|
||||
p, ul, ol, dl {
|
||||
line-height: 1.5em;
|
||||
.hyphens();
|
||||
font-size: 1.00em;
|
||||
}
|
||||
p {
|
||||
text-align: justify;
|
||||
}
|
||||
ul, ol {
|
||||
li {
|
||||
list-style: initial;
|
||||
}
|
||||
}
|
||||
dl {
|
||||
dt {
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
h1, h2, h3 {
|
||||
font-weight: bold;
|
||||
}
|
||||
h1 {
|
||||
font-size: 1.50em;
|
||||
}
|
||||
h2 {
|
||||
font-size: 1.30em;
|
||||
}
|
||||
h3 {
|
||||
font-size: 1.20em;
|
||||
}
|
||||
table {
|
||||
colgroup:not(:first-child) col:first-child{
|
||||
border-left: solid thin;
|
||||
}
|
||||
thead {
|
||||
th {
|
||||
font-size: 100%;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
form {
|
||||
fieldset {
|
||||
legend {
|
||||
font-size: 1.30em;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Einstellungen für die Pagination-Navigation
|
||||
.pagination-nav {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding-top: 1rem;
|
||||
.option {
|
||||
&.number, &.ellipsis {
|
||||
line-height: 1.25rem;
|
||||
span {
|
||||
margin: .25rem .5rem;
|
||||
}
|
||||
&.active {
|
||||
text-decoration: underline solid;
|
||||
}
|
||||
}
|
||||
&.disabled {
|
||||
svg {
|
||||
path {
|
||||
fill: darken(@mainColorLight,40%,relativ);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
Definition der Farben
|
||||
*/
|
||||
|
||||
@hueMain: 35;
|
||||
@hueSub: 215;
|
||||
|
||||
@mainColor: hsl(@hueMain, 90%, 29%);
|
||||
@redColor: hsl(0, 90%, 29%);
|
||||
@mainColorDark: darken(@mainColor,66%,relativ);
|
||||
@mainColorLight: lighten(@mainColor,66%,relativ);
|
||||
@subColor: hsl(@hueSub, 90%, 29%);
|
||||
@subColorDark: darken(@subColor,66%,relativ);
|
||||
@subColorLight: lighten(@subColor,66%,relativ);
|
||||
@@ -0,0 +1,43 @@
|
||||
@font-face {
|
||||
font-family: OpenSans;
|
||||
src: url(../fonts/opensans-regular.eot);
|
||||
src: url(../fonts/opensans-regular.eot?#iefix) format("embedded-opentype"),
|
||||
url(../fonts/opensans-regular.woff) format("woff"),
|
||||
url(../fonts/opensans-regular.ttf) format("truetype"),
|
||||
url(../fonts/opensans-regular.svg#open_sansregular) format("svg");
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: OpenSans;
|
||||
src: url(../fonts/opensans-italic.eot);
|
||||
src: url(../fonts/opensans-italic.eot?#iefix) format("embedded-opentype"),
|
||||
url(../fonts/opensans-italic.woff) format("woff"),
|
||||
url(../fonts/opensans-italic.ttf) format("truetype"),
|
||||
url(../fonts/opensans-italic.svg#open_sansitalic) format("svg");
|
||||
font-weight: normal;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: OpenSans;
|
||||
src: url(../fonts/opensans-bold.eot);
|
||||
src: url(../fonts/opensans-bold.eot?#iefix) format("embedded-opentype"),
|
||||
url(../fonts/opensans-bold.woff) format("woff"),
|
||||
url(../fonts/opensans-bold.ttf) format("truetype"),
|
||||
url(../fonts/opensans-bold.svg#open_sansbold) format("svg");
|
||||
font-weight: bold;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: OpenSans;
|
||||
src: url(../fonts/opensans-bolditalic.eot);
|
||||
src: url(../fonts/opensans-bolditalic.eot?#iefix) format("embedded-opentype"),
|
||||
url(../fonts/opensans-bolditalic.woff) format("woff"),
|
||||
url(../fonts/opensans-bolditalic.ttf) format("truetype"),
|
||||
url(../fonts/opensans-bolditalic.svg#open_sansbold_italic) format("svg");
|
||||
font-weight: bold;
|
||||
font-style: italic;
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
@breiteMainBereich: 70rem;
|
||||
@breiteTextBereich: 60rem;
|
||||
@breiteNavBereich: @breiteMainBereich - @breiteTextBereich;
|
||||
|
||||
html {
|
||||
body {
|
||||
background: @mainColorLight;
|
||||
div {
|
||||
> header, > footer {
|
||||
color: @mainColorDark;
|
||||
background: @mainColor;
|
||||
font-family: serif;
|
||||
|
||||
h1 {
|
||||
font-size: 3rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
p {
|
||||
text-indent: inherit;
|
||||
|
||||
&:first-letter {
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
font-style: italic;
|
||||
}
|
||||
}
|
||||
|
||||
> header h1 {
|
||||
margin: 1rem auto;
|
||||
}
|
||||
|
||||
> footer {
|
||||
color: contrast(@mainColorDark);
|
||||
}
|
||||
|
||||
& > nav {
|
||||
ul {
|
||||
background: darken(@mainColorLight, 33%, relativ);
|
||||
|
||||
li {
|
||||
font-weight: bold;
|
||||
|
||||
&:not(:first-child) {
|
||||
border-left: @mainColorDark solid thin;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: @mainColorDark;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: lighten(@mainColor, 20%, relativ);
|
||||
|
||||
a {
|
||||
color: lighten(@mainColor, 66%, relativ);
|
||||
}
|
||||
}
|
||||
|
||||
&.selected, &.selected:hover {
|
||||
background: lighten(@mainColor, 10%, relativ);
|
||||
|
||||
a {
|
||||
color: lighten(@mainColor, 66%, relativ);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
main {
|
||||
* {
|
||||
border-style: none;
|
||||
border-color: @mainColorDark;
|
||||
border-width: medium;
|
||||
}
|
||||
|
||||
section {
|
||||
@sectionMargin: 1rem;
|
||||
|
||||
h1, h2 {
|
||||
margin: @sectionMargin;
|
||||
}
|
||||
|
||||
p, ul, ol, dl {
|
||||
margin: @sectionMargin;
|
||||
text-indent: @sectionMargin * 2;
|
||||
}
|
||||
|
||||
table {
|
||||
thead {
|
||||
tr:last-child {
|
||||
td, th {
|
||||
border-bottom: @mainColor solid thin;
|
||||
}
|
||||
}
|
||||
}
|
||||
;
|
||||
|
||||
tbody {
|
||||
td.absence-time {
|
||||
color: @redColor;
|
||||
text-decoration-line: underline;
|
||||
text-decoration-style: dashed;
|
||||
}
|
||||
}
|
||||
tfoot {
|
||||
* {
|
||||
border: initial;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
form {
|
||||
@formLabelWidth: 10rem;
|
||||
@formItemWidth: 40rem;
|
||||
|
||||
nav.menu {
|
||||
width: @formItemWidth + @formLabelWidth + 4rem;
|
||||
border-color: @mainColor;
|
||||
border-bottom-style: solid;
|
||||
margin: 1rem auto;
|
||||
|
||||
span {
|
||||
border-color: @mainColor;
|
||||
border-style: solid solid none;
|
||||
padding: 0.5rem;
|
||||
display: inline-block;
|
||||
font-weight: bolder;
|
||||
margin-right: 1rem;
|
||||
|
||||
&:first-child {
|
||||
margin-left: 1.5rem;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: darken(@mainColorLight, 10%, relativ);
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: darken(@mainColorLight, 20%, relativ);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fieldset {
|
||||
width: @formItemWidth + @formLabelWidth + 2rem;
|
||||
margin: 1rem auto;
|
||||
border-style: solid;
|
||||
border-color: @mainColor;
|
||||
padding: @sectionMargin;
|
||||
|
||||
&.tasten {
|
||||
border: none;
|
||||
background-color: @mainColorLight;
|
||||
}
|
||||
|
||||
legend {
|
||||
margin: @sectionMargin;
|
||||
margin-left: @sectionMargin * 2;
|
||||
}
|
||||
|
||||
div {
|
||||
& > label {
|
||||
width: @formLabelWidth;
|
||||
display: inline-block;
|
||||
margin: 0.5rem;
|
||||
}
|
||||
|
||||
input {
|
||||
display: inline-block;
|
||||
width: @formItemWidth;
|
||||
padding: 0.25rem;
|
||||
margin-bottom: 1.0rem;
|
||||
border-style: solid;
|
||||
border-width: thin;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
.button(@mainColor, spin(lighten(@mainColor, 66%, relativ), @subColor));
|
||||
|
||||
&.sub-button {
|
||||
.button(lighten(@mainColor, 20%, relativ), spin(@mainColor, @subColor));
|
||||
}
|
||||
}
|
||||
|
||||
footer {
|
||||
text-align: right;
|
||||
padding: 0;
|
||||
|
||||
p {
|
||||
padding: 0.5rem;
|
||||
display: inline-block;
|
||||
margin: inherit;
|
||||
text-indent: inherit;
|
||||
|
||||
&:first-letter {
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
background: @mainColorLight;
|
||||
color: @mainColor;
|
||||
font-style: italic;
|
||||
font-size: 85%;
|
||||
}
|
||||
|
||||
nav li {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
border-bottom-style: solid;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,306 @@
|
||||
//Media Queries
|
||||
|
||||
@media print {
|
||||
* {
|
||||
font-size: 10pt;
|
||||
}
|
||||
header, footer, main, nav, aside {
|
||||
body div > & {
|
||||
width: 19cm;
|
||||
margin: auto;
|
||||
.border-box();
|
||||
}
|
||||
}
|
||||
header, footer{
|
||||
padding: 0.5rem;
|
||||
;
|
||||
}
|
||||
body div {
|
||||
& > header {
|
||||
font-size: 200%;
|
||||
font-weight: bolder;
|
||||
}
|
||||
& > nav, > header {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
main {
|
||||
padding: 1.5rem 0.5rem 3.25rem;
|
||||
.flexKinder(flow,'row');
|
||||
width: @breiteTextBereich;
|
||||
section {
|
||||
padding: 0 0.5rem;
|
||||
h1, h2, p {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
footer {
|
||||
padding: 0;
|
||||
p {
|
||||
padding: 0.5rem;
|
||||
font-size: 85%;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
article.liste {
|
||||
page-break-after: always;
|
||||
}
|
||||
}
|
||||
h1, h2 {
|
||||
margin-left: 5em;
|
||||
}
|
||||
h1 {
|
||||
font-size: 2em;
|
||||
}
|
||||
h2 {
|
||||
font-size: 1.75em;
|
||||
}
|
||||
table {
|
||||
margin: auto;
|
||||
td, th {
|
||||
padding: 0.5rem;
|
||||
collapse: true;
|
||||
}
|
||||
}
|
||||
}
|
||||
aside {
|
||||
padding-top: 2rem;
|
||||
.flexKinder(flow,'row');
|
||||
width: @breiteNavBereich;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 831px) {
|
||||
header, footer, main, nav, aside {
|
||||
body div > & {
|
||||
width: @breiteMainBereich;
|
||||
margin: auto;
|
||||
.border-box();
|
||||
}
|
||||
}
|
||||
header, footer{
|
||||
padding: 0.5rem;
|
||||
}
|
||||
body div {
|
||||
& > header {
|
||||
font-size: 200%;
|
||||
font-weight: bolder;
|
||||
}
|
||||
& > nav {
|
||||
ul {
|
||||
.flexEltern();
|
||||
margin: 0;
|
||||
li {
|
||||
display: inline-block;
|
||||
height: 1.5rem;
|
||||
padding: 0.5rem 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
main {
|
||||
padding: 1.5rem 0.5rem 3.25rem;
|
||||
.flexKinder(flow,'row');
|
||||
width: @breiteTextBereich;
|
||||
section {
|
||||
padding: 0 0.5rem;
|
||||
h1, h2, p {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
footer {
|
||||
padding: 0;
|
||||
p {
|
||||
padding: 0.5rem;
|
||||
font-size: 85%;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
h1, h2 {
|
||||
margin-left: 5em;
|
||||
}
|
||||
h1 {
|
||||
font-size: 2em;
|
||||
}
|
||||
h2 {
|
||||
font-size: 1.75em;
|
||||
}
|
||||
table {
|
||||
margin: auto;
|
||||
td, th {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
aside {
|
||||
padding-top: 2rem;
|
||||
.flexKinder(flow,'row');
|
||||
width: @breiteNavBereich;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 830px) {
|
||||
html body div {
|
||||
@boxHeight: 5rem;
|
||||
& > header {
|
||||
height: @boxHeight;
|
||||
font-size: @boxHeight;
|
||||
text-align: center;
|
||||
padding: 1rem;
|
||||
}
|
||||
& > footer {
|
||||
padding: 1rem;
|
||||
}
|
||||
& > nav {
|
||||
ul {
|
||||
li {
|
||||
display: inline-block;
|
||||
border: none;
|
||||
width: auto;
|
||||
height: 1.5em;
|
||||
padding-top: 0.25em;
|
||||
a {
|
||||
padding: 0.75rem;
|
||||
}
|
||||
&:not(:first-child) {
|
||||
border-left: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
main section {
|
||||
header {
|
||||
padding: 0 0.5rem;
|
||||
h1, h2, p {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
h1, h2 {
|
||||
margin-left: 5em;
|
||||
}
|
||||
h1 {
|
||||
font-size: 2em;
|
||||
}
|
||||
h2 {
|
||||
font-size: 1.75em;
|
||||
}
|
||||
}
|
||||
form {
|
||||
@formLabelWidth: 10rem;
|
||||
@formItemWidth: 40rem;
|
||||
@sectionMargin: 1rem;
|
||||
margin: @sectionMargin;
|
||||
fieldset {
|
||||
width: auto;
|
||||
margin: 1rem auto;
|
||||
padding: @sectionMargin;
|
||||
legend {
|
||||
margin: @sectionMargin;
|
||||
margin-left: @sectionMargin * 2;
|
||||
}
|
||||
div {
|
||||
& > label {
|
||||
width: auto;
|
||||
display: inline-block;
|
||||
margin: 0.5rem;
|
||||
}
|
||||
input, textarea {
|
||||
display: inline-block;
|
||||
width: auto;
|
||||
padding: 0.25rem;
|
||||
margin: 0.5rem;
|
||||
margin-bottom: 1.0rem;
|
||||
border-style: solid;
|
||||
border-width: thin;
|
||||
}
|
||||
div.radio-button {
|
||||
padding-left: 20%;
|
||||
label, input {
|
||||
display: inline;
|
||||
width: auto;
|
||||
}
|
||||
input {
|
||||
margin-top: inherit;
|
||||
margin-right: 1rem;
|
||||
margin-left: 1rem;
|
||||
margin-bottom: inherit;
|
||||
}
|
||||
}
|
||||
textarea {
|
||||
height: 10rem;
|
||||
}
|
||||
ul.suche {
|
||||
width: 30rem;
|
||||
margin-left: @formLabelWidth + 1.5rem;
|
||||
margin-top: -1rem;
|
||||
background: white;
|
||||
position: absolute;
|
||||
border: solid thin;
|
||||
li {list-style: none;
|
||||
text-indent: 0;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background: @mainColorLight;
|
||||
color: @mainColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
table {
|
||||
width: 90%;
|
||||
margin: 5%;
|
||||
td, th {
|
||||
padding: 0.25rem;
|
||||
}
|
||||
colgroup:not(:first-child) col:first-child{
|
||||
border-left: solid thin;
|
||||
}
|
||||
thead {
|
||||
th {
|
||||
font-size: 100%;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: 720px) {
|
||||
html body div {
|
||||
@boxHeight: 2.5rem;
|
||||
& > header {
|
||||
height: @boxHeight;
|
||||
font-size: @boxHeight;
|
||||
text-align: center;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
& > nav {
|
||||
.hamburgerMenu();
|
||||
}
|
||||
main section header {
|
||||
h1, h2 {
|
||||
text-align: center;
|
||||
margin: 0.5rem auto;
|
||||
}
|
||||
}
|
||||
table {
|
||||
width: 100%;
|
||||
margin: 1rem 0;
|
||||
td, th {
|
||||
padding: 0.25rem;
|
||||
}
|
||||
colgroup:not(:first-child) col:first-child{
|
||||
border-left: solid thin;
|
||||
}
|
||||
thead {
|
||||
th {
|
||||
font-size: 100%;
|
||||
font-weight: bold;
|
||||
.hyphens();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
.hyphens() {
|
||||
-webkit-hyphens: auto;
|
||||
-moz-hyphens: auto;
|
||||
hyphens: auto;
|
||||
}
|
||||
|
||||
.border-box() {
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.vertical-gradient(@stop1, @stop2) {
|
||||
.gradient(0deg, @stop1, @stop2);
|
||||
}
|
||||
|
||||
.horizontal-gradient(@stop1, @stop2) {
|
||||
.gradient(-90deg, @stop1, @stop2);
|
||||
}
|
||||
|
||||
.gradient(@winkel, @stop1, @stop2) {
|
||||
@winkelOld: @winkel + 90deg;
|
||||
@groundColor: @stop2;
|
||||
background: @groundColor -moz-linear-gradient(@winkelOld, @stop2, @stop1);
|
||||
background: @groundColor -webkit-linear-gradient(@winkelOld, @stop2, @stop1);
|
||||
background: @groundColor -o-linear-gradient(@winkelOld, @stop2, @stop1);
|
||||
background: @groundColor -ms-linear-gradient(@winkelOld, @stop2, @stop1);
|
||||
background: @groundColor linear-gradient(@winkel, @stop2, @stop1);
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.box-shadow(...) {
|
||||
-webkit-box-shadow: @arguments;
|
||||
-moz-box-shadow: @arguments;
|
||||
box-shadow: @arguments;
|
||||
}
|
||||
|
||||
.border-radius(...) {
|
||||
-webkit-border-radius: @arguments;
|
||||
-moz-border-radius: @arguments;
|
||||
border-radius: @arguments;
|
||||
}
|
||||
|
||||
.nowrap() {
|
||||
display: block;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.hamburgerMenu() {
|
||||
color: white;
|
||||
width: 3rem;
|
||||
height: 2rem;
|
||||
top: 0.25rem;
|
||||
left: 0.25rem;
|
||||
.border-radius(4px);
|
||||
border: none;
|
||||
.vertical-gradient(@mainColorDark; @mainColor);
|
||||
> ul {
|
||||
height: auto;
|
||||
width: auto;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 100%;
|
||||
background: fade(@mainColorDark,80%);
|
||||
.border-radius(5px);
|
||||
display: none;
|
||||
> li {
|
||||
display: block;
|
||||
text-align: left;
|
||||
line-height: normal;
|
||||
border: none;
|
||||
&:not(:first-child) {
|
||||
border-top: 1px #888 solid;
|
||||
}
|
||||
a {
|
||||
.nowrap();
|
||||
display: block;
|
||||
height: auto;
|
||||
width: auto;
|
||||
padding: 0.5rem 1rem;
|
||||
color: white;
|
||||
&:hover,
|
||||
&:focus {
|
||||
color: white;
|
||||
.vertical-gradient(@mainColorLight; @mainColor);
|
||||
}
|
||||
}
|
||||
&:first-child {
|
||||
a {
|
||||
.border-radius( 5px 5px 0 0 );
|
||||
}
|
||||
}
|
||||
&:last-child {
|
||||
a {
|
||||
.border-radius( 0 0 5px 5px );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
&:hover, &:focus, &:active, &:checked {
|
||||
ul {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
&:before {
|
||||
content: "\2261";
|
||||
font-size: 2rem;
|
||||
text-align: center;
|
||||
line-height: 0.8em;
|
||||
display: block;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.flexEltern() {
|
||||
display:-ms-flexbox;
|
||||
display: flexbox;
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.flexEltern(column) {
|
||||
display:-ms-flexbox;
|
||||
display: flexbox;
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
-webkit-flex-direction: column;
|
||||
-ms-flex-direction: column;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.flexKinder(flow, @rest...) {
|
||||
-webkit-flex-flow: @rest;
|
||||
-ms-flex-flow: @rest;
|
||||
flex-flow: @rest;
|
||||
}
|
||||
|
||||
.flexKinder(glow, @num) {
|
||||
-webkit-flex-grow: @num;
|
||||
-ms-flex: @num;
|
||||
flex: @num;
|
||||
flex-grow: @num;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
.button(@color, @colorFont) {
|
||||
@colorBackground: @color;
|
||||
@colorBackgroundLight: lighten(@colorBackground,50%,relativ);
|
||||
@colorShadow: fade(@colorBackground,50%);
|
||||
height: 2.5em;
|
||||
.border-radius(1.25em);
|
||||
padding: 0.5em 1.25em;
|
||||
.vertical-gradient(@colorBackgroundLight, @colorBackground);
|
||||
.box-shadow(0.1em 0.2em 0.5em @colorShadow);
|
||||
color: @colorFont;
|
||||
font-weight: bold;
|
||||
&:hover {
|
||||
.vertical-gradient(@colorBackground, @colorBackgroundLight);
|
||||
}
|
||||
margin: 0.25rem;
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
html {
|
||||
font-size: 100%;
|
||||
}
|
||||
|
||||
body div {
|
||||
color: #000;
|
||||
font-size: 1em;
|
||||
font-family: OpenSans, "Lucida Grande", "Lucida Sans Unicode", Verdana, Helvetica, Arial, sans-serif;
|
||||
text-align: left;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
strong, b {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
em, i {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
small {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
section article {
|
||||
font-size: 95%;
|
||||
p, ul, ol, dl {
|
||||
line-height: 1.5em;
|
||||
//noinspection TaskProblemsInspection,LessResolvedByNameOnly
|
||||
.hyphens();
|
||||
font-size: 1.00em;
|
||||
}
|
||||
p {
|
||||
text-align: justify;
|
||||
}
|
||||
ul, ol {
|
||||
li {
|
||||
list-style: initial;
|
||||
}
|
||||
}
|
||||
dl {
|
||||
dt {
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
h1, h2, h3 {
|
||||
font-weight: bold;
|
||||
}
|
||||
h1 {
|
||||
font-size: 1.50em;
|
||||
}
|
||||
h2 {
|
||||
font-size: 1.30em;
|
||||
}
|
||||
h3 {
|
||||
font-size: 1.20em;
|
||||
}
|
||||
table {
|
||||
thead {
|
||||
th {
|
||||
font-size: 100%;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
form {
|
||||
fieldset {
|
||||
legend {
|
||||
font-size: 1.30em;
|
||||
font-weight: bold;
|
||||
}
|
||||
div.zelle > label {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
&.http-error {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
Definition der Farben
|
||||
*/
|
||||
|
||||
@hueMain: 180;
|
||||
@hueSub: 60;
|
||||
|
||||
@mainColor: hsl(@hueMain, 90%, 29%);
|
||||
@mainColorDark: darken(@mainColor,66%,relativ);
|
||||
@mainColorLight: lighten(@mainColor,66%,relativ);
|
||||
@subColor: hsl(@hueSub, 90%, 29%);
|
||||
@subColorDark: darken(@subColor,66%,relativ);
|
||||
@subColorLight: lighten(@subColor,66%,relativ);
|
||||
@@ -0,0 +1,99 @@
|
||||
html {
|
||||
body div {
|
||||
> header, > footer {
|
||||
background: none;
|
||||
font-family: serif;
|
||||
h1 {
|
||||
font-size: 3rem;
|
||||
text-align: left;
|
||||
}
|
||||
p {
|
||||
text-indent: inherit;
|
||||
&:first-letter {
|
||||
font-size: inherit;
|
||||
}
|
||||
font-style: italic;
|
||||
}
|
||||
}
|
||||
> header {
|
||||
border-bottom: black solid thick;
|
||||
h1 {
|
||||
margin: 1rem;
|
||||
}
|
||||
}
|
||||
> nav {
|
||||
display: none;
|
||||
}
|
||||
> footer {
|
||||
color: contrast(@mainColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
main {
|
||||
* {
|
||||
border-style: none;
|
||||
border-color: @mainColorDark;
|
||||
border-width: medium;
|
||||
}
|
||||
section {
|
||||
@sectionMargin: 1rem;
|
||||
header {
|
||||
text-align: center;
|
||||
}
|
||||
h1, h2 {
|
||||
margin: @sectionMargin;
|
||||
}
|
||||
p {
|
||||
margin: @sectionMargin;
|
||||
text-indent: @sectionMargin * 2;
|
||||
}
|
||||
table {
|
||||
colgroup:not(:first-child) col:first-child{
|
||||
border-left: solid medium;
|
||||
}
|
||||
thead {
|
||||
tr:last-child {
|
||||
td, th {
|
||||
border-bottom: @mainColorDark solid thick;
|
||||
}
|
||||
}
|
||||
}
|
||||
tbody div {
|
||||
th {
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
padding: 1rem 0.5rem;
|
||||
}
|
||||
th, td {
|
||||
padding: 0.5rem;
|
||||
border-bottom: solid thin;
|
||||
}
|
||||
}
|
||||
}
|
||||
form {
|
||||
display: none;
|
||||
}
|
||||
button {
|
||||
display: none;
|
||||
}
|
||||
footer {
|
||||
text-align: right;
|
||||
padding: 0;
|
||||
p {
|
||||
padding: 0.5rem;
|
||||
display: inline-block;
|
||||
margin: inherit;
|
||||
text-indent: inherit;
|
||||
&:first-letter {
|
||||
font-size: inherit;
|
||||
}
|
||||
background: @mainColorLight ;
|
||||
color: @mainColor ;
|
||||
font-style: italic;
|
||||
font-size: 85%;
|
||||
}
|
||||
border-bottom-style: solid;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// Einstellungen auf die Grundlagen zurücksetzen,
|
||||
// beinhaltet auch Einstellungen für die neuen HTML5-Tags
|
||||
//noinspection CssUnknownTarget
|
||||
@import "meyer_resets.css";
|
||||
// Bestimmung der Farben
|
||||
@import "lesslib/colors.less";
|
||||
// Mixins des Layouts
|
||||
@import "lesslib/mixins.less";
|
||||
// Layout-Unabhängige Mixins
|
||||
@import "lesslib/mixins.framework.less";
|
||||
// Bestimmung des grundsätzlichen Erscheinungsbildes der einzelnen Elemente,
|
||||
// einschließlich des Hauptbereiches
|
||||
@import "lesslib/print/basics.less";
|
||||
// Darstellung unabhängig der Darstellungsgrößen
|
||||
@import "lesslib/print/layout.less";
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "svelte-demo",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"prepare": "svelte-kit sync || echo ''",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch"
|
||||
},
|
||||
"dependencies": {
|
||||
"ganalytics": "^3.1.2",
|
||||
"navaid": "^1.0.2",
|
||||
"open-iconic": "^1.1.1",
|
||||
"papaparse": "^5.5.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/adapter-auto": "^6.0.0",
|
||||
"@sveltejs/kit": "^2.16.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^5.0.0",
|
||||
"@sveltejs/adapter-node": "^5.2.12",
|
||||
"svelte": "^5.0.0",
|
||||
"svelte-check": "^4.0.0",
|
||||
"typescript": "^5.0.0",
|
||||
"vite": "^6.2.6",
|
||||
"dotenv": "^8.2.0",
|
||||
"wx-svelte-grid": "^2.1.5",
|
||||
"@iconify-icons/oi": "^1.1.0",
|
||||
"@iconify/svelte": "^5.0.0"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
"svelte": "./puplic/index.js"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<!doctype html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<base href="/"/>
|
||||
<meta charset="utf8">
|
||||
<meta name="viewport" content="width=device-width">
|
||||
|
||||
<title>Svelte app</title>
|
||||
|
||||
<link rel='icon' type='image/png' href='%sveltekit.assets%/favicon.png'>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<meta name="color-scheme" content="dark light"/>
|
||||
%sveltekit.head%
|
||||
<link rel="stylesheet" href="%sveltekit.assets%/global.css" media="screen">
|
||||
<link rel="stylesheet" href="%sveltekit.assets%/print.css" media="print">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div style="display: contents">
|
||||
%sveltekit.body%
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,18 @@
|
||||
export class PeriodRecord {
|
||||
/**
|
||||
* @param {{ workingDays: Number; totalHours: any; overtime: string; period: string; homeOfficeDays: Number }} record
|
||||
*/
|
||||
constructor(record) {
|
||||
this.workingDays = Number(record.workingDays)
|
||||
this.homeOfficeDays = Number(record.homeOfficeDays)
|
||||
this.totalHours = String(record.totalHours)
|
||||
this.period = String(record.period)
|
||||
this.overtime = String('##:##:##')
|
||||
this.isMinus = false
|
||||
const interval = record.overtime.match(/^(-?)(\d\d:\d\d:\d\d)$/)
|
||||
if (interval) {
|
||||
this.overtime = String(interval[2])
|
||||
this.isMinus = String(interval[1]) === '-'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
<script module>
|
||||
|
||||
const MyFetch = {
|
||||
/**
|
||||
* @param {String} rest_url
|
||||
* @param {String} request_body
|
||||
*/
|
||||
'post': async (rest_url, request_body) => {
|
||||
return MyFetch._fetch(rest_url, MyFetch._buildRequestOptions(request_body, false))
|
||||
},
|
||||
/**
|
||||
* @param {String} rest_url
|
||||
* @param {String} http_body
|
||||
*/
|
||||
'put': async (rest_url, http_body) => {
|
||||
return MyFetch._fetch(rest_url, MyFetch._buildRequestOptions(http_body, true))
|
||||
},
|
||||
/**
|
||||
* @param {String} rest_url
|
||||
* @param {Object|null} query_parameters
|
||||
*/
|
||||
'get': async (rest_url, query_parameters = null) => {
|
||||
if (query_parameters !== null) {
|
||||
let filter = []
|
||||
for (let parameter in query_parameters) {
|
||||
filter.push(parameter + '=' + query_parameters[parameter])
|
||||
}
|
||||
if (filter.length > 0) {
|
||||
rest_url += '?' + filter.join('&')
|
||||
}
|
||||
}
|
||||
return MyFetch._fetch(rest_url, {})
|
||||
},
|
||||
/**
|
||||
* @param {String} rest_url
|
||||
* @param {Object} request_options
|
||||
*/
|
||||
'_fetch': async (rest_url, request_options) => {
|
||||
// noinspection JSUnresolvedVariable
|
||||
let response = await MyFetch._pureFetch(rest_url, request_options).catch(err => {
|
||||
console.error(err)
|
||||
return null
|
||||
})
|
||||
if (response === null || !response.ok) {
|
||||
throw new Error('Fail!')
|
||||
}
|
||||
return response.json()
|
||||
},
|
||||
/**
|
||||
* @param {String} request_body
|
||||
* @param {Boolean} needPut
|
||||
*/
|
||||
'_buildRequestOptions': (request_body, needPut) => {
|
||||
return {
|
||||
method: (needPut === true) ? 'PUT' : 'POST',
|
||||
mode: 'cors',
|
||||
cache: 'no-cache',
|
||||
credentials: 'omit',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: request_body,
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @param {String} rest_url
|
||||
* @param {Object} request_options
|
||||
*/
|
||||
'_pureFetch': async (rest_url, request_options = {}) => {
|
||||
// noinspection JSUnresolvedVariable
|
||||
return await fetch(rest_url, request_options)
|
||||
}
|
||||
}
|
||||
|
||||
export const WorkingHoursRepository = {
|
||||
/**
|
||||
* @param {String} api_url
|
||||
* @param {String} startDate
|
||||
* @param {String} endDate
|
||||
*/
|
||||
'browse': (api_url, startDate = '', endDate = '') => {
|
||||
let filter = {}
|
||||
if (startDate !== '') {
|
||||
filter['start-date'] = startDate
|
||||
}
|
||||
if (endDate !== '') {
|
||||
filter['end-date'] = endDate
|
||||
}
|
||||
return MyFetch.get(api_url + '/working-hours', filter)
|
||||
},
|
||||
/**
|
||||
* @param {String} api_url
|
||||
* @param {String} date
|
||||
*/
|
||||
'read': (api_url, date) => {
|
||||
return MyFetch.get(api_url + '/working-hours/' + date)
|
||||
},
|
||||
/**
|
||||
* @param {String} api_url
|
||||
* @param {String} date
|
||||
*/
|
||||
'readOrNew': async (api_url, date) => {
|
||||
let response = await MyFetch._pureFetch(api_url + '/working-hours/' + date)
|
||||
if (response.status === 404) {
|
||||
return {workingTime: '00:00:00', workingDay: date}
|
||||
} else if (!response.ok) {
|
||||
throw new Error('Fail!')
|
||||
}
|
||||
return response.json()
|
||||
},
|
||||
/**
|
||||
* @param {String} api_url
|
||||
* @param {Object} record
|
||||
*/
|
||||
'add': async (api_url, record) => {
|
||||
return MyFetch.post(api_url + '/working-hours', JSON.stringify(record))
|
||||
},
|
||||
/**
|
||||
* @param {String} api_url
|
||||
* @param {String} date
|
||||
* @param {Object} record
|
||||
*/
|
||||
'update': async (api_url, date, record) => {
|
||||
return MyFetch.put(api_url + '/working-hours/' + date, JSON.stringify(record))
|
||||
},
|
||||
/**
|
||||
* @param {String} api_url
|
||||
* @param {String} date
|
||||
* @param {Object} record
|
||||
*/
|
||||
'addOrUpdate': async (api_url, date, record) => {
|
||||
let request_options = MyFetch._buildRequestOptions(JSON.stringify(record), false)
|
||||
let response = await MyFetch._pureFetch(api_url + '/working-hours', request_options)
|
||||
if (response.status === 409) {
|
||||
return WorkingHoursRepository.update(api_url, date, record)
|
||||
} else if (!response.ok) {
|
||||
throw new Error('Fail!')
|
||||
}
|
||||
return response.json()
|
||||
}
|
||||
}
|
||||
|
||||
export const WorkingHoursViewsRepository = {
|
||||
/**
|
||||
* @param {String} api_url
|
||||
*/
|
||||
'weekly': async api_url => {
|
||||
return MyFetch.get(api_url + '/views/working-hours/weekly', {})
|
||||
},
|
||||
/**
|
||||
* @param {String} api_url
|
||||
*/
|
||||
'monthly': async api_url => {
|
||||
return MyFetch.get(api_url + '/views/working-hours/monthly', {})
|
||||
},
|
||||
/**
|
||||
* @param {String} api_url
|
||||
*/
|
||||
'yearly': async api_url => {
|
||||
return MyFetch.get(api_url + '/views/working-hours/yearly', {})
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,72 @@
|
||||
<script lang="ts">
|
||||
let {promise_data, actual_records= $bindable(), page_count = 10} = $props();
|
||||
let records: Array<any[]> = $state([])
|
||||
let page_number = $state(0)
|
||||
let max_page_number = $state(0)
|
||||
const setPage = (new_page: number) => {
|
||||
page_number = new_page
|
||||
actual_records = records[page_number - 1]
|
||||
}
|
||||
const init = async () => {
|
||||
let page_records: any[] = []
|
||||
console.log(promise_data, actual_records)
|
||||
for (let index in promise_data) {
|
||||
page_records.push(promise_data[index])
|
||||
if (page_records.length >= page_count) {
|
||||
records.push(page_records.values().toArray())
|
||||
page_records = []
|
||||
}
|
||||
}
|
||||
if (page_records.length > 0) {
|
||||
records.push(page_records.values().toArray())
|
||||
}
|
||||
max_page_number = records.length
|
||||
setPage(1)
|
||||
}
|
||||
init()
|
||||
</script>
|
||||
<style>
|
||||
div.table-pagination {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
div.table-pagination > button {
|
||||
display: inline-block;
|
||||
width: 2rem;
|
||||
}
|
||||
|
||||
div.table-pagination > button[disabled] {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
div.table-pagination > span {
|
||||
display: inline-block;
|
||||
width: 5rem;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="table-pagination">
|
||||
<button
|
||||
type="button"
|
||||
disabled={page_number === 1}
|
||||
onclick={() => setPage(1)}>≪
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
disabled={page_number === 1}
|
||||
onclick={() => setPage(page_number - 1)}><
|
||||
</button>
|
||||
<span>{page_number} von {max_page_number}</span>
|
||||
<button
|
||||
type="button"
|
||||
disabled={page_number === max_page_number}
|
||||
onclick={() => setPage(page_number + 1)}>>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
disabled={page_number === max_page_number}
|
||||
onclick={() => setPage(max_page_number)}>≫
|
||||
</button>
|
||||
</div>
|
||||
@@ -0,0 +1,72 @@
|
||||
<script>
|
||||
import {PeriodRecord} from './Models.svelte.js'
|
||||
import TablePagination from "$lib/TablePagination.svelte";
|
||||
|
||||
let { promise, title } = $props();
|
||||
/** @type PeriodRecord[] **/
|
||||
let records = $state([])
|
||||
/** @type PeriodRecord[] **/
|
||||
let actual_records = $state([])
|
||||
let isLoading = $state(true)
|
||||
const init = async () => await promise.then(
|
||||
/**
|
||||
* @param {Array<{ workingDays: Number; totalHours: any; overtime: string; period: string; homeOfficeDays: Number; }>} data
|
||||
*/
|
||||
data => {
|
||||
records = data.map(row => new PeriodRecord(row))
|
||||
isLoading = false
|
||||
}
|
||||
)
|
||||
init()
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>{title}</title>
|
||||
</svelte:head>
|
||||
|
||||
<section id="list-records">
|
||||
<header>
|
||||
<h1>{title}</h1>
|
||||
</header>
|
||||
<article>
|
||||
{#if isLoading === true}
|
||||
<p>Loading...</p>
|
||||
{:else}
|
||||
<table>
|
||||
<colgroup>
|
||||
<col/>
|
||||
<col/>
|
||||
<col/>
|
||||
<col/>
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Zeitraum</th>
|
||||
<th>Arbeitstage</th>
|
||||
<th>davon HomeOffice</th>
|
||||
<th>Arbeitsstunden</th>
|
||||
<th>Über- / Unterstunden</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each actual_records as record}
|
||||
<tr>
|
||||
<td>{record.period}</td>
|
||||
<td>{record.workingDays}</td>
|
||||
<td>{record.homeOfficeDays}</td>
|
||||
<td>{record.totalHours}</td>
|
||||
<td class="{record.isMinus ? 'absence-time' : ''}">{record.overtime}</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td colspan="4">
|
||||
<TablePagination promise_data={records} bind:actual_records={actual_records}/>
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
{/if}
|
||||
</article>
|
||||
</section>
|
||||
@@ -0,0 +1,24 @@
|
||||
<script>
|
||||
import { page } from '$app/state';
|
||||
let { children } = $props();
|
||||
/** @param {String} str **/
|
||||
const isActive = str => page.url.pathname === str;
|
||||
/** @param {String} str **/
|
||||
const isSelected = str => isActive(str) ? 'selected' : '';
|
||||
</script>
|
||||
|
||||
<nav>
|
||||
<ul>
|
||||
<li class="{isSelected('/')}"><a href="/" aria-current={isActive('/')}>Home</a></li>
|
||||
<li class="{isSelected('/views/weekly')}"><a href="/views/weekly" aria-current={isActive('/views/weekly')}>Wochenansicht</a></li>
|
||||
<li class="{isSelected('/views/monthly')}"><a href="/views/monthly" aria-current={isActive('/views/monthly')}>Monatsansicht</a></li>
|
||||
<li class="{isSelected('/views/yearly')}"><a href="/views/yearly" aria-current={isActive('/views/yearly')}>Jahresansicht</a></li>
|
||||
<li class="{isSelected('/working-hours')}"><a href="/working-hours" aria-current={isActive('/working-hours')}>Einträge</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<main>
|
||||
{@render children()}
|
||||
</main>
|
||||
|
||||
<footer><p>© Torsten Lücke 2021</p></footer>
|
||||
@@ -0,0 +1,9 @@
|
||||
import {env} from '$env/dynamic/private';
|
||||
import {WorkingHoursViewsRepository} from "../lib/Repositories.svelte";
|
||||
|
||||
export function load() {
|
||||
return {
|
||||
title: 'Wochenansicht',
|
||||
promise: WorkingHoursViewsRepository.weekly(env.API_URL)
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<script>
|
||||
import Views from "$lib/Views.svelte";
|
||||
let { data } = $props();
|
||||
</script>
|
||||
|
||||
<Views params={data}/>
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import {env} from '$env/dynamic/private';
|
||||
import {WorkingHoursViewsRepository} from "$lib/Repositories.svelte";
|
||||
|
||||
export function load() {
|
||||
return {
|
||||
title: 'Monatsansicht',
|
||||
promise: WorkingHoursViewsRepository.monthly(env.API_URL)
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<script>
|
||||
import Views from "$lib/Views.svelte";
|
||||
let { data } = $props();
|
||||
</script>
|
||||
|
||||
<Views {...data}/>
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import {env} from '$env/dynamic/private';
|
||||
import {WorkingHoursViewsRepository} from "$lib/Repositories.svelte";
|
||||
|
||||
export function load() {
|
||||
return {
|
||||
title: 'Wochenansicht',
|
||||
promise: WorkingHoursViewsRepository.weekly(env.API_URL)
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<script>
|
||||
import Views from "$lib/Views.svelte";
|
||||
let { data } = $props();
|
||||
</script>
|
||||
|
||||
<Views {...data}/>
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import {env} from '$env/dynamic/private';
|
||||
import {WorkingHoursViewsRepository} from "$lib/Repositories.svelte";
|
||||
|
||||
export function load() {
|
||||
return {
|
||||
title: 'Jahresansicht',
|
||||
promise: WorkingHoursViewsRepository.yearly(env.API_URL)
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<script>
|
||||
import Views from "$lib/Views.svelte";
|
||||
let { data } = $props();
|
||||
</script>
|
||||
|
||||
<Views {...data}/>
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
import {env} from '$env/dynamic/private';
|
||||
import {WorkingHoursRepository} from "$lib/Repositories.svelte";
|
||||
|
||||
export function load() {
|
||||
return {
|
||||
promise: WorkingHoursRepository.browse(env.API_URL)
|
||||
};
|
||||
}
|
||||
|
||||
/** @param {Date} actualDayObject **/
|
||||
const getMonday = actualDayObject => {
|
||||
if (actualDayObject.getDate() === 0) {
|
||||
actualDayObject.setDate(actualDayObject.getDate() - 7)
|
||||
}
|
||||
if (actualDayObject.getDate() !== 1) {
|
||||
actualDayObject.setDate(actualDayObject.getDate() - (actualDayObject.getDay() - 1))
|
||||
}
|
||||
return actualDayObject
|
||||
}
|
||||
|
||||
/** @type {String[]} **/
|
||||
const weekday_list = ['Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag', 'Sontag']
|
||||
|
||||
/** @param {Date} actualDayObject **/
|
||||
const getDateString = actualDayObject => {
|
||||
return actualDayObject.getFullYear() + "-"
|
||||
+ (actualDayObject.getMonth() + 1).toString().padStart(2, '0') + "-"
|
||||
+ actualDayObject.getDate().toString().padStart(2, '0')
|
||||
}
|
||||
|
||||
export const actions = {
|
||||
update: async ({ request }) => {
|
||||
const data = await request.formData();
|
||||
const body_data = {
|
||||
workingDay: data.get('workingDay'),
|
||||
workingTime: data.get('workingTime'),
|
||||
isHomeOffice: data.get('isHomeOffice'),
|
||||
}
|
||||
await WorkingHoursRepository.update(env.API_URL, data.get('workingDay'), body_data);
|
||||
},
|
||||
create: async ({ request }) => {
|
||||
const form_data = await request.formData();
|
||||
const body_data = {
|
||||
workingDay: form_data.get('workingDay'),
|
||||
workingTime: form_data.get('workingTime'),
|
||||
isHomeOffice: form_data.get('isHomeOffice'),
|
||||
}
|
||||
await WorkingHoursRepository.add(env.API_URL, body_data);
|
||||
},
|
||||
"create-by-week": async ({ request }) => {
|
||||
const form_data = await request.formData();
|
||||
// @ts-ignore
|
||||
let actualDayObject = getMonday(new Date(form_data.get('week')))
|
||||
/** @type {Object<String,String>} **/
|
||||
let day_list = {}
|
||||
weekday_list.forEach(day => {
|
||||
day_list[day] = getDateString(actualDayObject)
|
||||
actualDayObject.setDate(actualDayObject.getDate() + 1)
|
||||
})
|
||||
for (const pair of form_data.entries()) {
|
||||
let key = /(?<day>\w+)\[time]/.exec(pair[0])
|
||||
if (key === null) {
|
||||
continue
|
||||
}
|
||||
let day = key?.groups?.day ?? ''
|
||||
const working_day = day_list[day] ?? null
|
||||
if (working_day === null || pair[1] === '00:00:00' || pair[1] === '00:00') {
|
||||
continue
|
||||
}
|
||||
const body_data = {
|
||||
workingDay: working_day,
|
||||
workingTime: pair[1],
|
||||
isHomeOffice: form_data.has(day + '[isHomeOffice]')
|
||||
}
|
||||
await WorkingHoursRepository.addOrUpdate(env.API_URL, working_day, body_data);
|
||||
}
|
||||
},
|
||||
'import-csv': async ({ request }) => {
|
||||
const form_data = await request.formData();
|
||||
for (const pair of form_data.entries()) {
|
||||
let key = /^(?<day>\d{4}-\d{2}-\d{2})\[time]$/.exec(pair[0])
|
||||
if (key === null) {
|
||||
continue
|
||||
}
|
||||
let working_day = key?.groups?.day ?? ''
|
||||
const body_data = {
|
||||
workingDay: working_day,
|
||||
workingTime: pair[1],
|
||||
isHomeOffice: form_data.has(working_day + '[isHomeOffice]')
|
||||
}
|
||||
await WorkingHoursRepository.addOrUpdate(env.API_URL, working_day, body_data);
|
||||
}
|
||||
}
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user