feat: feedback export to excel
addresses #869dv9k9z
This commit is contained in:
@@ -32,6 +32,7 @@
|
|||||||
"nesbot/carbon": "^2.70",
|
"nesbot/carbon": "^2.70",
|
||||||
"oneup/uploader-bundle": "^5.1",
|
"oneup/uploader-bundle": "^5.1",
|
||||||
"phpdocumentor/reflection-docblock": "^5.3",
|
"phpdocumentor/reflection-docblock": "^5.3",
|
||||||
|
"phpoffice/phpspreadsheet": "^5.9",
|
||||||
"phpstan/phpdoc-parser": "^1.22",
|
"phpstan/phpdoc-parser": "^1.22",
|
||||||
"scienta/doctrine-json-functions": "^6.3",
|
"scienta/doctrine-json-functions": "^6.3",
|
||||||
"setasign/fpdf": "^1.8",
|
"setasign/fpdf": "^1.8",
|
||||||
|
|||||||
Generated
+344
-77
@@ -4,7 +4,7 @@
|
|||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "f243bce9751e17382a0b20bc5926bc4c",
|
"content-hash": "68abd860121347dcd60c10cbfc4e3434",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "beberlei/doctrineextensions",
|
"name": "beberlei/doctrineextensions",
|
||||||
@@ -209,6 +209,82 @@
|
|||||||
],
|
],
|
||||||
"time": "2026-08-21T14:57:29+00:00"
|
"time": "2026-08-21T14:57:29+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "composer/pcre",
|
||||||
|
"version": "3.4.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/composer/pcre.git",
|
||||||
|
"reference": "d5a341b3fb61f3001970940afb1d332968a183ed"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/composer/pcre/zipball/d5a341b3fb61f3001970940afb1d332968a183ed",
|
||||||
|
"reference": "d5a341b3fb61f3001970940afb1d332968a183ed",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^7.4 || ^8.0"
|
||||||
|
},
|
||||||
|
"conflict": {
|
||||||
|
"phpstan/phpstan": "<2.2.2"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpstan/phpstan": "^2",
|
||||||
|
"phpstan/phpstan-deprecation-rules": "^2",
|
||||||
|
"phpstan/phpstan-strict-rules": "^2",
|
||||||
|
"phpunit/phpunit": "^9"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"phpstan": {
|
||||||
|
"includes": [
|
||||||
|
"extension.neon"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-main": "3.x-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Composer\\Pcre\\": "src"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Jordi Boggiano",
|
||||||
|
"email": "[email protected]",
|
||||||
|
"homepage": "http://seld.be"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "PCRE wrapping library that offers type-safe preg_* replacements.",
|
||||||
|
"keywords": [
|
||||||
|
"PCRE",
|
||||||
|
"preg",
|
||||||
|
"regex",
|
||||||
|
"regular expression"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/composer/pcre/issues",
|
||||||
|
"source": "https://github.com/composer/pcre/tree/3.4.0"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://packagist.com",
|
||||||
|
"type": "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/composer",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2026-06-07T11:47:49+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "dflydev/dot-access-data",
|
"name": "dflydev/dot-access-data",
|
||||||
"version": "v3.0.3",
|
"version": "v3.0.3",
|
||||||
@@ -3326,6 +3402,113 @@
|
|||||||
],
|
],
|
||||||
"time": "2026-04-11T18:38:28+00:00"
|
"time": "2026-04-11T18:38:28+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "markbaker/complex",
|
||||||
|
"version": "3.0.2",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/MarkBaker/PHPComplex.git",
|
||||||
|
"reference": "95c56caa1cf5c766ad6d65b6344b807c1e8405b9"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/MarkBaker/PHPComplex/zipball/95c56caa1cf5c766ad6d65b6344b807c1e8405b9",
|
||||||
|
"reference": "95c56caa1cf5c766ad6d65b6344b807c1e8405b9",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^7.2 || ^8.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"dealerdirect/phpcodesniffer-composer-installer": "dev-master",
|
||||||
|
"phpcompatibility/php-compatibility": "^9.3",
|
||||||
|
"phpunit/phpunit": "^7.0 || ^8.0 || ^9.0",
|
||||||
|
"squizlabs/php_codesniffer": "^3.7"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Complex\\": "classes/src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Mark Baker",
|
||||||
|
"email": "[email protected]"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "PHP Class for working with complex numbers",
|
||||||
|
"homepage": "https://github.com/MarkBaker/PHPComplex",
|
||||||
|
"keywords": [
|
||||||
|
"complex",
|
||||||
|
"mathematics"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/MarkBaker/PHPComplex/issues",
|
||||||
|
"source": "https://github.com/MarkBaker/PHPComplex/tree/3.0.2"
|
||||||
|
},
|
||||||
|
"time": "2022-12-06T16:21:08+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "markbaker/matrix",
|
||||||
|
"version": "3.0.1",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/MarkBaker/PHPMatrix.git",
|
||||||
|
"reference": "728434227fe21be27ff6d86621a1b13107a2562c"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/MarkBaker/PHPMatrix/zipball/728434227fe21be27ff6d86621a1b13107a2562c",
|
||||||
|
"reference": "728434227fe21be27ff6d86621a1b13107a2562c",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^7.1 || ^8.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"dealerdirect/phpcodesniffer-composer-installer": "dev-master",
|
||||||
|
"phpcompatibility/php-compatibility": "^9.3",
|
||||||
|
"phpdocumentor/phpdocumentor": "2.*",
|
||||||
|
"phploc/phploc": "^4.0",
|
||||||
|
"phpmd/phpmd": "2.*",
|
||||||
|
"phpunit/phpunit": "^7.0 || ^8.0 || ^9.0",
|
||||||
|
"sebastian/phpcpd": "^4.0",
|
||||||
|
"squizlabs/php_codesniffer": "^3.7"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Matrix\\": "classes/src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Mark Baker",
|
||||||
|
"email": "[email protected]"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "PHP Class for working with matrices",
|
||||||
|
"homepage": "https://github.com/MarkBaker/PHPMatrix",
|
||||||
|
"keywords": [
|
||||||
|
"mathematics",
|
||||||
|
"matrix",
|
||||||
|
"vector"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/MarkBaker/PHPMatrix/issues",
|
||||||
|
"source": "https://github.com/MarkBaker/PHPMatrix/tree/3.0.1"
|
||||||
|
},
|
||||||
|
"time": "2022-12-02T22:17:43+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "monolog/monolog",
|
"name": "monolog/monolog",
|
||||||
"version": "3.10.0",
|
"version": "3.10.0",
|
||||||
@@ -4154,6 +4337,115 @@
|
|||||||
},
|
},
|
||||||
"time": "2025-11-21T15:09:14+00:00"
|
"time": "2025-11-21T15:09:14+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "phpoffice/phpspreadsheet",
|
||||||
|
"version": "5.9.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/PHPOffice/PhpSpreadsheet.git",
|
||||||
|
"reference": "05e99ebf61238a70227b4d9cc02d0030d34f6339"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/05e99ebf61238a70227b4d9cc02d0030d34f6339",
|
||||||
|
"reference": "05e99ebf61238a70227b4d9cc02d0030d34f6339",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"composer/pcre": "^1||^2||^3",
|
||||||
|
"ext-ctype": "*",
|
||||||
|
"ext-dom": "*",
|
||||||
|
"ext-fileinfo": "*",
|
||||||
|
"ext-filter": "*",
|
||||||
|
"ext-gd": "*",
|
||||||
|
"ext-iconv": "*",
|
||||||
|
"ext-libxml": "*",
|
||||||
|
"ext-mbstring": "*",
|
||||||
|
"ext-simplexml": "*",
|
||||||
|
"ext-xml": "*",
|
||||||
|
"ext-xmlreader": "*",
|
||||||
|
"ext-xmlwriter": "*",
|
||||||
|
"ext-zip": "*",
|
||||||
|
"ext-zlib": "*",
|
||||||
|
"maennchen/zipstream-php": "^2.1 || ^3.0",
|
||||||
|
"markbaker/complex": "^3.0",
|
||||||
|
"markbaker/matrix": "^3.0",
|
||||||
|
"php": "^8.2",
|
||||||
|
"psr/simple-cache": "^1.0 || ^2.0 || ^3.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"dealerdirect/phpcodesniffer-composer-installer": "dev-main",
|
||||||
|
"dompdf/dompdf": "^2.0 || ^3.0",
|
||||||
|
"ext-intl": "*",
|
||||||
|
"friendsofphp/php-cs-fixer": "^3.2",
|
||||||
|
"mitoteam/jpgraph": "^10.5",
|
||||||
|
"mpdf/mpdf": "^8.1.1",
|
||||||
|
"phpcompatibility/php-compatibility": "^9.3",
|
||||||
|
"phpstan/phpstan": "^1.1 || ^2.0",
|
||||||
|
"phpstan/phpstan-deprecation-rules": "^1.0 || ^2.0",
|
||||||
|
"phpstan/phpstan-phpunit": "^1.0 || ^2.0",
|
||||||
|
"phpunit/phpunit": "^10.5 || ^11.0",
|
||||||
|
"squizlabs/php_codesniffer": "^3.7",
|
||||||
|
"tecnickcom/tcpdf": "^6.5"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"dompdf/dompdf": "Option for rendering PDF with PDF Writer",
|
||||||
|
"ext-intl": "PHP Internationalization Functions, required for NumberFormat Wizard and StringHelper::setLocale()",
|
||||||
|
"mitoteam/jpgraph": "Option for rendering charts, or including charts with PDF or HTML Writers",
|
||||||
|
"mpdf/mpdf": "Option for rendering PDF with PDF Writer",
|
||||||
|
"tecnickcom/tcpdf": "Option for rendering PDF with PDF Writer"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"PhpOffice\\PhpSpreadsheet\\": "src/PhpSpreadsheet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Maarten Balliauw",
|
||||||
|
"homepage": "https://blog.maartenballiauw.be"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Mark Baker",
|
||||||
|
"homepage": "https://markbakeruk.net"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Franck Lefevre",
|
||||||
|
"homepage": "https://rootslabs.net"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Erik Tilt"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Adrien Crivelli"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Owen Leibman"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "PHPSpreadsheet - Read, Create and Write Spreadsheet documents in PHP - Spreadsheet engine",
|
||||||
|
"homepage": "https://github.com/PHPOffice/PhpSpreadsheet",
|
||||||
|
"keywords": [
|
||||||
|
"OpenXML",
|
||||||
|
"excel",
|
||||||
|
"gnumeric",
|
||||||
|
"ods",
|
||||||
|
"php",
|
||||||
|
"spreadsheet",
|
||||||
|
"xls",
|
||||||
|
"xlsx"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/PHPOffice/PhpSpreadsheet/issues",
|
||||||
|
"source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/5.9.0"
|
||||||
|
},
|
||||||
|
"time": "2026-07-12T19:17:39+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "phpseclib/phpseclib",
|
"name": "phpseclib/phpseclib",
|
||||||
"version": "3.0.57",
|
"version": "3.0.57",
|
||||||
@@ -4777,6 +5069,57 @@
|
|||||||
},
|
},
|
||||||
"time": "2024-09-11T13:17:53+00:00"
|
"time": "2024-09-11T13:17:53+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "psr/simple-cache",
|
||||||
|
"version": "3.0.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/php-fig/simple-cache.git",
|
||||||
|
"reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/php-fig/simple-cache/zipball/764e0b3939f5ca87cb904f570ef9be2d78a07865",
|
||||||
|
"reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=8.0.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "3.0.x-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Psr\\SimpleCache\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "PHP-FIG",
|
||||||
|
"homepage": "https://www.php-fig.org/"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Common interfaces for simple caching",
|
||||||
|
"keywords": [
|
||||||
|
"cache",
|
||||||
|
"caching",
|
||||||
|
"psr",
|
||||||
|
"psr-16",
|
||||||
|
"simple-cache"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"source": "https://github.com/php-fig/simple-cache/tree/3.0.0"
|
||||||
|
},
|
||||||
|
"time": "2021-10-29T13:26:27+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "ralouphie/getallheaders",
|
"name": "ralouphie/getallheaders",
|
||||||
"version": "3.0.3",
|
"version": "3.0.3",
|
||||||
@@ -11810,82 +12153,6 @@
|
|||||||
],
|
],
|
||||||
"time": "2022-12-23T10:58:28+00:00"
|
"time": "2022-12-23T10:58:28+00:00"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "composer/pcre",
|
|
||||||
"version": "3.4.0",
|
|
||||||
"source": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/composer/pcre.git",
|
|
||||||
"reference": "d5a341b3fb61f3001970940afb1d332968a183ed"
|
|
||||||
},
|
|
||||||
"dist": {
|
|
||||||
"type": "zip",
|
|
||||||
"url": "https://api.github.com/repos/composer/pcre/zipball/d5a341b3fb61f3001970940afb1d332968a183ed",
|
|
||||||
"reference": "d5a341b3fb61f3001970940afb1d332968a183ed",
|
|
||||||
"shasum": ""
|
|
||||||
},
|
|
||||||
"require": {
|
|
||||||
"php": "^7.4 || ^8.0"
|
|
||||||
},
|
|
||||||
"conflict": {
|
|
||||||
"phpstan/phpstan": "<2.2.2"
|
|
||||||
},
|
|
||||||
"require-dev": {
|
|
||||||
"phpstan/phpstan": "^2",
|
|
||||||
"phpstan/phpstan-deprecation-rules": "^2",
|
|
||||||
"phpstan/phpstan-strict-rules": "^2",
|
|
||||||
"phpunit/phpunit": "^9"
|
|
||||||
},
|
|
||||||
"type": "library",
|
|
||||||
"extra": {
|
|
||||||
"phpstan": {
|
|
||||||
"includes": [
|
|
||||||
"extension.neon"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"branch-alias": {
|
|
||||||
"dev-main": "3.x-dev"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"autoload": {
|
|
||||||
"psr-4": {
|
|
||||||
"Composer\\Pcre\\": "src"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"notification-url": "https://packagist.org/downloads/",
|
|
||||||
"license": [
|
|
||||||
"MIT"
|
|
||||||
],
|
|
||||||
"authors": [
|
|
||||||
{
|
|
||||||
"name": "Jordi Boggiano",
|
|
||||||
"email": "[email protected]",
|
|
||||||
"homepage": "http://seld.be"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "PCRE wrapping library that offers type-safe preg_* replacements.",
|
|
||||||
"keywords": [
|
|
||||||
"PCRE",
|
|
||||||
"preg",
|
|
||||||
"regex",
|
|
||||||
"regular expression"
|
|
||||||
],
|
|
||||||
"support": {
|
|
||||||
"issues": "https://github.com/composer/pcre/issues",
|
|
||||||
"source": "https://github.com/composer/pcre/tree/3.4.0"
|
|
||||||
},
|
|
||||||
"funding": [
|
|
||||||
{
|
|
||||||
"url": "https://packagist.com",
|
|
||||||
"type": "custom"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://github.com/composer",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"time": "2026-06-07T11:47:49+00:00"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "composer/semver",
|
"name": "composer/semver",
|
||||||
"version": "3.4.4",
|
"version": "3.4.4",
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Controller\Administrative\Feedback;
|
||||||
|
|
||||||
|
use App\Repository\FeedbackRepository;
|
||||||
|
use App\Service\Common\FeedbackFilterHandler;
|
||||||
|
use App\Service\Export\FeedbackExporter;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
use Symfony\Component\HttpFoundation\HeaderUtils;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||||
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
|
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||||
|
|
||||||
|
class ExportController extends AbstractController
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly FeedbackFilterHandler $filterHandler,
|
||||||
|
private readonly FeedbackRepository $feedbackRepository,
|
||||||
|
private readonly FeedbackExporter $feedbackExporter,
|
||||||
|
private readonly LoggerInterface $logger,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/administrative/feedback/export', name: 'app_administrative_feedback_export')]
|
||||||
|
#[IsGranted('ROLE_ADMINISTRATIVE')]
|
||||||
|
public function index(): Response
|
||||||
|
{
|
||||||
|
$filterDto = $this->filterHandler->getFilterSettings();
|
||||||
|
|
||||||
|
$feedback = $this
|
||||||
|
->feedbackRepository
|
||||||
|
->getExportQuery($filterDto)
|
||||||
|
->getResult()
|
||||||
|
;
|
||||||
|
|
||||||
|
if (0 === count($feedback)) {
|
||||||
|
$this->addFlash('error', 'Keine Daten für den Export vorhanden');
|
||||||
|
|
||||||
|
return $this->redirectToRoute('app_administrative_feedback_index');
|
||||||
|
}
|
||||||
|
|
||||||
|
$spreadsheet = $this->feedbackExporter->export($feedback);
|
||||||
|
|
||||||
|
$filename = sprintf('feedback-export_%s.xlsx', date('YmdHi'));
|
||||||
|
|
||||||
|
$response = new StreamedResponse(static function () use ($spreadsheet): void {
|
||||||
|
(new Xlsx($spreadsheet))->save('php://output');
|
||||||
|
});
|
||||||
|
|
||||||
|
$response->headers->set(
|
||||||
|
'Content-Type',
|
||||||
|
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
||||||
|
);
|
||||||
|
$response->headers->set(
|
||||||
|
'Content-Disposition',
|
||||||
|
HeaderUtils::makeDisposition(HeaderUtils::DISPOSITION_ATTACHMENT, $filename, md5($filename))
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->logger->info('Export feedback', [
|
||||||
|
'count' => count($feedback),
|
||||||
|
'filter_active' => $filterDto->isActive(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@ use App\Model\FeedbackFilterDto;
|
|||||||
use App\Repository\Traits\QueryHelperTrait;
|
use App\Repository\Traits\QueryHelperTrait;
|
||||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||||
use Doctrine\ORM\Query;
|
use Doctrine\ORM\Query;
|
||||||
|
use Doctrine\ORM\QueryBuilder;
|
||||||
use Doctrine\Persistence\ManagerRegistry;
|
use Doctrine\Persistence\ManagerRegistry;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -38,6 +39,38 @@ class FeedbackRepository extends ServiceEntityRepository
|
|||||||
->innerJoin('teamer.user', 'user')
|
->innerJoin('teamer.user', 'user')
|
||||||
;
|
;
|
||||||
|
|
||||||
|
$this->applyFilterCriteria($qb, $filterDto);
|
||||||
|
|
||||||
|
return $qb->getQuery();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Published feedback matching the given filter, one row per record, with the
|
||||||
|
* FeedbackSet eagerly loaded and rows grouped per teamer and ordered by season.
|
||||||
|
*/
|
||||||
|
public function getExportQuery(FeedbackFilterDto $filterDto): Query
|
||||||
|
{
|
||||||
|
$qb = $this->createQueryBuilder('feedback');
|
||||||
|
|
||||||
|
$qb
|
||||||
|
->select('feedback', 'teamer', 'user', 'feedbackSet')
|
||||||
|
->innerJoin('feedback.teamer', 'teamer')
|
||||||
|
->innerJoin('teamer.user', 'user')
|
||||||
|
->innerJoin('feedback.feedbackSet', 'feedbackSet')
|
||||||
|
->andWhere($qb->expr()->eq('feedback.status', ':status'))
|
||||||
|
->setParameter('status', Feedback::STATUS_PUBLISHED)
|
||||||
|
->orderBy('teamer.lastName', 'ASC')
|
||||||
|
->addOrderBy('teamer.firstName', 'ASC')
|
||||||
|
->addOrderBy('feedback.assignmentDateFrom', 'ASC')
|
||||||
|
;
|
||||||
|
|
||||||
|
$this->applyFilterCriteria($qb, $filterDto);
|
||||||
|
|
||||||
|
return $qb->getQuery();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function applyFilterCriteria(QueryBuilder $qb, FeedbackFilterDto $filterDto): void
|
||||||
|
{
|
||||||
if (null !== $teamer = $filterDto->getTeamer()) {
|
if (null !== $teamer = $filterDto->getTeamer()) {
|
||||||
$qb
|
$qb
|
||||||
->andWhere($qb->expr()->eq('feedback.teamer', ':teamer'))
|
->andWhere($qb->expr()->eq('feedback.teamer', ':teamer'))
|
||||||
@@ -73,8 +106,6 @@ class FeedbackRepository extends ServiceEntityRepository
|
|||||||
->setParameter('dateTo', $dateTo)
|
->setParameter('dateTo', $dateTo)
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $qb->getQuery();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getNew(int $limit = 5): array
|
public function getNew(int $limit = 5): array
|
||||||
|
|||||||
@@ -0,0 +1,262 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Service\Export;
|
||||||
|
|
||||||
|
use App\Config\SeasonCalendar;
|
||||||
|
use App\Entity\Feedback;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds an .xlsx workbook of feedback records, one worksheet per FeedbackSet, with the
|
||||||
|
* per-question marks stored in Feedback::$ratings expanded into one column per question.
|
||||||
|
*/
|
||||||
|
final class FeedbackExporter
|
||||||
|
{
|
||||||
|
private const FIXED_HEADERS = [
|
||||||
|
'Name',
|
||||||
|
'Vorname',
|
||||||
|
'Saison',
|
||||||
|
'Einsatz von',
|
||||||
|
'Einsatz bis',
|
||||||
|
'Reiseziel',
|
||||||
|
'Haus',
|
||||||
|
'Haus-Code',
|
||||||
|
'Job-Profil',
|
||||||
|
'Erfasst am',
|
||||||
|
'Erfasst von',
|
||||||
|
];
|
||||||
|
|
||||||
|
private const TRAILING_HEADERS = [
|
||||||
|
'Ø Bewertung',
|
||||||
|
'Kommentar (öffentlich)',
|
||||||
|
'Kommentar (intern)',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Excel's hard limit on the number of characters a single cell can hold.
|
||||||
|
*/
|
||||||
|
private const CELL_CHARACTER_LIMIT = 32767;
|
||||||
|
|
||||||
|
public function __construct(private readonly SeasonCalendar $seasonCalendar)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param iterable<Feedback> $feedback
|
||||||
|
*/
|
||||||
|
public function export(iterable $feedback): Spreadsheet
|
||||||
|
{
|
||||||
|
// Keyed by object identity: Doctrine hands back the same FeedbackSet instance for every
|
||||||
|
// row that shares it, and this stays correct for not-yet-persisted sets (id still null).
|
||||||
|
$buckets = [];
|
||||||
|
|
||||||
|
foreach ($feedback as $item) {
|
||||||
|
$set = $item->getFeedbackSet();
|
||||||
|
|
||||||
|
if (null === $set) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$key = spl_object_id($set);
|
||||||
|
$buckets[$key]['set'] = $set;
|
||||||
|
$buckets[$key]['rows'][] = $item;
|
||||||
|
}
|
||||||
|
|
||||||
|
$spreadsheet = new Spreadsheet();
|
||||||
|
|
||||||
|
if ([] === $buckets) {
|
||||||
|
$spreadsheet->getActiveSheet()->setTitle('Feedback');
|
||||||
|
|
||||||
|
return $spreadsheet;
|
||||||
|
}
|
||||||
|
|
||||||
|
$spreadsheet->removeSheetByIndex(0);
|
||||||
|
|
||||||
|
$usedTitles = [];
|
||||||
|
|
||||||
|
foreach ($buckets as $bucket) {
|
||||||
|
$sheet = $spreadsheet->createSheet();
|
||||||
|
$sheet->setTitle($this->uniqueTitle($bucket['set']->getName() ?? 'Feedback', $usedTitles));
|
||||||
|
|
||||||
|
$this->fillSheet($sheet, $bucket['rows']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $spreadsheet;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param list<Feedback> $rows
|
||||||
|
*/
|
||||||
|
private function fillSheet(Worksheet $sheet, array $rows): void
|
||||||
|
{
|
||||||
|
$questions = $this->collectQuestionLabels($rows);
|
||||||
|
|
||||||
|
$headers = [...self::FIXED_HEADERS, ...$questions, ...self::TRAILING_HEADERS];
|
||||||
|
$sheet->fromArray($headers, null, 'A1');
|
||||||
|
|
||||||
|
$rowNumber = 2;
|
||||||
|
|
||||||
|
foreach ($rows as $feedback) {
|
||||||
|
$marks = $this->marksByLabel($feedback);
|
||||||
|
|
||||||
|
$values = [
|
||||||
|
$feedback->getTeamer()?->getLastName(),
|
||||||
|
$feedback->getTeamer()?->getFirstName(),
|
||||||
|
$this->season($feedback),
|
||||||
|
$this->formatDate($feedback->getAssignmentDateFrom()),
|
||||||
|
$this->formatDate($feedback->getAssignmentDateTo()),
|
||||||
|
$feedback->getDestinationName(),
|
||||||
|
$feedback->getHotel(),
|
||||||
|
$feedback->getHotelCode(),
|
||||||
|
$feedback->getJobProfileName(),
|
||||||
|
$this->formatDate($feedback->getCreatedAt()),
|
||||||
|
$feedback->getAuthor(),
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($questions as $label) {
|
||||||
|
$values[] = $marks[$label] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$values[] = null !== $feedback->getAverageRating()
|
||||||
|
? round($feedback->getAverageRating() / 100, 1)
|
||||||
|
: null;
|
||||||
|
$values[] = $this->clip($feedback->getComment());
|
||||||
|
$values[] = $this->clip($feedback->getCommentInternal());
|
||||||
|
|
||||||
|
$sheet->fromArray($values, null, 'A'.$rowNumber);
|
||||||
|
++$rowNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->style($sheet, count($headers), count($questions));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The distinct question labels across the given rows, in first-seen order. Columns are
|
||||||
|
* derived from the stored data rather than FeedbackSet::getRatings() so that reworded or
|
||||||
|
* reordered set questions never drop marks that were captured under the old wording.
|
||||||
|
*
|
||||||
|
* @param list<Feedback> $rows
|
||||||
|
*
|
||||||
|
* @return list<string>
|
||||||
|
*/
|
||||||
|
private function collectQuestionLabels(array $rows): array
|
||||||
|
{
|
||||||
|
$labels = [];
|
||||||
|
|
||||||
|
foreach ($rows as $feedback) {
|
||||||
|
foreach ($feedback->getRatings() as $entry) {
|
||||||
|
if (false === is_array($entry) || false === isset($entry['rating'])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$label = (string) $entry['rating'];
|
||||||
|
|
||||||
|
if (false === in_array($label, $labels, true)) {
|
||||||
|
$labels[] = $label;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $labels;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, int|string>
|
||||||
|
*/
|
||||||
|
private function marksByLabel(Feedback $feedback): array
|
||||||
|
{
|
||||||
|
$marks = [];
|
||||||
|
|
||||||
|
foreach ($feedback->getRatings() as $entry) {
|
||||||
|
if (false === is_array($entry) || false === isset($entry['rating'], $entry['mark'])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$marks[(string) $entry['rating']] = $entry['mark'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $marks;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function season(Feedback $feedback): string
|
||||||
|
{
|
||||||
|
$from = $feedback->getAssignmentDateFrom();
|
||||||
|
|
||||||
|
if (null === $from) {
|
||||||
|
return '—';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->seasonCalendar->label($this->seasonCalendar->seasonYearFor($from));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function formatDate(?\DateTimeInterface $date): ?string
|
||||||
|
{
|
||||||
|
return $date?->format('d.m.Y');
|
||||||
|
}
|
||||||
|
|
||||||
|
private function clip(?string $value): ?string
|
||||||
|
{
|
||||||
|
if (null === $value) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return mb_substr($value, 0, self::CELL_CHARACTER_LIMIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sanitises a feedback-set name into a worksheet title: strips the characters Excel forbids,
|
||||||
|
* caps it at 31 characters and appends a numeric suffix on collision.
|
||||||
|
*
|
||||||
|
* @param list<string> $usedTitles lower-cased titles already handed out; appended to in place
|
||||||
|
*/
|
||||||
|
private function uniqueTitle(string $name, array &$usedTitles): string
|
||||||
|
{
|
||||||
|
$clean = preg_replace('#[\\\\/*?:\[\]]#', ' ', $name) ?? '';
|
||||||
|
$clean = trim(preg_replace('/\s+/', ' ', $clean) ?? '');
|
||||||
|
|
||||||
|
if ('' === $clean) {
|
||||||
|
$clean = 'Feedback';
|
||||||
|
}
|
||||||
|
|
||||||
|
$clean = mb_substr($clean, 0, 31);
|
||||||
|
$title = $clean;
|
||||||
|
$suffix = 2;
|
||||||
|
|
||||||
|
while (in_array(mb_strtolower($title), $usedTitles, true)) {
|
||||||
|
$tail = ' ('.$suffix.')';
|
||||||
|
$title = mb_substr($clean, 0, 31 - mb_strlen($tail)).$tail;
|
||||||
|
++$suffix;
|
||||||
|
}
|
||||||
|
|
||||||
|
$usedTitles[] = mb_strtolower($title);
|
||||||
|
|
||||||
|
return $title;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function style(Worksheet $sheet, int $columnCount, int $questionCount): void
|
||||||
|
{
|
||||||
|
$lastColumn = Coordinate::stringFromColumnIndex($columnCount);
|
||||||
|
|
||||||
|
$sheet->getStyle('A1:'.$lastColumn.'1')->getFont()->setBold(true);
|
||||||
|
$sheet->freezePane('A2');
|
||||||
|
|
||||||
|
$fixedCount = count(self::FIXED_HEADERS);
|
||||||
|
$firstQuestion = $fixedCount + 1;
|
||||||
|
$lastQuestion = $fixedCount + $questionCount;
|
||||||
|
$commentStart = $lastQuestion + 2; // the column after "Ø Bewertung"
|
||||||
|
|
||||||
|
for ($i = 1; $i <= $columnCount; ++$i) {
|
||||||
|
$dimension = $sheet->getColumnDimension(Coordinate::stringFromColumnIndex($i));
|
||||||
|
|
||||||
|
if ($i >= $firstQuestion && $i <= $lastQuestion) {
|
||||||
|
$dimension->setWidth(14);
|
||||||
|
} elseif ($i >= $commentStart) {
|
||||||
|
$dimension->setWidth(60);
|
||||||
|
} else {
|
||||||
|
$dimension->setAutoSize(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,6 +26,10 @@
|
|||||||
Reset
|
Reset
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<a href="{{ path('app_administrative_feedback_export') }}" class="btn btn--small" title="Nach Excel exportieren">
|
||||||
|
{{ icon('excel', 'w-4 h-4 shrink-0') }}
|
||||||
|
<span>Export</span>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,122 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Tests\Service\Export;
|
||||||
|
|
||||||
|
use App\Config\SeasonCalendar;
|
||||||
|
use App\Entity\Feedback;
|
||||||
|
use App\Entity\FeedbackSet;
|
||||||
|
use App\Entity\Teamer;
|
||||||
|
use App\Entity\User;
|
||||||
|
use App\Service\Export\FeedbackExporter;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
|
||||||
|
|
||||||
|
class FeedbackExporterTest extends WebTestCase
|
||||||
|
{
|
||||||
|
public function testExportsOneSheetPerFeedbackSetWithExpandedQuestions(): void
|
||||||
|
{
|
||||||
|
$projectDir = static::getContainer()->getParameter('kernel.project_dir');
|
||||||
|
/** @var SeasonCalendar $seasonCalendar */
|
||||||
|
$seasonCalendar = static::getContainer()->get(SeasonCalendar::class);
|
||||||
|
|
||||||
|
$exporter = new FeedbackExporter($seasonCalendar);
|
||||||
|
|
||||||
|
$author = (new User())->setFirstName('Carmen')->setLastName('Bär');
|
||||||
|
|
||||||
|
$teamerMueller = (new Teamer())->setFirstName('Mara')->setLastName('Müller');
|
||||||
|
$teamerSchmidt = (new Teamer())->setFirstName('Timo')->setLastName('Schmidt');
|
||||||
|
|
||||||
|
$guideSet = (new FeedbackSet())->setName('Reiseleiter-Feedback');
|
||||||
|
$careSet = (new FeedbackSet())->setName('Betreuer-Feedback');
|
||||||
|
|
||||||
|
// Müller, season 2024/25, guide set - full question list.
|
||||||
|
$first = $this->makeFeedback($author, $teamerMueller, $guideSet, new \DateTimeImmutable('2024-12-01'), [
|
||||||
|
['rating' => 'Pünktlichkeit', 'mark' => 5],
|
||||||
|
['rating' => 'Freundlichkeit', 'mark' => 4],
|
||||||
|
]);
|
||||||
|
$first->setComment('Alles bestens');
|
||||||
|
|
||||||
|
// Müller again, same season, guide set - extra question the first row lacks.
|
||||||
|
$second = $this->makeFeedback($author, $teamerMueller, $guideSet, new \DateTimeImmutable('2025-02-20'), [
|
||||||
|
['rating' => 'Pünktlichkeit', 'mark' => 3],
|
||||||
|
['rating' => 'Freundlichkeit', 'mark' => 4],
|
||||||
|
['rating' => 'Sprachkenntnisse', 'mark' => 2],
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Schmidt, care set, no assignment date -> season falls back to a dash.
|
||||||
|
$third = $this->makeFeedback($author, $teamerSchmidt, $careSet, null, [
|
||||||
|
['rating' => 'Organisation', 'mark' => 4],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$spreadsheet = $exporter->export([$first, $second, $third]);
|
||||||
|
|
||||||
|
self::assertSame(2, $spreadsheet->getSheetCount());
|
||||||
|
self::assertSame('Reiseleiter-Feedback', $spreadsheet->getSheet(0)->getTitle());
|
||||||
|
self::assertSame('Betreuer-Feedback', $spreadsheet->getSheet(1)->getTitle());
|
||||||
|
|
||||||
|
$guideSheet = $spreadsheet->getSheet(0);
|
||||||
|
$header = $guideSheet->rangeToArray('A1:Q1')[0];
|
||||||
|
|
||||||
|
self::assertSame('Name', $header[0]);
|
||||||
|
self::assertSame('Saison', $header[2]);
|
||||||
|
// Fixed block is 11 columns (A-K); questions follow in first-seen order.
|
||||||
|
self::assertSame('Pünktlichkeit', $header[11]);
|
||||||
|
self::assertSame('Freundlichkeit', $header[12]);
|
||||||
|
self::assertSame('Sprachkenntnisse', $header[13]);
|
||||||
|
self::assertSame('Ø Bewertung', $header[14]);
|
||||||
|
self::assertSame('Kommentar (öffentlich)', $header[15]);
|
||||||
|
self::assertSame('Kommentar (intern)', $header[16]);
|
||||||
|
|
||||||
|
// Row 2 = first Müller feedback: mark present, missing question is blank.
|
||||||
|
self::assertSame('Müller', $guideSheet->getCell('A2')->getValue());
|
||||||
|
self::assertSame('Saison 2024/25', $guideSheet->getCell('C2')->getValue());
|
||||||
|
self::assertSame(5, $guideSheet->getCell('L2')->getValue());
|
||||||
|
self::assertSame(4, $guideSheet->getCell('M2')->getValue());
|
||||||
|
self::assertNull($guideSheet->getCell('N2')->getValue());
|
||||||
|
self::assertSame(4.5, $guideSheet->getCell('O2')->getValue());
|
||||||
|
self::assertSame('Alles bestens', $guideSheet->getCell('P2')->getValue());
|
||||||
|
|
||||||
|
// Row 3 = second Müller feedback: the third question now carries a mark.
|
||||||
|
self::assertSame(2, $guideSheet->getCell('N3')->getValue());
|
||||||
|
|
||||||
|
$careSheet = $spreadsheet->getSheet(1);
|
||||||
|
self::assertSame('—', $careSheet->getCell('C2')->getValue());
|
||||||
|
self::assertSame('Organisation', $careSheet->rangeToArray('A1:O1')[0][11]);
|
||||||
|
|
||||||
|
(new Xlsx($spreadsheet))->save($projectDir.'/temp/feedback-export.xlsx');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param list<array{rating: string, mark: int}> $ratings
|
||||||
|
*/
|
||||||
|
private function makeFeedback(
|
||||||
|
User $author,
|
||||||
|
Teamer $teamer,
|
||||||
|
FeedbackSet $set,
|
||||||
|
?\DateTimeImmutable $dateFrom,
|
||||||
|
array $ratings,
|
||||||
|
): Feedback {
|
||||||
|
$feedback = (new Feedback($author))
|
||||||
|
->setStatus(Feedback::STATUS_PUBLISHED)
|
||||||
|
->setTeamer($teamer)
|
||||||
|
->setFeedbackSet($set)
|
||||||
|
->setDestinationName('Skireise')
|
||||||
|
->setHotel('Unterkunft')
|
||||||
|
->setHotelCode('ABC123')
|
||||||
|
->setJobProfileName('Allrounder')
|
||||||
|
->setRatings($ratings)
|
||||||
|
;
|
||||||
|
|
||||||
|
$feedback->setCreatedAt(new \DateTimeImmutable('2025-03-01'));
|
||||||
|
$feedback->setAverageRating();
|
||||||
|
|
||||||
|
if (null !== $dateFrom) {
|
||||||
|
$feedback
|
||||||
|
->setAssignmentDateFrom($dateFrom)
|
||||||
|
->setAssignmentDateTo($dateFrom->modify('+2 months'))
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $feedback;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user