Release 2.0.15 (#3970)
This commit is contained in:
@@ -34,10 +34,10 @@ p {
|
|||||||
.text-center {
|
.text-center {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
.text-left {
|
.text-left, .text-begin {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
.text-right {
|
.text-right, .text-end {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
.text-nowrap {
|
.text-nowrap {
|
||||||
|
|||||||
@@ -80,7 +80,6 @@ body {
|
|||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
|
|
||||||
> small {
|
> small {
|
||||||
color: #666;
|
|
||||||
display: block;
|
display: block;
|
||||||
margin-top: 5px;
|
margin-top: 5px;
|
||||||
}
|
}
|
||||||
@@ -107,6 +106,13 @@ body {
|
|||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.text-left {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
.text-right {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
.invoice-items {
|
.invoice-items {
|
||||||
.table {
|
.table {
|
||||||
thead th {
|
thead th {
|
||||||
|
|||||||
98
composer.lock
generated
98
composer.lock
generated
@@ -1449,16 +1449,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "doctrine/orm",
|
"name": "doctrine/orm",
|
||||||
"version": "2.14.1",
|
"version": "2.14.2",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/doctrine/orm.git",
|
"url": "https://github.com/doctrine/orm.git",
|
||||||
"reference": "de7eee5ed7b1b35c99b118f26f210a8281e6db8e"
|
"reference": "e5fb1a4a8f5aa4e37612f66a22aac10291bcbb52"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/doctrine/orm/zipball/de7eee5ed7b1b35c99b118f26f210a8281e6db8e",
|
"url": "https://api.github.com/repos/doctrine/orm/zipball/e5fb1a4a8f5aa4e37612f66a22aac10291bcbb52",
|
||||||
"reference": "de7eee5ed7b1b35c99b118f26f210a8281e6db8e",
|
"reference": "e5fb1a4a8f5aa4e37612f66a22aac10291bcbb52",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -1487,14 +1487,14 @@
|
|||||||
"doctrine/annotations": "^1.13 || ^2",
|
"doctrine/annotations": "^1.13 || ^2",
|
||||||
"doctrine/coding-standard": "^9.0.2 || ^11.0",
|
"doctrine/coding-standard": "^9.0.2 || ^11.0",
|
||||||
"phpbench/phpbench": "^0.16.10 || ^1.0",
|
"phpbench/phpbench": "^0.16.10 || ^1.0",
|
||||||
"phpstan/phpstan": "~1.4.10 || 1.9.8",
|
"phpstan/phpstan": "~1.4.10 || 1.10.6",
|
||||||
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.5",
|
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.6",
|
||||||
"psr/log": "^1 || ^2 || ^3",
|
"psr/log": "^1 || ^2 || ^3",
|
||||||
"squizlabs/php_codesniffer": "3.7.1",
|
"squizlabs/php_codesniffer": "3.7.2",
|
||||||
"symfony/cache": "^4.4 || ^5.4 || ^6.0",
|
"symfony/cache": "^4.4 || ^5.4 || ^6.0",
|
||||||
"symfony/var-exporter": "^4.4 || ^5.4 || ^6.2",
|
"symfony/var-exporter": "^4.4 || ^5.4 || ^6.2",
|
||||||
"symfony/yaml": "^3.4 || ^4.0 || ^5.0 || ^6.0",
|
"symfony/yaml": "^3.4 || ^4.0 || ^5.0 || ^6.0",
|
||||||
"vimeo/psalm": "4.30.0 || 5.4.0"
|
"vimeo/psalm": "4.30.0 || 5.9.0"
|
||||||
},
|
},
|
||||||
"suggest": {
|
"suggest": {
|
||||||
"ext-dom": "Provides support for XSD validation for XML mapping files",
|
"ext-dom": "Provides support for XSD validation for XML mapping files",
|
||||||
@@ -1544,9 +1544,9 @@
|
|||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/doctrine/orm/issues",
|
"issues": "https://github.com/doctrine/orm/issues",
|
||||||
"source": "https://github.com/doctrine/orm/tree/2.14.1"
|
"source": "https://github.com/doctrine/orm/tree/2.14.2"
|
||||||
},
|
},
|
||||||
"time": "2023-01-16T18:36:59+00:00"
|
"time": "2023-03-30T15:18:54+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "doctrine/persistence",
|
"name": "doctrine/persistence",
|
||||||
@@ -4068,16 +4068,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "phpstan/phpdoc-parser",
|
"name": "phpstan/phpdoc-parser",
|
||||||
"version": "1.17.1",
|
"version": "1.18.1",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/phpstan/phpdoc-parser.git",
|
"url": "https://github.com/phpstan/phpdoc-parser.git",
|
||||||
"reference": "d3753fcb3abc6f78f5de6f72153d4b9c99c72dee"
|
"reference": "22dcdfd725ddf99583bfe398fc624ad6c5004a0f"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/d3753fcb3abc6f78f5de6f72153d4b9c99c72dee",
|
"url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/22dcdfd725ddf99583bfe398fc624ad6c5004a0f",
|
||||||
"reference": "d3753fcb3abc6f78f5de6f72153d4b9c99c72dee",
|
"reference": "22dcdfd725ddf99583bfe398fc624ad6c5004a0f",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -4107,9 +4107,9 @@
|
|||||||
"description": "PHPDoc parser with support for nullable, intersection and generic types",
|
"description": "PHPDoc parser with support for nullable, intersection and generic types",
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/phpstan/phpdoc-parser/issues",
|
"issues": "https://github.com/phpstan/phpdoc-parser/issues",
|
||||||
"source": "https://github.com/phpstan/phpdoc-parser/tree/1.17.1"
|
"source": "https://github.com/phpstan/phpdoc-parser/tree/1.18.1"
|
||||||
},
|
},
|
||||||
"time": "2023-04-04T11:11:22+00:00"
|
"time": "2023-04-07T11:51:11+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "psr/cache",
|
"name": "psr/cache",
|
||||||
@@ -4265,21 +4265,21 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "psr/http-client",
|
"name": "psr/http-client",
|
||||||
"version": "1.0.1",
|
"version": "1.0.2",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/php-fig/http-client.git",
|
"url": "https://github.com/php-fig/http-client.git",
|
||||||
"reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621"
|
"reference": "0955afe48220520692d2d09f7ab7e0f93ffd6a31"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/php-fig/http-client/zipball/2dfb5f6c5eff0e91e20e913f8c5452ed95b86621",
|
"url": "https://api.github.com/repos/php-fig/http-client/zipball/0955afe48220520692d2d09f7ab7e0f93ffd6a31",
|
||||||
"reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621",
|
"reference": "0955afe48220520692d2d09f7ab7e0f93ffd6a31",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^7.0 || ^8.0",
|
"php": "^7.0 || ^8.0",
|
||||||
"psr/http-message": "^1.0"
|
"psr/http-message": "^1.0 || ^2.0"
|
||||||
},
|
},
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"extra": {
|
"extra": {
|
||||||
@@ -4299,7 +4299,7 @@
|
|||||||
"authors": [
|
"authors": [
|
||||||
{
|
{
|
||||||
"name": "PHP-FIG",
|
"name": "PHP-FIG",
|
||||||
"homepage": "http://www.php-fig.org/"
|
"homepage": "https://www.php-fig.org/"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"description": "Common interface for HTTP clients",
|
"description": "Common interface for HTTP clients",
|
||||||
@@ -4311,27 +4311,27 @@
|
|||||||
"psr-18"
|
"psr-18"
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"source": "https://github.com/php-fig/http-client/tree/master"
|
"source": "https://github.com/php-fig/http-client/tree/1.0.2"
|
||||||
},
|
},
|
||||||
"time": "2020-06-29T06:28:15+00:00"
|
"time": "2023-04-10T20:12:12+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "psr/http-factory",
|
"name": "psr/http-factory",
|
||||||
"version": "1.0.1",
|
"version": "1.0.2",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/php-fig/http-factory.git",
|
"url": "https://github.com/php-fig/http-factory.git",
|
||||||
"reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be"
|
"reference": "e616d01114759c4c489f93b099585439f795fe35"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be",
|
"url": "https://api.github.com/repos/php-fig/http-factory/zipball/e616d01114759c4c489f93b099585439f795fe35",
|
||||||
"reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be",
|
"reference": "e616d01114759c4c489f93b099585439f795fe35",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"php": ">=7.0.0",
|
"php": ">=7.0.0",
|
||||||
"psr/http-message": "^1.0"
|
"psr/http-message": "^1.0 || ^2.0"
|
||||||
},
|
},
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"extra": {
|
"extra": {
|
||||||
@@ -4351,7 +4351,7 @@
|
|||||||
"authors": [
|
"authors": [
|
||||||
{
|
{
|
||||||
"name": "PHP-FIG",
|
"name": "PHP-FIG",
|
||||||
"homepage": "http://www.php-fig.org/"
|
"homepage": "https://www.php-fig.org/"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"description": "Common interfaces for PSR-7 HTTP message factories",
|
"description": "Common interfaces for PSR-7 HTTP message factories",
|
||||||
@@ -4366,9 +4366,9 @@
|
|||||||
"response"
|
"response"
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"source": "https://github.com/php-fig/http-factory/tree/master"
|
"source": "https://github.com/php-fig/http-factory/tree/1.0.2"
|
||||||
},
|
},
|
||||||
"time": "2019-04-30T12:38:16+00:00"
|
"time": "2023-04-10T20:10:41+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "psr/http-message",
|
"name": "psr/http-message",
|
||||||
@@ -10691,16 +10691,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "doctrine/doctrine-fixtures-bundle",
|
"name": "doctrine/doctrine-fixtures-bundle",
|
||||||
"version": "3.4.2",
|
"version": "3.4.3",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/doctrine/DoctrineFixturesBundle.git",
|
"url": "https://github.com/doctrine/DoctrineFixturesBundle.git",
|
||||||
"reference": "601988c5b46dbd20a0f886f967210aba378a6fd5"
|
"reference": "fd39829fed8f090ef6e185d33449d47c2fb59c9c"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/doctrine/DoctrineFixturesBundle/zipball/601988c5b46dbd20a0f886f967210aba378a6fd5",
|
"url": "https://api.github.com/repos/doctrine/DoctrineFixturesBundle/zipball/fd39829fed8f090ef6e185d33449d47c2fb59c9c",
|
||||||
"reference": "601988c5b46dbd20a0f886f967210aba378a6fd5",
|
"reference": "fd39829fed8f090ef6e185d33449d47c2fb59c9c",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -10754,7 +10754,7 @@
|
|||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/doctrine/DoctrineFixturesBundle/issues",
|
"issues": "https://github.com/doctrine/DoctrineFixturesBundle/issues",
|
||||||
"source": "https://github.com/doctrine/DoctrineFixturesBundle/tree/3.4.2"
|
"source": "https://github.com/doctrine/DoctrineFixturesBundle/tree/3.4.3"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -10770,7 +10770,7 @@
|
|||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2022-04-28T17:58:29+00:00"
|
"time": "2023-04-11T12:37:36+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "fakerphp/faker",
|
"name": "fakerphp/faker",
|
||||||
@@ -11174,16 +11174,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "phpstan/phpstan",
|
"name": "phpstan/phpstan",
|
||||||
"version": "1.10.11",
|
"version": "1.10.12",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/phpstan/phpstan.git",
|
"url": "https://github.com/phpstan/phpstan.git",
|
||||||
"reference": "8aa62e6ea8b58ffb650e02940e55a788cbc3fe21"
|
"reference": "7b2aaf999e522e2b10011a594cf06dc37de7fbd8"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/8aa62e6ea8b58ffb650e02940e55a788cbc3fe21",
|
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/7b2aaf999e522e2b10011a594cf06dc37de7fbd8",
|
||||||
"reference": "8aa62e6ea8b58ffb650e02940e55a788cbc3fe21",
|
"reference": "7b2aaf999e522e2b10011a594cf06dc37de7fbd8",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -11232,7 +11232,7 @@
|
|||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2023-04-04T19:17:42+00:00"
|
"time": "2023-04-12T10:33:29+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "phpstan/phpstan-doctrine",
|
"name": "phpstan/phpstan-doctrine",
|
||||||
@@ -11407,16 +11407,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "phpstan/phpstan-symfony",
|
"name": "phpstan/phpstan-symfony",
|
||||||
"version": "1.2.24",
|
"version": "1.2.25",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/phpstan/phpstan-symfony.git",
|
"url": "https://github.com/phpstan/phpstan-symfony.git",
|
||||||
"reference": "db81b1861aac7cc2e66115cb33b4d1ea2a73d096"
|
"reference": "1da7bf450c6b351fec08ca0aa97298473d4f6ab3"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/db81b1861aac7cc2e66115cb33b4d1ea2a73d096",
|
"url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/1da7bf450c6b351fec08ca0aa97298473d4f6ab3",
|
||||||
"reference": "db81b1861aac7cc2e66115cb33b4d1ea2a73d096",
|
"reference": "1da7bf450c6b351fec08ca0aa97298473d4f6ab3",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -11472,9 +11472,9 @@
|
|||||||
"description": "Symfony Framework extensions and rules for PHPStan",
|
"description": "Symfony Framework extensions and rules for PHPStan",
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/phpstan/phpstan-symfony/issues",
|
"issues": "https://github.com/phpstan/phpstan-symfony/issues",
|
||||||
"source": "https://github.com/phpstan/phpstan-symfony/tree/1.2.24"
|
"source": "https://github.com/phpstan/phpstan-symfony/tree/1.2.25"
|
||||||
},
|
},
|
||||||
"time": "2023-03-30T08:38:10+00:00"
|
"time": "2023-04-05T12:16:20+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "phpunit/php-code-coverage",
|
"name": "phpunit/php-code-coverage",
|
||||||
|
|||||||
40
phpstan.neon
40
phpstan.neon
@@ -4330,11 +4330,6 @@ parameters:
|
|||||||
count: 2
|
count: 2
|
||||||
path: src/Invoice/Hydrator/InvoiceItemDefaultHydrator.php
|
path: src/Invoice/Hydrator/InvoiceItemDefaultHydrator.php
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Cannot call method getAlias\\(\\) on App\\\\Entity\\\\User\\|null\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Invoice/Hydrator/InvoiceItemDefaultHydrator.php
|
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Cannot call method getCurrency\\(\\) on App\\\\Entity\\\\Customer\\|null\\.$#"
|
message: "#^Cannot call method getCurrency\\(\\) on App\\\\Entity\\\\Customer\\|null\\.$#"
|
||||||
count: 1
|
count: 1
|
||||||
@@ -4345,26 +4340,11 @@ parameters:
|
|||||||
count: 1
|
count: 1
|
||||||
path: src/Invoice/Hydrator/InvoiceItemDefaultHydrator.php
|
path: src/Invoice/Hydrator/InvoiceItemDefaultHydrator.php
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Cannot call method getId\\(\\) on App\\\\Entity\\\\User\\|null\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Invoice/Hydrator/InvoiceItemDefaultHydrator.php
|
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Cannot call method getTimestamp\\(\\) on DateTime\\|null\\.$#"
|
message: "#^Cannot call method getTimestamp\\(\\) on DateTime\\|null\\.$#"
|
||||||
count: 2
|
count: 2
|
||||||
path: src/Invoice/Hydrator/InvoiceItemDefaultHydrator.php
|
path: src/Invoice/Hydrator/InvoiceItemDefaultHydrator.php
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Cannot call method getTitle\\(\\) on App\\\\Entity\\\\User\\|null\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Invoice/Hydrator/InvoiceItemDefaultHydrator.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Cannot call method getUserIdentifier\\(\\) on App\\\\Entity\\\\User\\|null\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Invoice/Hydrator/InvoiceItemDefaultHydrator.php
|
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Method App\\\\Invoice\\\\Hydrator\\\\InvoiceItemDefaultHydrator\\:\\:hydrate\\(\\) return type has no value type specified in iterable type array\\.$#"
|
message: "#^Method App\\\\Invoice\\\\Hydrator\\\\InvoiceItemDefaultHydrator\\:\\:hydrate\\(\\) return type has no value type specified in iterable type array\\.$#"
|
||||||
count: 1
|
count: 1
|
||||||
@@ -4430,11 +4410,6 @@ parameters:
|
|||||||
count: 1
|
count: 1
|
||||||
path: src/Invoice/Hydrator/InvoiceModelDefaultHydrator.php
|
path: src/Invoice/Hydrator/InvoiceModelDefaultHydrator.php
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Cannot call method getBegin\\(\\) on App\\\\Repository\\\\Query\\\\InvoiceQuery\\|null\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Invoice/Hydrator/InvoiceModelDefaultHydrator.php
|
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Cannot call method getCompany\\(\\) on App\\\\Entity\\\\InvoiceTemplate\\|null\\.$#"
|
message: "#^Cannot call method getCompany\\(\\) on App\\\\Entity\\\\InvoiceTemplate\\|null\\.$#"
|
||||||
count: 1
|
count: 1
|
||||||
@@ -4450,11 +4425,6 @@ parameters:
|
|||||||
count: 1
|
count: 1
|
||||||
path: src/Invoice/Hydrator/InvoiceModelDefaultHydrator.php
|
path: src/Invoice/Hydrator/InvoiceModelDefaultHydrator.php
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Cannot call method getEnd\\(\\) on App\\\\Repository\\\\Query\\\\InvoiceQuery\\|null\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Invoice/Hydrator/InvoiceModelDefaultHydrator.php
|
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Cannot call method getLanguage\\(\\) on App\\\\Entity\\\\InvoiceTemplate\\|null\\.$#"
|
message: "#^Cannot call method getLanguage\\(\\) on App\\\\Entity\\\\InvoiceTemplate\\|null\\.$#"
|
||||||
count: 1
|
count: 1
|
||||||
@@ -4790,11 +4760,6 @@ parameters:
|
|||||||
count: 1
|
count: 1
|
||||||
path: src/Kernel.php
|
path: src/Kernel.php
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Ldap\\\\FormLoginLdapFactory\\:\\:createAuthenticator\\(\\) has parameter \\$config with no value type specified in iterable type array\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Ldap/FormLoginLdapFactory.php
|
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Method App\\\\Ldap\\\\LdapAuthenticator\\:\\:supports\\(\\) should return bool but returns bool\\|null\\.$#"
|
message: "#^Method App\\\\Ldap\\\\LdapAuthenticator\\:\\:supports\\(\\) should return bool but returns bool\\|null\\.$#"
|
||||||
count: 1
|
count: 1
|
||||||
@@ -5735,11 +5700,6 @@ parameters:
|
|||||||
count: 1
|
count: 1
|
||||||
path: src/Repository/TagRepository.php
|
path: src/Repository/TagRepository.php
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Repository\\\\TagRepository\\:\\:findIdsByTagNameList\\(\\) return type has no value type specified in iterable type array\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Repository/TagRepository.php
|
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Method App\\\\Repository\\\\TagRepository\\:\\:findTagsByName\\(\\) has parameter \\$tagNames with no value type specified in iterable type array\\.$#"
|
message: "#^Method App\\\\Repository\\\\TagRepository\\:\\:findTagsByName\\(\\) has parameter \\$tagNames with no value type specified in iterable type array\\.$#"
|
||||||
count: 1
|
count: 1
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
"/build/invoice.c8ae95ad.js"
|
"/build/invoice.c8ae95ad.js"
|
||||||
],
|
],
|
||||||
"css": [
|
"css": [
|
||||||
"/build/invoice.3c80ee80.css"
|
"/build/invoice.d0ae14e1.css"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"invoice-pdf": {
|
"invoice-pdf": {
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
"/build/invoice-pdf.d86b82ee.js"
|
"/build/invoice-pdf.d86b82ee.js"
|
||||||
],
|
],
|
||||||
"css": [
|
"css": [
|
||||||
"/build/invoice-pdf.c88953bb.css"
|
"/build/invoice-pdf.2b749265.css"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"chart": {
|
"chart": {
|
||||||
@@ -68,9 +68,9 @@
|
|||||||
"/build/export-pdf.587575e7.js": "sha384-J50GStmmfVwUTN4dIRQ02eg9hyzGFPSzpTtpPody92j0V6zCqw+s5l8+ZhVTugeW",
|
"/build/export-pdf.587575e7.js": "sha384-J50GStmmfVwUTN4dIRQ02eg9hyzGFPSzpTtpPody92j0V6zCqw+s5l8+ZhVTugeW",
|
||||||
"/build/export-pdf.d8a6c23b.css": "sha384-ztepocHE4rnGE9eKZ4kL6jTKaePUyiwiB9TjJjstjpf/ckcKg1HedrEOOk/8ElJg",
|
"/build/export-pdf.d8a6c23b.css": "sha384-ztepocHE4rnGE9eKZ4kL6jTKaePUyiwiB9TjJjstjpf/ckcKg1HedrEOOk/8ElJg",
|
||||||
"/build/invoice.c8ae95ad.js": "sha384-2eVY7MBiMQxo1vhfizU+fYfEZbz9bYUdzqxnpTQhxpYiLaxeSV4WnaObq2G7/Pks",
|
"/build/invoice.c8ae95ad.js": "sha384-2eVY7MBiMQxo1vhfizU+fYfEZbz9bYUdzqxnpTQhxpYiLaxeSV4WnaObq2G7/Pks",
|
||||||
"/build/invoice.3c80ee80.css": "sha384-xjFM2m/EeN7z42ygpt77ll5zAcHTeOjbZFAw2Qcu3IJutgebiAbrXjnH5aJfF5Z6",
|
"/build/invoice.d0ae14e1.css": "sha384-DZ9f2EsTAIOVoFBZe4jMaW3R6yNVGCtcn3Dq3hxZnWRDdFzhyt1O1iQqRZUyQFTM",
|
||||||
"/build/invoice-pdf.d86b82ee.js": "sha384-A0HJqP+MvEqQr1uG8wViCeEWxBRKyS6l8D+Ao4pFYHUA12gCC1gRYhk9I+SJPvZq",
|
"/build/invoice-pdf.d86b82ee.js": "sha384-A0HJqP+MvEqQr1uG8wViCeEWxBRKyS6l8D+Ao4pFYHUA12gCC1gRYhk9I+SJPvZq",
|
||||||
"/build/invoice-pdf.c88953bb.css": "sha384-ZvSi1e+ZKGzvZJUtAPLjzOSTh13N9zRevq44GKdYdBja/DAplGE55saY2Ur+83yv",
|
"/build/invoice-pdf.2b749265.css": "sha384-DXXgkz2WWnrWnfBnXX5fmfPQSPb98upMnWxYKwTGYS04EhrPIWfDCutB2unIrWh7",
|
||||||
"/build/chart.f5becfac.js": "sha384-GSqETm8wULiVXyizvwRompfwu63r/C0Qd/AvrHDE4cqAKiIGCssb3QyBtGu1WN+W",
|
"/build/chart.f5becfac.js": "sha384-GSqETm8wULiVXyizvwRompfwu63r/C0Qd/AvrHDE4cqAKiIGCssb3QyBtGu1WN+W",
|
||||||
"/build/calendar.8577d9cd.js": "sha384-1qlF+4tr7/zve+wvZef7sVcbUxhK0/yEm5Hbuct8K/ZV4JtENdNY7YRLqOcZ/8Y3",
|
"/build/calendar.8577d9cd.js": "sha384-1qlF+4tr7/zve+wvZef7sVcbUxhK0/yEm5Hbuct8K/ZV4JtENdNY7YRLqOcZ/8Y3",
|
||||||
"/build/calendar.bb473428.css": "sha384-900W9o8666Qpw21rLP7hn5Ql8tWd40k0IcE3QpFm7E7V6bKva7xQgGlyzVuuh4GK",
|
"/build/calendar.bb473428.css": "sha384-900W9o8666Qpw21rLP7hn5Ql8tWd40k0IcE3QpFm7E7V6bKva7xQgGlyzVuuh4GK",
|
||||||
|
|||||||
1
public/build/invoice-pdf.2b749265.css
Normal file
1
public/build/invoice-pdf.2b749265.css
Normal file
@@ -0,0 +1 @@
|
|||||||
|
body{font-family:sans-serif;font-size:10pt;margin:4px;padding:0}h1,h2,h3,h4,h5,p,table,td,th,tr{margin:0;padding:0}th{border:none;text-align:left}td{vertical-align:top}p{margin-bottom:12px}.text-small{font-size:7pt}.padding-left{padding-left:15px}.text-center{text-align:center}.text-begin,.text-left{text-align:left}.text-end,.text-right{text-align:right}.text-nowrap{white-space:nowrap}.date{font-size:80%;padding-top:6px;text-align:right}table{border-spacing:0;width:100%}p,table.addresses{line-height:14pt}table.addresses{margin-bottom:30px}table.header{border-bottom:1px solid #999;padding-bottom:4px}table.header .title{font-size:140%;font-weight:400}table.footer{border-top:1px solid #999;font-size:80%;line-height:14px;padding-top:8px}table.footer td{vertical-align:top}table.items{border-collapse:collapse;margin-bottom:60px;margin-top:50px}table.items tbody td,table.items thead th{border-bottom:1px solid #ccc}table.items tbody td.first,table.items thead th.first{padding-left:0}table.items tbody td.last,table.items tfoot td.last,table.items thead th.last{padding-right:0}table.items td,table.items th{padding:8px 6px}table.items tfoot td,table.items tfoot th{line-height:1px;padding:20px 0 0}table.items tfoot td.last{padding-left:10px}table.items tr.odd{background-color:#f5f5f5}
|
||||||
@@ -1 +0,0 @@
|
|||||||
body{font-family:sans-serif;font-size:10pt;margin:4px;padding:0}h1,h2,h3,h4,h5,p,table,td,th,tr{margin:0;padding:0}th{border:none;text-align:left}td{vertical-align:top}p{margin-bottom:12px}.text-small{font-size:7pt}.padding-left{padding-left:15px}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.text-nowrap{white-space:nowrap}.date{font-size:80%;padding-top:6px;text-align:right}table{border-spacing:0;width:100%}p,table.addresses{line-height:14pt}table.addresses{margin-bottom:30px}table.header{border-bottom:1px solid #999;padding-bottom:4px}table.header .title{font-size:140%;font-weight:400}table.footer{border-top:1px solid #999;font-size:80%;line-height:14px;padding-top:8px}table.footer td{vertical-align:top}table.items{border-collapse:collapse;margin-bottom:60px;margin-top:50px}table.items tbody td,table.items thead th{border-bottom:1px solid #ccc}table.items tbody td.first,table.items thead th.first{padding-left:0}table.items tbody td.last,table.items tfoot td.last,table.items thead th.last{padding-right:0}table.items td,table.items th{padding:8px 6px}table.items tfoot td,table.items tfoot th{line-height:1px;padding:20px 0 0}table.items tfoot td.last{padding-left:10px}table.items tr.odd{background-color:#f5f5f5}
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -3,9 +3,9 @@
|
|||||||
"build/app.js": "/build/app.cd312ca8.js",
|
"build/app.js": "/build/app.cd312ca8.js",
|
||||||
"build/export-pdf.css": "/build/export-pdf.d8a6c23b.css",
|
"build/export-pdf.css": "/build/export-pdf.d8a6c23b.css",
|
||||||
"build/export-pdf.js": "/build/export-pdf.587575e7.js",
|
"build/export-pdf.js": "/build/export-pdf.587575e7.js",
|
||||||
"build/invoice.css": "/build/invoice.3c80ee80.css",
|
"build/invoice.css": "/build/invoice.d0ae14e1.css",
|
||||||
"build/invoice.js": "/build/invoice.c8ae95ad.js",
|
"build/invoice.js": "/build/invoice.c8ae95ad.js",
|
||||||
"build/invoice-pdf.css": "/build/invoice-pdf.c88953bb.css",
|
"build/invoice-pdf.css": "/build/invoice-pdf.2b749265.css",
|
||||||
"build/invoice-pdf.js": "/build/invoice-pdf.d86b82ee.js",
|
"build/invoice-pdf.js": "/build/invoice-pdf.d86b82ee.js",
|
||||||
"build/chart.js": "/build/chart.f5becfac.js",
|
"build/chart.js": "/build/chart.f5becfac.js",
|
||||||
"build/calendar.css": "/build/calendar.bb473428.css",
|
"build/calendar.css": "/build/calendar.bb473428.css",
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ final class TimesheetController extends BaseApiController
|
|||||||
#[ApiSecurity(name: 'apiUser')]
|
#[ApiSecurity(name: 'apiUser')]
|
||||||
#[ApiSecurity(name: 'apiToken')]
|
#[ApiSecurity(name: 'apiToken')]
|
||||||
#[Rest\QueryParam(name: 'user', requirements: '\d+|all', strict: true, nullable: true, description: "User ID to filter timesheets. Needs permission 'view_other_timesheet', pass 'all' to fetch data for all user (default: current user)")]
|
#[Rest\QueryParam(name: 'user', requirements: '\d+|all', strict: true, nullable: true, description: "User ID to filter timesheets. Needs permission 'view_other_timesheet', pass 'all' to fetch data for all user (default: current user)")]
|
||||||
|
#[Rest\QueryParam(name: 'users', map: true, requirements: '\d+', strict: true, nullable: true, default: [], description: 'List of user IDs to filter, e.g.: users[]=1&users[]=2 (ignored if user=all)')]
|
||||||
#[Rest\QueryParam(name: 'customer', requirements: '\d+', strict: true, nullable: true, description: 'Customer ID to filter timesheets')]
|
#[Rest\QueryParam(name: 'customer', requirements: '\d+', strict: true, nullable: true, description: 'Customer ID to filter timesheets')]
|
||||||
#[Rest\QueryParam(name: 'customers', map: true, requirements: '\d+', strict: true, nullable: true, default: [], description: 'List of customer IDs to filter, e.g.: customers[]=1&customers[]=2')]
|
#[Rest\QueryParam(name: 'customers', map: true, requirements: '\d+', strict: true, nullable: true, default: [], description: 'List of customer IDs to filter, e.g.: customers[]=1&customers[]=2')]
|
||||||
#[Rest\QueryParam(name: 'project', requirements: '\d+', strict: true, nullable: true, description: 'Project ID to filter timesheets')]
|
#[Rest\QueryParam(name: 'project', requirements: '\d+', strict: true, nullable: true, description: 'Project ID to filter timesheets')]
|
||||||
@@ -98,24 +99,32 @@ final class TimesheetController extends BaseApiController
|
|||||||
public function cgetAction(ParamFetcherInterface $paramFetcher, CustomerRepository $customerRepository, ProjectRepository $projectRepository, ActivityRepository $activityRepository, UserRepository $userRepository): Response
|
public function cgetAction(ParamFetcherInterface $paramFetcher, CustomerRepository $customerRepository, ProjectRepository $projectRepository, ActivityRepository $activityRepository, UserRepository $userRepository): Response
|
||||||
{
|
{
|
||||||
$query = new TimesheetQuery(false);
|
$query = new TimesheetQuery(false);
|
||||||
$query->setUser($this->getUser());
|
$seeAll = false;
|
||||||
|
|
||||||
if ($this->isGranted('view_other_timesheet')) {
|
if ($this->isGranted('view_other_timesheet')) {
|
||||||
|
/** @var array<int> $users */
|
||||||
|
$users = $paramFetcher->get('users');
|
||||||
$userId = $paramFetcher->get('user');
|
$userId = $paramFetcher->get('user');
|
||||||
if (\is_string($userId) && $userId !== '') {
|
|
||||||
if ('all' === $userId) {
|
if ('all' === $userId) {
|
||||||
$query->setUser(null);
|
$seeAll = true;
|
||||||
} else {
|
} elseif (\is_string($userId) && $userId !== '') {
|
||||||
/** @var User|null $user */
|
$users[] = (int) $userId;
|
||||||
$user = $userRepository->find($userId);
|
}
|
||||||
if ($user === null) {
|
|
||||||
throw $this->createNotFoundException('Unknown user: ' . $userId);
|
if (!$seeAll) {
|
||||||
}
|
foreach ($userRepository->findByIds($users) as $user) {
|
||||||
$query->setUser($user);
|
$query->addUser($user);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($seeAll) {
|
||||||
|
$query->setUser(null);
|
||||||
|
} elseif (!$query->hasUsers()) {
|
||||||
|
$query->setUser($this->getUser());
|
||||||
|
}
|
||||||
|
|
||||||
/** @var array<int> $customers */
|
/** @var array<int> $customers */
|
||||||
$customers = $paramFetcher->get('customers');
|
$customers = $paramFetcher->get('customers');
|
||||||
$customer = $paramFetcher->get('customer');
|
$customer = $paramFetcher->get('customer');
|
||||||
@@ -476,6 +485,7 @@ final class TimesheetController extends BaseApiController
|
|||||||
#[IsGranted('start', 'timesheet')]
|
#[IsGranted('start', 'timesheet')]
|
||||||
#[OA\Response(response: 200, description: 'Restarts a timesheet record for the same customer, project, activity combination. The current user will be the owner of the new record. Kimai tries to stop running records, which is expected to fail depending on the configured rules. Data will be copied from the original record if requested.', content: new OA\JsonContent(ref: '#/components/schemas/TimesheetEntity'))]
|
#[OA\Response(response: 200, description: 'Restarts a timesheet record for the same customer, project, activity combination. The current user will be the owner of the new record. Kimai tries to stop running records, which is expected to fail depending on the configured rules. Data will be copied from the original record if requested.', content: new OA\JsonContent(ref: '#/components/schemas/TimesheetEntity'))]
|
||||||
#[OA\Parameter(name: 'id', in: 'path', description: 'Timesheet record ID to restart', required: true)]
|
#[OA\Parameter(name: 'id', in: 'path', description: 'Timesheet record ID to restart', required: true)]
|
||||||
|
#[Rest\Get(path: '/{id}/restart', name: 'restart_timesheet_get', requirements: ['id' => '\d+'])]
|
||||||
#[Rest\Patch(path: '/{id}/restart', name: 'restart_timesheet', requirements: ['id' => '\d+'])]
|
#[Rest\Patch(path: '/{id}/restart', name: 'restart_timesheet', requirements: ['id' => '\d+'])]
|
||||||
#[ApiSecurity(name: 'apiUser')]
|
#[ApiSecurity(name: 'apiUser')]
|
||||||
#[ApiSecurity(name: 'apiToken')]
|
#[ApiSecurity(name: 'apiToken')]
|
||||||
|
|||||||
@@ -17,11 +17,11 @@ class Constants
|
|||||||
/**
|
/**
|
||||||
* The current release version
|
* The current release version
|
||||||
*/
|
*/
|
||||||
public const VERSION = '2.0.14';
|
public const VERSION = '2.0.15';
|
||||||
/**
|
/**
|
||||||
* The current release: major * 10000 + minor * 100 + patch
|
* The current release: major * 10000 + minor * 100 + patch
|
||||||
*/
|
*/
|
||||||
public const VERSION_ID = 20014;
|
public const VERSION_ID = 20015;
|
||||||
/**
|
/**
|
||||||
* The software name
|
* The software name
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -672,6 +672,8 @@ class Timesheet implements EntityWithMetaFields, ExportableItem
|
|||||||
$this->id = null;
|
$this->id = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// field will not be set, if it contains a value
|
||||||
|
$this->modifiedAt = null;
|
||||||
$this->exported = false;
|
$this->exported = false;
|
||||||
|
|
||||||
$currentMeta = $this->meta;
|
$currentMeta = $this->meta;
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ final class ExportToolbarForm extends AbstractType
|
|||||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||||
{
|
{
|
||||||
$this->addSearchTermInputField($builder);
|
$this->addSearchTermInputField($builder);
|
||||||
$this->addDateRange($builder, ['timezone' => $options['timezone']], false, true);
|
$this->addDateRange($builder, ['timezone' => $options['timezone']]);
|
||||||
$this->addCustomerMultiChoice($builder, ['start_date_param' => null, 'end_date_param' => null, 'ignore_date' => true], true);
|
$this->addCustomerMultiChoice($builder, ['start_date_param' => null, 'end_date_param' => null, 'ignore_date' => true], true);
|
||||||
$this->addProjectMultiChoice($builder, ['ignore_date' => true], true, true);
|
$this->addProjectMultiChoice($builder, ['ignore_date' => true], true, true);
|
||||||
$this->addActivitySelect($builder, [], true, true, false);
|
$this->addActivitySelect($builder, [], true, true, false);
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ final class InvoiceToolbarForm extends AbstractType
|
|||||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||||
{
|
{
|
||||||
$this->addSearchTermInputField($builder);
|
$this->addSearchTermInputField($builder);
|
||||||
$this->addDateRange($builder, ['timezone' => $options['timezone']], false, true);
|
$this->addDateRange($builder, ['timezone' => $options['timezone']]);
|
||||||
$this->addCustomerMultiChoice($builder, ['start_date_param' => null, 'end_date_param' => null, 'ignore_date' => true], true);
|
$this->addCustomerMultiChoice($builder, ['start_date_param' => null, 'end_date_param' => null, 'ignore_date' => true], true);
|
||||||
$this->addProjectMultiChoice($builder, ['ignore_date' => true], true, true);
|
$this->addProjectMultiChoice($builder, ['ignore_date' => true], true, true);
|
||||||
$this->addActivitySelect($builder, [], true, true, false);
|
$this->addActivitySelect($builder, [], true, true, false);
|
||||||
|
|||||||
@@ -155,10 +155,10 @@ trait ToolbarFormTrait
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function addDateRange(FormBuilderInterface $builder, array $options, bool $allowEmpty = true, bool $required = false): void
|
protected function addDateRange(FormBuilderInterface $builder, array $options, bool $allowEmpty = true): void
|
||||||
{
|
{
|
||||||
$params = [
|
$params = [
|
||||||
'required' => $required,
|
'required' => !$allowEmpty,
|
||||||
'allow_empty' => $allowEmpty,
|
'allow_empty' => $allowEmpty,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -134,6 +134,10 @@ final class DateRangeType extends AbstractType
|
|||||||
return $range;
|
return $range;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($dates === null) {
|
||||||
|
throw new TransformationFailedException('Date range missing');
|
||||||
|
}
|
||||||
|
|
||||||
if (preg_match($pattern, $dates) !== 1) {
|
if (preg_match($pattern, $dates) !== 1) {
|
||||||
throw new TransformationFailedException('Invalid date range given');
|
throw new TransformationFailedException('Invalid date range given');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,9 +76,20 @@ final class TagsSelectType extends AbstractType
|
|||||||
$foundTagNames[] = $tag->getName();
|
$foundTagNames[] = $tag->getName();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @var array<string> $newNames */
|
/** @var array<string> $newNamesCreate */
|
||||||
$newNames = array_diff($newNames, $foundTagNames);
|
$newNamesCreate = array_udiff($newNames, $foundTagNames, function (mixed $userTag, mixed $existingTag) {
|
||||||
foreach ($newNames as $name) {
|
if (!\is_string($userTag) || !\is_string($existingTag)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mb_strtolower($userTag) === mb_strtolower($existingTag)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return strcmp($userTag, $existingTag);
|
||||||
|
});
|
||||||
|
|
||||||
|
foreach ($newNamesCreate as $name) {
|
||||||
$tag = new Tag();
|
$tag = new Tag();
|
||||||
$tag->setName(mb_substr($name, 0, 100));
|
$tag->setName(mb_substr($name, 0, 100));
|
||||||
$this->tagRepository->saveTag($tag);
|
$this->tagRepository->saveTag($tag);
|
||||||
|
|||||||
@@ -77,4 +77,9 @@ final class DefaultInvoiceFormatter implements InvoiceFormatter
|
|||||||
$this->locale = $locale;
|
$this->locale = $locale;
|
||||||
$this->formatter = null;
|
$this->formatter = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getFormattedAmount(float $amount): string
|
||||||
|
{
|
||||||
|
return $this->getFormatter()->amount($amount);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ final class InvoiceItemDefaultHydrator implements InvoiceItemHydrator
|
|||||||
|
|
||||||
if ($item->isFixedRate()) {
|
if ($item->isFixedRate()) {
|
||||||
$appliedRate = $item->getFixedRate();
|
$appliedRate = $item->getFixedRate();
|
||||||
$amount = $item->getAmount();
|
$amount = $formatter->getFormattedAmount($item->getAmount());
|
||||||
}
|
}
|
||||||
|
|
||||||
$activity = $item->getActivity();
|
$activity = $item->getActivity();
|
||||||
@@ -83,12 +83,18 @@ final class InvoiceItemDefaultHydrator implements InvoiceItemHydrator
|
|||||||
'entry.date' => $formatter->getFormattedDateTime($begin),
|
'entry.date' => $formatter->getFormattedDateTime($begin),
|
||||||
'entry.week' => \intval($begin->format('W')),
|
'entry.week' => \intval($begin->format('W')),
|
||||||
'entry.weekyear' => $begin->format('o'),
|
'entry.weekyear' => $begin->format('o'),
|
||||||
'entry.user_id' => $user->getId(),
|
|
||||||
'entry.user_name' => $user->getUserIdentifier(),
|
|
||||||
'entry.user_title' => $user->getTitle() ?? '',
|
|
||||||
'entry.user_alias' => $user->getAlias() ?? '',
|
|
||||||
];
|
];
|
||||||
|
|
||||||
|
if (null !== $user) {
|
||||||
|
$values = array_merge($values, [
|
||||||
|
'entry.user_id' => $user->getId(),
|
||||||
|
'entry.user_name' => $user->getUserIdentifier(),
|
||||||
|
'entry.user_title' => $user->getTitle() ?? '',
|
||||||
|
'entry.user_alias' => $user->getAlias() ?? '',
|
||||||
|
'entry.user_display' => $user->getDisplayName() ?? '',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
if (null !== $activity) {
|
if (null !== $activity) {
|
||||||
$values = array_merge($values, [
|
$values = array_merge($values, [
|
||||||
'entry.activity' => $activity->getName(),
|
'entry.activity' => $activity->getName(),
|
||||||
|
|||||||
@@ -35,7 +35,8 @@ final class InvoiceModelCustomerHydrator implements InvoiceModelHydrator
|
|||||||
'customer.name' => $customer->getName() ?? '',
|
'customer.name' => $customer->getName() ?? '',
|
||||||
'customer.contact' => $customer->getContact() ?? '',
|
'customer.contact' => $customer->getContact() ?? '',
|
||||||
'customer.company' => $customer->getCompany() ?? '',
|
'customer.company' => $customer->getCompany() ?? '',
|
||||||
'customer.vat' => $customer->getVatId() ?? '',
|
'customer.vat' => $customer->getVatId() ?? '', // deprecated since 2.0.15
|
||||||
|
'customer.vat_id' => $customer->getVatId() ?? '',
|
||||||
'customer.number' => $customer->getNumber() ?? '',
|
'customer.number' => $customer->getNumber() ?? '',
|
||||||
'customer.country' => $customer->getCountry(),
|
'customer.country' => $customer->getCountry(),
|
||||||
'customer.homepage' => $customer->getHomepage() ?? '',
|
'customer.homepage' => $customer->getHomepage() ?? '',
|
||||||
|
|||||||
@@ -22,9 +22,6 @@ final class InvoiceModelDefaultHydrator implements InvoiceModelHydrator
|
|||||||
$subtotal = $model->getCalculator()->getSubtotal();
|
$subtotal = $model->getCalculator()->getSubtotal();
|
||||||
$formatter = $model->getFormatter();
|
$formatter = $model->getFormatter();
|
||||||
|
|
||||||
$begin = $model->getQuery()->getBegin();
|
|
||||||
$end = $model->getQuery()->getEnd();
|
|
||||||
|
|
||||||
$values = [
|
$values = [
|
||||||
'invoice.due_date' => $formatter->getFormattedDateTime($model->getDueDate()),
|
'invoice.due_date' => $formatter->getFormattedDateTime($model->getDueDate()),
|
||||||
'invoice.date' => $formatter->getFormattedDateTime($model->getInvoiceDate()),
|
'invoice.date' => $formatter->getFormattedDateTime($model->getInvoiceDate()),
|
||||||
@@ -33,6 +30,7 @@ final class InvoiceModelDefaultHydrator implements InvoiceModelHydrator
|
|||||||
'invoice.language' => $model->getTemplate()->getLanguage(), // since 1.9
|
'invoice.language' => $model->getTemplate()->getLanguage(), // since 1.9
|
||||||
'invoice.currency_symbol' => $formatter->getCurrencySymbol($currency),
|
'invoice.currency_symbol' => $formatter->getCurrencySymbol($currency),
|
||||||
'invoice.vat' => $model->getCalculator()->getVat(),
|
'invoice.vat' => $model->getCalculator()->getVat(),
|
||||||
|
'invoice.tax_hide' => $model->isHideZeroTax() && $tax === 0.00,
|
||||||
'invoice.tax' => $formatter->getFormattedMoney($tax, $currency),
|
'invoice.tax' => $formatter->getFormattedMoney($tax, $currency),
|
||||||
'invoice.tax_nc' => $formatter->getFormattedMoney($tax, $currency, false),
|
'invoice.tax_nc' => $formatter->getFormattedMoney($tax, $currency, false),
|
||||||
'invoice.tax_plain' => $tax,
|
'invoice.tax_plain' => $tax,
|
||||||
@@ -65,25 +63,66 @@ final class InvoiceModelDefaultHydrator implements InvoiceModelHydrator
|
|||||||
'query.end_month' => '', // since 1.9
|
'query.end_month' => '', // since 1.9
|
||||||
'query.end_month_number' => '', // since 1.9
|
'query.end_month_number' => '', // since 1.9
|
||||||
'query.end_year' => '', // since 1.9
|
'query.end_year' => '', // since 1.9
|
||||||
|
|
||||||
|
// since 2.0.15
|
||||||
|
'user.see_others' => ($model->getQuery()?->getUser() === null),
|
||||||
];
|
];
|
||||||
|
|
||||||
if ($begin !== null) {
|
$query = $model->getQuery();
|
||||||
$values = array_merge($values, [
|
if ($query !== null) {
|
||||||
'query.day' => $begin->format('d'), // @deprecated - but impossible to delete
|
$begin = $query->getBegin();
|
||||||
'query.month' => $formatter->getFormattedMonthName($begin), // @deprecated - but impossible to delete
|
$end = $query->getEnd();
|
||||||
'query.month_number' => $begin->format('m'), // @deprecated - but impossible to delete
|
|
||||||
'query.year' => $begin->format('Y'), // @deprecated - but impossible to delete
|
if ($begin !== null) {
|
||||||
'query.begin' => $formatter->getFormattedDateTime($begin),
|
$values = array_merge($values, [
|
||||||
'query.begin_day' => $begin->format('d'),
|
'query.day' => $begin->format('d'),
|
||||||
'query.begin_month' => $formatter->getFormattedMonthName($begin),
|
// @deprecated - but impossible to delete
|
||||||
'query.begin_month_number' => $begin->format('m'),
|
'query.month' => $formatter->getFormattedMonthName($begin),
|
||||||
'query.begin_year' => $begin->format('Y'),
|
// @deprecated - but impossible to delete
|
||||||
'query.end' => $formatter->getFormattedDateTime($end), // since 1.9
|
'query.month_number' => $begin->format('m'),
|
||||||
'query.end_day' => $end->format('d'), // since 1.9
|
// @deprecated - but impossible to delete
|
||||||
'query.end_month' => $formatter->getFormattedMonthName($end), // since 1.9
|
'query.year' => $begin->format('Y'),
|
||||||
'query.end_month_number' => $end->format('m'), // since 1.9
|
// @deprecated - but impossible to delete
|
||||||
'query.end_year' => $end->format('Y'), // since 1.9
|
'query.begin' => $formatter->getFormattedDateTime($begin),
|
||||||
]);
|
'query.begin_day' => $begin->format('d'),
|
||||||
|
'query.begin_month' => $formatter->getFormattedMonthName($begin),
|
||||||
|
'query.begin_month_number' => $begin->format('m'),
|
||||||
|
'query.begin_year' => $begin->format('Y'),
|
||||||
|
'query.end' => $formatter->getFormattedDateTime($end),
|
||||||
|
// since 1.9
|
||||||
|
'query.end_day' => $end->format('d'),
|
||||||
|
// since 1.9
|
||||||
|
'query.end_month' => $formatter->getFormattedMonthName($end),
|
||||||
|
// since 1.9
|
||||||
|
'query.end_month_number' => $end->format('m'),
|
||||||
|
// since 1.9
|
||||||
|
'query.end_year' => $end->format('Y'),
|
||||||
|
// since 1.9
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// since 2.0.15
|
||||||
|
$activity = $query->getActivity();
|
||||||
|
if ($activity !== null) {
|
||||||
|
$prefix = 'query.activity.';
|
||||||
|
|
||||||
|
$values = array_merge($values, [
|
||||||
|
$prefix . 'name' => $activity->getName() ?? '',
|
||||||
|
$prefix . 'comment' => $activity->getComment() ?? '',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// since 2.0.15
|
||||||
|
$project = $query->getProject();
|
||||||
|
if ($project !== null) {
|
||||||
|
$prefix = 'query.project.';
|
||||||
|
|
||||||
|
$values = array_merge($values, [
|
||||||
|
$prefix . 'name' => $project->getName() ?? '',
|
||||||
|
$prefix . 'comment' => $project->getComment() ?? '',
|
||||||
|
$prefix . 'order_number' => $project->getOrderNumber(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$entries = $model->getEntries();
|
$entries = $model->getEntries();
|
||||||
|
|||||||
@@ -9,7 +9,6 @@
|
|||||||
|
|
||||||
namespace App\Invoice\Hydrator;
|
namespace App\Invoice\Hydrator;
|
||||||
|
|
||||||
use App\Entity\UserPreference;
|
|
||||||
use App\Invoice\InvoiceModel;
|
use App\Invoice\InvoiceModel;
|
||||||
use App\Invoice\InvoiceModelHydrator;
|
use App\Invoice\InvoiceModelHydrator;
|
||||||
|
|
||||||
@@ -28,9 +27,9 @@ final class InvoiceModelUserHydrator implements InvoiceModelHydrator
|
|||||||
'user.email' => $user->getEmail(),
|
'user.email' => $user->getEmail(),
|
||||||
'user.title' => $user->getTitle() ?? '',
|
'user.title' => $user->getTitle() ?? '',
|
||||||
'user.alias' => $user->getAlias() ?? '',
|
'user.alias' => $user->getAlias() ?? '',
|
||||||
|
'user.display' => $user->getDisplayName() ?? '',
|
||||||
];
|
];
|
||||||
|
|
||||||
/** @var UserPreference $metaField */
|
|
||||||
foreach ($user->getPreferences() as $metaField) {
|
foreach ($user->getPreferences() as $metaField) {
|
||||||
$values = array_merge($values, [
|
$values = array_merge($values, [
|
||||||
'user.meta.' . $metaField->getName() => $metaField->getValue(),
|
'user.meta.' . $metaField->getName() => $metaField->getValue(),
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ interface InvoiceFormatter
|
|||||||
|
|
||||||
public function getFormattedTime(DateTime $date): string;
|
public function getFormattedTime(DateTime $date): string;
|
||||||
|
|
||||||
|
public function getFormattedAmount(float $amount): string;
|
||||||
|
|
||||||
public function getFormattedMoney(float $amount, ?string $currency, bool $withCurrency = true): string;
|
public function getFormattedMoney(float $amount, ?string $currency, bool $withCurrency = true): string;
|
||||||
|
|
||||||
public function getFormattedMonthName(DateTime $date): string;
|
public function getFormattedMonthName(DateTime $date): string;
|
||||||
|
|||||||
@@ -278,7 +278,7 @@ final class ServiceInvoice
|
|||||||
{
|
{
|
||||||
$document = $this->getDocumentByName($model->getTemplate()->getRenderer());
|
$document = $this->getDocumentByName($model->getTemplate()->getRenderer());
|
||||||
if (null === $document) {
|
if (null === $document) {
|
||||||
throw new \Exception('Unknown invoice document: ' . $model->getTemplate()->getRenderer());
|
throw new \Exception('Please adjust your invoice template, the renderer is invalid: ' . $model->getTemplate()->getRenderer());
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($this->getRenderer() as $renderer) {
|
foreach ($this->getRenderer() as $renderer) {
|
||||||
@@ -426,7 +426,7 @@ final class ServiceInvoice
|
|||||||
|
|
||||||
$calculator = $this->getCalculatorByName($template->getCalculator());
|
$calculator = $this->getCalculatorByName($template->getCalculator());
|
||||||
if (null === $calculator) {
|
if (null === $calculator) {
|
||||||
throw new \Exception('Please adjust your invoice template, the invoice calculator is invalid: ' . $template->getCalculator());
|
throw new \Exception('Please adjust your invoice template, the sum calculator is invalid: ' . $template->getCalculator());
|
||||||
}
|
}
|
||||||
|
|
||||||
$model->setCalculator($calculator);
|
$model->setCalculator($calculator);
|
||||||
|
|||||||
@@ -93,6 +93,14 @@ class TimesheetQuery extends ActivityQuery implements BillableInterface
|
|||||||
return array_values($this->users);
|
return array_values($this->users);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if there is one or more users in the query
|
||||||
|
*/
|
||||||
|
public function hasUsers(): bool
|
||||||
|
{
|
||||||
|
return $this->timesheetUser !== null || \count($this->users) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Limit the data exclusively to the user.
|
* Limit the data exclusively to the user.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -66,28 +66,6 @@ class TagRepository extends EntityRepository
|
|||||||
return $this->findOneBy(['name' => $tagName]);
|
return $this->findOneBy(['name' => $tagName]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Find ids of the given tagNames separated by comma
|
|
||||||
* @param string $tagNames
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function findIdsByTagNameList(string $tagNames): array
|
|
||||||
{
|
|
||||||
$qb = $this
|
|
||||||
->createQueryBuilder('t')
|
|
||||||
->select('t.id');
|
|
||||||
$list = array_filter(array_unique(array_map('trim', explode(',', $tagNames))));
|
|
||||||
$cnt = 0;
|
|
||||||
foreach ($list as $listElem) {
|
|
||||||
$qb
|
|
||||||
->orWhere('t.name like :elem' . $cnt)
|
|
||||||
->setParameter('elem' . $cnt, '%' . $listElem . '%');
|
|
||||||
$cnt++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return array_column($qb->getQuery()->getScalarResult(), 'id');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find all tag names in an alphabetical order
|
* Find all tag names in an alphabetical order
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -129,7 +129,7 @@
|
|||||||
<td class="text-nowrap text-end">{{ stats.internalRate|money(currency) }}</td>
|
<td class="text-nowrap text-end">{{ stats.internalRate|money(currency) }}</td>
|
||||||
<td class="text-nowrap text-end">{{ percentReached|number_format(2) }}%</td>
|
<td class="text-nowrap text-end">{{ percentReached|number_format(2) }}%</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% if not entity.isMonthlyBudget() and timeBudgetVisible and stats.duration > 0 %}
|
{% if not entity.isMonthlyBudget() and timeBudgetVisible and stats.duration > 0 and stats.duration|chart_duration > 0.00 %}
|
||||||
{% set realHourlyRate = stats.rateBillable / stats.duration|chart_duration %}
|
{% set realHourlyRate = stats.rateBillable / stats.duration|chart_duration %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ 'hourlyRate'|trans }} ({{ 'billable'|trans }} / {{ durationTrans|trans }})</td>
|
<td>{{ 'hourlyRate'|trans }} ({{ 'billable'|trans }} / {{ durationTrans|trans }})</td>
|
||||||
|
|||||||
@@ -1,13 +1,8 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
{%- set fallback = app.request is not null ? app.request.locale : 'en' -%}
|
<html lang="{{ invoice['invoice.language'] }}">
|
||||||
{%- set language = model.template.language|default(fallback) -%}
|
|
||||||
{%- set isDecimal = true -%}
|
|
||||||
{%- set currency = model.currency -%}
|
|
||||||
{%- set project = model.query.project -%}
|
|
||||||
<html lang="{{ language }}">
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="utf-8">
|
||||||
<title>{% block title %}{{ model.invoiceNumber }}-{{ model.customer.company|default(model.customer.name)|u.snake }}{% endblock %}</title>
|
<title>{% block title %}{{ invoice['invoice.number'] }}-{{ invoice['customer.company']|default(invoice['customer.name'])|u.snake }}{% endblock %}</title>
|
||||||
<style>
|
<style>
|
||||||
{{ encore_entry_css_source('invoice-pdf')|raw }}
|
{{ encore_entry_css_source('invoice-pdf')|raw }}
|
||||||
</style>
|
</style>
|
||||||
@@ -18,10 +13,10 @@
|
|||||||
<table class="header">
|
<table class="header">
|
||||||
<tr>
|
<tr>
|
||||||
<td class="title">
|
<td class="title">
|
||||||
{{ model.template.title }}
|
{{ invoice['template.title'] }}
|
||||||
</td>
|
</td>
|
||||||
<td class="date text-right">
|
<td class="date text-right">
|
||||||
{{ 'date'|trans }}: {{ model.invoiceDate|date_short }}
|
{{ 'date'|trans }}: {{ invoice['invoice.date'] }}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
@@ -32,10 +27,10 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<strong>{{ 'contact'|trans }}</strong>:
|
<strong>{{ 'contact'|trans }}</strong>:
|
||||||
{{ model.template.contact|nl2str(' – ') }}
|
{{ invoice['template.contact']|nl2str(' – ') }}
|
||||||
<br>
|
<br>
|
||||||
<strong>{{ 'invoice_bank_account'|trans }}</strong>:
|
<strong>{{ 'invoice_bank_account'|trans }}</strong>:
|
||||||
{{ model.template.paymentDetails|nl2str(' – ') }}
|
{{ invoice['template.payment_details']|nl2str(' – ') }}
|
||||||
</td>
|
</td>
|
||||||
<td align="right">
|
<td align="right">
|
||||||
{{ 'export.page_of'|trans({'%page%': '{PAGENO}', '%pages%': '{nb}'}) }}
|
{{ 'export.page_of'|trans({'%page%': '{PAGENO}', '%pages%': '{nb}'}) }}
|
||||||
@@ -50,24 +45,24 @@ mpdf-->
|
|||||||
<td style="width:60%">
|
<td style="width:60%">
|
||||||
{{ 'invoice.to'|trans }}
|
{{ 'invoice.to'|trans }}
|
||||||
<br>
|
<br>
|
||||||
<strong>{{ model.customer.company|default(model.customer.name) }}</strong>
|
<strong>{{ invoice['customer.company']|default(invoice['customer.name']) }}</strong>
|
||||||
<br>
|
<br>
|
||||||
{{ model.customer.address|nl2br }}
|
{{ invoice['customer.address']|nl2br }}
|
||||||
{% if model.customer.vatId is not empty %}
|
{% if invoice['customer.vat_id'] is not empty %}
|
||||||
<br>
|
<br>
|
||||||
{{ 'vat_id'|trans }}: {{ model.customer.vatId }}
|
{{ 'vat_id'|trans }}: {{ invoice['customer.vat_id'] }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{ 'invoice.from'|trans }}
|
{{ 'invoice.from'|trans }}
|
||||||
<br>
|
<br>
|
||||||
<strong>{{ model.template.company }}</strong>
|
<strong>{{ invoice['template.company'] }}</strong>
|
||||||
<br>
|
<br>
|
||||||
{{ model.template.address|trim|nl2br }}
|
{{ invoice['template.address']|trim|nl2br }}
|
||||||
{% if model.template.vatId is not empty %}
|
{% if invoice['template.vat_id'] is not empty %}
|
||||||
<br>
|
<br>
|
||||||
{{ 'vat_id'|trans }}:
|
{{ 'vat_id'|trans }}:
|
||||||
{{ model.template.vatId }}
|
{{ invoice['template.vat_id'] }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -75,27 +70,27 @@ mpdf-->
|
|||||||
|
|
||||||
<p>
|
<p>
|
||||||
{{ 'invoice.number'|trans }}:
|
{{ 'invoice.number'|trans }}:
|
||||||
{{ model.invoiceNumber }}
|
{{ invoice['invoice.number'] }}
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
{{ 'invoice.due_days'|trans }}:
|
{{ 'invoice.due_days'|trans }}:
|
||||||
{{ model.dueDate|date_short }}
|
{{ invoice['invoice.due_date'] }}
|
||||||
|
|
||||||
{% if model.customer.number is not empty %}
|
{% if invoice['customer.number'] is not empty %}
|
||||||
<br>
|
<br>
|
||||||
{{ 'number'|trans }}:
|
{{ 'number'|trans }}:
|
||||||
{{ model.customer.number }}
|
{{ invoice['customer.number'] }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if project is not null and project.orderNumber is not empty %}
|
{% if invoice['query.project.order_number'] is defined and invoice['query.project.order_number'] is not empty %}
|
||||||
<br>
|
<br>
|
||||||
{{ 'orderNumber'|trans }}:
|
{{ 'orderNumber'|trans }}:
|
||||||
{{ project.orderNumber }}
|
{{ invoice['query.project.order_number'] }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{% if model.customer.invoiceText is not empty%}
|
{% if invoice['customer.invoice_text'] is not empty%}
|
||||||
<p>{{ model.customer.invoiceText|md2html }}</p>
|
<p>{{ invoice['customer.invoice_text']|md2html }}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<table class="items">
|
<table class="items">
|
||||||
@@ -109,43 +104,39 @@ mpdf-->
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for id, entry in model.calculator.entries %}
|
{% for invoiceLineItem in entries %}
|
||||||
<!-- CONTENT_PART -->
|
<!-- CONTENT_PART -->
|
||||||
{% set duration = entry.duration|duration(isDecimal) %}
|
|
||||||
{% if entry.fixedRate %}
|
|
||||||
{% set rate = entry.fixedRate %}
|
|
||||||
{% set duration = entry.amount|amount %}
|
|
||||||
{% else %}
|
|
||||||
{% set rate = entry.hourlyRate %}
|
|
||||||
{% endif %}
|
|
||||||
<tr>
|
<tr>
|
||||||
<td nowrap class="first text-nowrap">{{ entry.begin|date_short }}</td>
|
<td nowrap class="first text-nowrap">{{ invoiceLineItem['entry.begin'] }}</td>
|
||||||
<td contenteditable="true">
|
<td>
|
||||||
{% if entry.description is not empty %}
|
{% if invoiceLineItem['entry.description'] is not empty %}
|
||||||
{{ entry.description|nl2br }}
|
{{ invoiceLineItem['entry.description']|nl2br }}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% if entry.activity is not null %}{{ entry.activity.name }} / {% endif %}{{ entry.project.name }}
|
{% if invoiceLineItem['entry.activity'] is defined %}
|
||||||
|
{{ invoiceLineItem['entry.activity'] }} /
|
||||||
|
{% endif %}
|
||||||
|
{{ invoiceLineItem['entry.project'] }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td nowrap class="text-nowrap text-right">{{ rate|money(currency) }}</td>
|
<td nowrap class="text-nowrap text-right">{{ invoiceLineItem['entry.rate'] }}</td>
|
||||||
<td nowrap class="text-nowrap text-right">{{ duration }}</td>
|
<td nowrap class="text-nowrap text-right">{{ invoiceLineItem['entry.amount'] }}</td>
|
||||||
<td nowrap class="last text-nowrap text-right">{{ entry.rate|money(currency) }}</td>
|
<td nowrap class="last text-nowrap text-right">{{ invoiceLineItem['entry.total'] }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
<tfoot>
|
<tfoot>
|
||||||
{% if model.calculator.tax > 0 or not model.isHideZeroTax() %}
|
{% if not invoice['invoice.tax_hide'] %}
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="4" class="text-right">
|
<td colspan="4" class="text-right">
|
||||||
{{ 'invoice.subtotal'|trans }}
|
{{ 'invoice.subtotal'|trans }}
|
||||||
</td>
|
</td>
|
||||||
<td class="last text-right">{{ model.calculator.subtotal|money(currency) }}</td>
|
<td class="last text-right">{{ invoice['invoice.subtotal'] }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="4" class="text-right">
|
<td colspan="4" class="text-right">
|
||||||
{{ 'invoice.tax'|trans }} ({{ model.calculator.vat }}%)
|
{{ 'invoice.tax'|trans }} ({{ invoice['invoice.vat'] }}%)
|
||||||
</td>
|
</td>
|
||||||
<td class="last text-right">{{ model.calculator.tax|money(currency) }}</td>
|
<td class="last text-right">{{ invoice['invoice.tax'] }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<tr>
|
<tr>
|
||||||
@@ -153,16 +144,16 @@ mpdf-->
|
|||||||
<strong>{{ 'invoice.total'|trans }}</strong>
|
<strong>{{ 'invoice.total'|trans }}</strong>
|
||||||
</td>
|
</td>
|
||||||
<td class="last text-right">
|
<td class="last text-right">
|
||||||
<strong>{{ model.calculator.total|money(currency) }}</strong>
|
<strong>{{ invoice['invoice.total'] }}</strong>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tfoot>
|
</tfoot>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
{% if model.template.paymentTerms is not empty %}
|
{% if invoice['template.payment_terms'] is not empty %}
|
||||||
<div class="paymentTerms">
|
<div class="paymentTerms">
|
||||||
{{ model.template.paymentTerms|md2html }}
|
{{ invoice['template.payment_terms']|md2html }}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1,166 +1,158 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
{% set fallback = app.request is not null ? app.request.locale : 'en' %}
|
<html lang="{{ invoice['invoice.language'] }}">
|
||||||
{% set language = model.template.language|default(fallback) %}
|
|
||||||
{% set isDecimal = true %}
|
|
||||||
{% set currency = model.currency %}
|
|
||||||
{% set project = model.query.project %}
|
|
||||||
<html lang="{{ language }}">
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<title>{% block title %}{{ invoice['invoice.number'] }}-{{ invoice['customer.company']|default(invoice['customer.name'])|u.snake }}{% endblock %}</title>
|
||||||
<meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport">
|
<style>
|
||||||
<title>{% block title %}{{ model.invoiceNumber }}-{{ model.customer.company|default(model.customer.name)|u.snake }}{% endblock %}</title>
|
|
||||||
<style type="text/css">
|
|
||||||
{{ encore_entry_css_source('invoice')|raw }}
|
{{ encore_entry_css_source('invoice')|raw }}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body class="invoice_print">
|
<body class="invoice_print">
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
<section class="invoice">
|
<section class="invoice">
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-12">
|
<div class="col-xs-12">
|
||||||
<h2 class="page-header">
|
<h2 class="page-header">
|
||||||
<span contenteditable="true">{{ model.template.title }}</span>
|
<span contenteditable="true">{{ invoice['template.title'] }}</span>
|
||||||
<small class="pull-right">{{ 'date'|trans }}: {{ model.invoiceDate|date_short }}</small>
|
</h2>
|
||||||
</h2>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-5">
|
<div class="col-sm-5">
|
||||||
{{ 'invoice.to'|trans }}
|
{{ 'invoice.to'|trans }}
|
||||||
<address contenteditable="true">
|
<address contenteditable="true">
|
||||||
<strong>{{ model.customer.company|default(model.customer.name) }}</strong><br>
|
<strong>{{ invoice['customer.company']|default(invoice['customer.name']) }}</strong><br>
|
||||||
{{ model.customer.address|nl2br }}
|
{{ invoice['customer.address']|nl2br }}
|
||||||
{% if model.customer.vatId is not empty %}
|
{% if invoice['customer.vat_id'] is not empty %}
|
||||||
<br>
|
<br>
|
||||||
{{ 'vat_id'|trans }}: {{ model.customer.vatId }}
|
{{ 'vat_id'|trans }}: {{ invoice['customer.vat_id'] }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if model.customer.number is not empty %}
|
{% if invoice['customer.number'] is not empty %}
|
||||||
<br>
|
<br>
|
||||||
{{ 'number'|trans }}: {{ model.customer.number }}
|
{{ 'number'|trans }}: {{ invoice['customer.number'] }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if project is not null and project.orderNumber is not empty %}
|
{% if invoice['query.project.order_number'] is defined and invoice['query.project.order_number'] is not empty %}
|
||||||
<br>
|
<br>
|
||||||
{{ 'orderNumber'|trans }}: {{ project.orderNumber }}
|
{{ 'orderNumber'|trans }}: {{ invoice['query.project.order_number'] }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</address>
|
</address>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-2"></div>
|
||||||
|
<div class="col-sm-5">
|
||||||
|
{{ 'invoice.from'|trans }}
|
||||||
|
<address contenteditable="true">
|
||||||
|
<strong>{{ invoice['template.company'] }}</strong><br>
|
||||||
|
{{ invoice['template.address']|trim|nl2br }}
|
||||||
|
{% if invoice['template.vat_id'] is not empty %}
|
||||||
|
<br>
|
||||||
|
{{ 'vat_id'|trans }}:
|
||||||
|
{{ invoice['template.vat_id'] }}
|
||||||
|
{% endif %}
|
||||||
|
</address>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-2"></div>
|
|
||||||
<div class="col-sm-5">
|
<div class="row">
|
||||||
{{ 'invoice.from'|trans }}
|
<div class="col-sm-5">
|
||||||
<address contenteditable="true">
|
<p contenteditable="true">
|
||||||
<strong>{{ model.template.company }}</strong><br>
|
<strong>{{ 'date'|trans }}:</strong>
|
||||||
{{ model.template.address|trim|nl2br }}
|
{{ invoice['invoice.date'] }}
|
||||||
{% if model.template.vatId is not empty %}
|
|
||||||
<br>
|
<br>
|
||||||
{{ 'vat_id'|trans }}:
|
<strong>{{ 'invoice.number'|trans }}:</strong>
|
||||||
{{ model.template.vatId }}
|
{{ invoice['invoice.number'] }}
|
||||||
{% endif %}
|
|
||||||
</address>
|
<br>
|
||||||
|
<strong>{{ 'invoice.due_days'|trans }}:</strong>
|
||||||
|
{{ invoice['invoice.due_date'] }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-7"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
<div class="row invoice-items mt-2 mb-3">
|
||||||
<div class="col-sm-5">
|
<div class="col-xs-12 table-responsive">
|
||||||
<p contenteditable="true">
|
<table class="table">
|
||||||
<strong>{{ 'invoice.number'|trans }}:</strong>
|
<thead>
|
||||||
{{ model.invoiceNumber }}
|
<tr>
|
||||||
|
<th>{{ 'date'|trans }}</th>
|
||||||
<br>
|
<th>{{ 'description'|trans }}</th>
|
||||||
<strong>{{ 'invoice.due_days'|trans }}:</strong>
|
<th class="text-end">{{ 'unit_price'|trans }}</th>
|
||||||
{{ model.dueDate|date_short }}
|
<th class="text-end">{{ 'amount'|trans }}</th>
|
||||||
</p>
|
<th class="text-end">{{ 'total_rate'|trans }}</th>
|
||||||
</div>
|
</tr>
|
||||||
<div class="col-sm-7"></div>
|
</thead>
|
||||||
</div>
|
<tbody>
|
||||||
|
{% for invoiceLineItem in entries %}
|
||||||
<div class="row invoice-items mt-2 mb-3">
|
<tr>
|
||||||
<div class="col-xs-12 table-responsive">
|
<td nowrap class="text-nowrap">{{ invoiceLineItem['entry.begin'] }}</td>
|
||||||
<table class="table">
|
<td contenteditable="true">
|
||||||
<thead>
|
{% if invoiceLineItem['entry.description'] is not empty %}
|
||||||
<tr>
|
{{ invoiceLineItem['entry.description']|nl2br }}
|
||||||
<th>{{ 'date'|trans }}</th>
|
{% else %}
|
||||||
<th>{{ 'description'|trans }}</th>
|
{% if invoiceLineItem['entry.activity'] is defined %}
|
||||||
<th class="text-end">{{ 'unit_price'|trans }}</th>
|
{{ invoiceLineItem['entry.activity'] }} /
|
||||||
<th class="text-end">{{ 'amount'|trans }}</th>
|
{% endif %}
|
||||||
<th class="text-end">{{ 'total_rate'|trans }}</th>
|
{{ invoiceLineItem['entry.project'] }}
|
||||||
</tr>
|
{% endif %}
|
||||||
</thead>
|
</td>
|
||||||
<tbody>
|
<td nowrap class="text-nowrap text-end">{{ invoiceLineItem['entry.rate'] }}</td>
|
||||||
{% for entry in model.calculator.entries %}
|
<td nowrap class="text-nowrap text-end">{{ invoiceLineItem['entry.amount'] }}</td>
|
||||||
{% set duration = entry.duration|duration(isDecimal) %}
|
<td nowrap class="text-nowrap text-end">{{ invoiceLineItem['entry.total'] }}</td>
|
||||||
{% if entry.fixedRate %}
|
</tr>
|
||||||
{% set rate = entry.fixedRate %}
|
{% endfor %}
|
||||||
{% set duration = entry.amount|amount %}
|
</tbody>
|
||||||
{% else %}
|
<tfoot>
|
||||||
{% set rate = entry.hourlyRate %}
|
{% if not invoice['invoice.tax_hide'] %}
|
||||||
|
<tr>
|
||||||
|
<td colspan="4" class="text-end">
|
||||||
|
{{ 'invoice.subtotal'|trans }}
|
||||||
|
</td>
|
||||||
|
<td class="text-end">{{ invoice['invoice.subtotal'] }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="4" class="text-end">
|
||||||
|
{{ 'invoice.tax'|trans }} ({{ invoice['invoice.vat'] }}%)
|
||||||
|
</td>
|
||||||
|
<td class="text-end">{{ invoice['invoice.tax'] }}</td>
|
||||||
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<tr>
|
<tr>
|
||||||
<td nowrap class="text-nowrap">{{ entry.begin|date_short }}</td>
|
<td colspan="4" class="text-end text-nowrap">
|
||||||
<td contenteditable="true">
|
<strong>{{ 'invoice.total'|trans }}</strong>
|
||||||
{% if entry.description is not empty %}
|
|
||||||
{{ entry.description|nl2br }}
|
|
||||||
{% else %}
|
|
||||||
{% if entry.activity is not null %}{{ entry.activity.name }} / {% endif %}{{ entry.project.name }}
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
</td>
|
||||||
<td nowrap class="text-nowrap text-end">{{ rate|money(currency) }}</td>
|
<td class="text-end">
|
||||||
<td nowrap class="text-nowrap text-end">{{ duration }}</td>
|
<strong>{{ invoice['invoice.total'] }}</strong>
|
||||||
<td nowrap class="text-nowrap text-end">{{ entry.rate|money(currency) }}</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
<tfoot>
|
|
||||||
{% if model.calculator.tax > 0 or not model.isHideZeroTax() %}
|
|
||||||
<tr>
|
|
||||||
<td colspan="4" class="text-end">
|
|
||||||
{{ 'invoice.subtotal'|trans }}
|
|
||||||
</td>
|
</td>
|
||||||
<td class="text-end">{{ model.calculator.subtotal|money(currency) }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td colspan="4" class="text-end">
|
|
||||||
{{ 'invoice.tax'|trans }} ({{ model.calculator.vat }}%)
|
|
||||||
</td>
|
|
||||||
<td class="text-end">{{ model.calculator.tax|money(currency) }}</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
|
</tfoot>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-12">
|
||||||
|
{% if invoice['template.payment_terms'] is not empty %}
|
||||||
|
<div contenteditable="true" class="paymentTerms">
|
||||||
|
{{ invoice['template.payment_terms']|md2html }}
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<tr>
|
</div>
|
||||||
<td colspan="4" class="text-end text-nowrap">
|
|
||||||
<strong>{{ 'invoice.total'|trans }}</strong>
|
|
||||||
</td>
|
|
||||||
<td class="text-end">
|
|
||||||
<strong>{{ model.calculator.total|money(currency) }}</strong>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tfoot>
|
|
||||||
</table>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
<footer class="footer">
|
||||||
<div class="col-xs-12">
|
<p>
|
||||||
{% if model.template.paymentTerms is not empty %}
|
<strong>{{ 'address'|trans }}</strong>: {{ invoice['template.company'] }} – {{ invoice['template.address']|nl2str(' – ') }}
|
||||||
<div contenteditable="true" class="paymentTerms">
|
<br>
|
||||||
{{ model.template.paymentTerms|md2html }}
|
<strong>{{ 'invoice_bank_account'|trans }}</strong>: {{ invoice['template.payment_details']|nl2str(' – ') }}
|
||||||
</div>
|
<br>
|
||||||
{% endif %}
|
<strong>{{ 'contact'|trans }}</strong>: {{ invoice['template.contact']|nl2str(' – ') }}
|
||||||
</div>
|
</p>
|
||||||
</div>
|
</footer>
|
||||||
|
|
||||||
<footer class="footer">
|
</section>
|
||||||
<p>
|
</div>
|
||||||
<strong>{{ 'address'|trans }}</strong>: {{ model.template.company }} – {{ model.template.address|nl2str(' – ') }}
|
|
||||||
<br>
|
|
||||||
<strong>{{ 'invoice_bank_account'|trans }}</strong>: {{ model.template.paymentDetails|nl2str(' – ') }}
|
|
||||||
<br>
|
|
||||||
<strong>{{ 'contact'|trans }}</strong>: {{ model.template.contact|nl2str(' – ') }}
|
|
||||||
</p>
|
|
||||||
</footer>
|
|
||||||
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1,13 +1,8 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
{%- set fallback = app.request is not null ? app.request.locale : 'en' -%}
|
<html lang="{{ invoice['invoice.language'] }}">
|
||||||
{%- set language = model.template.language|default(fallback) -%}
|
|
||||||
{%- set isDecimal = true -%}
|
|
||||||
{%- set currency = model.currency -%}
|
|
||||||
{%- set project = model.query.project -%}
|
|
||||||
<html lang="{{ language }}">
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="utf-8">
|
||||||
<title>{% block title %}{{ model.invoiceNumber }}-{{ model.customer.company|default(model.customer.name)|u.snake }}{% endblock %}</title>
|
<title>{% block title %}{{ invoice['invoice.number'] }}-{{ invoice['customer.company']|default(invoice['customer.name'])|u.snake }}{% endblock %}</title>
|
||||||
<style>
|
<style>
|
||||||
{{ encore_entry_css_source('invoice-pdf')|raw }}
|
{{ encore_entry_css_source('invoice-pdf')|raw }}
|
||||||
</style>
|
</style>
|
||||||
@@ -18,9 +13,9 @@
|
|||||||
<table class="header">
|
<table class="header">
|
||||||
<tr>
|
<tr>
|
||||||
<td class="text-small">
|
<td class="text-small">
|
||||||
{{ model.template.company }} – {{ model.template.address|nl2str(' – ') }}
|
{{ invoice['template.company'] }} – {{ invoice['template.address']|nl2str(' – ') }}
|
||||||
{% if model.template.vatId is not empty %}
|
{% if invoice['template.vat_id'] is not empty %}
|
||||||
– {{ 'vat_id'|trans }}: {{ model.template.vatId }}
|
– {{ 'vat_id'|trans }}: {{ invoice['template.vat_id'] }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td class="text-small text-right">
|
<td class="text-small text-right">
|
||||||
@@ -36,18 +31,18 @@
|
|||||||
<td style="width: 33%">
|
<td style="width: 33%">
|
||||||
<strong>{{ 'address'|trans }}</strong>
|
<strong>{{ 'address'|trans }}</strong>
|
||||||
<br>
|
<br>
|
||||||
{{ model.template.company }}<br>
|
{{ invoice['template.company'] }}<br>
|
||||||
{{ model.template.address|nl2br }}
|
{{ invoice['template.address']|nl2br }}
|
||||||
</td>
|
</td>
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
<strong>{{ 'invoice_bank_account'|trans }}</strong>
|
<strong>{{ 'invoice_bank_account'|trans }}</strong>
|
||||||
<br>
|
<br>
|
||||||
{{ model.template.paymentDetails|nl2br }}
|
{{ invoice['template.payment_details']|nl2br }}
|
||||||
</td>
|
</td>
|
||||||
<td class="text-right" style="width: 33%">
|
<td class="text-right" style="width: 33%">
|
||||||
<strong>{{ 'contact'|trans }}</strong>
|
<strong>{{ 'contact'|trans }}</strong>
|
||||||
<br>
|
<br>
|
||||||
{{ model.template.contact|nl2br }}
|
{{ invoice['template.contact']|nl2br }}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
@@ -57,8 +52,8 @@ mpdf-->
|
|||||||
<table class="addresses">
|
<table class="addresses">
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<strong>{{ model.customer.company|default(model.customer.name) }}</strong><br>
|
<strong>{{ invoice['customer.company']|default(invoice['customer.name']) }}</strong><br>
|
||||||
{{ model.customer.address|nl2br }}
|
{{ invoice['customer.address']|nl2br }}
|
||||||
</td>
|
</td>
|
||||||
<td class="text-right">
|
<td class="text-right">
|
||||||
{% set classLeft = 'text-left' %}
|
{% set classLeft = 'text-left' %}
|
||||||
@@ -66,36 +61,36 @@ mpdf-->
|
|||||||
<table style="width: 240px">
|
<table style="width: 240px">
|
||||||
<tr>
|
<tr>
|
||||||
<td class="{{ classLeft }}">{{ 'date'|trans }}</td>
|
<td class="{{ classLeft }}">{{ 'date'|trans }}</td>
|
||||||
<td class="{{ classRight }}">{{ model.invoiceDate|date_short }}</td>
|
<td class="{{ classRight }}">{{ invoice['invoice.date'] }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="{{ classLeft }}">{{ 'invoice.service_date'|trans }}</td>
|
<td class="{{ classLeft }}">{{ 'invoice.service_date'|trans }}</td>
|
||||||
<td class="{{ classRight }}">{{ model.query.end|month_name }} {{ model.query.end|date('Y') }}</td>
|
<td class="{{ classRight }}">{{ invoice['query.end_month'] }} {{ invoice['query.end_year'] }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="{{ classLeft }}">{{ 'invoice.number'|trans }}</td>
|
<td class="{{ classLeft }}">{{ 'invoice.number'|trans }}</td>
|
||||||
<td class="{{ classRight }}">{{ model.invoiceNumber }}</td>
|
<td class="{{ classRight }}">{{ invoice['invoice.number'] }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="{{ classLeft }}">{{ 'invoice.due_days'|trans }}</td>
|
<td class="{{ classLeft }}">{{ 'invoice.due_days'|trans }}</td>
|
||||||
<td class="{{ classRight }}">{{ model.dueDate|date_short }}</td>
|
<td class="{{ classRight }}">{{ invoice['invoice.due_date'] }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% if model.customer.number is not empty %}
|
{% if invoice['customer.number'] is not empty %}
|
||||||
<tr>
|
<tr>
|
||||||
<td class="{{ classLeft }}">{{ 'number'|trans }}</td>
|
<td class="{{ classLeft }}">{{ 'number'|trans }}</td>
|
||||||
<td class="{{ classRight }}">{{ model.customer.number }}</td>
|
<td class="{{ classRight }}">{{ invoice['customer.number'] }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if project is not null and project.orderNumber is not empty %}
|
{% if invoice['query.project.order_number'] is defined and invoice['query.project.order_number'] is not empty %}
|
||||||
<tr>
|
<tr>
|
||||||
<td class="{{ classLeft }}">{{ 'orderNumber'|trans }}</td>
|
<td class="{{ classLeft }}">{{ 'orderNumber'|trans }}</td>
|
||||||
<td class="{{ classRight }}">{{ project.orderNumber }}</td>
|
<td class="{{ classRight }}">{{ invoice['query.project.order_number'] }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if model.customer.vatId is not empty %}
|
{% if invoice['customer.vat_id'] is not empty %}
|
||||||
<tr>
|
<tr>
|
||||||
<td class="{{ classLeft }}">{{ 'vat_id'|trans }}</td>
|
<td class="{{ classLeft }}">{{ 'vat_id'|trans }}</td>
|
||||||
<td class="{{ classRight }}">{{ model.customer.vatId }}</td>
|
<td class="{{ classRight }}">{{ invoice['customer.vat_id'] }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</table>
|
</table>
|
||||||
@@ -103,10 +98,10 @@ mpdf-->
|
|||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<h2 style="margin-top: 60px">{{ model.template.title }}</h2>
|
<h2 style="margin-top: 60px">{{ invoice['template.title'] }}</h2>
|
||||||
|
|
||||||
{% if model.customer.invoiceText is not empty%}
|
{% if invoice['customer.invoice_text'] is not empty%}
|
||||||
<p>{{ model.customer.invoiceText|md2html }}</p>
|
<p>{{ invoice['customer.invoice_text']|md2html }}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<table class="items">
|
<table class="items">
|
||||||
@@ -119,43 +114,38 @@ mpdf-->
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for entry in model.calculator.entries %}
|
{% for invoiceLineItem in entries %}
|
||||||
<!-- CONTENT_PART -->
|
<!-- CONTENT_PART -->
|
||||||
{% set duration = entry.duration|duration(isDecimal) %}
|
|
||||||
{% if entry.fixedRate is not null %}
|
|
||||||
{% set rate = entry.fixedRate %}
|
|
||||||
{% set duration = entry.amount|amount %}
|
|
||||||
{% else %}
|
|
||||||
{% set rate = entry.hourlyRate %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<td class="first">
|
<td class="first">
|
||||||
{% if entry.description is not empty %}
|
{% if invoiceLineItem['entry.description'] is not empty %}
|
||||||
{{ entry.description|nl2br }}
|
{{ invoiceLineItem['entry.description']|nl2br }}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% if entry.activity is not null %}{{ entry.activity.name }} / {% endif %}{{ entry.project.name }}
|
{% if invoiceLineItem['entry.activity'] is defined %}
|
||||||
|
{{ invoiceLineItem['entry.activity'] }} /
|
||||||
|
{% endif %}
|
||||||
|
{{ invoiceLineItem['entry.project'] }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td class="text-right text-nowrap">{{ rate|money(currency) }}</td>
|
<td class="text-right text-nowrap">{{ invoiceLineItem['entry.rate'] }}</td>
|
||||||
<td class="text-right text-nowrap">{{ duration }}</td>
|
<td class="text-right text-nowrap">{{ invoiceLineItem['entry.amount'] }}</td>
|
||||||
<td class="last text-right text-nowrap">{{ entry.rate|money(currency) }}</td>
|
<td class="last text-right text-nowrap">{{ invoiceLineItem['entry.total'] }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
<tfoot>
|
<tfoot>
|
||||||
{% if model.calculator.tax > 0 or not model.isHideZeroTax() %}
|
{% if not invoice['invoice.tax_hide'] %}
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="3" class="text-right">
|
<td colspan="3" class="text-right">
|
||||||
{{ 'invoice.subtotal'|trans }}
|
{{ 'invoice.subtotal'|trans }}
|
||||||
</td>
|
</td>
|
||||||
<td class="last text-right">{{ model.calculator.subtotal|money(currency) }}</td>
|
<td class="last text-right">{{ invoice['invoice.subtotal'] }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="3" class="text-right">
|
<td colspan="3" class="text-right">
|
||||||
{{ 'invoice.tax'|trans }} ({{ model.calculator.vat }}%)
|
{{ 'invoice.tax'|trans }} ({{ invoice['invoice.vat'] }}%)
|
||||||
</td>
|
</td>
|
||||||
<td class="last text-right">{{ model.calculator.tax|money(currency) }}</td>
|
<td class="last text-right">{{ invoice['invoice.tax'] }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<tr>
|
<tr>
|
||||||
@@ -163,16 +153,16 @@ mpdf-->
|
|||||||
<strong>{{ 'invoice.total'|trans }}</strong>
|
<strong>{{ 'invoice.total'|trans }}</strong>
|
||||||
</td>
|
</td>
|
||||||
<td class="last text-right">
|
<td class="last text-right">
|
||||||
<strong>{{ model.calculator.total|money(currency) }}</strong>
|
<strong>{{ invoice['invoice.total'] }}</strong>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tfoot>
|
</tfoot>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
{% if model.template.paymentTerms is not empty %}
|
{% if invoice['template.payment_terms'] is not empty %}
|
||||||
<p>
|
<div class="paymentTerms">
|
||||||
{{ model.template.paymentTerms|md2html }}
|
{{ invoice['template.payment_terms']|md2html }}
|
||||||
</p>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1,88 +1,91 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
{% set fallback = app.request is not null ? app.request.locale : 'en' %}
|
<html lang="{{ invoice['invoice.language'] }}">
|
||||||
{% set language = model.template.language|default(fallback) %}
|
|
||||||
{% set isDecimal = true %}
|
|
||||||
{% set project = model.query.project %}
|
|
||||||
{% set activity = model.query.activity %}
|
|
||||||
<html lang="{{ language }}">
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport">
|
<meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport">
|
||||||
<title>{{ model.invoiceNumber }}-{{ model.customer.company|default(model.customer.name)|u.snake }}</title>
|
<title>{{ invoice['invoice.number'] }}-{{ invoice['customer.company']|default(invoice['customer.name'])|u.snake }}</title>
|
||||||
<style type="text/css">
|
<style>
|
||||||
{{ encore_entry_css_source('invoice')|raw }}
|
{{ encore_entry_css_source('invoice')|raw }}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body class="invoice_print">
|
<body class="invoice_print">
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
<section class="invoice">
|
<section class="invoice">
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-12">
|
<div class="col-xs-12">
|
||||||
<h2 class="page-header">
|
<h2 class="page-header">
|
||||||
<span contenteditable="true">{{ model.template.title }}</span>
|
<span contenteditable="true">{{ invoice['template.title'] }}</span>
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-12">
|
<div class="col-xs-12">
|
||||||
<table class="table no-border table-sm">
|
<table class="table no-border table-sm">
|
||||||
<tr>
|
<tr>
|
||||||
<th class="ps-0">{{ 'invoice.from'|trans }}</th>
|
<th class="ps-0">{{ 'invoice.from'|trans }}</th>
|
||||||
<td contenteditable="true">
|
<td contenteditable="true">
|
||||||
{% if model.query.user is not empty %}
|
{% if not invoice['user.see_others'] and invoice['user.display'] is defined %}
|
||||||
{{ model.query.user.displayName }}
|
{{ invoice['user.display'] }}
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ model.template.company }}
|
{{ invoice['template.company'] }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="ps-0">{{ 'date'|trans }}</th>
|
<th class="ps-0">{{ 'date'|trans }}</th>
|
||||||
<td contenteditable="true">
|
<td contenteditable="true">
|
||||||
{% if model.query.begin|date('m') != model.query.end|date('m') or model.query.begin|date('Y') != model.query.end|date('Y') %}
|
{% if invoice['query.begin_month_number'] != invoice['query.end_month_number'] or invoice['query.begin_year'] != invoice['query.end_year'] %}
|
||||||
{{ model.query.begin|date_short }} - {{ model.query.end|date_short }}
|
{{ invoice['query.begin'] }} - {{ invoice['query.end'] }}
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ model.query.end|month_name }} {{ model.query.end|date('Y') }}
|
{{ invoice['query.end_month'] }} {{ invoice['query.end_year'] }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="ps-0">{{ 'customer'|trans }}</th>
|
<th class="ps-0">{{ 'customer'|trans }}</th>
|
||||||
<td contenteditable="true">
|
<td contenteditable="true">
|
||||||
{% if model.customer.number is not empty %}[{{ model.customer.number }}]{% endif %}
|
{% if invoice['customer.number'] is not empty %}
|
||||||
{{ model.customer.name }}{% if model.customer.contact is not empty %} / {{ model.customer.contact }}{% endif %}
|
[{{ invoice['customer.number'] }}]
|
||||||
|
{% endif %}
|
||||||
|
{{ invoice['customer.name'] }}
|
||||||
|
{% if invoice['customer.contact'] is not empty %}
|
||||||
|
/ {{ invoice['customer.contact'] }}
|
||||||
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% if project is not null %}
|
{% if invoice['query.project.name'] is defined %}
|
||||||
<tr>
|
<tr>
|
||||||
<th class="ps-0">{{ 'project'|trans }}</th>
|
<th class="ps-0">{{ 'project'|trans }}</th>
|
||||||
<td contenteditable="true">
|
<td contenteditable="true">
|
||||||
{{ project.name }}
|
{{ invoice['query.project.name'] }}
|
||||||
{% if project.orderNumber is not empty %}
|
{% if invoice['query.project.order_number'] is not empty %}
|
||||||
({{ 'orderNumber'|trans }}: {{ project.orderNumber }})
|
({{ 'orderNumber'|trans }}: {{ invoice['query.project.order_number'] }})
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if activity is not null %}
|
{% if invoice['query.activity.name'] is defined %}
|
||||||
<tr>
|
<tr>
|
||||||
<th class="ps-0">{{ 'activity'|trans }}</th>
|
<th class="ps-0">{{ 'activity'|trans }}</th>
|
||||||
<td contenteditable="true">
|
<td contenteditable="true">
|
||||||
{{ activity.name }}
|
{{ invoice['query.activity.name'] }}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row invoice-items mt-2 mb-3">
|
<div class="row invoice-items mt-2 mb-3">
|
||||||
<div class="col-xs-12 table-responsive">
|
<div class="col-xs-12 table-responsive">
|
||||||
<table class="table table-striped">
|
<table class="table table-striped">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{{ 'date'|trans }}</th>
|
<th>{{ 'date'|trans }}</th>
|
||||||
{% if model.query.user is empty %}
|
{% if invoice['user.see_others'] %}
|
||||||
<th>{{ 'user'|trans }}</th>
|
<th>{{ 'user'|trans }}</th>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<th>{{ 'activity'|trans }}</th>
|
<th>{{ 'activity'|trans }}</th>
|
||||||
@@ -90,44 +93,48 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for entry in model.calculator.entries %}
|
{% for timesheet in entries %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ entry.begin|date_short }}</td>
|
<td>{{ timesheet['entry.begin'] }}</td>
|
||||||
{% if model.query.user is empty %}
|
{% if invoice['user.see_others'] %}
|
||||||
<td>{{ entry.user.displayName }}</td>
|
<td>{{ timesheet['entry.user_display'] }}</td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<td contenteditable="true">
|
<td contenteditable="true">
|
||||||
{% if entry.description is not empty %}
|
{% if timesheet['entry.description'] is not empty %}
|
||||||
{{ entry.description|nl2br }}
|
{{ timesheet['entry.description']|nl2br }}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% if entry.activity is not null %}{{ entry.activity.name }} / {% endif %}{{ entry.project.name }}
|
{% if timesheet['entry.activity'] is defined %}
|
||||||
|
{{ timesheet['entry.activity'] }} /
|
||||||
|
{% endif %}
|
||||||
|
{{ timesheet['entry.project'] }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td class="text-right text-nowrap">{{ entry.duration|duration(isDecimal) }}</td>
|
<td class="text-right text-nowrap">{{ timesheet['entry.amount'] }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
<tfoot>
|
<tfoot>
|
||||||
<tr>
|
<tr>
|
||||||
<th></th>
|
<th></th>
|
||||||
{% if model.query.user is empty %}
|
{% if invoice['user.see_others'] %}
|
||||||
<th></th>
|
<th></th>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<th>{{ 'invoice.total_working_time'|trans }}</th>
|
<th>{{ 'invoice.total_working_time'|trans }}</th>
|
||||||
<th class="text-right text-nowrap">{{ model.calculator.timeWorked|duration(isDecimal) }}</th>
|
<th class="text-right text-nowrap">{{ invoice['invoice.duration_decimal'] }}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</tfoot>
|
</tfoot>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-12">
|
<div class="col-xs-12">
|
||||||
{% if model.template.paymentTerms is not empty %}
|
{% if invoice['template.payment_terms'] is not empty %}
|
||||||
<p class="lead">{{ 'payment_terms'|trans }}</p>
|
<p class="lead">{{ 'payment_terms'|trans }}</p>
|
||||||
|
|
||||||
<p class="text-muted well well-sm no-shadow" contenteditable="true" style="margin-bottom: 100px">
|
<div class="paymentTerms" contenteditable="true" style="margin-bottom: 100px">
|
||||||
{{ model.template.paymentTerms|trim|nl2br }}
|
{{ invoice['template.payment_terms']|md2html }}
|
||||||
</p>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
@@ -136,6 +143,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -49,38 +49,44 @@ class TimesheetControllerTest extends APIControllerBaseTest
|
|||||||
return $this->importFixture($fixture);
|
return $this->importFixture($fixture);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testIsSecure()
|
public function testIsSecure(): void
|
||||||
{
|
{
|
||||||
$this->assertUrlIsSecured('/api/timesheets');
|
$this->assertUrlIsSecured('/api/timesheets');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetCollection()
|
public function testGetCollection(): void
|
||||||
{
|
{
|
||||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
||||||
$this->importFixtureForUser(User::ROLE_USER);
|
$this->importFixtureForUser(User::ROLE_USER);
|
||||||
$this->assertAccessIsGranted($client, '/api/timesheets');
|
$this->assertAccessIsGranted($client, '/api/timesheets');
|
||||||
$result = json_decode($client->getResponse()->getContent(), true);
|
|
||||||
|
|
||||||
$this->assertIsArray($result);
|
$content = $client->getResponse()->getContent();
|
||||||
$this->assertNotEmpty($result);
|
self::assertIsString($content);
|
||||||
$this->assertEquals(10, \count($result));
|
$result = json_decode($content, true);
|
||||||
|
|
||||||
|
self::assertIsArray($result);
|
||||||
|
self::assertNotEmpty($result);
|
||||||
|
self::assertEquals(10, \count($result));
|
||||||
self::assertApiResponseTypeStructure('TimesheetCollection', $result[0]);
|
self::assertApiResponseTypeStructure('TimesheetCollection', $result[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetCollectionFull()
|
public function testGetCollectionFull(): void
|
||||||
{
|
{
|
||||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
||||||
$this->importFixtureForUser(User::ROLE_USER);
|
$this->importFixtureForUser(User::ROLE_USER);
|
||||||
$this->assertAccessIsGranted($client, '/api/timesheets', 'GET', ['full' => 'true']);
|
$this->assertAccessIsGranted($client, '/api/timesheets', 'GET', ['full' => 'true']);
|
||||||
$result = json_decode($client->getResponse()->getContent(), true);
|
|
||||||
|
|
||||||
$this->assertIsArray($result);
|
$content = $client->getResponse()->getContent();
|
||||||
$this->assertNotEmpty($result);
|
self::assertIsString($content);
|
||||||
$this->assertEquals(10, \count($result));
|
$result = json_decode($content, true);
|
||||||
|
|
||||||
|
self::assertIsArray($result);
|
||||||
|
self::assertNotEmpty($result);
|
||||||
|
self::assertEquals(10, \count($result));
|
||||||
self::assertApiResponseTypeStructure('TimesheetCollectionFull', $result[0]);
|
self::assertApiResponseTypeStructure('TimesheetCollectionFull', $result[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetCollectionForOtherUser()
|
public function testGetCollectionForOtherUser(): void
|
||||||
{
|
{
|
||||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_TEAMLEAD);
|
$client = $this->getClientForAuthenticatedUser(User::ROLE_TEAMLEAD);
|
||||||
$this->importFixtureForUser(User::ROLE_USER);
|
$this->importFixtureForUser(User::ROLE_USER);
|
||||||
@@ -97,15 +103,30 @@ class TimesheetControllerTest extends APIControllerBaseTest
|
|||||||
|
|
||||||
$query = ['user' => 2];
|
$query = ['user' => 2];
|
||||||
$this->assertAccessIsGranted($client, '/api/timesheets', 'GET', $query);
|
$this->assertAccessIsGranted($client, '/api/timesheets', 'GET', $query);
|
||||||
$result = json_decode($client->getResponse()->getContent(), true);
|
|
||||||
|
|
||||||
$this->assertIsArray($result);
|
$content = $client->getResponse()->getContent();
|
||||||
$this->assertNotEmpty($result);
|
self::assertIsString($content);
|
||||||
$this->assertEquals(10, \count($result));
|
$result = json_decode($content, true);
|
||||||
|
|
||||||
|
self::assertIsArray($result);
|
||||||
|
self::assertNotEmpty($result);
|
||||||
|
self::assertEquals(10, \count($result));
|
||||||
|
self::assertApiResponseTypeStructure('TimesheetCollection', $result[0]);
|
||||||
|
|
||||||
|
$query = ['users' => [2]];
|
||||||
|
$this->assertAccessIsGranted($client, '/api/timesheets', 'GET', $query);
|
||||||
|
|
||||||
|
$content = $client->getResponse()->getContent();
|
||||||
|
self::assertIsString($content);
|
||||||
|
$result = json_decode($content, true);
|
||||||
|
|
||||||
|
self::assertIsArray($result);
|
||||||
|
self::assertNotEmpty($result);
|
||||||
|
self::assertEquals(10, \count($result));
|
||||||
self::assertApiResponseTypeStructure('TimesheetCollection', $result[0]);
|
self::assertApiResponseTypeStructure('TimesheetCollection', $result[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetCollectionForAllUser()
|
public function testGetCollectionForAllUser(): void
|
||||||
{
|
{
|
||||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_TEAMLEAD);
|
$client = $this->getClientForAuthenticatedUser(User::ROLE_TEAMLEAD);
|
||||||
$this->importFixtureForUser(User::ROLE_USER);
|
$this->importFixtureForUser(User::ROLE_USER);
|
||||||
@@ -122,26 +143,32 @@ class TimesheetControllerTest extends APIControllerBaseTest
|
|||||||
|
|
||||||
$query = ['user' => 'all'];
|
$query = ['user' => 'all'];
|
||||||
$this->assertAccessIsGranted($client, '/api/timesheets', 'GET', $query);
|
$this->assertAccessIsGranted($client, '/api/timesheets', 'GET', $query);
|
||||||
$result = json_decode($client->getResponse()->getContent(), true);
|
|
||||||
|
|
||||||
$this->assertIsArray($result);
|
$content = $client->getResponse()->getContent();
|
||||||
$this->assertNotEmpty($result);
|
self::assertIsString($content);
|
||||||
$this->assertEquals(17, \count($result));
|
$result = json_decode($content, true);
|
||||||
|
|
||||||
|
self::assertIsArray($result);
|
||||||
|
self::assertNotEmpty($result);
|
||||||
|
self::assertEquals(17, \count($result));
|
||||||
self::assertApiResponseTypeStructure('TimesheetCollection', $result[0]);
|
self::assertApiResponseTypeStructure('TimesheetCollection', $result[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetCollectionForEmptyResult()
|
public function testGetCollectionForEmptyResult(): void
|
||||||
{
|
{
|
||||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_TEAMLEAD);
|
$client = $this->getClientForAuthenticatedUser(User::ROLE_TEAMLEAD);
|
||||||
|
|
||||||
$this->assertAccessIsGranted($client, '/api/timesheets');
|
$this->assertAccessIsGranted($client, '/api/timesheets');
|
||||||
$result = json_decode($client->getResponse()->getContent(), true);
|
|
||||||
|
|
||||||
$this->assertIsArray($result);
|
$content = $client->getResponse()->getContent();
|
||||||
|
self::assertIsString($content);
|
||||||
|
$result = json_decode($content, true);
|
||||||
|
|
||||||
|
self::assertIsArray($result);
|
||||||
$this->assertEmpty($result);
|
$this->assertEmpty($result);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetCollectionWithQuery()
|
public function testGetCollectionWithQuery(): void
|
||||||
{
|
{
|
||||||
$modifiedAfter = new \DateTime('-1 hour');
|
$modifiedAfter = new \DateTime('-1 hour');
|
||||||
$begin = new \DateTime('first day of this month');
|
$begin = new \DateTime('first day of this month');
|
||||||
@@ -167,16 +194,18 @@ class TimesheetControllerTest extends APIControllerBaseTest
|
|||||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
||||||
$this->importFixtureForUser(User::ROLE_USER, 22);
|
$this->importFixtureForUser(User::ROLE_USER, 22);
|
||||||
$this->assertAccessIsGranted($client, '/api/timesheets', 'GET', $query);
|
$this->assertAccessIsGranted($client, '/api/timesheets', 'GET', $query);
|
||||||
$result = json_decode($client->getResponse()->getContent(), true);
|
$content = $client->getResponse()->getContent();
|
||||||
|
self::assertIsString($content);
|
||||||
|
$result = json_decode($content, true);
|
||||||
|
|
||||||
$this->assertPagination($client->getResponse(), 2, 4, 6, 22);
|
$this->assertPagination($client->getResponse(), 2, 4, 6, 22);
|
||||||
$this->assertIsArray($result);
|
self::assertIsArray($result);
|
||||||
$this->assertNotEmpty($result);
|
self::assertNotEmpty($result);
|
||||||
$this->assertEquals(4, \count($result));
|
self::assertEquals(4, \count($result));
|
||||||
self::assertApiResponseTypeStructure('TimesheetCollection', $result[0]);
|
self::assertApiResponseTypeStructure('TimesheetCollection', $result[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetCollectionWithQueryFailsWith404OnOutOfRangedPage()
|
public function testGetCollectionWithQueryFailsWith404OnOutOfRangedPage(): void
|
||||||
{
|
{
|
||||||
$begin = new \DateTime('first day of this month');
|
$begin = new \DateTime('first day of this month');
|
||||||
$begin->setTime(0, 0, 0);
|
$begin->setTime(0, 0, 0);
|
||||||
@@ -194,7 +223,7 @@ class TimesheetControllerTest extends APIControllerBaseTest
|
|||||||
$this->assertApiException($client->getResponse(), ['code' => 404, 'message' => 'Not Found']);
|
$this->assertApiException($client->getResponse(), ['code' => 404, 'message' => 'Not Found']);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetCollectionWithSingleParamsQuery()
|
public function testGetCollectionWithSingleParamsQuery(): void
|
||||||
{
|
{
|
||||||
$begin = new \DateTime('first day of this month');
|
$begin = new \DateTime('first day of this month');
|
||||||
$begin->setTime(0, 0, 0);
|
$begin->setTime(0, 0, 0);
|
||||||
@@ -218,15 +247,17 @@ class TimesheetControllerTest extends APIControllerBaseTest
|
|||||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
||||||
$this->importFixtureForUser(User::ROLE_USER);
|
$this->importFixtureForUser(User::ROLE_USER);
|
||||||
$this->assertAccessIsGranted($client, '/api/timesheets', 'GET', $query);
|
$this->assertAccessIsGranted($client, '/api/timesheets', 'GET', $query);
|
||||||
$result = json_decode($client->getResponse()->getContent(), true);
|
$content = $client->getResponse()->getContent();
|
||||||
|
self::assertIsString($content);
|
||||||
|
$result = json_decode($content, true);
|
||||||
|
|
||||||
$this->assertIsArray($result);
|
self::assertIsArray($result);
|
||||||
$this->assertNotEmpty($result);
|
self::assertNotEmpty($result);
|
||||||
$this->assertEquals(5, \count($result));
|
self::assertEquals(5, \count($result));
|
||||||
self::assertApiResponseTypeStructure('TimesheetCollection', $result[0]);
|
self::assertApiResponseTypeStructure('TimesheetCollection', $result[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testExportedFilter()
|
public function testExportedFilter(): void
|
||||||
{
|
{
|
||||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
||||||
$this->importFixtureForUser(User::ROLE_USER);
|
$this->importFixtureForUser(User::ROLE_USER);
|
||||||
@@ -255,11 +286,13 @@ class TimesheetControllerTest extends APIControllerBaseTest
|
|||||||
];
|
];
|
||||||
|
|
||||||
$this->assertAccessIsGranted($client, '/api/timesheets', 'GET', $query);
|
$this->assertAccessIsGranted($client, '/api/timesheets', 'GET', $query);
|
||||||
$result = json_decode($client->getResponse()->getContent(), true);
|
$content = $client->getResponse()->getContent();
|
||||||
|
self::assertIsString($content);
|
||||||
|
$result = json_decode($content, true);
|
||||||
|
|
||||||
$this->assertIsArray($result);
|
self::assertIsArray($result);
|
||||||
$this->assertNotEmpty($result);
|
self::assertNotEmpty($result);
|
||||||
$this->assertEquals(7, \count($result));
|
self::assertEquals(7, \count($result));
|
||||||
self::assertApiResponseTypeStructure('TimesheetCollection', $result[0]);
|
self::assertApiResponseTypeStructure('TimesheetCollection', $result[0]);
|
||||||
|
|
||||||
$query = [
|
$query = [
|
||||||
@@ -271,11 +304,13 @@ class TimesheetControllerTest extends APIControllerBaseTest
|
|||||||
];
|
];
|
||||||
|
|
||||||
$this->assertAccessIsGranted($client, '/api/timesheets', 'GET', $query);
|
$this->assertAccessIsGranted($client, '/api/timesheets', 'GET', $query);
|
||||||
$result = json_decode($client->getResponse()->getContent(), true);
|
$content = $client->getResponse()->getContent();
|
||||||
|
self::assertIsString($content);
|
||||||
|
$result = json_decode($content, true);
|
||||||
|
|
||||||
$this->assertIsArray($result);
|
self::assertIsArray($result);
|
||||||
$this->assertNotEmpty($result);
|
self::assertNotEmpty($result);
|
||||||
$this->assertEquals(10, \count($result));
|
self::assertEquals(10, \count($result));
|
||||||
self::assertApiResponseTypeStructure('TimesheetCollection', $result[0]);
|
self::assertApiResponseTypeStructure('TimesheetCollection', $result[0]);
|
||||||
|
|
||||||
$query = [
|
$query = [
|
||||||
@@ -285,15 +320,17 @@ class TimesheetControllerTest extends APIControllerBaseTest
|
|||||||
'end' => $end->format(self::DATE_FORMAT_HTML5),
|
'end' => $end->format(self::DATE_FORMAT_HTML5),
|
||||||
];
|
];
|
||||||
$this->assertAccessIsGranted($client, '/api/timesheets', 'GET', $query);
|
$this->assertAccessIsGranted($client, '/api/timesheets', 'GET', $query);
|
||||||
$result = json_decode($client->getResponse()->getContent(), true);
|
$content = $client->getResponse()->getContent();
|
||||||
|
self::assertIsString($content);
|
||||||
|
$result = json_decode($content, true);
|
||||||
|
|
||||||
$this->assertIsArray($result);
|
self::assertIsArray($result);
|
||||||
$this->assertNotEmpty($result);
|
self::assertNotEmpty($result);
|
||||||
$this->assertEquals(17, \count($result));
|
self::assertEquals(17, \count($result));
|
||||||
self::assertApiResponseTypeStructure('TimesheetCollection', $result[0]);
|
self::assertApiResponseTypeStructure('TimesheetCollection', $result[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetEntity()
|
public function testGetEntity(): void
|
||||||
{
|
{
|
||||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
||||||
$em = $this->getEntityManager();
|
$em = $this->getEntityManager();
|
||||||
@@ -323,9 +360,11 @@ class TimesheetControllerTest extends APIControllerBaseTest
|
|||||||
$em->flush();
|
$em->flush();
|
||||||
|
|
||||||
$this->assertAccessIsGranted($client, '/api/timesheets/' . $timesheet->getId());
|
$this->assertAccessIsGranted($client, '/api/timesheets/' . $timesheet->getId());
|
||||||
$result = json_decode($client->getResponse()->getContent(), true);
|
$content = $client->getResponse()->getContent();
|
||||||
|
self::assertIsString($content);
|
||||||
|
$result = json_decode($content, true);
|
||||||
|
|
||||||
$this->assertIsArray($result);
|
self::assertIsArray($result);
|
||||||
self::assertApiResponseTypeStructure('TimesheetEntity', $result);
|
self::assertApiResponseTypeStructure('TimesheetEntity', $result);
|
||||||
|
|
||||||
$expected = [
|
$expected = [
|
||||||
@@ -353,7 +392,7 @@ class TimesheetControllerTest extends APIControllerBaseTest
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetEntityAccessDenied()
|
public function testGetEntityAccessDenied(): void
|
||||||
{
|
{
|
||||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
||||||
$this->importFixtureForUser(User::ROLE_USER);
|
$this->importFixtureForUser(User::ROLE_USER);
|
||||||
@@ -363,23 +402,25 @@ class TimesheetControllerTest extends APIControllerBaseTest
|
|||||||
$this->assertApiAccessDenied($client, '/api/timesheets/' . $timesheets[0]->getId(), 'Access denied.');
|
$this->assertApiAccessDenied($client, '/api/timesheets/' . $timesheets[0]->getId(), 'Access denied.');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetEntityAccessAllowedForAdmin()
|
public function testGetEntityAccessAllowedForAdmin(): void
|
||||||
{
|
{
|
||||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||||
$timesheets = $this->importFixtureForUser(User::ROLE_USER);
|
$timesheets = $this->importFixtureForUser(User::ROLE_USER);
|
||||||
$this->assertAccessIsGranted($client, '/api/timesheets/' . $timesheets[0]->getId());
|
$this->assertAccessIsGranted($client, '/api/timesheets/' . $timesheets[0]->getId());
|
||||||
$result = json_decode($client->getResponse()->getContent(), true);
|
$content = $client->getResponse()->getContent();
|
||||||
|
self::assertIsString($content);
|
||||||
|
$result = json_decode($content, true);
|
||||||
|
|
||||||
$this->assertIsArray($result);
|
self::assertIsArray($result);
|
||||||
self::assertApiResponseTypeStructure('TimesheetEntity', $result);
|
self::assertApiResponseTypeStructure('TimesheetEntity', $result);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetEntityNotFound()
|
public function testGetEntityNotFound(): void
|
||||||
{
|
{
|
||||||
$this->assertEntityNotFound(User::ROLE_USER, '/api/timesheets/' . PHP_INT_MAX, 'GET', 'App\\Entity\\Timesheet object not found by the @ParamConverter annotation.');
|
$this->assertEntityNotFound(User::ROLE_USER, '/api/timesheets/' . PHP_INT_MAX, 'GET', 'App\\Entity\\Timesheet object not found by the @ParamConverter annotation.');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testPostAction()
|
public function testPostAction(): void
|
||||||
{
|
{
|
||||||
$dateTime = new DateTimeFactory(new \DateTimeZone(self::TEST_TIMEZONE));
|
$dateTime = new DateTimeFactory(new \DateTimeZone(self::TEST_TIMEZONE));
|
||||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||||
@@ -396,16 +437,19 @@ class TimesheetControllerTest extends APIControllerBaseTest
|
|||||||
$this->request($client, '/api/timesheets', 'POST', [], json_encode($data));
|
$this->request($client, '/api/timesheets', 'POST', [], json_encode($data));
|
||||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||||
|
|
||||||
$result = json_decode($client->getResponse()->getContent(), true);
|
$content = $client->getResponse()->getContent();
|
||||||
$this->assertIsArray($result);
|
self::assertIsString($content);
|
||||||
|
$result = json_decode($content, true);
|
||||||
|
|
||||||
|
self::assertIsArray($result);
|
||||||
self::assertApiResponseTypeStructure('TimesheetEntity', $result);
|
self::assertApiResponseTypeStructure('TimesheetEntity', $result);
|
||||||
$this->assertNotEmpty($result['id']);
|
self::assertNotEmpty($result['id']);
|
||||||
$this->assertTrue($result['duration'] == 28800 || $result['duration'] == 28860); // 1 minute rounding might be applied
|
$this->assertTrue($result['duration'] == 28800 || $result['duration'] == 28860); // 1 minute rounding might be applied
|
||||||
$this->assertEquals(2016, $result['rate']);
|
self::assertEquals(2016, $result['rate']);
|
||||||
$this->assertFalse($result['billable']);
|
$this->assertFalse($result['billable']);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testPostActionWithFullExpandedResponse()
|
public function testPostActionWithFullExpandedResponse(): void
|
||||||
{
|
{
|
||||||
$dateTime = new DateTimeFactory(new \DateTimeZone(self::TEST_TIMEZONE));
|
$dateTime = new DateTimeFactory(new \DateTimeZone(self::TEST_TIMEZONE));
|
||||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||||
@@ -422,16 +466,19 @@ class TimesheetControllerTest extends APIControllerBaseTest
|
|||||||
$this->request($client, '/api/timesheets?full=true', 'POST', [], json_encode($data));
|
$this->request($client, '/api/timesheets?full=true', 'POST', [], json_encode($data));
|
||||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||||
|
|
||||||
$result = json_decode($client->getResponse()->getContent(), true);
|
$content = $client->getResponse()->getContent();
|
||||||
$this->assertIsArray($result);
|
self::assertIsString($content);
|
||||||
|
$result = json_decode($content, true);
|
||||||
|
|
||||||
|
self::assertIsArray($result);
|
||||||
self::assertApiResponseTypeStructure('TimesheetExpanded', $result);
|
self::assertApiResponseTypeStructure('TimesheetExpanded', $result);
|
||||||
$this->assertNotEmpty($result['id']);
|
self::assertNotEmpty($result['id']);
|
||||||
$this->assertTrue($result['duration'] == 28800 || $result['duration'] == 28860); // 1 minute rounding might be applied
|
$this->assertTrue($result['duration'] == 28800 || $result['duration'] == 28860); // 1 minute rounding might be applied
|
||||||
$this->assertEquals(2016, $result['rate']);
|
self::assertEquals(2016, $result['rate']);
|
||||||
$this->assertTrue($result['billable']);
|
$this->assertTrue($result['billable']);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testPostActionForDifferentUser()
|
public function testPostActionForDifferentUser(): void
|
||||||
{
|
{
|
||||||
$dateTime = new DateTimeFactory(new \DateTimeZone(self::TEST_TIMEZONE));
|
$dateTime = new DateTimeFactory(new \DateTimeZone(self::TEST_TIMEZONE));
|
||||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||||
@@ -451,18 +498,21 @@ class TimesheetControllerTest extends APIControllerBaseTest
|
|||||||
$this->request($client, '/api/timesheets', 'POST', [], json_encode($data));
|
$this->request($client, '/api/timesheets', 'POST', [], json_encode($data));
|
||||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||||
|
|
||||||
$result = json_decode($client->getResponse()->getContent(), true);
|
$content = $client->getResponse()->getContent();
|
||||||
$this->assertIsArray($result);
|
self::assertIsString($content);
|
||||||
|
$result = json_decode($content, true);
|
||||||
|
|
||||||
|
self::assertIsArray($result);
|
||||||
self::assertApiResponseTypeStructure('TimesheetEntity', $result);
|
self::assertApiResponseTypeStructure('TimesheetEntity', $result);
|
||||||
$this->assertNotEmpty($result['id']);
|
self::assertNotEmpty($result['id']);
|
||||||
$this->assertEquals($user->getId(), $result['user']);
|
self::assertEquals($user->getId(), $result['user']);
|
||||||
$this->assertNotEquals($admin->getId(), $result['user']);
|
$this->assertNotEquals($admin->getId(), $result['user']);
|
||||||
$this->assertTrue($result['billable']);
|
$this->assertTrue($result['billable']);
|
||||||
}
|
}
|
||||||
|
|
||||||
// check for project, as this is a required field. It will not be included in the select, as it is
|
// check for project, as this is a required field. It will not be included in the select, as it is
|
||||||
// already filtered within the repository due to the hidden customer
|
// already filtered within the repository due to the hidden customer
|
||||||
public function testPostActionWithInvisibleProject()
|
public function testPostActionWithInvisibleProject(): void
|
||||||
{
|
{
|
||||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
||||||
|
|
||||||
@@ -496,7 +546,7 @@ class TimesheetControllerTest extends APIControllerBaseTest
|
|||||||
|
|
||||||
// check for activity, as this is a required field. It will not be included in the select, as it is
|
// check for activity, as this is a required field. It will not be included in the select, as it is
|
||||||
// already filtered within the repository due to the hidden flag
|
// already filtered within the repository due to the hidden flag
|
||||||
public function testPostActionWithInvisibleActivity()
|
public function testPostActionWithInvisibleActivity(): void
|
||||||
{
|
{
|
||||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
||||||
|
|
||||||
@@ -528,7 +578,7 @@ class TimesheetControllerTest extends APIControllerBaseTest
|
|||||||
$this->assertApiCallValidationError($client->getResponse(), ['activity']);
|
$this->assertApiCallValidationError($client->getResponse(), ['activity']);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testPostActionWithNonBillableCustomer()
|
public function testPostActionWithNonBillableCustomer(): void
|
||||||
{
|
{
|
||||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_TEAMLEAD);
|
$client = $this->getClientForAuthenticatedUser(User::ROLE_TEAMLEAD);
|
||||||
|
|
||||||
@@ -557,13 +607,16 @@ class TimesheetControllerTest extends APIControllerBaseTest
|
|||||||
$this->request($client, '/api/timesheets', 'POST', [], json_encode($data));
|
$this->request($client, '/api/timesheets', 'POST', [], json_encode($data));
|
||||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||||
|
|
||||||
$result = json_decode($client->getResponse()->getContent(), true);
|
$content = $client->getResponse()->getContent();
|
||||||
$this->assertIsArray($result);
|
self::assertIsString($content);
|
||||||
|
$result = json_decode($content, true);
|
||||||
|
|
||||||
|
self::assertIsArray($result);
|
||||||
self::assertApiResponseTypeStructure('TimesheetEntity', $result);
|
self::assertApiResponseTypeStructure('TimesheetEntity', $result);
|
||||||
$this->assertFalse($result['billable']);
|
$this->assertFalse($result['billable']);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testPostActionWithNonBillableCustomerExplicit()
|
public function testPostActionWithNonBillableCustomerExplicit(): void
|
||||||
{
|
{
|
||||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_TEAMLEAD);
|
$client = $this->getClientForAuthenticatedUser(User::ROLE_TEAMLEAD);
|
||||||
|
|
||||||
@@ -593,14 +646,17 @@ class TimesheetControllerTest extends APIControllerBaseTest
|
|||||||
$this->request($client, '/api/timesheets', 'POST', [], json_encode($data));
|
$this->request($client, '/api/timesheets', 'POST', [], json_encode($data));
|
||||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||||
|
|
||||||
$result = json_decode($client->getResponse()->getContent(), true);
|
$content = $client->getResponse()->getContent();
|
||||||
$this->assertIsArray($result);
|
self::assertIsString($content);
|
||||||
|
$result = json_decode($content, true);
|
||||||
|
|
||||||
|
self::assertIsArray($result);
|
||||||
self::assertApiResponseTypeStructure('TimesheetEntity', $result);
|
self::assertApiResponseTypeStructure('TimesheetEntity', $result);
|
||||||
// explicit overwritten values win!
|
// explicit overwritten values win!
|
||||||
$this->assertTrue($result['billable']);
|
$this->assertTrue($result['billable']);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testPatchAction()
|
public function testPatchAction(): void
|
||||||
{
|
{
|
||||||
$dateTime = new DateTimeFactory(new \DateTimeZone(self::TEST_TIMEZONE));
|
$dateTime = new DateTimeFactory(new \DateTimeZone(self::TEST_TIMEZONE));
|
||||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_TEAMLEAD);
|
$client = $this->getClientForAuthenticatedUser(User::ROLE_TEAMLEAD);
|
||||||
@@ -616,16 +672,19 @@ class TimesheetControllerTest extends APIControllerBaseTest
|
|||||||
$this->request($client, '/api/timesheets/' . $timesheets[0]->getId(), 'PATCH', [], json_encode($data));
|
$this->request($client, '/api/timesheets/' . $timesheets[0]->getId(), 'PATCH', [], json_encode($data));
|
||||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||||
|
|
||||||
$result = json_decode($client->getResponse()->getContent(), true);
|
$content = $client->getResponse()->getContent();
|
||||||
$this->assertIsArray($result);
|
self::assertIsString($content);
|
||||||
|
$result = json_decode($content, true);
|
||||||
|
|
||||||
|
self::assertIsArray($result);
|
||||||
self::assertApiResponseTypeStructure('TimesheetEntity', $result);
|
self::assertApiResponseTypeStructure('TimesheetEntity', $result);
|
||||||
$this->assertNotEmpty($result['id']);
|
self::assertNotEmpty($result['id']);
|
||||||
$this->assertEquals(25200, $result['duration']);
|
self::assertEquals(25200, $result['duration']);
|
||||||
$this->assertEquals('foo', $result['description']);
|
self::assertEquals('foo', $result['description']);
|
||||||
$this->assertFalse($result['billable']);
|
$this->assertFalse($result['billable']);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testPatchActionWithInvalidUser()
|
public function testPatchActionWithInvalidUser(): void
|
||||||
{
|
{
|
||||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
||||||
$this->importFixtureForUser(User::ROLE_USER);
|
$this->importFixtureForUser(User::ROLE_USER);
|
||||||
@@ -654,12 +713,12 @@ class TimesheetControllerTest extends APIControllerBaseTest
|
|||||||
$this->assertApiResponseAccessDenied($response);
|
$this->assertApiResponseAccessDenied($response);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testPatchActionWithUnknownTimesheet()
|
public function testPatchActionWithUnknownTimesheet(): void
|
||||||
{
|
{
|
||||||
$this->assertEntityNotFoundForPatch(User::ROLE_USER, '/api/timesheets/255', []);
|
$this->assertEntityNotFoundForPatch(User::ROLE_USER, '/api/timesheets/255', []);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testInvalidPatchAction()
|
public function testInvalidPatchAction(): void
|
||||||
{
|
{
|
||||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
||||||
$timesheets = $this->importFixtureForUser(User::ROLE_USER);
|
$timesheets = $this->importFixtureForUser(User::ROLE_USER);
|
||||||
@@ -674,48 +733,51 @@ class TimesheetControllerTest extends APIControllerBaseTest
|
|||||||
$this->request($client, '/api/timesheets/' . $timesheets[0]->getId(), 'PATCH', [], json_encode($data));
|
$this->request($client, '/api/timesheets/' . $timesheets[0]->getId(), 'PATCH', [], json_encode($data));
|
||||||
|
|
||||||
$response = $client->getResponse();
|
$response = $client->getResponse();
|
||||||
$this->assertEquals(400, $response->getStatusCode());
|
self::assertEquals(400, $response->getStatusCode());
|
||||||
$this->assertApiCallValidationError($response, ['activity'], false, ['End date must not be earlier then start date.']);
|
$this->assertApiCallValidationError($response, ['activity'], false, ['End date must not be earlier then start date.']);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: TEST PATCH FOR EXPORTED TIMESHEET FOR USER WITHOUT PERMISSION IS REJECTED
|
// TODO: TEST PATCH FOR EXPORTED TIMESHEET FOR USER WITHOUT PERMISSION IS REJECTED
|
||||||
|
|
||||||
public function testDeleteAction()
|
public function testDeleteAction(): void
|
||||||
{
|
{
|
||||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
||||||
$timesheets = $this->importFixtureForUser(User::ROLE_USER);
|
$timesheets = $this->importFixtureForUser(User::ROLE_USER);
|
||||||
$this->assertAccessIsGranted($client, '/api/timesheets/' . $timesheets[0]->getId());
|
$this->assertAccessIsGranted($client, '/api/timesheets/' . $timesheets[0]->getId());
|
||||||
$result = json_decode($client->getResponse()->getContent(), true);
|
|
||||||
|
|
||||||
$this->assertIsArray($result);
|
$content = $client->getResponse()->getContent();
|
||||||
|
self::assertIsString($content);
|
||||||
|
$result = json_decode($content, true);
|
||||||
|
|
||||||
|
self::assertIsArray($result);
|
||||||
self::assertApiResponseTypeStructure('TimesheetEntity', $result);
|
self::assertApiResponseTypeStructure('TimesheetEntity', $result);
|
||||||
$this->assertNotEmpty($result['id']);
|
self::assertNotEmpty($result['id']);
|
||||||
$id = $result['id'];
|
$id = $result['id'];
|
||||||
|
|
||||||
$this->request($client, '/api/timesheets/' . $id, 'DELETE');
|
$this->request($client, '/api/timesheets/' . $id, 'DELETE');
|
||||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||||
$this->assertEquals(Response::HTTP_NO_CONTENT, $client->getResponse()->getStatusCode());
|
self::assertEquals(Response::HTTP_NO_CONTENT, $client->getResponse()->getStatusCode());
|
||||||
$this->assertEmpty($client->getResponse()->getContent());
|
$this->assertEmpty($client->getResponse()->getContent());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testDeleteActionWithUnknownTimesheet()
|
public function testDeleteActionWithUnknownTimesheet(): void
|
||||||
{
|
{
|
||||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||||
$this->assertNotFoundForDelete($client, '/api/timesheets/255');
|
$this->assertNotFoundForDelete($client, '/api/timesheets/255');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testDeleteActionForDifferentUser()
|
public function testDeleteActionForDifferentUser(): void
|
||||||
{
|
{
|
||||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||||
$timesheets = $this->importFixtureForUser(User::ROLE_USER);
|
$timesheets = $this->importFixtureForUser(User::ROLE_USER);
|
||||||
|
|
||||||
$this->request($client, '/api/timesheets/' . $timesheets[0]->getId(), 'DELETE');
|
$this->request($client, '/api/timesheets/' . $timesheets[0]->getId(), 'DELETE');
|
||||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||||
$this->assertEquals(Response::HTTP_NO_CONTENT, $client->getResponse()->getStatusCode());
|
self::assertEquals(Response::HTTP_NO_CONTENT, $client->getResponse()->getStatusCode());
|
||||||
$this->assertEmpty($client->getResponse()->getContent());
|
$this->assertEmpty($client->getResponse()->getContent());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testDeleteActionWithoutAuthorization()
|
public function testDeleteActionWithoutAuthorization(): void
|
||||||
{
|
{
|
||||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
||||||
$this->importFixtureForUser(User::ROLE_USER);
|
$this->importFixtureForUser(User::ROLE_USER);
|
||||||
@@ -727,7 +789,7 @@ class TimesheetControllerTest extends APIControllerBaseTest
|
|||||||
$this->assertApiResponseAccessDenied($response);
|
$this->assertApiResponseAccessDenied($response);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testDeleteActionForExportedRecordIsNotAllowed()
|
public function testDeleteActionForExportedRecordIsNotAllowed(): void
|
||||||
{
|
{
|
||||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
||||||
$this->importFixtureForUser(User::ROLE_USER);
|
$this->importFixtureForUser(User::ROLE_USER);
|
||||||
@@ -746,7 +808,7 @@ class TimesheetControllerTest extends APIControllerBaseTest
|
|||||||
$this->assertApiResponseAccessDenied($response);
|
$this->assertApiResponseAccessDenied($response);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testDeleteActionForExportedRecordIsAllowedForAdmin()
|
public function testDeleteActionForExportedRecordIsAllowedForAdmin(): void
|
||||||
{
|
{
|
||||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||||
$this->importFixtureForUser(User::ROLE_USER);
|
$this->importFixtureForUser(User::ROLE_USER);
|
||||||
@@ -763,7 +825,7 @@ class TimesheetControllerTest extends APIControllerBaseTest
|
|||||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetRecentAction()
|
public function testGetRecentAction(): void
|
||||||
{
|
{
|
||||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||||
|
|
||||||
@@ -785,15 +847,18 @@ class TimesheetControllerTest extends APIControllerBaseTest
|
|||||||
];
|
];
|
||||||
|
|
||||||
$this->assertAccessIsGranted($client, '/api/timesheets/recent', 'GET', $query);
|
$this->assertAccessIsGranted($client, '/api/timesheets/recent', 'GET', $query);
|
||||||
$result = json_decode($client->getResponse()->getContent(), true);
|
|
||||||
|
|
||||||
$this->assertIsArray($result);
|
$content = $client->getResponse()->getContent();
|
||||||
$this->assertNotEmpty($result);
|
self::assertIsString($content);
|
||||||
$this->assertEquals(1, \count($result));
|
$result = json_decode($content, true);
|
||||||
|
|
||||||
|
self::assertIsArray($result);
|
||||||
|
self::assertNotEmpty($result);
|
||||||
|
self::assertEquals(1, \count($result));
|
||||||
self::assertApiResponseTypeStructure('TimesheetCollectionFull', $result[0]);
|
self::assertApiResponseTypeStructure('TimesheetCollectionFull', $result[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testActiveAction()
|
public function testActiveAction(): void
|
||||||
{
|
{
|
||||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
||||||
|
|
||||||
@@ -813,14 +878,17 @@ class TimesheetControllerTest extends APIControllerBaseTest
|
|||||||
$this->request($client, '/api/timesheets/active');
|
$this->request($client, '/api/timesheets/active');
|
||||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||||
|
|
||||||
$results = json_decode($client->getResponse()->getContent(), true);
|
$content = $client->getResponse()->getContent();
|
||||||
$this->assertEquals(3, \count($results));
|
self::assertIsString($content);
|
||||||
foreach ($results as $timesheet) {
|
$result = json_decode($content, true);
|
||||||
|
|
||||||
|
self::assertEquals(3, \count($result));
|
||||||
|
foreach ($result as $timesheet) {
|
||||||
self::assertApiResponseTypeStructure('TimesheetCollectionFull', $timesheet);
|
self::assertApiResponseTypeStructure('TimesheetCollectionFull', $timesheet);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testStopAction()
|
public function testStopAction(): void
|
||||||
{
|
{
|
||||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
||||||
$this->importFixtureForUser(User::ROLE_USER);
|
$this->importFixtureForUser(User::ROLE_USER);
|
||||||
@@ -842,9 +910,12 @@ class TimesheetControllerTest extends APIControllerBaseTest
|
|||||||
$this->request($client, '/api/timesheets/' . $id . '/stop', 'PATCH');
|
$this->request($client, '/api/timesheets/' . $id . '/stop', 'PATCH');
|
||||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||||
|
|
||||||
$result = json_decode($client->getResponse()->getContent(), true);
|
$content = $client->getResponse()->getContent();
|
||||||
$this->assertIsArray($result);
|
self::assertIsString($content);
|
||||||
$this->assertNotEmpty($result);
|
$result = json_decode($content, true);
|
||||||
|
|
||||||
|
self::assertIsArray($result);
|
||||||
|
self::assertNotEmpty($result);
|
||||||
self::assertApiResponseTypeStructure('TimesheetEntity', $result);
|
self::assertApiResponseTypeStructure('TimesheetEntity', $result);
|
||||||
|
|
||||||
$em = $this->getEntityManager();
|
$em = $this->getEntityManager();
|
||||||
@@ -853,7 +924,7 @@ class TimesheetControllerTest extends APIControllerBaseTest
|
|||||||
$this->assertInstanceOf(\DateTime::class, $timesheet->getEnd());
|
$this->assertInstanceOf(\DateTime::class, $timesheet->getEnd());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testStopActionTriggersValidationOnLongRunning()
|
public function testStopActionTriggersValidationOnLongRunning(): void
|
||||||
{
|
{
|
||||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
||||||
$this->setSystemConfiguration('timesheet.rules.long_running_duration', 750);
|
$this->setSystemConfiguration('timesheet.rules.long_running_duration', 750);
|
||||||
@@ -877,12 +948,12 @@ class TimesheetControllerTest extends APIControllerBaseTest
|
|||||||
$this->assertApiCallValidationError($client->getResponse(), ['duration' => 'Maximum 12:30 hours allowed.']);
|
$this->assertApiCallValidationError($client->getResponse(), ['duration' => 'Maximum 12:30 hours allowed.']);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testStopThrowsNotFound()
|
public function testStopThrowsNotFound(): void
|
||||||
{
|
{
|
||||||
$this->assertEntityNotFoundForPatch(User::ROLE_USER, '/api/timesheets/11/stop', []);
|
$this->assertEntityNotFoundForPatch(User::ROLE_USER, '/api/timesheets/11/stop', []);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testStopNotAllowedForUser()
|
public function testStopNotAllowedForUser(): void
|
||||||
{
|
{
|
||||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
||||||
$this->importFixtureForUser(User::ROLE_USER);
|
$this->importFixtureForUser(User::ROLE_USER);
|
||||||
@@ -905,7 +976,7 @@ class TimesheetControllerTest extends APIControllerBaseTest
|
|||||||
$this->assertApiResponseAccessDenied($client->getResponse(), 'Access denied.');
|
$this->assertApiResponseAccessDenied($client->getResponse(), 'Access denied.');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetCollectionWithTags()
|
public function testGetCollectionWithTags(): void
|
||||||
{
|
{
|
||||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
||||||
$this->importFixtureForUser(User::ROLE_USER);
|
$this->importFixtureForUser(User::ROLE_USER);
|
||||||
@@ -924,33 +995,42 @@ class TimesheetControllerTest extends APIControllerBaseTest
|
|||||||
|
|
||||||
$query = ['tags' => ['Test']];
|
$query = ['tags' => ['Test']];
|
||||||
$this->assertAccessIsGranted($client, '/api/timesheets', 'GET', $query);
|
$this->assertAccessIsGranted($client, '/api/timesheets', 'GET', $query);
|
||||||
$result = json_decode($client->getResponse()->getContent(), true);
|
|
||||||
|
|
||||||
$this->assertIsArray($result);
|
$content = $client->getResponse()->getContent();
|
||||||
$this->assertNotEmpty($result);
|
self::assertIsString($content);
|
||||||
$this->assertEquals(10, \count($result));
|
$result = json_decode($content, true);
|
||||||
|
|
||||||
|
self::assertIsArray($result);
|
||||||
|
self::assertNotEmpty($result);
|
||||||
|
self::assertEquals(10, \count($result));
|
||||||
self::assertApiResponseTypeStructure('TimesheetCollection', $result[0]);
|
self::assertApiResponseTypeStructure('TimesheetCollection', $result[0]);
|
||||||
|
|
||||||
$query = ['tags' => ['Test', 'Admin']];
|
$query = ['tags' => ['Test', 'Admin']];
|
||||||
$this->assertAccessIsGranted($client, '/api/timesheets', 'GET', $query);
|
$this->assertAccessIsGranted($client, '/api/timesheets', 'GET', $query);
|
||||||
$result = json_decode($client->getResponse()->getContent(), true);
|
|
||||||
|
|
||||||
$this->assertIsArray($result);
|
$content = $client->getResponse()->getContent();
|
||||||
$this->assertNotEmpty($result);
|
self::assertIsString($content);
|
||||||
$this->assertEquals(10, \count($result));
|
$result = json_decode($content, true);
|
||||||
|
|
||||||
|
self::assertIsArray($result);
|
||||||
|
self::assertNotEmpty($result);
|
||||||
|
self::assertEquals(10, \count($result));
|
||||||
self::assertApiResponseTypeStructure('TimesheetCollection', $result[0]);
|
self::assertApiResponseTypeStructure('TimesheetCollection', $result[0]);
|
||||||
|
|
||||||
$query = ['tags' => ['Nothing-2-see', 'here']];
|
$query = ['tags' => ['Nothing-2-see', 'here']];
|
||||||
$this->assertAccessIsGranted($client, '/api/timesheets', 'GET', $query);
|
$this->assertAccessIsGranted($client, '/api/timesheets', 'GET', $query);
|
||||||
$result = json_decode($client->getResponse()->getContent(), true);
|
|
||||||
|
|
||||||
$this->assertIsArray($result);
|
$content = $client->getResponse()->getContent();
|
||||||
$this->assertNotEmpty($result);
|
self::assertIsString($content);
|
||||||
$this->assertEquals(20, \count($result));
|
$result = json_decode($content, true);
|
||||||
|
|
||||||
|
self::assertIsArray($result);
|
||||||
|
self::assertNotEmpty($result);
|
||||||
|
self::assertEquals(20, \count($result));
|
||||||
self::assertApiResponseTypeStructure('TimesheetCollection', $result[0]);
|
self::assertApiResponseTypeStructure('TimesheetCollection', $result[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRestartAction()
|
public function testRestartAction(): void
|
||||||
{
|
{
|
||||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
||||||
$timesheets = $this->importFixtureForUser(User::ROLE_USER);
|
$timesheets = $this->importFixtureForUser(User::ROLE_USER);
|
||||||
@@ -965,7 +1045,10 @@ class TimesheetControllerTest extends APIControllerBaseTest
|
|||||||
$this->request($client, '/api/timesheets/' . $id . '/restart', 'PATCH');
|
$this->request($client, '/api/timesheets/' . $id . '/restart', 'PATCH');
|
||||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||||
|
|
||||||
$result = json_decode($client->getResponse()->getContent(), true);
|
$content = $client->getResponse()->getContent();
|
||||||
|
self::assertIsString($content);
|
||||||
|
$result = json_decode($content, true);
|
||||||
|
|
||||||
self::assertApiResponseTypeStructure('TimesheetEntity', $result);
|
self::assertApiResponseTypeStructure('TimesheetEntity', $result);
|
||||||
$this->assertEmpty($result['description']);
|
$this->assertEmpty($result['description']);
|
||||||
$this->assertEmpty($result['tags']);
|
$this->assertEmpty($result['tags']);
|
||||||
@@ -975,13 +1058,13 @@ class TimesheetControllerTest extends APIControllerBaseTest
|
|||||||
$timesheet = $em->getRepository(Timesheet::class)->find($result['id']);
|
$timesheet = $em->getRepository(Timesheet::class)->find($result['id']);
|
||||||
$this->assertInstanceOf(\DateTime::class, $timesheet->getBegin());
|
$this->assertInstanceOf(\DateTime::class, $timesheet->getBegin());
|
||||||
$this->assertNull($timesheet->getEnd());
|
$this->assertNull($timesheet->getEnd());
|
||||||
$this->assertEquals(1, $timesheet->getActivity()->getId());
|
self::assertEquals(1, $timesheet->getActivity()->getId());
|
||||||
$this->assertEquals(1, $timesheet->getProject()->getId());
|
self::assertEquals(1, $timesheet->getProject()->getId());
|
||||||
$this->assertEmpty($timesheet->getDescription());
|
$this->assertEmpty($timesheet->getDescription());
|
||||||
$this->assertEmpty($timesheet->getTags());
|
$this->assertEmpty($timesheet->getTags());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRestartActionWithBegin()
|
public function testRestartActionWithBegin(): void
|
||||||
{
|
{
|
||||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
||||||
$timesheets = $this->importFixtureForUser(User::ROLE_USER);
|
$timesheets = $this->importFixtureForUser(User::ROLE_USER);
|
||||||
@@ -997,7 +1080,10 @@ class TimesheetControllerTest extends APIControllerBaseTest
|
|||||||
$this->request($client, '/api/timesheets/' . $id . '/restart', 'PATCH', ['begin' => $begin->format(BaseApiController::DATE_FORMAT_PHP)]);
|
$this->request($client, '/api/timesheets/' . $id . '/restart', 'PATCH', ['begin' => $begin->format(BaseApiController::DATE_FORMAT_PHP)]);
|
||||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||||
|
|
||||||
$result = json_decode($client->getResponse()->getContent(), true);
|
$content = $client->getResponse()->getContent();
|
||||||
|
self::assertIsString($content);
|
||||||
|
$result = json_decode($content, true);
|
||||||
|
|
||||||
self::assertApiResponseTypeStructure('TimesheetEntity', $result);
|
self::assertApiResponseTypeStructure('TimesheetEntity', $result);
|
||||||
$this->assertEmpty($result['description']);
|
$this->assertEmpty($result['description']);
|
||||||
$this->assertEmpty($result['tags']);
|
$this->assertEmpty($result['tags']);
|
||||||
@@ -1006,15 +1092,15 @@ class TimesheetControllerTest extends APIControllerBaseTest
|
|||||||
/** @var Timesheet $timesheet */
|
/** @var Timesheet $timesheet */
|
||||||
$timesheet = $em->getRepository(Timesheet::class)->find($result['id']);
|
$timesheet = $em->getRepository(Timesheet::class)->find($result['id']);
|
||||||
$this->assertInstanceOf(\DateTime::class, $timesheet->getBegin());
|
$this->assertInstanceOf(\DateTime::class, $timesheet->getBegin());
|
||||||
$this->assertEquals($begin->format(BaseApiController::DATE_FORMAT_PHP), $timesheet->getBegin()->format(BaseApiController::DATE_FORMAT_PHP));
|
self::assertEquals($begin->format(BaseApiController::DATE_FORMAT_PHP), $timesheet->getBegin()->format(BaseApiController::DATE_FORMAT_PHP));
|
||||||
$this->assertNull($timesheet->getEnd());
|
$this->assertNull($timesheet->getEnd());
|
||||||
$this->assertEquals(1, $timesheet->getActivity()->getId());
|
self::assertEquals(1, $timesheet->getActivity()->getId());
|
||||||
$this->assertEquals(1, $timesheet->getProject()->getId());
|
self::assertEquals(1, $timesheet->getProject()->getId());
|
||||||
$this->assertEmpty($timesheet->getDescription());
|
$this->assertEmpty($timesheet->getDescription());
|
||||||
$this->assertEmpty($timesheet->getTags());
|
$this->assertEmpty($timesheet->getTags());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRestartActionWithCopyData()
|
public function testRestartActionWithCopyData(): void
|
||||||
{
|
{
|
||||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
||||||
$timesheets = $this->importFixtureForUser(User::ROLE_USER);
|
$timesheets = $this->importFixtureForUser(User::ROLE_USER);
|
||||||
@@ -1034,29 +1120,32 @@ class TimesheetControllerTest extends APIControllerBaseTest
|
|||||||
$em->flush();
|
$em->flush();
|
||||||
|
|
||||||
$timesheet = $em->getRepository(Timesheet::class)->find($id);
|
$timesheet = $em->getRepository(Timesheet::class)->find($id);
|
||||||
$this->assertEquals('foo', $timesheet->getDescription());
|
self::assertEquals('foo', $timesheet->getDescription());
|
||||||
|
|
||||||
$this->request($client, '/api/timesheets/' . $id . '/restart', 'PATCH', ['copy' => 'all']);
|
$this->request($client, '/api/timesheets/' . $id . '/restart', 'PATCH', ['copy' => 'all']);
|
||||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||||
|
|
||||||
$result = json_decode($client->getResponse()->getContent(), true);
|
$content = $client->getResponse()->getContent();
|
||||||
|
self::assertIsString($content);
|
||||||
|
$result = json_decode($content, true);
|
||||||
|
|
||||||
self::assertApiResponseTypeStructure('TimesheetEntity', $result);
|
self::assertApiResponseTypeStructure('TimesheetEntity', $result);
|
||||||
$this->assertEquals('foo', $result['description']);
|
self::assertEquals('foo', $result['description']);
|
||||||
$this->assertEquals([['name' => 'sdfsdf', 'value' => 'nnnnn'], ['name' => '1234567890', 'value' => '1234567890']], $result['metaFields']);
|
self::assertEquals([['name' => 'sdfsdf', 'value' => 'nnnnn'], ['name' => '1234567890', 'value' => '1234567890']], $result['metaFields']);
|
||||||
$this->assertEquals(['another', 'testing', 'bar'], $result['tags']);
|
self::assertEquals(['another', 'testing', 'bar'], $result['tags']);
|
||||||
|
|
||||||
$em = $this->getEntityManager();
|
$em = $this->getEntityManager();
|
||||||
/** @var Timesheet $timesheet */
|
/** @var Timesheet $timesheet */
|
||||||
$timesheet = $em->getRepository(Timesheet::class)->find($result['id']);
|
$timesheet = $em->getRepository(Timesheet::class)->find($result['id']);
|
||||||
$this->assertInstanceOf(\DateTime::class, $timesheet->getBegin());
|
$this->assertInstanceOf(\DateTime::class, $timesheet->getBegin());
|
||||||
$this->assertNull($timesheet->getEnd());
|
$this->assertNull($timesheet->getEnd());
|
||||||
$this->assertEquals(1, $timesheet->getActivity()->getId());
|
self::assertEquals(1, $timesheet->getActivity()->getId());
|
||||||
$this->assertEquals(1, $timesheet->getProject()->getId());
|
self::assertEquals(1, $timesheet->getProject()->getId());
|
||||||
$this->assertEquals('foo', $timesheet->getDescription());
|
self::assertEquals('foo', $timesheet->getDescription());
|
||||||
$this->assertEquals(['another', 'testing', 'bar'], $timesheet->getTagsAsArray());
|
self::assertEquals(['another', 'testing', 'bar'], $timesheet->getTagsAsArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRestartNotAllowedForUser()
|
public function testRestartNotAllowedForUser(): void
|
||||||
{
|
{
|
||||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
||||||
|
|
||||||
@@ -1078,12 +1167,12 @@ class TimesheetControllerTest extends APIControllerBaseTest
|
|||||||
$this->assertApiResponseAccessDenied($client->getResponse(), 'Access denied.');
|
$this->assertApiResponseAccessDenied($client->getResponse(), 'Access denied.');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRestartThrowsNotFound()
|
public function testRestartThrowsNotFound(): void
|
||||||
{
|
{
|
||||||
$this->assertEntityNotFoundForPatch(User::ROLE_USER, '/api/timesheets/42/restart', []);
|
$this->assertEntityNotFoundForPatch(User::ROLE_USER, '/api/timesheets/42/restart', []);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testDuplicateAction()
|
public function testDuplicateAction(): void
|
||||||
{
|
{
|
||||||
$dateTime = new DateTimeFactory(new \DateTimeZone(self::TEST_TIMEZONE));
|
$dateTime = new DateTimeFactory(new \DateTimeZone(self::TEST_TIMEZONE));
|
||||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||||
@@ -1099,30 +1188,36 @@ class TimesheetControllerTest extends APIControllerBaseTest
|
|||||||
$this->request($client, '/api/timesheets', 'POST', [], json_encode($data));
|
$this->request($client, '/api/timesheets', 'POST', [], json_encode($data));
|
||||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||||
|
|
||||||
$result = json_decode($client->getResponse()->getContent(), true);
|
$content = $client->getResponse()->getContent();
|
||||||
$this->assertIsArray($result);
|
self::assertIsString($content);
|
||||||
|
$result = json_decode($content, true);
|
||||||
|
|
||||||
|
self::assertIsArray($result);
|
||||||
self::assertApiResponseTypeStructure('TimesheetEntity', $result);
|
self::assertApiResponseTypeStructure('TimesheetEntity', $result);
|
||||||
$this->assertNotEmpty($result['id']);
|
self::assertNotEmpty($result['id']);
|
||||||
$this->assertTrue($result['duration'] == 28800 || $result['duration'] == 28860); // 1 minute rounding might be applied
|
$this->assertTrue($result['duration'] == 28800 || $result['duration'] == 28860); // 1 minute rounding might be applied
|
||||||
$this->assertEquals(2016, $result['rate']);
|
self::assertEquals(2016, $result['rate']);
|
||||||
|
|
||||||
$this->request($client, '/api/timesheets/' . $result['id'] . '/duplicate', 'PATCH');
|
$this->request($client, '/api/timesheets/' . $result['id'] . '/duplicate', 'PATCH');
|
||||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||||
|
|
||||||
$result = json_decode($client->getResponse()->getContent(), true);
|
$content = $client->getResponse()->getContent();
|
||||||
$this->assertIsArray($result);
|
self::assertIsString($content);
|
||||||
|
$result = json_decode($content, true);
|
||||||
|
|
||||||
|
self::assertIsArray($result);
|
||||||
self::assertApiResponseTypeStructure('TimesheetEntity', $result);
|
self::assertApiResponseTypeStructure('TimesheetEntity', $result);
|
||||||
$this->assertNotEmpty($result['id']);
|
self::assertNotEmpty($result['id']);
|
||||||
$this->assertTrue($result['duration'] == 28800 || $result['duration'] == 28860); // 1 minute rounding might be applied
|
$this->assertTrue($result['duration'] == 28800 || $result['duration'] == 28860); // 1 minute rounding might be applied
|
||||||
$this->assertEquals(2016, $result['rate']);
|
self::assertEquals(2016, $result['rate']);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testDuplicateThrowsNotFound()
|
public function testDuplicateThrowsNotFound(): void
|
||||||
{
|
{
|
||||||
$this->assertEntityNotFoundForPatch(User::ROLE_ADMIN, '/api/timesheets/11/duplicate', []);
|
$this->assertEntityNotFoundForPatch(User::ROLE_ADMIN, '/api/timesheets/11/duplicate', []);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testExportAction()
|
public function testExportAction(): void
|
||||||
{
|
{
|
||||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||||
$timesheets = $this->importFixtureForUser(User::ROLE_USER);
|
$timesheets = $this->importFixtureForUser(User::ROLE_USER);
|
||||||
@@ -1135,7 +1230,11 @@ class TimesheetControllerTest extends APIControllerBaseTest
|
|||||||
|
|
||||||
$this->request($client, '/api/timesheets/' . $id . '/export', 'PATCH');
|
$this->request($client, '/api/timesheets/' . $id . '/export', 'PATCH');
|
||||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||||
$result = json_decode($client->getResponse()->getContent(), true);
|
|
||||||
|
$content = $client->getResponse()->getContent();
|
||||||
|
self::assertIsString($content);
|
||||||
|
$result = json_decode($content, true);
|
||||||
|
|
||||||
self::assertApiResponseTypeStructure('TimesheetEntity', $result);
|
self::assertApiResponseTypeStructure('TimesheetEntity', $result);
|
||||||
|
|
||||||
$em->clear();
|
$em->clear();
|
||||||
@@ -1151,7 +1250,7 @@ class TimesheetControllerTest extends APIControllerBaseTest
|
|||||||
$this->assertFalse($timesheet->isExported());
|
$this->assertFalse($timesheet->isExported());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testExportNotAllowedForUser()
|
public function testExportNotAllowedForUser(): void
|
||||||
{
|
{
|
||||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
||||||
$timesheets = $this->importFixtureForUser(User::ROLE_USER);
|
$timesheets = $this->importFixtureForUser(User::ROLE_USER);
|
||||||
@@ -1161,17 +1260,17 @@ class TimesheetControllerTest extends APIControllerBaseTest
|
|||||||
$this->assertApiResponseAccessDenied($client->getResponse(), 'Access denied.');
|
$this->assertApiResponseAccessDenied($client->getResponse(), 'Access denied.');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testExportThrowsNotFound()
|
public function testExportThrowsNotFound(): void
|
||||||
{
|
{
|
||||||
$this->assertEntityNotFoundForPatch(User::ROLE_ADMIN, '/api/timesheets/' . PHP_INT_MAX . '/export', []);
|
$this->assertEntityNotFoundForPatch(User::ROLE_ADMIN, '/api/timesheets/' . PHP_INT_MAX . '/export', []);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testMetaActionThrowsNotFound()
|
public function testMetaActionThrowsNotFound(): void
|
||||||
{
|
{
|
||||||
$this->assertEntityNotFoundForPatch(User::ROLE_ADMIN, '/api/timesheets/' . PHP_INT_MAX . '/meta', []);
|
$this->assertEntityNotFoundForPatch(User::ROLE_ADMIN, '/api/timesheets/' . PHP_INT_MAX . '/meta', []);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testMetaActionThrowsExceptionOnMissingName()
|
public function testMetaActionThrowsExceptionOnMissingName(): void
|
||||||
{
|
{
|
||||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
||||||
$timesheets = $this->importFixtureForUser(User::ROLE_USER);
|
$timesheets = $this->importFixtureForUser(User::ROLE_USER);
|
||||||
@@ -1183,7 +1282,7 @@ class TimesheetControllerTest extends APIControllerBaseTest
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testMetaActionThrowsExceptionOnMissingValue()
|
public function testMetaActionThrowsExceptionOnMissingValue(): void
|
||||||
{
|
{
|
||||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
||||||
$timesheets = $this->importFixtureForUser(User::ROLE_USER);
|
$timesheets = $this->importFixtureForUser(User::ROLE_USER);
|
||||||
@@ -1195,7 +1294,7 @@ class TimesheetControllerTest extends APIControllerBaseTest
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testMetaActionThrowsExceptionOnMissingMetafield()
|
public function testMetaActionThrowsExceptionOnMissingMetafield(): void
|
||||||
{
|
{
|
||||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
||||||
$timesheets = $this->importFixtureForUser(User::ROLE_USER);
|
$timesheets = $this->importFixtureForUser(User::ROLE_USER);
|
||||||
@@ -1207,7 +1306,7 @@ class TimesheetControllerTest extends APIControllerBaseTest
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testMetaAction()
|
public function testMetaAction(): void
|
||||||
{
|
{
|
||||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
||||||
$timesheets = $this->importFixtureForUser(User::ROLE_USER);
|
$timesheets = $this->importFixtureForUser(User::ROLE_USER);
|
||||||
@@ -1222,13 +1321,16 @@ class TimesheetControllerTest extends APIControllerBaseTest
|
|||||||
|
|
||||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||||
|
|
||||||
$result = json_decode($client->getResponse()->getContent(), true);
|
$content = $client->getResponse()->getContent();
|
||||||
|
self::assertIsString($content);
|
||||||
|
$result = json_decode($content, true);
|
||||||
|
|
||||||
self::assertApiResponseTypeStructure('TimesheetEntity', $result);
|
self::assertApiResponseTypeStructure('TimesheetEntity', $result);
|
||||||
$this->assertEquals(['name' => 'metatestmock', 'value' => 'another,testing,bar'], $result['metaFields'][0]);
|
self::assertEquals(['name' => 'metatestmock', 'value' => 'another,testing,bar'], $result['metaFields'][0]);
|
||||||
|
|
||||||
$em = $this->getEntityManager();
|
$em = $this->getEntityManager();
|
||||||
/** @var Timesheet $timesheet */
|
/** @var Timesheet $timesheet */
|
||||||
$timesheet = $em->getRepository(Timesheet::class)->find($id);
|
$timesheet = $em->getRepository(Timesheet::class)->find($id);
|
||||||
$this->assertEquals('another,testing,bar', $timesheet->getMetaField('metatestmock')->getValue());
|
self::assertEquals('another,testing,bar', $timesheet->getMetaField('metatestmock')->getValue());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -179,9 +179,20 @@ class TimesheetTest extends TestCase
|
|||||||
public function testClone(): void
|
public function testClone(): void
|
||||||
{
|
{
|
||||||
$sut = new Timesheet();
|
$sut = new Timesheet();
|
||||||
|
self::assertNull($sut->getModifiedAt());
|
||||||
$sut->setExported(true);
|
$sut->setExported(true);
|
||||||
$sut->setDescription('Invalid timesheet category "foo" given, expected one of: work, holiday, sickness, parental, overtime');
|
$sut->setDescription('Invalid timesheet category "foo" given, expected one of: work, holiday, sickness, parental, overtime');
|
||||||
|
|
||||||
|
$modifiedDate = new \DateTime();
|
||||||
|
|
||||||
|
$reflection = new \ReflectionClass($sut);
|
||||||
|
$property = $reflection->getProperty('modifiedAt');
|
||||||
|
$property->setAccessible(true);
|
||||||
|
$property->setValue($sut, $modifiedDate);
|
||||||
|
$property->setAccessible(false);
|
||||||
|
|
||||||
|
self::assertEquals($modifiedDate, $sut->getModifiedAt());
|
||||||
|
|
||||||
$meta = new TimesheetMeta();
|
$meta = new TimesheetMeta();
|
||||||
$meta->setName('blabla');
|
$meta->setName('blabla');
|
||||||
$meta->setValue('1234567890');
|
$meta->setValue('1234567890');
|
||||||
@@ -198,6 +209,9 @@ class TimesheetTest extends TestCase
|
|||||||
|
|
||||||
$clone = clone $sut;
|
$clone = clone $sut;
|
||||||
|
|
||||||
|
self::assertNotNull($sut->getModifiedAt());
|
||||||
|
self::assertNull($clone->getModifiedAt());
|
||||||
|
|
||||||
foreach ($sut->getMetaFields() as $metaField) {
|
foreach ($sut->getMetaFields() as $metaField) {
|
||||||
$cloneMeta = $clone->getMetaField($metaField->getName());
|
$cloneMeta = $clone->getMetaField($metaField->getName());
|
||||||
self::assertEquals($cloneMeta->getValue(), $metaField->getValue());
|
self::assertEquals($cloneMeta->getValue(), $metaField->getValue());
|
||||||
|
|||||||
@@ -65,4 +65,9 @@ class DebugFormatter implements InvoiceFormatter
|
|||||||
{
|
{
|
||||||
// does nothing
|
// does nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getFormattedAmount(float $amount): string
|
||||||
|
{
|
||||||
|
return (string) $amount;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ class InvoiceItemDefaultHydratorTest extends TestCase
|
|||||||
'entry.user_id',
|
'entry.user_id',
|
||||||
'entry.user_name',
|
'entry.user_name',
|
||||||
'entry.user_alias',
|
'entry.user_alias',
|
||||||
|
'entry.user_display',
|
||||||
'entry.user_title',
|
'entry.user_title',
|
||||||
'entry.activity',
|
'entry.activity',
|
||||||
'entry.activity_id',
|
'entry.activity_id',
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ class InvoiceModelCustomerHydratorTest extends TestCase
|
|||||||
'customer.contact',
|
'customer.contact',
|
||||||
'customer.company',
|
'customer.company',
|
||||||
'customer.vat',
|
'customer.vat',
|
||||||
|
'customer.vat_id',
|
||||||
'customer.country',
|
'customer.country',
|
||||||
'customer.number',
|
'customer.number',
|
||||||
'customer.homepage',
|
'customer.homepage',
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ class InvoiceModelDefaultHydratorTest extends TestCase
|
|||||||
'invoice.vat',
|
'invoice.vat',
|
||||||
'invoice.language',
|
'invoice.language',
|
||||||
'invoice.tax',
|
'invoice.tax',
|
||||||
|
'invoice.tax_hide',
|
||||||
'invoice.tax_nc',
|
'invoice.tax_nc',
|
||||||
'invoice.tax_plain',
|
'invoice.tax_plain',
|
||||||
'invoice.total_time',
|
'invoice.total_time',
|
||||||
@@ -76,6 +77,7 @@ class InvoiceModelDefaultHydratorTest extends TestCase
|
|||||||
'query.end_month',
|
'query.end_month',
|
||||||
'query.end_month_number',
|
'query.end_month_number',
|
||||||
'query.end_year',
|
'query.end_year',
|
||||||
|
'user.see_others',
|
||||||
];
|
];
|
||||||
|
|
||||||
$givenKeys = array_keys($model);
|
$givenKeys = array_keys($model);
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ class InvoiceModelUserHydratorTest extends TestCase
|
|||||||
protected function assertModelStructure(array $model)
|
protected function assertModelStructure(array $model)
|
||||||
{
|
{
|
||||||
$keys = [
|
$keys = [
|
||||||
|
'user.display',
|
||||||
'user.alias',
|
'user.alias',
|
||||||
'user.email',
|
'user.email',
|
||||||
'user.name',
|
'user.name',
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ class DebugRendererTest extends TestCase
|
|||||||
'invoice.currency',
|
'invoice.currency',
|
||||||
'invoice.currency_symbol',
|
'invoice.currency_symbol',
|
||||||
'invoice.vat',
|
'invoice.vat',
|
||||||
|
'invoice.tax_hide',
|
||||||
'invoice.tax',
|
'invoice.tax',
|
||||||
'invoice.language',
|
'invoice.language',
|
||||||
'invoice.tax_nc',
|
'invoice.tax_nc',
|
||||||
@@ -127,6 +128,7 @@ class DebugRendererTest extends TestCase
|
|||||||
'customer.contact',
|
'customer.contact',
|
||||||
'customer.company',
|
'customer.company',
|
||||||
'customer.vat',
|
'customer.vat',
|
||||||
|
'customer.vat_id',
|
||||||
'customer.country',
|
'customer.country',
|
||||||
'customer.number',
|
'customer.number',
|
||||||
'customer.homepage',
|
'customer.homepage',
|
||||||
@@ -150,14 +152,23 @@ class DebugRendererTest extends TestCase
|
|||||||
'activity.time_budget_open',
|
'activity.time_budget_open',
|
||||||
'activity.time_budget_open_plain',
|
'activity.time_budget_open_plain',
|
||||||
'user.alias',
|
'user.alias',
|
||||||
|
'user.display',
|
||||||
'user.email',
|
'user.email',
|
||||||
'user.name',
|
'user.name',
|
||||||
'user.title',
|
'user.title',
|
||||||
|
'user.see_others',
|
||||||
'user.meta.hello',
|
'user.meta.hello',
|
||||||
'user.meta.kitty',
|
'user.meta.kitty',
|
||||||
'testFromModelHydrator',
|
'testFromModelHydrator',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
if ($activityCounter === 1) {
|
||||||
|
$keys = array_merge($keys, [
|
||||||
|
'query.activity.comment',
|
||||||
|
'query.activity.name',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
if ($activityCounter > 1) {
|
if ($activityCounter > 1) {
|
||||||
$keys = array_merge($keys, [
|
$keys = array_merge($keys, [
|
||||||
'activity.1.budget_open',
|
'activity.1.budget_open',
|
||||||
@@ -171,6 +182,14 @@ class DebugRendererTest extends TestCase
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($projectCounter === 1) {
|
||||||
|
$keys = array_merge($keys, [
|
||||||
|
'query.project.name',
|
||||||
|
'query.project.comment',
|
||||||
|
'query.project.order_number',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
if ($projectCounter > 0) {
|
if ($projectCounter > 0) {
|
||||||
$keys = array_merge($keys, [
|
$keys = array_merge($keys, [
|
||||||
'project.id',
|
'project.id',
|
||||||
@@ -254,6 +273,7 @@ class DebugRendererTest extends TestCase
|
|||||||
'entry.weekyear',
|
'entry.weekyear',
|
||||||
'entry.user_id',
|
'entry.user_id',
|
||||||
'entry.user_name',
|
'entry.user_name',
|
||||||
|
'entry.user_display',
|
||||||
'entry.user_alias',
|
'entry.user_alias',
|
||||||
'entry.user_title',
|
'entry.user_title',
|
||||||
'entry.activity',
|
'entry.activity',
|
||||||
|
|||||||
@@ -61,20 +61,27 @@ class TimesheetQueryTest extends BaseQueryTest
|
|||||||
protected function assertUser(TimesheetQuery $sut): void
|
protected function assertUser(TimesheetQuery $sut): void
|
||||||
{
|
{
|
||||||
self::assertNull($sut->getUser());
|
self::assertNull($sut->getUser());
|
||||||
|
self::assertFalse($sut->hasUsers());
|
||||||
|
|
||||||
$expected = new User();
|
$expected = new User();
|
||||||
$expected->setUserIdentifier('foo-bar');
|
$expected->setUserIdentifier('foo-bar');
|
||||||
$sut->setUser($expected);
|
$sut->setUser($expected);
|
||||||
self::assertEquals($expected, $sut->getUser());
|
self::assertEquals($expected, $sut->getUser());
|
||||||
|
self::assertTrue($sut->hasUsers());
|
||||||
|
|
||||||
|
$sut->setUser(null);
|
||||||
|
self::assertFalse($sut->hasUsers());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function assertUsers(TimesheetQuery $sut): void
|
protected function assertUsers(TimesheetQuery $sut): void
|
||||||
{
|
{
|
||||||
self::assertEmpty($sut->getUsers());
|
self::assertEmpty($sut->getUsers());
|
||||||
|
self::assertFalse($sut->hasUsers());
|
||||||
|
|
||||||
$user = $this->getMockBuilder(User::class)->disableOriginalConstructor()->getMock();
|
$user = $this->getMockBuilder(User::class)->disableOriginalConstructor()->getMock();
|
||||||
$user->method('getId')->willReturn(1);
|
$user->method('getId')->willReturn(1);
|
||||||
$sut->addUser($user);
|
$sut->addUser($user);
|
||||||
|
self::assertTrue($sut->hasUsers());
|
||||||
|
|
||||||
$user = $this->getMockBuilder(User::class)->disableOriginalConstructor()->getMock();
|
$user = $this->getMockBuilder(User::class)->disableOriginalConstructor()->getMock();
|
||||||
$user->method('getId')->willReturn(1);
|
$user->method('getId')->willReturn(1);
|
||||||
|
|||||||
@@ -28,30 +28,6 @@ class TagRepositoryTest extends AbstractRepositoryTest
|
|||||||
$this->importFixture($data);
|
$this->importFixture($data);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testFindIds()
|
|
||||||
{
|
|
||||||
$em = $this->getEntityManager();
|
|
||||||
/** @var TagRepository $repository */
|
|
||||||
$repository = $em->getRepository(Tag::class);
|
|
||||||
|
|
||||||
$result = $repository->findIdsByTagNameList('2018,Test');
|
|
||||||
$this->assertIsArray($result);
|
|
||||||
$this->assertNotEmpty($result);
|
|
||||||
$this->assertEquals(7, \count($result));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testFindNoIds()
|
|
||||||
{
|
|
||||||
$em = $this->getEntityManager();
|
|
||||||
/** @var TagRepository $repository */
|
|
||||||
$repository = $em->getRepository(Tag::class);
|
|
||||||
|
|
||||||
$result = $repository->findIdsByTagNameList('Simply');
|
|
||||||
$this->assertIsArray($result);
|
|
||||||
$this->assertEmpty($result);
|
|
||||||
$this->assertEquals(0, \count($result));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testFindAllTagNames()
|
public function testFindAllTagNames()
|
||||||
{
|
{
|
||||||
$em = $this->getEntityManager();
|
$em = $this->getEntityManager();
|
||||||
|
|||||||
@@ -1277,276 +1277,6 @@ parameters:
|
|||||||
count: 1
|
count: 1
|
||||||
path: API/TimesheetControllerTest.php
|
path: API/TimesheetControllerTest.php
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Tests\\\\API\\\\TimesheetControllerTest\\:\\:testActiveAction\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: API/TimesheetControllerTest.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Tests\\\\API\\\\TimesheetControllerTest\\:\\:testDeleteAction\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: API/TimesheetControllerTest.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Tests\\\\API\\\\TimesheetControllerTest\\:\\:testDeleteActionForDifferentUser\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: API/TimesheetControllerTest.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Tests\\\\API\\\\TimesheetControllerTest\\:\\:testDeleteActionForExportedRecordIsAllowedForAdmin\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: API/TimesheetControllerTest.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Tests\\\\API\\\\TimesheetControllerTest\\:\\:testDeleteActionForExportedRecordIsNotAllowed\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: API/TimesheetControllerTest.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Tests\\\\API\\\\TimesheetControllerTest\\:\\:testDeleteActionWithUnknownTimesheet\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: API/TimesheetControllerTest.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Tests\\\\API\\\\TimesheetControllerTest\\:\\:testDeleteActionWithoutAuthorization\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: API/TimesheetControllerTest.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Tests\\\\API\\\\TimesheetControllerTest\\:\\:testDuplicateAction\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: API/TimesheetControllerTest.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Tests\\\\API\\\\TimesheetControllerTest\\:\\:testDuplicateThrowsNotFound\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: API/TimesheetControllerTest.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Tests\\\\API\\\\TimesheetControllerTest\\:\\:testExportAction\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: API/TimesheetControllerTest.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Tests\\\\API\\\\TimesheetControllerTest\\:\\:testExportNotAllowedForUser\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: API/TimesheetControllerTest.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Tests\\\\API\\\\TimesheetControllerTest\\:\\:testExportThrowsNotFound\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: API/TimesheetControllerTest.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Tests\\\\API\\\\TimesheetControllerTest\\:\\:testExportedFilter\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: API/TimesheetControllerTest.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Tests\\\\API\\\\TimesheetControllerTest\\:\\:testGetCollection\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: API/TimesheetControllerTest.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Tests\\\\API\\\\TimesheetControllerTest\\:\\:testGetCollectionForAllUser\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: API/TimesheetControllerTest.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Tests\\\\API\\\\TimesheetControllerTest\\:\\:testGetCollectionForEmptyResult\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: API/TimesheetControllerTest.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Tests\\\\API\\\\TimesheetControllerTest\\:\\:testGetCollectionForOtherUser\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: API/TimesheetControllerTest.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Tests\\\\API\\\\TimesheetControllerTest\\:\\:testGetCollectionFull\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: API/TimesheetControllerTest.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Tests\\\\API\\\\TimesheetControllerTest\\:\\:testGetCollectionWithQuery\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: API/TimesheetControllerTest.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Tests\\\\API\\\\TimesheetControllerTest\\:\\:testGetCollectionWithQueryFailsWith404OnOutOfRangedPage\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: API/TimesheetControllerTest.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Tests\\\\API\\\\TimesheetControllerTest\\:\\:testGetCollectionWithSingleParamsQuery\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: API/TimesheetControllerTest.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Tests\\\\API\\\\TimesheetControllerTest\\:\\:testGetCollectionWithTags\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: API/TimesheetControllerTest.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Tests\\\\API\\\\TimesheetControllerTest\\:\\:testGetEntity\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: API/TimesheetControllerTest.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Tests\\\\API\\\\TimesheetControllerTest\\:\\:testGetEntityAccessAllowedForAdmin\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: API/TimesheetControllerTest.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Tests\\\\API\\\\TimesheetControllerTest\\:\\:testGetEntityAccessDenied\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: API/TimesheetControllerTest.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Tests\\\\API\\\\TimesheetControllerTest\\:\\:testGetEntityNotFound\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: API/TimesheetControllerTest.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Tests\\\\API\\\\TimesheetControllerTest\\:\\:testGetRecentAction\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: API/TimesheetControllerTest.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Tests\\\\API\\\\TimesheetControllerTest\\:\\:testInvalidPatchAction\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: API/TimesheetControllerTest.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Tests\\\\API\\\\TimesheetControllerTest\\:\\:testIsSecure\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: API/TimesheetControllerTest.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Tests\\\\API\\\\TimesheetControllerTest\\:\\:testMetaAction\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: API/TimesheetControllerTest.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Tests\\\\API\\\\TimesheetControllerTest\\:\\:testMetaActionThrowsExceptionOnMissingMetafield\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: API/TimesheetControllerTest.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Tests\\\\API\\\\TimesheetControllerTest\\:\\:testMetaActionThrowsExceptionOnMissingName\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: API/TimesheetControllerTest.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Tests\\\\API\\\\TimesheetControllerTest\\:\\:testMetaActionThrowsExceptionOnMissingValue\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: API/TimesheetControllerTest.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Tests\\\\API\\\\TimesheetControllerTest\\:\\:testMetaActionThrowsNotFound\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: API/TimesheetControllerTest.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Tests\\\\API\\\\TimesheetControllerTest\\:\\:testPatchAction\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: API/TimesheetControllerTest.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Tests\\\\API\\\\TimesheetControllerTest\\:\\:testPatchActionWithInvalidUser\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: API/TimesheetControllerTest.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Tests\\\\API\\\\TimesheetControllerTest\\:\\:testPatchActionWithUnknownTimesheet\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: API/TimesheetControllerTest.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Tests\\\\API\\\\TimesheetControllerTest\\:\\:testPostAction\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: API/TimesheetControllerTest.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Tests\\\\API\\\\TimesheetControllerTest\\:\\:testPostActionForDifferentUser\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: API/TimesheetControllerTest.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Tests\\\\API\\\\TimesheetControllerTest\\:\\:testPostActionWithFullExpandedResponse\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: API/TimesheetControllerTest.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Tests\\\\API\\\\TimesheetControllerTest\\:\\:testPostActionWithInvisibleActivity\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: API/TimesheetControllerTest.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Tests\\\\API\\\\TimesheetControllerTest\\:\\:testPostActionWithInvisibleProject\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: API/TimesheetControllerTest.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Tests\\\\API\\\\TimesheetControllerTest\\:\\:testPostActionWithNonBillableCustomer\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: API/TimesheetControllerTest.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Tests\\\\API\\\\TimesheetControllerTest\\:\\:testPostActionWithNonBillableCustomerExplicit\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: API/TimesheetControllerTest.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Tests\\\\API\\\\TimesheetControllerTest\\:\\:testRestartAction\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: API/TimesheetControllerTest.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Tests\\\\API\\\\TimesheetControllerTest\\:\\:testRestartActionWithBegin\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: API/TimesheetControllerTest.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Tests\\\\API\\\\TimesheetControllerTest\\:\\:testRestartActionWithCopyData\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: API/TimesheetControllerTest.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Tests\\\\API\\\\TimesheetControllerTest\\:\\:testRestartNotAllowedForUser\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: API/TimesheetControllerTest.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Tests\\\\API\\\\TimesheetControllerTest\\:\\:testRestartThrowsNotFound\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: API/TimesheetControllerTest.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Tests\\\\API\\\\TimesheetControllerTest\\:\\:testStopAction\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: API/TimesheetControllerTest.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Tests\\\\API\\\\TimesheetControllerTest\\:\\:testStopActionTriggersValidationOnLongRunning\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: API/TimesheetControllerTest.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Tests\\\\API\\\\TimesheetControllerTest\\:\\:testStopNotAllowedForUser\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: API/TimesheetControllerTest.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Tests\\\\API\\\\TimesheetControllerTest\\:\\:testStopThrowsNotFound\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: API/TimesheetControllerTest.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Parameter \\#1 \\$json of function json_decode expects string, string\\|false given\\.$#"
|
|
||||||
count: 32
|
|
||||||
path: API/TimesheetControllerTest.php
|
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Parameter \\#1 \\$value of function count expects array\\|Countable, mixed given\\.$#"
|
message: "#^Parameter \\#1 \\$value of function count expects array\\|Countable, mixed given\\.$#"
|
||||||
count: 1
|
count: 1
|
||||||
@@ -8692,16 +8422,6 @@ parameters:
|
|||||||
count: 1
|
count: 1
|
||||||
path: Repository/TagRepositoryTest.php
|
path: Repository/TagRepositoryTest.php
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Tests\\\\Repository\\\\TagRepositoryTest\\:\\:testFindIds\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: Repository/TagRepositoryTest.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Tests\\\\Repository\\\\TagRepositoryTest\\:\\:testFindNoIds\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: Repository/TagRepositoryTest.php
|
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Method App\\\\Tests\\\\Repository\\\\TagRepositoryTest\\:\\:testFindNoTagNames\\(\\) has no return type specified\\.$#"
|
message: "#^Method App\\\\Tests\\\\Repository\\\\TagRepositoryTest\\:\\:testFindNoTagNames\\(\\) has no return type specified\\.$#"
|
||||||
count: 1
|
count: 1
|
||||||
|
|||||||
Reference in New Issue
Block a user