Merge pull request 'feature-ui/eintrag-per-woche' (#23) from feature-ui/eintrag-per-woche into master

Reviewed-on: #23
This commit was merged in pull request #23.
This commit is contained in:
2022-02-28 16:04:31 +01:00
18 changed files with 677 additions and 67 deletions
+2 -1
View File
@@ -21,7 +21,8 @@
"slim/psr7": "^1.3", "slim/psr7": "^1.3",
"php-di/slim-bridge": "^3.1.0", "php-di/slim-bridge": "^3.1.0",
"jetbrains/phpstorm-attributes": "^1.0.0", "jetbrains/phpstorm-attributes": "^1.0.0",
"myclabs/php-enum": "^1.8.0" "myclabs/php-enum": "^1.8.0",
"ext-pdo": "*"
}, },
"require-dev": { "require-dev": {
"phpstan/phpstan": "^0.12.80", "phpstan/phpstan": "^0.12.80",
+29
View File
@@ -18,6 +18,15 @@
"tags": [ "tags": [
"Eintragungen Arbeitszeiten" "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": { "responses": {
"200": { "200": {
"$ref": "#/components/responses/ListEntities" "$ref": "#/components/responses/ListEntities"
@@ -259,6 +268,26 @@
"type": "string", "type": "string",
"format": "date" "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": { "responses": {
@@ -12,6 +12,7 @@ use Slim\Exception\HttpNotFoundException;
use Slim\Psr7\Request; use Slim\Psr7\Request;
use Slim\Psr7\Response; use Slim\Psr7\Response;
use TorstenHettstedt\TimekeepingApi\Models\WorkingHours; use TorstenHettstedt\TimekeepingApi\Models\WorkingHours;
use TorstenHettstedt\TimekeepingApi\Repositories\RepositoryBadWhereDataException;
use TorstenHettstedt\TimekeepingApi\Repositories\RepositoryRecordAlreadyExistException; use TorstenHettstedt\TimekeepingApi\Repositories\RepositoryRecordAlreadyExistException;
use TorstenHettstedt\TimekeepingApi\Repositories\RepositoryRecordNotFoundException; use TorstenHettstedt\TimekeepingApi\Repositories\RepositoryRecordNotFoundException;
use TorstenHettstedt\TimekeepingApi\Repositories\WorkingHoursRepository; use TorstenHettstedt\TimekeepingApi\Repositories\WorkingHoursRepository;
@@ -50,13 +51,20 @@ class WorkingHoursController extends AbstractController
* @return Response * @return Response
* *
* @throws HttpInternalServerErrorException * @throws HttpInternalServerErrorException
* @throws HttpBadRequestException
* @noinspection PhpUnusedParameterInspection * @noinspection PhpUnusedParameterInspection
*/ */
public function browse(Request $request, Response $response, array $args): Response public function browse(Request $request, Response $response, array $args): Response
{ {
$repository = new WorkingHoursRepository($this->databases); $repository = new WorkingHoursRepository($this->databases);
$queryParams = $request->getQueryParams();
try { try {
return $this->printResponse($response, $repository->findAll(), StatusCodeInterface::STATUS_OK); 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) { } catch (Exception $exception) {
throw new HttpInternalServerErrorException($request, 'Der Wert für das Datum oder die Zeit ist falsch in der Datenbank', $exception); throw new HttpInternalServerErrorException($request, 'Der Wert für das Datum oder die Zeit ist falsch in der Datenbank', $exception);
} }
@@ -0,0 +1,10 @@
<?php
namespace TorstenHettstedt\TimekeepingApi\Repositories;
class RepositoryBadWhereDataException extends RepositoryException
{
}
@@ -10,6 +10,7 @@ use Exception;
use InvalidArgumentException; use InvalidArgumentException;
use PDO; use PDO;
use PDOException; use PDOException;
use PDOStatement;
use TorstenHettstedt\TimekeepingApi\Models\ModelInterface; use TorstenHettstedt\TimekeepingApi\Models\ModelInterface;
use TorstenHettstedt\TimekeepingApi\Models\WorkingHours; use TorstenHettstedt\TimekeepingApi\Models\WorkingHours;
@@ -42,10 +43,33 @@ class WorkingHoursRepository implements RepositoryReaderInterface, RepositoryWri
* @throws PDOException * @throws PDOException
*/ */
public function findAll(): array 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 = []; $models = [];
$stmt = $this->database->prepare('select "Datum" as "workingDay", "Arbeitszeit" as "workingTime" from public."Arbeitszeiten" order by "Datum"'); $stmt = $this->buildFindFilteredStatement($start, $end);
$stmt->execute(); try {
$stmt->execute();
} catch (PDOException $exception) {
if ($exception->getCode() === '22007' || $exception->getCode() === '22008') {
throw new RepositoryBadWhereDataException($exception);
}
throw $exception;
}
while ($row = $stmt->fetch()) { while ($row = $stmt->fetch()) {
$model = new WorkingHours(); $model = new WorkingHours();
$model $model
@@ -56,6 +80,35 @@ class WorkingHoursRepository implements RepositoryReaderInterface, RepositoryWri
return $models; 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 = 'select "Datum" as "workingDay", "Arbeitszeit" as "workingTime" from public."Arbeitszeiten" ';
$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 * Gibt den Eintrag zurück, der durch die ID
* *
@@ -3,6 +3,7 @@
namespace TorstenHettstedt\TimekeepingApi\Tests\Api\WorkingHours; namespace TorstenHettstedt\TimekeepingApi\Tests\Api\WorkingHours;
use ApiTester; use ApiTester;
use Codeception\Example;
use Codeception\Util\HttpCode; use Codeception\Util\HttpCode;
use Helper\Api; use Helper\Api;
@@ -22,14 +23,67 @@ class BrowseWorkingHoursCest
/** /**
* @param ApiTester $I * @param ApiTester $I
*
* @dataProvider validWorkingHoursFilterProvider
*/ */
public function browseWorkingHours(ApiTester $I): void public function browseEmptyWorkingHours(ApiTester $I): void
{ {
$I->haveHttpHeader('Origin', Api::TEST_ORIGIN); $I->haveHttpHeader('Origin', Api::TEST_ORIGIN);
$I->sendGet('/working-hours'); $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->seeResponseCodeIs(HttpCode::OK);
$I->canSeeHttpHeader('Access-Control-Allow-Origin', Api::TEST_ORIGIN); $I->canSeeHttpHeader('Access-Control-Allow-Origin', Api::TEST_ORIGIN);
$I->seeResponseIsJson(); $I->seeResponseIsJson();
$I->seeResponseMatchesJsonType(Api::WORKING_HOURS_JSON_FORMAT); $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);
}
} }
@@ -2,6 +2,7 @@
namespace TorstenHettstedt\TimekeepingApi\Tests\Unit\Controller; namespace TorstenHettstedt\TimekeepingApi\Tests\Unit\Controller;
use Codeception\Example;
use Codeception\Test\Unit; use Codeception\Test\Unit;
use Exception; use Exception;
use PDO; use PDO;
@@ -25,8 +26,8 @@ class WorkingHoursControllerTest extends Unit
const NEW_INTERVAL = '07:59:00'; const NEW_INTERVAL = '07:59:00';
protected ContainerInterface $container; protected ContainerInterface $container;
protected Request $request; protected Request $request;
protected Response $response; protected Response $response;
/** /**
* @throws Exception * @throws Exception
@@ -37,16 +38,16 @@ class WorkingHoursControllerTest extends Unit
/** @noinspection SpellCheckingInspection */ /** @noinspection SpellCheckingInspection */
$this->container = $this->makeEmpty(ContainerInterface::class, [ $this->container = $this->makeEmpty(ContainerInterface::class, [
'has' => true, 'has' => true,
'get' => new PDO('pgsql:host=psql.torsten-hettstedt.net;port=5432;dbname=testdb;user=bruce;password=mypass') 'get' => new PDO('pgsql:host=psql.torsten-hettstedt.net;port=5432;dbname=testdb;user=bruce;password=mypass'),
]); ]);
$this->request = $this->makeEmpty(Request::class, [ $this->request = $this->makeEmpty(Request::class, [
'getParsedBody' => [ 'getParsedBody' => [
'workingDay' => self::EXISTING_DATE, 'workingDay' => self::EXISTING_DATE,
'workingTime' => self::EXISTING_INTERVAL, 'workingTime' => self::EXISTING_INTERVAL,
] ],
]); ]);
$this->response = $this->makeEmpty(Response::class, [ $this->response = $this->makeEmpty(Response::class, [
'getBody' => $this->makeEmpty(StreamInterface::class, [ 'getBody' => $this->makeEmpty(StreamInterface::class, [
'write' => function (mixed $data) { 'write' => function (mixed $data) {
$this->assertIsString($data); $this->assertIsString($data);
$this->assertJson($data); $this->assertJson($data);
@@ -65,23 +66,202 @@ class WorkingHoursControllerTest extends Unit
public function testConstructWithNonDatabase(): void public function testConstructWithNonDatabase(): void
{ {
$this->container = $this->makeEmpty(ContainerInterface::class, [ $this->container = $this->makeEmpty(ContainerInterface::class, [
'has' => false 'has' => false,
]); ]);
$this->expectException(NotDatabasesException::class); $this->expectException(NotDatabasesException::class);
new WorkingHoursController($this->container); 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
*
* @dataProvider valideFilterDataProvider
*
* @throws HttpInternalServerErrorException
* @throws NotDatabasesException * @throws NotDatabasesException
* @throws Exception * @throws Exception
*/ */
public function testBrowse(): void public function testBrowseWithValideParameter(array $queryParams): void
{ {
$this->request = $this->makeEmpty(Request::class, [
'getQueryParams' => $queryParams,
]);
$controller = new WorkingHoursController($this->container); $controller = new WorkingHoursController($this->container);
$response = $controller->browse($this->request, $this->response, []); $response = $controller->browse($this->request, $this->response, []);
$this->assertInstanceOf(Response::class, $response); $this->assertInstanceOf(Response::class, $response);
} }
/**
* @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
*
* @dataProvider invalideFilterDataProvider
*
* @throws HttpInternalServerErrorException
* @throws NotDatabasesException
* @throws Exception
*/
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 NotDatabasesException * @throws NotDatabasesException
* @throws HttpBadRequestException * @throws HttpBadRequestException
@@ -91,7 +271,7 @@ class WorkingHoursControllerTest extends Unit
{ {
$controller = new WorkingHoursController($this->container); $controller = new WorkingHoursController($this->container);
$response = $controller->update($this->request, $this->response, [ $response = $controller->update($this->request, $this->response, [
'id' => self::EXISTING_DATE 'id' => self::EXISTING_DATE,
]); ]);
$this->assertInstanceOf(Response::class, $response); $this->assertInstanceOf(Response::class, $response);
} }
@@ -106,23 +286,23 @@ class WorkingHoursControllerTest extends Unit
$controller = new WorkingHoursController($this->container); $controller = new WorkingHoursController($this->container);
$this->expectException(HttpNotFoundException::class); $this->expectException(HttpNotFoundException::class);
$controller->update($this->request, $this->response, [ $controller->update($this->request, $this->response, [
'id' => self::NEW_DATE 'id' => self::NEW_DATE,
]); ]);
} }
/** /**
* @return string[][] * @return string[][]
*/ */
public function invalidDataProvider(): array public function invalidCreatDataProvider(): array
{ {
return [ return [
[ [
self::NEW_DATE, self::NEW_DATE,
'07:59' '07:59',
], ],
[ [
'2020-13-33', '2020-13-33',
self::NEW_INTERVAL self::NEW_INTERVAL,
], ],
]; ];
} }
@@ -131,7 +311,7 @@ class WorkingHoursControllerTest extends Unit
* @param string $date * @param string $date
* @param string $time * @param string $time
* *
* @dataProvider invalidDataProvider * @dataProvider invalidCreatDataProvider
* *
* @throws HttpBadRequestException * @throws HttpBadRequestException
* @throws HttpConflictRequestException * @throws HttpConflictRequestException
@@ -145,7 +325,7 @@ class WorkingHoursControllerTest extends Unit
'getParsedBody' => [ 'getParsedBody' => [
'workingDay' => $date, 'workingDay' => $date,
'workingTime' => $time, 'workingTime' => $time,
] ],
]); ]);
$controller = new WorkingHoursController($this->container); $controller = new WorkingHoursController($this->container);
$this->expectException(HttpBadRequestException::class); $this->expectException(HttpBadRequestException::class);
@@ -165,7 +345,7 @@ class WorkingHoursControllerTest extends Unit
'getParsedBody' => [ 'getParsedBody' => [
'workingDay' => self::NEW_DATE, 'workingDay' => self::NEW_DATE,
'workingTime' => self::NEW_INTERVAL, 'workingTime' => self::NEW_INTERVAL,
] ],
]); ]);
$controller = new WorkingHoursController($this->container); $controller = new WorkingHoursController($this->container);
$response = $controller->creat($this->request, $this->response, []); $response = $controller->creat($this->request, $this->response, []);
@@ -185,7 +365,7 @@ class WorkingHoursControllerTest extends Unit
'getParsedBody' => [ 'getParsedBody' => [
'workingDay' => self::EXISTING_DATE, 'workingDay' => self::EXISTING_DATE,
'workingTime' => self::EXISTING_INTERVAL, 'workingTime' => self::EXISTING_INTERVAL,
] ],
]); ]);
$controller = new WorkingHoursController($this->container); $controller = new WorkingHoursController($this->container);
$this->expectException(HttpConflictRequestException::class); $this->expectException(HttpConflictRequestException::class);
@@ -201,7 +381,7 @@ class WorkingHoursControllerTest extends Unit
{ {
$controller = new WorkingHoursController($this->container); $controller = new WorkingHoursController($this->container);
$response = $controller->read($this->request, $this->response, [ $response = $controller->read($this->request, $this->response, [
'id' => self::EXISTING_DATE 'id' => self::EXISTING_DATE,
]); ]);
$this->assertInstanceOf(Response::class, $response); $this->assertInstanceOf(Response::class, $response);
} }
@@ -216,7 +396,7 @@ class WorkingHoursControllerTest extends Unit
$controller = new WorkingHoursController($this->container); $controller = new WorkingHoursController($this->container);
$this->expectException(HttpNotFoundException::class); $this->expectException(HttpNotFoundException::class);
$controller->read($this->request, $this->response, [ $controller->read($this->request, $this->response, [
'id' => self::NEW_DATE 'id' => self::NEW_DATE,
]); ]);
} }
} }
@@ -2,6 +2,7 @@
namespace TorstenHettstedt\TimekeepingApi\Tests\Unit\Repositories; namespace TorstenHettstedt\TimekeepingApi\Tests\Unit\Repositories;
use Codeception\Example;
use Codeception\Test\Unit; use Codeception\Test\Unit;
use DateInterval; use DateInterval;
use DateTime; use DateTime;
@@ -10,6 +11,7 @@ use InvalidArgumentException;
use PDO; use PDO;
use TorstenHettstedt\TimekeepingApi\Models\ModelInterface; use TorstenHettstedt\TimekeepingApi\Models\ModelInterface;
use TorstenHettstedt\TimekeepingApi\Models\WorkingHours; use TorstenHettstedt\TimekeepingApi\Models\WorkingHours;
use TorstenHettstedt\TimekeepingApi\Repositories\RepositoryBadWhereDataException;
use TorstenHettstedt\TimekeepingApi\Repositories\RepositoryRecordAlreadyExistException; use TorstenHettstedt\TimekeepingApi\Repositories\RepositoryRecordAlreadyExistException;
use TorstenHettstedt\TimekeepingApi\Repositories\RepositoryRecordNotFoundException; use TorstenHettstedt\TimekeepingApi\Repositories\RepositoryRecordNotFoundException;
use TorstenHettstedt\TimekeepingApi\Repositories\WorkingHoursRepository; use TorstenHettstedt\TimekeepingApi\Repositories\WorkingHoursRepository;
@@ -22,7 +24,7 @@ class WorkingHoursRepositoryTest extends Unit
const NEW_INTERVAL = 'PT7H59M'; const NEW_INTERVAL = 'PT7H59M';
const RECORDS_COUNT = 61; const RECORDS_COUNT = 61;
protected PDO $pdoObject; protected PDO $pdoObject;
protected function _before(): void protected function _before(): void
{ {
@@ -34,7 +36,7 @@ class WorkingHoursRepositoryTest extends Unit
/** /**
* @throws Exception * @throws Exception
*/ */
public function testFindAll(): void public function testFindAllWithBlankParameter(): void
{ {
$repository = new WorkingHoursRepository($this->pdoObject); $repository = new WorkingHoursRepository($this->pdoObject);
$models = $repository->findAll(); $models = $repository->findAll();
@@ -43,6 +45,81 @@ class WorkingHoursRepositoryTest extends Unit
$this->assertCount(self::RECORDS_COUNT, $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
*
* @dataProvider valideFilterParameter
*
* @throws Exception
*/
public function testFindAllWithValideParameter(?string $start, ?string $ende, int $count): void
{
$repository = new WorkingHoursRepository($this->pdoObject);
$models = $repository->findFiltered($start, $ende);
$this->assertIsArray($models);
$this->assertContainsOnly(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
*
* @dataProvider invalideFilterParameter
*
* @throws Exception
*/
public function testFindAllWithInvalideParameter(?string $start, ?string $ende): void
{
$repository = new WorkingHoursRepository($this->pdoObject);
$this->expectException(RepositoryBadWhereDataException::class);
$repository->findFiltered($start, $ende);
}
/** /**
* @throws RepositoryRecordNotFoundException * @throws RepositoryRecordNotFoundException
*/ */
+4 -3
View File
@@ -3,9 +3,10 @@
salt salt
{ {
Datum |"Datum <&calendar>" {/ <b>Täglich | Wöchentlich }
Uhrzeit | "Uhrzeit <&clock>" Datum |"0000-00-00 <&calendar>"
[Übernehmen <&plus>] Uhrzeit | "00:00:00 <&clock>"
[Übernehmen und weiter <&plus>]
[Abbrechen <&action-undo>] [Abbrechen <&action-undo>]
} }
@enduml @enduml
@@ -3,7 +3,9 @@
salt salt
{ {
Woche vom <&calendar> bis zum <&calendar> {/ Täglich | <b>Wöchentlich }
Woche vom
"0000-00-00 <&calendar>"
[<&chevron-left> Woche zurück] | [Woche vor <&chevron-right>] [<&chevron-left> Woche zurück] | [Woche vor <&chevron-right>]
{! {!
Montag | "00:00:00 <&clock>" Montag | "00:00:00 <&clock>"
@@ -14,7 +16,7 @@ salt
<color:red>Samstag | "<color:red>00:00:00 <&clock>" <color:red>Samstag | "<color:red>00:00:00 <&clock>"
<color:red>Sontag | "<color:red>00:00:00 <&clock>" <color:red>Sontag | "<color:red>00:00:00 <&clock>"
} }
[Übernehmen <&plus>] [Übernehmen und weiter <&plus>]
[Abbrechen <&action-undo>] [Abbrechen <&action-undo>]
} }
@enduml @enduml
+15
View File
@@ -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
+23
View File
@@ -91,6 +91,29 @@ main {
form { form {
@formLabelWidth: 10rem; @formLabelWidth: 10rem;
@formItemWidth: 40rem; @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 { fieldset {
width: @formItemWidth + @formLabelWidth + 2rem; width: @formItemWidth + @formLabelWidth + 2rem;
margin: 1rem auto; margin: 1rem auto;
+14 -7
View File
@@ -20,7 +20,10 @@
}, },
'_fetch': async (rest_url, request_options) => { '_fetch': async (rest_url, request_options) => {
// noinspection JSUnresolvedVariable // noinspection JSUnresolvedVariable
let response = await MyFetch._pureFetch(rest_url, request_options) let response = await MyFetch._pureFetch(rest_url, request_options).catch(err => {
console.error(err)
return null
})
if (!response.ok) { if (!response.ok) {
throw new Error('Fail!') throw new Error('Fail!')
} }
@@ -40,16 +43,20 @@
}, },
'_pureFetch': async (rest_url, request_options) => { '_pureFetch': async (rest_url, request_options) => {
// noinspection JSUnresolvedVariable // noinspection JSUnresolvedVariable
return await fetch(env.API_URL + rest_url, request_options).catch(err => { return await fetch(env.API_URL + rest_url, request_options)
console.error(err)
return null
})
} }
} }
export const WorkingHoursRepository = { export const WorkingHoursRepository = {
'browse': () => { 'browse': (startDate = '', endDate = '') => {
return MyFetch.get('/working-hours') let filter = {}
if (startDate !== '') {
filter['start-date'] = startDate
}
if (endDate !== '') {
filter['end-date'] = endDate
}
return MyFetch.get('/working-hours', filter)
}, },
'read': date => { 'read': date => {
+3 -2
View File
@@ -1,5 +1,6 @@
<Views /> <WeeklyViews />
<script> <script>
import Views from "./WeeklyViews.svelte"; import WeeklyViews from "./WeeklyViews.svelte";
export const params = {};
</script> </script>
+1 -1
View File
@@ -1,7 +1,7 @@
<script> <script>
import List from "./WorkingHours/List.svelte"; import List from "./WorkingHours/List.svelte";
import Formular from "./WorkingHours/Formular.svelte"; import Formular from "./WorkingHours/Formular.svelte";
export let params = {}; export const params = {};
let TITLE = "Bearbeitung Einträge" let TITLE = "Bearbeitung Einträge"
let activeRecord = null; let activeRecord = null;
+86 -23
View File
@@ -3,39 +3,102 @@
import IconifyIcon from '@iconify/svelte' import IconifyIcon from '@iconify/svelte'
import checkIcon from '@iconify-icons/oi/check' import checkIcon from '@iconify-icons/oi/check'
import xIcon from '@iconify-icons/oi/x' import xIcon from '@iconify-icons/oi/x'
import {TimekeepingDate} from "../../components/TimekeepingDate.svelte" import EditByDay from "./Formular/EditByDay.svelte";
import EditByWeek from "./Formular/EditByWeek.svelte";
import {TimekeepingDate} from "../../components/TimekeepingDate.svelte";
const DAILY_EDIT = 0
const WEEKLY_EDIT = 1
export let activeRecord = null
const Controller = { const Controller = {
'saveRecord': () => { 'saveRecord': () => {
WorkingHoursRepository.addOrUpdate(activeRecord.workingDay, activeRecord).then(() => { activeRecordList.forEach((actualRecord) => {
Controller.nextDay() if (actualRecord.workingTime === '00:00:00') {
}); return
}, }
'nextDay': async () => { WorkingHoursRepository.addOrUpdate(actualRecord.workingDay, actualRecord);
let actDate = new TimekeepingDate(activeRecord.workingDay) })
let nextDay = actDate.getNextDay() activeDate = nextDate
activeRecord = await WorkingHoursRepository.readOrNew(nextDay.getDateString()) makeTimekeepingList()
}, },
'cancelActiveRecord': () => { 'cancelActiveRecord': () => {
activeRecord = null; activeRecordList = []
activeRecord = null
} }
} }
const WeekController = {
'makeTimekeepingList': () => {
let actualDay = WeekController.getMondayFromWeek(
activeDate === null ? new TimekeepingDate() : new TimekeepingDate(activeDate)
)
activeRecordList = []
activeDate = actualDay.getDateString()
for (const daysDiff in [0, 1, 2, 3, 4, 5, 6]) {
activeRecordList.push({workingTime: '00:00:00', workingDay: actualDay.getDateString()})
actualDay = actualDay.getNextDay()
}
nextDate = actualDay.getDateString()
WorkingHoursRepository.browse(activeRecordList[0].workingDay, activeRecordList[6].workingDay).then(data => {
data.forEach(item => {
let actualDay = new TimekeepingDate(item.workingDay)
activeRecordList[actualDay.getDay()-1].workingTime = item.workingTime
})
})
},
'getMondayFromWeek': actualDayObject => {
if (actualDayObject.getDate() === 0) {
actualDayObject.setDate(actualDayObject.getDate() - 7)
}
if (actualDayObject.getDate() !== 1) {
actualDayObject.setDate(actualDayObject.getDate() - (actualDayObject.getDay() - 1))
}
return actualDayObject
}
}
const DayController = {
'makeTimekeepingList': () => {
let actualDay = activeDate === null ? new TimekeepingDate() : new TimekeepingDate(activeDate)
WorkingHoursRepository.readOrNew(actualDay.getDateString()).then(data => {
activeRecordList = [ data ]
activeDate = actualDay.getDateString()
nextDate = actualDay.getNextDay().getDateString()
})
}
}
const MenuController = {
'dailyEdit': () => {
period_of_edit = DAILY_EDIT
makeTimekeepingList = DayController.makeTimekeepingList
makeTimekeepingList()
},
'weeklyEdit': () => {
period_of_edit = WEEKLY_EDIT
makeTimekeepingList = WeekController.makeTimekeepingList
makeTimekeepingList()
}
}
let activeDate = null
let nextDate = null
let activeRecordList = []
export let activeRecord = null
let period_of_edit
let makeTimekeepingList
MenuController.dailyEdit()
</script> </script>
<!--suppress HtmlUnknownTarget --> <!--suppress HtmlUnknownTarget -->
<form action="/working-hours" name="editRecord"> <form action="/working-hours" name="editRecord">
<fieldset name="record-data"> <nav class="menu">
<div> <span on:click={MenuController.dailyEdit} class:active={period_of_edit === DAILY_EDIT}>Täglich</span>
<label for="workingDay">Arbeitstag</label> <span on:click={MenuController.weeklyEdit} class:active={period_of_edit === WEEKLY_EDIT}>Wöchentlich</span>
<input bind:value={activeRecord.workingDay} type="date" </nav>
id="workingDay" required pattern="\d\{4}-[0-1]\d-[0-3]\d\"> {#if period_of_edit === DAILY_EDIT}
</div> <EditByDay bind:activeRecordList bind:activeDate bind:makeTimekeepingList/>
<div> {/if}
<label for="workingTime">Arbeitszeit</label> {#if period_of_edit === WEEKLY_EDIT}
<input bind:value={activeRecord.workingTime} placeholder="Arbeitszeit" type="time" <EditByWeek bind:activeRecordList bind:activeDate bind:makeTimekeepingList/>
id="workingTime" required pattern="[0-5]\d:[0-5]\d:[0-5]\d"> {/if}
</div>
</fieldset>
<fieldset name="buttons"> <fieldset name="buttons">
<button on:click={Controller.saveRecord} type="button">Übernehmen und Weiter <button on:click={Controller.saveRecord} type="button">Übernehmen und Weiter
<IconifyIcon icon={checkIcon} color="green"/> <IconifyIcon icon={checkIcon} color="green"/>
@@ -0,0 +1,24 @@
<script>
export let activeDate = null
export let activeRecordList = []
export let makeTimekeepingList = () => {}
makeTimekeepingList()
</script>
{#if activeRecordList.length < 1}
<p>Loading...</p>
{:else}
<fieldset name="record-data-by-day">
<div>
<label for="workingDay">Arbeitstag</label>
<input bind:value={activeDate} type="date" on:change={makeTimekeepingList}
id="workingDay" required pattern="\d\{4}-[0-1]\d-[0-3]\d\">
</div>
<div>
<label for="workingTime">Arbeitszeit</label>
<input bind:value={activeRecordList[0].workingTime} placeholder="Arbeitszeit" type="time"
id="workingTime" required pattern="[0-5]\d:[0-5]\d:[0-5]\d">
</div>
</fieldset>
{/if}
@@ -0,0 +1,62 @@
<script>
export let activeDate = null
export let activeRecordList = []
export let makeTimekeepingList = () => {}
makeTimekeepingList()
</script>
<style>
tbody.weekend td input, tbody.weekend td {
color: red;
}
input#monday {
display: inline;
width: auto;
}
</style>
{#if activeRecordList.length < 1}
<p>Loading...</p>
{:else}
<fieldset name="how-week-is-use">
<div>
<label for="monday">Woche vom</label>
<input bind:value={activeDate} type="date" on:change={makeTimekeepingList}
id="monday" required pattern="\d\{4}-[0-1]\d-[0-3]\d\">
</div>
</fieldset>
<fieldset name="record-data-by-week">
<div>
<table>
<tbody>
{#each ['Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag'] as day, index}
<tr>
<td>
{day}
</td>
<td>
<input bind:value={activeRecordList[index].workingTime} placeholder="Arbeitszeit" type="time"
required pattern="[0-5]\d:[0-5]\d:[0-5]\d">
</td>
</tr>
{/each}
</tbody>
<tbody class="weekend">
{#each ['Samstag', 'Sontag'] as day, index}
<tr>
<td>
{day}
</td>
<td>
<input bind:value={activeRecordList[index+5].workingTime} placeholder="Arbeitszeit" type="time"
required pattern="[0-5]\d:[0-5]\d:[0-5]\d">
</td>
</tr>
{/each}
</tbody>
</table>
</div>
</fieldset>
{/if}