Compare commits
192
Commits
681734533f
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3c34cfa76a | ||
|
|
9d0cdd7f52 | ||
|
|
9583612fdc | ||
|
|
0bc77b7463 | ||
|
|
4ff963d1e2 | ||
|
|
f9d2ff084d | ||
|
|
7aefe88610 | ||
|
|
09eea86183 | ||
|
|
ddfaa4b440 | ||
|
|
d3b9b0611b | ||
|
|
fe0c854f1d | ||
|
|
0b736b51fb | ||
|
|
af97468b2d | ||
|
|
a0170f5952 | ||
|
|
2f4f9800dd | ||
|
|
9fedc5b5d3 | ||
|
|
a35f91ac63 | ||
|
|
890e04f8a6 | ||
|
|
5e4e574076 | ||
|
|
2a041d25a0 | ||
|
|
df6734c939 | ||
|
|
8070fda64d | ||
|
|
d06b647dac | ||
|
|
a84275b40e | ||
|
|
2ec2a4c93f | ||
|
|
de4aedd26e | ||
|
|
5090f72fab | ||
|
|
c00dd86eb4 | ||
|
|
300bb2058f | ||
|
|
3fe510e64e | ||
|
|
48691227cb | ||
|
|
34ff630b75 | ||
|
|
3f806669c6 | ||
|
|
e31f607f66 | ||
|
|
8c53676133 | ||
|
|
2de985b009 | ||
|
|
1cda9d378a | ||
|
|
9fcba26942 | ||
|
|
13e5b6c3af | ||
|
|
493a43d952 | ||
|
|
7493afde45 | ||
|
|
d159cb212c | ||
|
|
c7e0ad67e6 | ||
|
|
f0ad5374e9 | ||
|
|
96d49fc488 | ||
|
|
2a554bb7bf | ||
|
|
c16e4bf53f | ||
|
|
18fb3382c6 | ||
|
|
0e2021e96d | ||
|
|
2fc9806c69 | ||
|
|
3116d23199 | ||
|
|
4aa2d7a5e6 | ||
|
|
7e182e3d51 | ||
|
|
c04d9e0faf | ||
|
|
3188f5f3da | ||
|
|
57781ba347 | ||
|
|
849da8d471 | ||
|
|
da039d0527 | ||
|
|
1446211d22 | ||
|
|
838b8944a2 | ||
|
|
a4206f51eb | ||
|
|
2329fe17a0 | ||
|
|
72be779038 | ||
|
|
d8ff148f86 | ||
|
|
db529d2dc9 | ||
|
|
aa66c50979 | ||
|
|
0f76c5a7b6 | ||
|
|
5c7e702eab | ||
|
|
169a17e19c | ||
|
|
6c0a512c05 | ||
|
|
2e9328fc51 | ||
|
|
1505f9b50b | ||
|
|
392e67448e | ||
|
|
8da9af46f5 | ||
|
|
ca4a73eb4f | ||
|
|
1a7c272414 | ||
|
|
7cf39850e6 | ||
|
|
4cab57d136 | ||
|
|
80c6016da3 | ||
|
|
69b8e549ee | ||
|
|
77986776c2 | ||
|
|
cf876ed224 | ||
|
|
675cec1b69 | ||
|
|
e460b18661 | ||
|
|
5141673bcd | ||
|
|
b530679658 | ||
|
|
d5f4b12f0a | ||
|
|
aa728ddc99 | ||
|
|
19364df2db | ||
|
|
cb2d64f2cd | ||
|
|
f073a2014d | ||
|
|
30ad5e8488 | ||
|
|
f48e53fdd0 | ||
|
|
3f4586ce06 | ||
|
|
2a8649fc6d | ||
|
|
161cfb9e62 | ||
|
|
eac125517f | ||
|
|
af46c57bd1 | ||
|
|
d6327f033e | ||
|
|
752db081b3 | ||
|
|
518f3a46d0 | ||
|
|
a05143bfbe | ||
|
|
29cedd5916 | ||
|
|
5c57dc823f | ||
|
|
d4908b6ab9 | ||
|
|
39a343911a | ||
|
|
66d560f98d | ||
|
|
86817731f4 | ||
|
|
9d639c8db1 | ||
|
|
8e599a5dd7 | ||
|
|
9072e764c0 | ||
|
|
24451d0f36 | ||
|
|
d86eb1fd2e | ||
|
|
3cdaee1df3 | ||
|
|
4b20aad98e | ||
|
|
52926ec6cc | ||
|
|
16d847137d | ||
|
|
66527515ef | ||
|
|
3d4d92b5c4 | ||
|
|
7acfbb9d17 | ||
|
|
3af2e42ecf | ||
|
|
7ed5bcd29a | ||
|
|
bc2af4d8e7 | ||
|
|
332406532f | ||
|
|
357c43eead | ||
|
|
df1e38b008 | ||
|
|
10ab2bd04b | ||
|
|
a60e8357e8 | ||
|
|
2459af122f | ||
|
|
8ed20db90c | ||
|
|
496a707a16 | ||
|
|
2f747fc22f | ||
|
|
0d06bc4271 | ||
|
|
f6667dcfc7 | ||
|
|
410c99a9b0 | ||
|
|
b2cc6e2dcf | ||
|
|
e66351a2cc | ||
|
|
84441d9e69 | ||
|
|
53465cc66a | ||
|
|
703d41d6ae | ||
|
|
127ced4378 | ||
|
|
86c5d23181 | ||
|
|
39e6f35374 | ||
|
|
e4d64558b9 | ||
|
|
b2488e2ff4 | ||
|
|
702f04d946 | ||
|
|
7691a8bb03 | ||
|
|
15a4af93f9 | ||
|
|
e7f3233dbc | ||
|
|
c33c1b6bf0 | ||
|
|
5979988dcc | ||
|
|
2662d56ac1 | ||
|
|
e210021fe3 | ||
|
|
f3eae5462d | ||
|
|
cc280907e5 | ||
|
|
dfa3e63926 | ||
|
|
43968e4e7b | ||
|
|
4ae23bf8a1 | ||
|
|
6795a668da | ||
|
|
e6e6e041a9 | ||
|
|
c9de66df9c | ||
|
|
5912a75c81 | ||
|
|
f0e850978b | ||
|
|
1fd0fbc21e | ||
|
|
25f5840085 | ||
|
|
31149fdd13 | ||
|
|
0e1df6da7c | ||
|
|
489aafed3e | ||
|
|
3e08965e48 | ||
|
|
804dab335f | ||
|
|
bdd06f0187 | ||
|
|
644bfa225d | ||
|
|
44fab8bf6c | ||
|
|
3b116add75 | ||
|
|
450f5b0515 | ||
|
|
8b09845d96 | ||
|
|
642102263e | ||
|
|
07f0bf3f3e | ||
|
|
e0a9562fc6 | ||
|
|
a5c98025d0 | ||
|
|
74ebefcf65 | ||
|
|
898a7c7505 | ||
|
|
078ef8b9a6 | ||
|
|
696d3f8798 | ||
|
|
6511f821ac | ||
|
|
48cd2d6b67 | ||
|
|
102e1c1c3c | ||
|
|
d654ce77fb | ||
|
|
388ecf9603 | ||
|
|
fd5d478a5c | ||
|
|
b39a78da82 | ||
|
|
b01c2e84c7 |
No files matched your search
@@ -0,0 +1,4 @@
|
||||
# Tailwind 4's floor: browsers without cascade layers, or Chrome before 111, can't render the
|
||||
# app's CSS, so the JS isn't transpiled for them either.
|
||||
defaults and fully supports css-cascade-layers
|
||||
not chrome < 111
|
||||
+48
-33
@@ -1,24 +1,26 @@
|
||||
type: php
|
||||
type: symfony
|
||||
docroot: public
|
||||
php_version: "8.3"
|
||||
php_version: "8.5"
|
||||
webserver_type: apache-fpm
|
||||
xdebug_enabled: false
|
||||
additional_hostnames: []
|
||||
additional_fqdns: []
|
||||
database:
|
||||
type: mysql
|
||||
version: "8.4"
|
||||
type: mariadb
|
||||
version: "11.8"
|
||||
hooks:
|
||||
post-start:
|
||||
- exec: mysql -uroot -proot -hdb -e "CREATE DATABASE IF NOT EXISTS db_test CHARACTER SET utf8mb4; GRANT ALL ON db_test.* TO 'db'@'%';"
|
||||
- exec: php bin/console doctrine:schema:update --force --env=test --no-interaction
|
||||
omit_containers: [ddev-ssh-agent]
|
||||
use_dns_when_possible: true
|
||||
timezone: Europe/Berlin
|
||||
composer_version: "2"
|
||||
web_environment: []
|
||||
nodejs_version: "18"
|
||||
nodejs_version: "24"
|
||||
corepack_enable: false
|
||||
disable_upload_dirs_warning: true
|
||||
|
||||
xhgui_https_port: "8142"
|
||||
xhgui_http_port: "8143"
|
||||
|
||||
# Key features of DDEV's config.yaml:
|
||||
|
||||
# name: <projectname> # Name of the project, automatically provides
|
||||
@@ -26,16 +28,16 @@ xhgui_http_port: "8143"
|
||||
# If the name is omitted, the project will take the name of the enclosing directory,
|
||||
# which is useful if you want to have a copy of the project side by side with this one.
|
||||
|
||||
# type: <projecttype> # backdrop, cakephp, craftcms, drupal, drupal6, drupal7, drupal8, drupal9, drupal10, drupal11, generic, laravel, magento, magento2, php, shopware6, silverstripe, symfony, typo3, wordpress
|
||||
# type: <projecttype> # asterios, backdrop, cakephp, codeigniter, craftcms, drupal, drupal6, drupal7, drupal8, drupal9, drupal10, drupal11, drupal12, generic, joomla, laravel, magento, magento2, maho, modx, php, shopware6, silverstripe, symfony, typo3, wordpress, wp-bedrock
|
||||
# See https://docs.ddev.com/en/stable/users/quickstart/ for more
|
||||
# information on the different project types
|
||||
|
||||
# docroot: <relative_path> # Relative path to the directory containing index.php.
|
||||
|
||||
# php_version: "8.3" # PHP version to use, "5.6" through "8.5"
|
||||
# php_version: "8.4" # PHP version to use, "5.6" through "8.5"
|
||||
|
||||
# You can explicitly specify the webimage but this
|
||||
# is not recommended, as the images are often closely tied to DDEV's' behavior,
|
||||
# is not recommended, as the images are often closely tied to DDEV's behavior,
|
||||
# so this can break upgrades.
|
||||
|
||||
# webimage: <docker_image>
|
||||
@@ -45,10 +47,18 @@ xhgui_http_port: "8143"
|
||||
# database:
|
||||
# type: <dbtype> # mysql, mariadb, postgres
|
||||
# version: <version> # database version, like "10.11" or "8.0"
|
||||
# MariaDB versions can be 5.5-10.8, 10.11, 11.4, 11.8
|
||||
# MySQL versions can be 5.5-8.0, 8.4
|
||||
# MariaDB versions can be 5.5-10.8, 10.11, 11.4, 11.8, 12.3
|
||||
# MySQL versions can be 5.5-8.0, 8.4, 9.7
|
||||
# PostgreSQL versions can be 9-18
|
||||
|
||||
# You can explicitly specify the dbimage but this
|
||||
# is not recommended, as the images are often closely tied to DDEV's behavior,
|
||||
# so this can break upgrades.
|
||||
|
||||
# dbimage: <docker_image>
|
||||
# It’s unusual to change this option, and we don’t recommend it without Docker experience and a good reason.
|
||||
# Typically, this means additions to the existing db image using a .ddev/db-build/Dockerfile.*
|
||||
|
||||
# router_http_port: <port> # Port to be used for http (defaults to global configuration, usually 80)
|
||||
# router_https_port: <port> # Port for https (defaults to global configuration, usually 443)
|
||||
|
||||
@@ -68,8 +78,7 @@ xhgui_http_port: "8143"
|
||||
# bind_all_ports is used (normally with router disabled)
|
||||
|
||||
# xhprof_mode: [prepend|xhgui|global]
|
||||
# Set to "xhgui" to enable XHGui features
|
||||
# "xhgui" will become default in a future major release
|
||||
# Default is "xhgui"
|
||||
|
||||
# webserver_type: nginx-fpm, apache-fpm, generic
|
||||
|
||||
@@ -87,22 +96,24 @@ xhgui_http_port: "8143"
|
||||
# commands are executed.
|
||||
|
||||
# composer_version: "2"
|
||||
# You can set it to "" or "2" (default) for Composer v2 or "1" for Composer v1
|
||||
# You can set it to "" or "2" (default) for Composer v2
|
||||
# to use the latest major version available at the time your container is built.
|
||||
# It is also possible to use each other Composer version channel. This includes:
|
||||
# It is also possible to use any other Composer version channel. This includes:
|
||||
# - 2.2 (latest Composer LTS version)
|
||||
# - stable
|
||||
# - preview
|
||||
# - snapshot
|
||||
# Alternatively, an explicit Composer version may be specified, for example "2.2.18".
|
||||
# To reinstall Composer after the image was built, run "ddev debug rebuild".
|
||||
# To reinstall Composer after the image was built, run "ddev utility rebuild".
|
||||
|
||||
# nodejs_version: "22"
|
||||
# nodejs_root: <relative_path>
|
||||
# Relative path to the directory containing the Node.js version file from the
|
||||
# project root. Only used with "nodejs_version: auto" or "nodejs_version: engine".
|
||||
|
||||
# nodejs_version: "24"
|
||||
# change from the default system Node.js version to any other version.
|
||||
# See https://docs.ddev.com/en/stable/users/configuration/config/#nodejs_version for more information
|
||||
# and https://www.npmjs.com/package/n#specifying-nodejs-versions for the full documentation,
|
||||
# Note that using of 'ddev nvm' is discouraged because "nodejs_version" is much easier to use,
|
||||
# can specify any version, and is more robust than using 'nvm'.
|
||||
# and https://www.npmjs.com/package/n#specifying-nodejs-versions for the full documentation.
|
||||
|
||||
# corepack_enable: false
|
||||
# Change to 'true' to 'corepack enable' and gain access to latest versions of yarn/pnpm
|
||||
@@ -162,9 +173,7 @@ xhgui_http_port: "8143"
|
||||
# - "global": uses the value from the global config.
|
||||
# - "none": disables performance optimization for this project.
|
||||
# - "mutagen": enables Mutagen for this project.
|
||||
# - "nfs": enables NFS for this project.
|
||||
#
|
||||
# See https://docs.ddev.com/en/stable/users/install/performance/#nfs
|
||||
# See https://docs.ddev.com/en/stable/users/install/performance/#mutagen
|
||||
|
||||
# fail_on_hook_fail: False
|
||||
@@ -194,10 +203,10 @@ xhgui_http_port: "8143"
|
||||
# The mailpit port is not normally bound on the host at all, instead being routed
|
||||
# through ddev-router, but it can be bound directly to localhost if specified here.
|
||||
|
||||
# webimage_extra_packages: [php7.4-tidy, php-bcmath]
|
||||
# webimage_extra_packages: ['php${DDEV_PHP_VERSION}-tidy', 'php${DDEV_PHP_VERSION}-yac']
|
||||
# Extra Debian packages that are needed in the webimage can be added here
|
||||
|
||||
# dbimage_extra_packages: [telnet,netcat]
|
||||
# dbimage_extra_packages: [netcat, telnet, sudo]
|
||||
# Extra Debian packages that are needed in the dbimage can be added here
|
||||
|
||||
# use_dns_when_possible: true
|
||||
@@ -209,12 +218,15 @@ xhgui_http_port: "8143"
|
||||
# project_tld: ddev.site
|
||||
# The top-level domain used for project URLs
|
||||
# The default "ddev.site" allows DNS lookup via a wildcard
|
||||
# If you prefer you can change this to "ddev.local" to preserve
|
||||
# pre-v1.9 behavior.
|
||||
|
||||
# ngrok_args: --basic-auth username:pass1234
|
||||
# Provide extra flags to the "ngrok http" command, see
|
||||
# https://ngrok.com/docs/agent/config/v3/#agent-configuration or run "ngrok http -h"
|
||||
# share_default_provider: ngrok
|
||||
# The default share provider to use for "ddev share"
|
||||
# Defaults to global configuration, usually "ngrok"
|
||||
# Can be "ngrok" or "cloudflared" or the name of a custom provider from .ddev/share-providers/
|
||||
|
||||
# share_provider_args: --basic-auth username:pass1234
|
||||
# Provide extra flags to the share provider script
|
||||
# See https://docs.ddev.com/en/stable/users/configuration/config/#share_provider_args
|
||||
|
||||
# disable_settings_management: false
|
||||
# If true, DDEV will not create CMS-specific settings files like
|
||||
@@ -276,7 +288,7 @@ xhgui_http_port: "8143"
|
||||
# override_config: false
|
||||
# By default, config.*.yaml files are *merged* into the configuration
|
||||
# But this means that some things can't be overridden
|
||||
# For example, if you have 'use_dns_when_possible: true'' you can't override it with a merge
|
||||
# For example, if you have 'use_dns_when_possible: true' you can't override it with a merge
|
||||
# and you can't erase existing hooks or all environment variables.
|
||||
# However, with "override_config: true" in a particular config.*.yaml file,
|
||||
# 'use_dns_when_possible: false' can override the existing values, and
|
||||
@@ -286,7 +298,7 @@ xhgui_http_port: "8143"
|
||||
# web_environment: []
|
||||
# or
|
||||
# additional_hostnames: []
|
||||
# can have their intended affect. 'override_config' affects only behavior of the
|
||||
# can have their intended effect. 'override_config' affects only behavior of the
|
||||
# config.*.yaml file it exists in.
|
||||
|
||||
# Many DDEV commands can be extended to run tasks before or after the
|
||||
@@ -296,3 +308,6 @@ xhgui_http_port: "8143"
|
||||
# information on the commands that can be extended and the tasks you can define
|
||||
# for them. Example:
|
||||
#hooks:
|
||||
## Un-comment to consume async message.
|
||||
# post-start:
|
||||
# - exec: symfony run --daemon --watch=config,src,templates,vendor bin/console messenger:consume async -vv
|
||||
@@ -26,7 +26,11 @@ APP_SECRET=b3cb0285aea14bbffb57df0cc3aa84f4
|
||||
# DATABASE_URL="sqlite:///%kernel.project_dir%/var/data.db"
|
||||
# DATABASE_URL="mysql://app:[email protected]:3306/app?serverVersion=8.0.32&charset=utf8mb4"
|
||||
# DATABASE_URL="mysql://app:[email protected]:3306/app?serverVersion=10.11.2-MariaDB&charset=utf8mb4"
|
||||
DATABASE_URL="postgresql://app:[email protected]:5432/app?serverVersion=15&charset=utf8"
|
||||
# DATABASE_URL="postgresql://app:[email protected]:5432/app?serverVersion=15&charset=utf8"
|
||||
# ddev runs MariaDB to match production. The serverVersion is what makes DBAL pick its
|
||||
# MariaDB platform - left at a MySQL version it speaks MySQL to a MariaDB server, and the
|
||||
# test suite then runs against a different platform than production.
|
||||
DATABASE_URL="mysql://db:db@db:3306/db?serverVersion=mariadb-11.8.9&charset=utf8mb4"
|
||||
###< doctrine/doctrine-bundle ###
|
||||
|
||||
###> symfony/messenger ###
|
||||
@@ -38,9 +42,17 @@ MESSENGER_TRANSPORT_DSN=doctrine://default?auto_setup=0
|
||||
|
||||
###> symfony/mailer ###
|
||||
MAILER_DSN=null://null
|
||||
# Has to resolve in every environment or the container does not compile, see the
|
||||
# mailjet section below for the production value
|
||||
MAILING_MAILER_DSN=null://null
|
||||
###< symfony/mailer ###
|
||||
|
||||
APP_BASE_URI=https://myep-team.ddev.site
|
||||
APP_BASE_URI=https://epteam.ddev.site
|
||||
|
||||
# Regex matched against the Host header; requests for any other host are refused. The
|
||||
# OAuth2 redirect_uri is generated from the request, so this is what pins it. Production
|
||||
# sets its own value outside the repository, like APP_BASE_URI above.
|
||||
APP_TRUSTED_HOSTS=^epteam\.ddev\.site$
|
||||
|
||||
APP_BPN_USER=
|
||||
APP_BPN_PASSWORD=
|
||||
@@ -51,18 +63,63 @@ APP_BPN_DEBUG=false
|
||||
# This hotel code will be assigned to admin users together with ROLE_HOTEL_MANAGER
|
||||
# in dev and staging environments for testing purposes
|
||||
APP_BPN_DEFAULT_HOTEL_CODE=
|
||||
APP_BPN_CRM_ID_ADMIN=1292
|
||||
APP_BPN_CRM_ID_TEAM_ADMIN=1484
|
||||
APP_BPN_CRM_ID_MANAGER=1293
|
||||
APP_BPN_CRM_ID_TEAMER=1070
|
||||
|
||||
APP_DEFAULT_EMAIL_FROM=[email protected]
|
||||
APP_DEFAULT_EMAIL_TO=[email protected]
|
||||
|
||||
XML_EXPORT_PATH="%kernel.project_dir%/var/xmlexport"
|
||||
# Regular expressions of the only recipients a mail may actually reach. Everything else is
|
||||
# stripped from the envelope and a mail left without a recipient is not sent at all, see
|
||||
# App\EventListener\EmailRecipientWhitelistSubscriber. Empty means no filtering, which is what
|
||||
# production runs with. Set this on staging: it runs the real transports against real teamer
|
||||
# addresses. The patterns have to match the full address.
|
||||
# Example: APP_MAIL_RECIPIENT_WHITELIST='.*@ep-reisen\.de,qa@example\.com'
|
||||
APP_MAIL_RECIPIENT_WHITELIST=
|
||||
|
||||
|
||||
# Machine to machine access to the MyE&P API. This is a separate confidential client
|
||||
# from the login one below, so it can be rotated without locking anyone out. The base
|
||||
# url is the host root: both the token endpoint and the api paths hang off it.
|
||||
MYEP_API_BASE_URL=https://my.ep-reisen.de/
|
||||
MYEP_API_CLIENT_ID=
|
||||
MYEP_API_CLIENT_SECRET=
|
||||
|
||||
MYEP_OAUTH2_CLIENT_ID=
|
||||
MYEP_OAUTH2_CLIENT_SECRET=
|
||||
MYEP_OAUTH2_URL_AUTHORIZE=https://my.ep-reisen.de/authorize
|
||||
MYEP_OAUTH2_URL_ACCESS_TOKEN=https://my.ep-reisen.de/token
|
||||
MYEP_OAUTH2_URL_RESOURCE_OWNER_DETAILS=https://my.ep-reisen.de/api/userinfo
|
||||
MYEP_OAUTH2_SCOPES=email,id,roles,profile
|
||||
# Shared key the sibling websites send in the X-Api-Key header. Empty here: set it per stage.
|
||||
APPLICATION_FORM_API_KEY=
|
||||
# Shared key n8n reads the assignments feed with, in the X-Api-Key header. Empty here: set it per stage.
|
||||
N8N_API_KEY=
|
||||
# Booking link for the Team-Infocall, substituted for {bookingUrl} in the application form
|
||||
# receipt mail. Empty here: set it per stage. While it is empty that mail is not sent at all -
|
||||
# an invitation with a dead booking link is worse than none.
|
||||
APPLICATION_FORM_BOOKING_URL=
|
||||
|
||||
# SaaS time tracking. Season teamers clock their hours here; app:saas:work-hours imports them.
|
||||
# The API key travels in the URL path, so it never belongs in the committed file - set both
|
||||
# values in .env.local / the server environment.
|
||||
APP_SAAS_BASE_URL=
|
||||
APP_SAAS_API_KEY=
|
||||
|
||||
###> symfony/mailjet-mailer ###
|
||||
# MAILER_DSN=mailjet+api://PUBLIC_KEY:[email protected]
|
||||
# #MAILER_DSN=mailjet+smtp://PUBLIC_KEY:[email protected]
|
||||
# Bulk teamer mailings only, everything else goes out over MAILER_DSN. Set the real
|
||||
# credentials in .env.local - the sending domain has to be verified in Mailjet (SPF,
|
||||
# DKIM, DMARC) or the mail is delivered but lands in spam.
|
||||
#
|
||||
# Use smtp. The api transport does not send the message we built: it takes the parts as
|
||||
# json fields and has Mailjet assemble the mail, which drops the embedded logo into an
|
||||
# ordinary attachment. Over smtp our own MIME goes out unchanged and the mail arrives as
|
||||
# it was rendered. Deliverability is identical either way, it is the same account, the
|
||||
# same ip pool and the same dkim signature. The api is only worth revisiting if the host
|
||||
# ever blocks outbound port 587, since it talks https instead.
|
||||
# MAILING_MAILER_DSN=mailjet+smtp://PUBLIC_KEY:[email protected]
|
||||
# MAILING_MAILER_DSN=mailjet+api://PUBLIC_KEY:PRIVATE_KEY@default
|
||||
###< symfony/mailjet-mailer ###
|
||||
|
||||
BIN_GS=/usr/bin/gs
|
||||
@@ -73,7 +130,11 @@ [email protected]
|
||||
|
||||
FEATURE_SANITIZE_UPLOADS=false
|
||||
FEATURE_STAMP_INVOICES=false
|
||||
FEATURE_DATEV_EMAIL=false
|
||||
FEATURE_MYEP_LOGIN=false
|
||||
FEATURE_UPLOAD_REPLACEMENT=false
|
||||
|
||||
UPLOAD_REPLACEMENT_FILE=assets/pdf/chicken.pdf
|
||||
|
||||
PERSONAL_DATA_CHECK_DEADLINES=04-01,10-01
|
||||
MAINTENANCE_MODE_ENABLED=false
|
||||
MAINTENANCE_MODE_IP_WHITELIST=188.245.62.21
|
||||
MAINTENANCE_MODE_ROUTE_WHITELIST=
|
||||
MAINTENANCE_MODE_PATH_WHITELIST=
|
||||
@@ -1,6 +1,16 @@
|
||||
# define your env variables for the test env here
|
||||
KERNEL_CLASS='App\Kernel'
|
||||
APP_SECRET='$ecretf0rt3st'
|
||||
SYMFONY_DEPRECATIONS_HELPER=999999
|
||||
SYMFONY_DEPRECATIONS_HELPER=max[self]=0
|
||||
PANTHER_APP_ENV=panther
|
||||
PANTHER_ERROR_SCREENSHOT_DIR=./var/error-screenshots
|
||||
# the test client requests http://localhost/
|
||||
APP_TRUSTED_HOSTS='^localhost$'
|
||||
# The api firewall rejects every request while no key is configured, and .env.local is not
|
||||
# loaded in the test environment - so the throwaway key the tests send belongs here.
|
||||
# Keep it in sync with ApplicationFormControllerTest::KEY.
|
||||
APPLICATION_FORM_API_KEY=test-application-form-api-key
|
||||
# Keep in sync with Api\Assignment\ListControllerTest::KEY.
|
||||
N8N_API_KEY=test-n8n-api-key
|
||||
# The receipt mail refuses to render without it, and several tests send that mail.
|
||||
APPLICATION_FORM_BOOKING_URL=https://bookings.example.com/team-infocall
|
||||
+5
-2
@@ -4,6 +4,7 @@
|
||||
/.env.local.php
|
||||
/.env.*.local
|
||||
/config/secrets/prod/prod.decrypt.private.php
|
||||
/config/reference.php
|
||||
/public/bundles/
|
||||
/var/
|
||||
/vendor/
|
||||
@@ -11,11 +12,11 @@
|
||||
|
||||
###> phpunit/phpunit ###
|
||||
/phpunit.xml
|
||||
.phpunit.result.cache
|
||||
/.phpunit.cache/
|
||||
###< phpunit/phpunit ###
|
||||
|
||||
###> symfony/phpunit-bridge ###
|
||||
.phpunit.result.cache
|
||||
/.phpunit.cache/
|
||||
/phpunit.xml
|
||||
###< symfony/phpunit-bridge ###
|
||||
|
||||
@@ -34,3 +35,5 @@ yarn-error.log
|
||||
/.php-cs-fixer.php
|
||||
/.php-cs-fixer.cache
|
||||
###< friendsofphp/php-cs-fixer ###
|
||||
|
||||
/http-client.private.env.json
|
||||
@@ -0,0 +1,57 @@
|
||||
### Full submission, as the TYPO3 ApplicationApiFinisher sends it.
|
||||
### The key is APPLICATION_FORM_API_KEY of the stage being called; keep the real values in
|
||||
### http-client.private.env.json, which is not in the repository.
|
||||
POST https://myep-team.ddev.site/api/application-forms
|
||||
Content-Type: application/json
|
||||
X-Api-Key: {{applicationFormApiKey}}
|
||||
|
||||
{
|
||||
"form": "applicationform",
|
||||
"formLabel": "Bewerbungsformular",
|
||||
"submittedAt": "2026-09-22T12:30:00+02:00",
|
||||
"pageUid": 1421,
|
||||
"pageUrl": "https://www.ep-reisen.de/jobs/bewerbung",
|
||||
"firstName": "Anna",
|
||||
"lastName": "Muster",
|
||||
"email": "[email protected]",
|
||||
"phone": "+49 30 1234567",
|
||||
"mobile": "+49 170 1234567",
|
||||
"birthDate": "01.01.1990",
|
||||
"street": "Musterstraße 1",
|
||||
"zipCode": "10115",
|
||||
"city": "Berlin",
|
||||
"details": {
|
||||
"Nationalität": "deutsch",
|
||||
"Führerschein": "ja",
|
||||
"Ausbildung/Studium": "Tourismusmanagement",
|
||||
"Bewerbung als": ["Reiseleitung", "Skilehrer"],
|
||||
"Ich fahre": ["Ski", "Snowboard"],
|
||||
"Wie gut fahre ich Ski": "fortgeschritten",
|
||||
"Skifahrer seit": "2004",
|
||||
"Einsatz datum": "ab 20.12.2026"
|
||||
}
|
||||
}
|
||||
|
||||
### A form that maps nothing but the basics - valid, everything else is absent, not empty.
|
||||
POST https://myep-team.ddev.site/api/application-forms
|
||||
Content-Type: application/json
|
||||
X-Api-Key: {{applicationFormApiKey}}
|
||||
|
||||
{
|
||||
"form": "contactform",
|
||||
"formLabel": "Kontaktformular",
|
||||
"submittedAt": "2026-09-22T12:30:00+02:00",
|
||||
"details": {}
|
||||
}
|
||||
|
||||
### Wrong key: 401, and the submission is dropped on the TYPO3 side without a retry.
|
||||
POST https://myep-team.ddev.site/api/application-forms
|
||||
Content-Type: application/json
|
||||
X-Api-Key: definitely-not-the-key
|
||||
|
||||
{
|
||||
"form": "applicationform",
|
||||
"formLabel": "Bewerbungsformular",
|
||||
"submittedAt": "2026-09-22T12:30:00+02:00",
|
||||
"details": {}
|
||||
}
|
||||
@@ -3,6 +3,15 @@ htmx.config.includeIndicatorStyles = false
|
||||
htmx.config.historyCacheSize = 0
|
||||
htmx.config.allowScriptTags = false
|
||||
|
||||
// A boosted link is a page navigation: show the error page like the browser would, instead of
|
||||
// swallowing the 4xx/5xx and leaving the click without effect.
|
||||
document.addEventListener('htmx:beforeSwap', (event) => {
|
||||
if (true === event.detail.boosted && event.detail.xhr.status >= 400) {
|
||||
event.detail.shouldSwap = true
|
||||
event.detail.isError = false
|
||||
}
|
||||
})
|
||||
|
||||
import './bootstrap.js';
|
||||
/*
|
||||
* Welcome to your app's main JavaScript file!
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
import { Controller } from '@hotwired/stimulus'
|
||||
|
||||
/*
|
||||
* Shows a questionnaire question only while its show_if holds, mirroring
|
||||
* App\Model\Questionnaire\Definition::isVisible(): the condition must be met and the question it
|
||||
* depends on must itself be shown.
|
||||
*
|
||||
* Every question row carries data-question-key; a conditional one also data-show-if, the JSON
|
||||
* {question, operator, value} that App\Form\QuestionnaireType writes. A hidden row stays in the
|
||||
* form and is submitted: a draft keeps its answer, and the server drops it on submit.
|
||||
*/
|
||||
export default class extends Controller {
|
||||
|
||||
connect() {
|
||||
this.update()
|
||||
}
|
||||
|
||||
update() {
|
||||
const visible = {}
|
||||
|
||||
// Rows come in definition order, and a condition may only name an earlier question, so
|
||||
// the question a row depends on has always been decided by the time the row is reached.
|
||||
for (const row of this.element.querySelectorAll('[data-question-key]')) {
|
||||
const key = row.dataset.questionKey
|
||||
const condition = row.dataset.showIf ? JSON.parse(row.dataset.showIf) : null
|
||||
|
||||
visible[key] = null === condition
|
||||
|| (true === visible[condition.question] && this.isMet(condition))
|
||||
|
||||
row.hidden = false === visible[key]
|
||||
}
|
||||
}
|
||||
|
||||
isMet({ question, operator, value }) {
|
||||
const answer = this.answer(question)
|
||||
|
||||
if (null === answer) {
|
||||
return false
|
||||
}
|
||||
|
||||
switch (operator) {
|
||||
case 'equals':
|
||||
return answer === value
|
||||
case 'in':
|
||||
return value.includes(answer)
|
||||
case 'lte':
|
||||
return Number(answer) <= value
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
answer(key) {
|
||||
const row = this.element.querySelector(`[data-question-key="${key}"]`)
|
||||
|
||||
if (null === row) {
|
||||
return null
|
||||
}
|
||||
|
||||
const checked = row.querySelector('input[type="radio"]:checked')
|
||||
|
||||
if (null !== checked) {
|
||||
return checked.value
|
||||
}
|
||||
|
||||
const select = row.querySelector('select')
|
||||
|
||||
if (null !== select) {
|
||||
return '' === select.value ? null : select.value
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,18 @@
|
||||
import { Controller } from '@hotwired/stimulus'
|
||||
|
||||
export default class extends Controller {
|
||||
// the default of one row keeps every existing textarea exactly as it was, fields that
|
||||
// are written into at length can ask for a taller floor to grow from
|
||||
static values = { minRows: { type: Number, default: 1 } }
|
||||
|
||||
connect() {
|
||||
this.element.rows = 1
|
||||
this.element.rows = this.minRowsValue
|
||||
this.minHeight = this.element.scrollHeight
|
||||
this.resize()
|
||||
}
|
||||
|
||||
resize() {
|
||||
this.element.style.height = 'auto'
|
||||
this.element.style.height = `${this.element.scrollHeight}px`
|
||||
this.element.style.height = `${Math.max(this.element.scrollHeight, this.minHeight)}px`
|
||||
}
|
||||
}
|
||||
@@ -198,11 +198,11 @@
|
||||
<path d="M24 0C10.7 0 0 10.7 0 24S10.7 48 24 48h8V67c0 40.3 16 79 44.5 107.5L158.1 256 76.5 337.5C48 366 32 404.7 32 445v19H24c-13.3 0-24 10.7-24 24s10.7 24 24 24H360c13.3 0 24-10.7 24-24s-10.7-24-24-24h-8V445c0-40.3-16-79-44.5-107.5L225.9 256l81.5-81.5C336 146 352 107.3 352 67V48h8c13.3 0 24-10.7 24-24s-10.7-24-24-24H24zM192 289.9l81.5 81.5C293 391 304 417.4 304 445v19H80V445c0-27.6 11-54 30.5-73.5L192 289.9zm0-67.9l-81.5-81.5C91 121 80 94.6 80 67V48H304V67c0 27.6-11 54-30.5 73.5L192 222.1z"/>
|
||||
</symbol>
|
||||
<symbol id="icon-car" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><rect width="256" height="256" fill="none"/><circle cx="64" cy="176" r="24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/><circle cx="192" cy="176" r="24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/><line x1="88" y1="176" x2="168" y2="176" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/><path d="M216,176h24a8,8,0,0,0,8-8V128a8,8,0,0,0-8-8H208L162.34,74.34A8,8,0,0,0,156.69,72H44.28a8,8,0,0,0-6.65,3.56L8,120v48a8,8,0,0,0,8,8H40" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/><line x1="208" y1="120" x2="8" y2="120" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/></symbol>
|
||||
<symbol id="icon-flag-a" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 480"><g fill-rule="evenodd"><path fill="#fff" d="M640 480H0V0h640z"/><path fill="#ed2939" d="M640 480H0V320h640zm0-319.9H0V.1h640z"/></g></symbol>
|
||||
<symbol id="icon-flag-at" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 480"><g fill-rule="evenodd"><path fill="#fff" d="M640 480H0V0h640z"/><path fill="#ed2939" d="M640 480H0V320h640zm0-319.9H0V.1h640z"/></g></symbol>
|
||||
<symbol id="icon-flag-ch" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 480"><g fill-rule="evenodd" stroke-width="1pt"><path fill="#d52b1e" d="M0 0h640v480H0z"/><g fill="#fff"><path d="M170 195h300v90H170z"/><path d="M275 90h90v300h-90z"/></g></g></symbol>
|
||||
<symbol id="icon-flag-d" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 480"><path fill="#ffce00" d="M0 320h640v160H0z"/><path d="M0 0h640v160H0z"/><path fill="#d00" d="M0 160h640v160H0z"/></symbol>
|
||||
<symbol id="icon-flag-f" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 480"><g fill-rule="evenodd" stroke-width="1pt"><path fill="#fff" d="M0 0h640v480H0z"/><path fill="#00267f" d="M0 0h213.3v480H0z"/><path fill="#f31830" d="M426.7 0H640v480H426.7z"/></g></symbol>
|
||||
<symbol id="icon-flag-i" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 480"><g fill-rule="evenodd" stroke-width="1pt"><path fill="#fff" d="M0 0h640v480H0z"/><path fill="#009246" d="M0 0h213.3v480H0z"/><path fill="#ce2b37" d="M426.7 0H640v480H426.7z"/></g></symbol>
|
||||
<symbol id="icon-flag-de" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 480"><path fill="#ffce00" d="M0 320h640v160H0z"/><path d="M0 0h640v160H0z"/><path fill="#d00" d="M0 160h640v160H0z"/></symbol>
|
||||
<symbol id="icon-flag-fr" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 480"><g fill-rule="evenodd" stroke-width="1pt"><path fill="#fff" d="M0 0h640v480H0z"/><path fill="#00267f" d="M0 0h213.3v480H0z"/><path fill="#f31830" d="M426.7 0H640v480H426.7z"/></g></symbol>
|
||||
<symbol id="icon-flag-it" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 480"><g fill-rule="evenodd" stroke-width="1pt"><path fill="#fff" d="M0 0h640v480H0z"/><path fill="#009246" d="M0 0h213.3v480H0z"/><path fill="#ce2b37" d="M426.7 0H640v480H426.7z"/></g></symbol>
|
||||
<symbol id="icon-flag-nl" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 480"><g fill-rule="evenodd"><path fill="#21468b" d="M0 320h640v160H0z"/><path fill="#fff" d="M0 160h640v160H0z"/><path fill="#ae1c28" d="M0 0h640v160H0z"/></g></symbol>
|
||||
<!-- By Sam Herbert (@sherb), for everyone. More @ http://goo.gl/7AJzbL -->
|
||||
<symbol id="icon-spinner" viewBox="0 0 44 44" xmlns="http://www.w3.org/2000/svg" stroke="currentColor">
|
||||
@@ -241,4 +241,8 @@
|
||||
</circle>
|
||||
</g>
|
||||
</symbol>
|
||||
<symbol id="icon-typo3" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">
|
||||
<!--! Font Awesome Pro 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. -->
|
||||
<path d="M178.7 78.4c0-24.7 5.4-32.4 13.9-39.4-69.5 8.5-149.3 34-176.3 66.4-5.4 7.7-9.3 20.8-9.3 37.1C7 246 113.8 480 191.1 480c36.3 0 97.3-59.5 146.7-139-7 2.3-11.6 2.3-18.5 2.3-57.2 0-140.6-198.5-140.6-264.9zM301.5 32c-30.1 0-41.7 5.4-41.7 36.3 0 66.4 53.8 198.5 101.7 198.5 26.3 0 78.8-99.7 78.8-182.3 0-40.9-67-52.5-138.8-52.5z"/>
|
||||
</symbol>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 36 KiB |
+38
-2
@@ -19,7 +19,15 @@
|
||||
font-size: 20px;
|
||||
line-height: 24px;
|
||||
margin-bottom: 0;
|
||||
padding-bottom: 0;
|
||||
/* Was 0 while an h2 only ever preceded a p, which brings its own spacing. */
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
h3 {
|
||||
font-weight: bold;
|
||||
font-size: 17px;
|
||||
line-height: 22px;
|
||||
margin-bottom: 0;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
p {
|
||||
font-size: 16px;
|
||||
@@ -48,6 +56,18 @@
|
||||
strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
em {
|
||||
font-style: italic;
|
||||
}
|
||||
ul, ol {
|
||||
/* Margin, not padding: Outlook on Windows renders list padding unreliably. */
|
||||
margin: 0 0 16px 24px;
|
||||
padding: 0;
|
||||
font-size: 16px;
|
||||
}
|
||||
li {
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
</mj-style>
|
||||
</mj-head>
|
||||
<mj-body background-color="#666666">
|
||||
@@ -70,8 +90,19 @@
|
||||
</h2>
|
||||
<p>
|
||||
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Architecto culpa delectus dolores
|
||||
<strong>earum eius fugiat in nesciunt quas quidem vitae</strong>?
|
||||
<strong>earum eius fugiat in nesciunt quas quidem vitae</strong>, <em>eius fugiat</em>?
|
||||
</p>
|
||||
<ul>
|
||||
<li>Lorem ipsum dolor</li>
|
||||
<li>Consectetur adipisicing elit</li>
|
||||
</ul>
|
||||
<h3>
|
||||
Lorem ipsum
|
||||
</h3>
|
||||
<ol>
|
||||
<li>Lorem ipsum dolor</li>
|
||||
<li>Consectetur adipisicing elit</li>
|
||||
</ol>
|
||||
<p>
|
||||
<a href="#" class="button">
|
||||
Button
|
||||
@@ -81,6 +112,11 @@
|
||||
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Doloribus, eveniet!
|
||||
</p>
|
||||
{% endblock %}
|
||||
{% block button %}
|
||||
<p>
|
||||
<a href="{{ url('app_index') }}" class="button">Zum Portal</a>
|
||||
</p>
|
||||
{% endblock %}
|
||||
</mj-text>
|
||||
</mj-column>
|
||||
</mj-section>
|
||||
|
||||
Binary file not shown.
@@ -1,8 +1,9 @@
|
||||
@import "components/badge.css";
|
||||
@import "components/autocomplete.css";
|
||||
@import "components/button.css";
|
||||
@import "components/datatable.css";
|
||||
@import "components/datepicker.css";
|
||||
@import "components/toast.css";
|
||||
@import "components/tooltip.css";
|
||||
@import "components/timeline.css";
|
||||
@import "./components/badge.css";
|
||||
@import "./components/autocomplete.css";
|
||||
@import "./components/button.css";
|
||||
@import "./components/datatable.css";
|
||||
@import "./components/datepicker.css";
|
||||
@import "./components/toast.css";
|
||||
@import "./components/tooltip.css";
|
||||
@import "./components/timeline.css";
|
||||
@import "./components/mail-body.css";
|
||||
+44
-5
@@ -1,7 +1,46 @@
|
||||
@import "tailwindcss/base";
|
||||
@import "_base.css";
|
||||
@import "tailwindcss";
|
||||
@import "./_base.css" layer(base);
|
||||
@import "./_components.css" layer(components);
|
||||
|
||||
@import "tailwindcss/components";
|
||||
@import "_components.css";
|
||||
@plugin "@tailwindcss/forms";
|
||||
|
||||
@import "tailwindcss/utilities";
|
||||
@source inline("rotate-90");
|
||||
|
||||
@theme {
|
||||
--font-sans: Lato, sans-serif;
|
||||
--font-weight-semibold: 700;
|
||||
--font-weight-bold: 900;
|
||||
--color-primary: #3d8ccb;
|
||||
--color-secondary: #f9c700;
|
||||
}
|
||||
|
||||
/*
|
||||
* Tailwind 3 defaults that Tailwind 4 changed: borders were gray-200 instead of currentColor,
|
||||
* and buttons had a pointer cursor.
|
||||
*/
|
||||
@layer base {
|
||||
*,
|
||||
::after,
|
||||
::before,
|
||||
::backdrop,
|
||||
::file-selector-button {
|
||||
border-color: var(--color-gray-200, currentcolor);
|
||||
}
|
||||
|
||||
button:not(:disabled),
|
||||
[role="button"]:not(:disabled) {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Thin scrollbars without a track for inner scrolling areas. The page scrollbar takes its
|
||||
* style from <html>, which body * leaves out. Firefox and Chrome/Edge 121+ honour these;
|
||||
* Safari keeps its own overlay scrollbars.
|
||||
*/
|
||||
@layer base {
|
||||
body * {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--color-gray-300) transparent;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
.btn {
|
||||
@apply inline-flex space-x-1 justify-center rounded-lg text-sm uppercase font-semibold py-2.5 px-4;
|
||||
@apply inline-flex space-x-1 justify-center items-center rounded-lg text-sm uppercase font-semibold py-2.5 px-4;
|
||||
@apply bg-primary text-white hover:bg-primary/80 shadow-md disabled:opacity-50 disabled:cursor-not-allowed;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
.flatpickr-day.selected,
|
||||
.flatpickr-day.selected:hover,
|
||||
.flatpickr-day.selected:focus {
|
||||
background-color: theme('colors.secondary');
|
||||
border-color: theme('colors.secondary');
|
||||
background-color: var(--color-secondary);
|
||||
border-color: var(--color-secondary);
|
||||
}
|
||||
|
||||
.flatpickr-monthDropdown-months,
|
||||
.flatpickr-current-month .flatpickr-monthDropdown-months {
|
||||
@apply form-select pt-0 pb-0 focus:outline-none focus:shadow-none;
|
||||
@apply form-select pt-0 pb-0 focus:outline-hidden focus:shadow-none;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Mirrors the rules the mail layout carries in assets/mjml/layout.mjml, so both admin
|
||||
* previews show what the rendered mail will look like. Kept out of Tailwind's typography
|
||||
* plugin on purpose: prose brings its own type scale, and the point here is to match the
|
||||
* mail rather than to look good on its own.
|
||||
*/
|
||||
.mail-body > p {
|
||||
@apply pb-4;
|
||||
}
|
||||
|
||||
.mail-body h2 {
|
||||
@apply text-xl pb-2;
|
||||
}
|
||||
|
||||
.mail-body h3 {
|
||||
@apply text-lg font-bold pb-1;
|
||||
}
|
||||
|
||||
.mail-body strong {
|
||||
@apply font-bold;
|
||||
}
|
||||
|
||||
.mail-body em {
|
||||
@apply italic;
|
||||
}
|
||||
|
||||
.mail-body ul {
|
||||
@apply list-disc ml-6 pb-4;
|
||||
}
|
||||
|
||||
.mail-body ol {
|
||||
@apply list-decimal ml-6 pb-4;
|
||||
}
|
||||
|
||||
.mail-body li {
|
||||
@apply pb-1;
|
||||
}
|
||||
|
||||
.mail-body a {
|
||||
@apply underline;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
.scroll-container {
|
||||
@apply overflow-scroll;
|
||||
@apply overflow-auto;
|
||||
max-height: calc(100vh - 320px);
|
||||
}
|
||||
@@ -1,20 +1,20 @@
|
||||
@import "toastify-js/src/toastify.css";
|
||||
|
||||
.toastify {
|
||||
@apply shadow-lg rounded;
|
||||
@apply shadow-lg rounded-sm;
|
||||
}
|
||||
|
||||
.toastify--success {
|
||||
@apply text-gray-100;
|
||||
background: theme('colors.emerald.600');
|
||||
background: var(--color-emerald-600);
|
||||
}
|
||||
|
||||
.toastify--warning {
|
||||
@apply text-gray-100;
|
||||
background: theme('colors.red.600');
|
||||
background: var(--color-red-600);
|
||||
}
|
||||
|
||||
.toastify--info {
|
||||
@apply text-gray-100;
|
||||
background: theme('colors.blue.600');
|
||||
background: var(--color-blue-600);
|
||||
}
|
||||
@@ -1,18 +1,18 @@
|
||||
@import "tippy.js/dist/tippy.css";
|
||||
|
||||
.tippy-box {
|
||||
background-color: theme('colors.primary');
|
||||
background-color: var(--color-primary);
|
||||
}
|
||||
|
||||
.tippy-box[data-placement^='top'] > .tippy-arrow::before {
|
||||
border-top-color: theme('colors.primary');
|
||||
border-top-color: var(--color-primary);
|
||||
}
|
||||
.tippy-box[data-placement^='bottom'] > .tippy-arrow::before {
|
||||
border-bottom-color: theme('colors.primary');
|
||||
border-bottom-color: var(--color-primary);
|
||||
}
|
||||
.tippy-box[data-placement^='left'] > .tippy-arrow::before {
|
||||
border-left-color: theme('colors.primary');
|
||||
border-left-color: var(--color-primary);
|
||||
}
|
||||
.tippy-box[data-placement^='right'] > .tippy-arrow::before {
|
||||
border-right-color: theme('colors.primary');
|
||||
border-right-color: var(--color-primary);
|
||||
}
|
||||
+5
-11
@@ -5,15 +5,9 @@ if (!ini_get('date.timezone')) {
|
||||
ini_set('date.timezone', 'UTC');
|
||||
}
|
||||
|
||||
if (is_file(dirname(__DIR__).'/vendor/phpunit/phpunit/phpunit')) {
|
||||
define('PHPUNIT_COMPOSER_INSTALL', dirname(__DIR__).'/vendor/autoload.php');
|
||||
require PHPUNIT_COMPOSER_INSTALL;
|
||||
PHPUnit\TextUI\Command::main();
|
||||
} else {
|
||||
if (!is_file(dirname(__DIR__).'/vendor/symfony/phpunit-bridge/bin/simple-phpunit.php')) {
|
||||
echo "Unable to find the `simple-phpunit.php` script in `vendor/symfony/phpunit-bridge/bin/`.\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
require dirname(__DIR__).'/vendor/symfony/phpunit-bridge/bin/simple-phpunit.php';
|
||||
if (!is_file(dirname(__DIR__).'/vendor/phpunit/phpunit/phpunit')) {
|
||||
echo "Unable to find PHPUnit in `vendor/phpunit/phpunit/`. Run `composer install`.\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
require dirname(__DIR__).'/vendor/phpunit/phpunit/phpunit';
|
||||
+49
-41
@@ -10,26 +10,30 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=8.1",
|
||||
"php": ">=8.4",
|
||||
"ext-ctype": "*",
|
||||
"ext-iconv": "*",
|
||||
"ext-simplexml": "*",
|
||||
"ext-sockets": "*",
|
||||
"beberlei/doctrineextensions": "^1.5",
|
||||
"doctrine/doctrine-bundle": "^2.10",
|
||||
"doctrine/doctrine-bundle": "^2.15",
|
||||
"doctrine/doctrine-migrations-bundle": "^3.2",
|
||||
"doctrine/orm": "^2.15",
|
||||
"doctrine/orm": "^3.6",
|
||||
"dompdf/dompdf": "^3.1",
|
||||
"flagception/flagception-bundle": "^6.0",
|
||||
"knplabs/knp-menu-bundle": "^3.2",
|
||||
"knplabs/knp-paginator-bundle": "^6.2",
|
||||
"league/commonmark": "^2.10",
|
||||
"league/flysystem-bundle": "^3.4",
|
||||
"league/flysystem-sftp-v3": "^3.29",
|
||||
"league/oauth2-client": "^2.9",
|
||||
"liip/imagine-bundle": "^2.11",
|
||||
"maennchen/zipstream-php": "^3.1",
|
||||
"nelmio/security-bundle": "^3.0",
|
||||
"nesbot/carbon": "^2.70",
|
||||
"oneup/uploader-bundle": "^4.0",
|
||||
"nesbot/carbon": "^3.8",
|
||||
"oneup/uploader-bundle": "^5.1",
|
||||
"phpdocumentor/reflection-docblock": "^5.3",
|
||||
"phpoffice/phpspreadsheet": "^5.9",
|
||||
"phpstan/phpdoc-parser": "^1.22",
|
||||
"scienta/doctrine-json-functions": "^6.3",
|
||||
"setasign/fpdf": "^1.8",
|
||||
@@ -38,38 +42,38 @@
|
||||
"spatie/blink": "^1.4",
|
||||
"spatie/icalendar-generator": "^2.5",
|
||||
"symfony/apache-pack": "^1.0",
|
||||
"symfony/asset": "6.4.*",
|
||||
"symfony/console": "6.4.*",
|
||||
"symfony/doctrine-messenger": "6.4.*",
|
||||
"symfony/dotenv": "6.4.*",
|
||||
"symfony/expression-language": "6.4.*",
|
||||
"symfony/asset": "7.4.*",
|
||||
"symfony/console": "7.4.*",
|
||||
"symfony/doctrine-messenger": "7.4.*",
|
||||
"symfony/dotenv": "7.4.*",
|
||||
"symfony/expression-language": "7.4.*",
|
||||
"symfony/flex": "^2",
|
||||
"symfony/form": "6.4.*",
|
||||
"symfony/framework-bundle": "6.4.*",
|
||||
"symfony/http-client": "6.4.*",
|
||||
"symfony/intl": "6.4.*",
|
||||
"symfony/mailer": "6.4.*",
|
||||
"symfony/mailjet-mailer": "6.4.*",
|
||||
"symfony/mime": "6.4.*",
|
||||
"symfony/form": "7.4.*",
|
||||
"symfony/framework-bundle": "7.4.*",
|
||||
"symfony/http-client": "7.4.*",
|
||||
"symfony/intl": "7.4.*",
|
||||
"symfony/mailer": "7.4.*",
|
||||
"symfony/mailjet-mailer": "7.4.*",
|
||||
"symfony/mime": "7.4.*",
|
||||
"symfony/monolog-bundle": "^3.0",
|
||||
"symfony/notifier": "6.4.*",
|
||||
"symfony/process": "6.4.*",
|
||||
"symfony/property-access": "6.4.*",
|
||||
"symfony/property-info": "6.4.*",
|
||||
"symfony/runtime": "6.4.*",
|
||||
"symfony/security-bundle": "6.4.*",
|
||||
"symfony/serializer": "6.4.*",
|
||||
"symfony/notifier": "7.4.*",
|
||||
"symfony/process": "7.4.*",
|
||||
"symfony/property-access": "7.4.*",
|
||||
"symfony/property-info": "7.4.*",
|
||||
"symfony/runtime": "7.4.*",
|
||||
"symfony/security-bundle": "7.4.*",
|
||||
"symfony/serializer": "7.4.*",
|
||||
"symfony/stimulus-bundle": "^2.11",
|
||||
"symfony/string": "6.4.*",
|
||||
"symfony/translation": "6.4.*",
|
||||
"symfony/twig-bundle": "6.4.*",
|
||||
"symfony/uid": "6.4.*",
|
||||
"symfony/string": "7.4.*",
|
||||
"symfony/translation": "7.4.*",
|
||||
"symfony/twig-bundle": "7.4.*",
|
||||
"symfony/uid": "7.4.*",
|
||||
"symfony/ux-twig-component": "^2.16",
|
||||
"symfony/validator": "6.4.*",
|
||||
"symfony/web-link": "6.4.*",
|
||||
"symfony/validator": "7.4.*",
|
||||
"symfony/web-link": "7.4.*",
|
||||
"symfony/webpack-encore-bundle": "^2.0",
|
||||
"symfony/workflow": "6.4.*",
|
||||
"symfony/yaml": "6.4.*",
|
||||
"symfony/workflow": "7.4.*",
|
||||
"symfony/yaml": "7.4.*",
|
||||
"twig/extra-bundle": "^2.12|^3.0",
|
||||
"twig/html-extra": "^3.7",
|
||||
"twig/intl-extra": "^3.7",
|
||||
@@ -83,7 +87,10 @@
|
||||
"symfony/flex": true,
|
||||
"symfony/runtime": true
|
||||
},
|
||||
"sort-packages": true
|
||||
"sort-packages": true,
|
||||
"platform": {
|
||||
"php": "8.5.9"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
@@ -123,7 +130,7 @@
|
||||
"extra": {
|
||||
"symfony": {
|
||||
"allow-contrib": true,
|
||||
"require": "6.4.*"
|
||||
"require": "7.4.*"
|
||||
}
|
||||
},
|
||||
"require-dev": {
|
||||
@@ -131,14 +138,15 @@
|
||||
"fakerphp/faker": "*",
|
||||
"friendsofphp/php-cs-fixer": "^3.84",
|
||||
"marcocesarato/php-conventional-changelog": "^1.17",
|
||||
"phpunit/phpunit": "^9.5",
|
||||
"symfony/browser-kit": "6.4.*",
|
||||
"symfony/css-selector": "6.4.*",
|
||||
"symfony/debug-bundle": "6.4.*",
|
||||
"phpunit/phpunit": "^12.5",
|
||||
"rector/rector": "^2.6",
|
||||
"symfony/browser-kit": "7.4.*",
|
||||
"symfony/css-selector": "7.4.*",
|
||||
"symfony/debug-bundle": "7.4.*",
|
||||
"symfony/maker-bundle": "^1.0",
|
||||
"symfony/phpunit-bridge": "^6.3",
|
||||
"symfony/stopwatch": "6.4.*",
|
||||
"symfony/web-profiler-bundle": "6.4.*"
|
||||
"symfony/phpunit-bridge": "^7.4",
|
||||
"symfony/stopwatch": "7.4.*",
|
||||
"symfony/web-profiler-bundle": "7.4.*"
|
||||
},
|
||||
"version": "0.2.5"
|
||||
}
|
||||
Generated
+4112
-2315
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,276 @@
|
||||
# Default wording of the admin-editable mails (App\Config\EmailTextKey), used until an
|
||||
# admin overrides a mail under /admin/system/email-text. Removing that override is what
|
||||
# "Zurücksetzen" does, so a wording fix made here reaches every mail nobody has edited.
|
||||
#
|
||||
# Parsed on demand by App\Config\EmailTextCatalog. This file is NOT loaded by the
|
||||
# container, which is why it does not live in config/packages/.
|
||||
#
|
||||
# The body is Markdown, never markup: **fett**, *kursiv*, "## Ueberschrift", "- Liste"
|
||||
# and "1. Nummerierung" (write \* for a literal asterisk), a blank line starts a new
|
||||
# paragraph, a single newline stays a line break, and bare URLs and mail addresses become
|
||||
# links by themselves. Rendered by App\Email\MailBodyRenderer. The headline is plain text
|
||||
# and takes no Markdown. {platzhalter} tokens are substituted after rendering, escaped, so
|
||||
# what a teamer wrote can never become markup. A placeholder without a value prints "-".
|
||||
#
|
||||
# Every mail closes with the same "Zum Portal" button, rendered by email/layout.html.twig.
|
||||
|
||||
texts:
|
||||
application_accepted:
|
||||
label: 'Bewerbung angenommen'
|
||||
placeholders: ['destination', 'specialAgreements', 'contractUploadDeadlineDays']
|
||||
subject: 'Dein Einsatz wurde angenommen'
|
||||
headline: 'Herzlichen Glückwunsch!'
|
||||
body: |-
|
||||
**Dein Einsatz {destination} wurde angenommen!**
|
||||
|
||||
Zusätzliche Absprachen: {specialAgreements}
|
||||
|
||||
Schau einmal in das E&P-Team Portal, um die Einsatzdetails einzusehen und deinen Honorarvertrag zu unterschreiben.
|
||||
|
||||
Mit dem Erhalt dieser E-Mail hast du {contractUploadDeadlineDays} Tage Zeit deinen Einsatz zu bestätigen, indem du den unterschriebenen Vertrag hochlädst. Ist diese Frist vergangen, wird der Einsatz für deine Teamkolleg:innen freigeschaltet.
|
||||
|
||||
Schön, dass du dabei bist und ganz viel Spaß in den Bergen! 😊
|
||||
|
||||
Bitte wende dich bei Rückfragen an [email protected].
|
||||
|
||||
application_accepted_no_contract:
|
||||
label: 'Bewerbung angenommen (ohne Vertrag)'
|
||||
placeholders: ['destination', 'specialAgreements']
|
||||
subject: 'Dein Einsatz wurde angenommen'
|
||||
headline: 'Herzlichen Glückwunsch!'
|
||||
body: |-
|
||||
**Dein Einsatz {destination} wurde angenommen!**
|
||||
|
||||
Zusätzliche Absprachen: {specialAgreements}
|
||||
|
||||
Damit ist für dich alles erledigt - für diesen Einsatz brauchen wir weder einen Honorarvertrag noch eine Honorarnote von dir.
|
||||
|
||||
Schau einmal in das E&P-Team Portal, um die Einsatzdetails einzusehen.
|
||||
|
||||
Schön, dass du dabei bist und ganz viel Spaß in den Bergen! 😊
|
||||
|
||||
Bitte wende dich bei Rückfragen an [email protected].
|
||||
|
||||
application_form_received:
|
||||
label: 'Eingegangenes Formular - Eingangsbestätigung'
|
||||
# Only bookingUrl, which is not caller-supplied like the others: it comes from
|
||||
# APPLICATION_FORM_BOOKING_URL and is substituted into the Markdown before it is parsed,
|
||||
# which is what lets it be a link target (see App\Email\EmailTextRenderer). The mailer
|
||||
# also passes firstName, lastName and formLabel, so an admin who adds one of those tokens
|
||||
# by hand still gets it substituted - they are just not advertised, because the wording
|
||||
# greets from Köln rather than by name and EmailTextCatalogTest insists every declared
|
||||
# placeholder appears in the default text.
|
||||
placeholders: ['bookingUrl', 'season']
|
||||
subject: 'Einladung zum Team-Infocall'
|
||||
headline: 'Hallo aus Köln,'
|
||||
body: |-
|
||||
vielen Dank für deine Bewerbung für die kommende Wintersaison {season}!
|
||||
|
||||
Die Teamplanung der Wintersaison läuft wieder an und um herauszufinden, wie du zu E&P Reisen passt, laden wir dich hiermit zum Team-Infocall ein.
|
||||
|
||||
Beim Team-Infocall erhältst du allgemeine Infos über E&P Reisen, welche Reisekonzepte und Destinationen wir anbieten und welche Jobs es dort gibt. Natürlich hast du auch die Gelegenheit offene Fragen zu stellen.
|
||||
|
||||
Der Termin wird ca. eine Stunde in Anspruch nehmen und mit mehreren Teilnehmenden als Videocall über Teams stattfinden. Nach der Buchung des Termins erhältst du alle Infos, um an dem Call teilzunehmen, das geht auch als Gast über einen Webbrowser.
|
||||
|
||||
Bitte buche dir einen passenden Termin unter [diesem Buchungslink]({bookingUrl}). Bis zu Beginn der Saison werden noch weitere Termine für eine Teilnahme online gestellt.
|
||||
|
||||
Folge uns doch gerne schon auf Instagram unter team_ep_reisen für kurzfristige Einsätze, relevante News und coolen Ski- und Boardcontent.
|
||||
|
||||
Vielleicht hast du auch Lust im Sommer schon für uns zu arbeiten? Schreibe uns gerne in einer separaten Mail 🙂
|
||||
|
||||
Wir freuen uns darauf, dich kennenzulernen und hoffen, dich bald in unserem Team begrüßen zu können!
|
||||
|
||||
application_form_follow_up:
|
||||
label: 'Eingegangenes Formular - Nachfassen'
|
||||
# No placeholders, same as the receipt: the wording greets from Köln, not by name, and
|
||||
# EmailTextCatalogTest insists every declared placeholder appears in the default text.
|
||||
#
|
||||
# This wording goes stale by design - it names concrete call dates and a Bookings outage.
|
||||
# It is the fallback for a fresh database only: the moment somebody edits the mail under
|
||||
# /admin/system/email-text, that override wins and this block stops being sent. Maintain
|
||||
# it there, not here.
|
||||
placeholders: []
|
||||
subject: 'Termine für die Wochenteam-Calls'
|
||||
headline: 'Hallo aus Köln,'
|
||||
body: |-
|
||||
schön, dass du bei unserem Digitalen Infotreffen dabei warst! Ich hoffe wir konnten bereits einige deiner Fragen beantworten. Aufgrund der Fülle an Informationen habe ich ein kleines [Handout](https://PLATZHALTER-HANDOUT) zusammengestellt, damit du die wichtigsten Infos nochmal auf einen Blick hast.
|
||||
|
||||
Nun geht es für dich in die 2. Runde! Wir wollen dich besser kennenlernen.
|
||||
|
||||
## Wochenteam
|
||||
|
||||
Leider gibt es aktuell technische Probleme mit Bookings, sodass ihr momentan keine Termine für die Wochenteam-Calls einsehen oder buchen könnt. Deshalb organisieren wir die Anmeldung vorübergehend manuell. Hier findest du alle Termine für die Wochenteam-Calls. Wenn du an einem der Termine teilnehmen möchtest, antworte bitte einfach auf diese E-Mail und teile uns deinen Wunschtermin mit. Wir melden dich dann manuell an und senden dir rechtzeitig die Zugangsdaten für den entsprechenden Call zu. **Wichtig:** Sobald das Wochenteam-Seminar vollständig besetzt ist, werden keine weiteren Wochenteam-Calls mehr angeboten.
|
||||
|
||||
Folgende Termine sind möglich:
|
||||
|
||||
- Mi 30.9 14:30-15:15Uhr
|
||||
- Fr 9.10 9:30-10:15Uhr
|
||||
- Fr 16.10 14:30-15:15Uhr
|
||||
- Mi 21.10 14:30-15:15Uhr
|
||||
- Fr 30.10 11-11:45Uhr
|
||||
|
||||
## Saisonpersonal
|
||||
|
||||
Interessierst du dich dafür als Saisonmitarbeiter\*in (die ganze Saison) in den Bergen mitzuarbeiten? Wir haben unser Saisonpersonal für diesen Winter bereits vollständig besetzt. Bei Interesse kannst du uns gerne eine E-Mail mit deinem Interesse und dem gewünschten Zeitraum schicken – falls es durch kurzfristige Änderungen, wie z. B. Verletzungen, zu einer Ausnahme kommt, würden wir darauf zurückkommen. Um dir sicherer noch in diesem Winter einen Platz in unserem Team und in den Bergen zu ermöglichen, empfehlen wir dir, Teil unseres Wochenteams zu werden.
|
||||
|
||||
Wenn du in der Zwischenzeit eine Frage hast, dann kannst du dich gerne per E-Mail oder telefonisch melden! Wir freuen uns darauf, dich besser kennenzulernen. 😊
|
||||
|
||||
Schöne Grüße aus Köln
|
||||
|
||||
Eure Personalabteilung
|
||||
|
||||
application_rejected:
|
||||
label: 'Bewerbung abgelehnt'
|
||||
placeholders: ['destination']
|
||||
subject: 'Deine Bewerbung wurde abgelehnt'
|
||||
headline: 'Hallo aus Köln,'
|
||||
body: |-
|
||||
leider konnte deine Bewerbung für den Einsatz '{destination}' nicht angenommen werden. Dies kann unterschiedliche Gründe haben, wie bspw. Unpassende Buszustiege, zu späte Bewerbung, unpassendes Jobprofil, zu viele andere Bewerbungen o.Ä.
|
||||
|
||||
Bewirb dich doch gerne auf weitere Einsätze, vielleicht hast du diesmal Glück! 😊
|
||||
|
||||
assignment_called_off:
|
||||
label: 'Reise abgesagt'
|
||||
placeholders: ['destination', 'jobProfile']
|
||||
subject: 'Die Reise zu deinem Einsatz wurde abgesagt'
|
||||
headline: 'Hallo aus Köln,'
|
||||
body: |-
|
||||
leider mussten wir die Reise zu deinem Einsatz als {jobProfile} ('{destination}') absagen, sodass dein Einsatz nicht stattfinden kann.
|
||||
|
||||
Bitte entschuldige die Umstände!
|
||||
|
||||
Bei Fragen melde dich gerne unter [email protected]
|
||||
|
||||
disposition_called_off:
|
||||
label: 'Einsatz abgesagt'
|
||||
placeholders: ['destination', 'jobProfile', 'reason']
|
||||
subject: 'Dein Einsatz wurde abgesagt'
|
||||
headline: 'Hallo aus Köln,'
|
||||
body: |-
|
||||
leider mussten wir deinen Einsatz als {jobProfile} ('{destination}') absagen, sodass dein Einsatz nicht stattfinden kann:
|
||||
|
||||
{reason}
|
||||
|
||||
Bei Fragen melde dich gerne unter [email protected]
|
||||
|
||||
document_rejected:
|
||||
label: 'Dokument abgelehnt'
|
||||
# documentType arrives capitalised, because subject and body both open with it.
|
||||
placeholders: ['destination', 'documentType', 'comment']
|
||||
subject: '{documentType} wurde abgelehnt'
|
||||
headline: 'Hallo aus Köln,'
|
||||
body: |-
|
||||
{documentType} wurde für den Einsatz {destination} leider mit folgender Begründung abgelehnt:
|
||||
|
||||
{comment}
|
||||
|
||||
Überarbeite das Dokument bitte und lade es erneut hoch.
|
||||
|
||||
reminder_contract_upload:
|
||||
label: 'Erinnerung: Honorarvertrag hochladen'
|
||||
placeholders: ['destination']
|
||||
subject: 'Reminder: Fehlende Dokumente'
|
||||
headline: 'Hallo aus Köln,'
|
||||
body: |-
|
||||
bitte denke daran, deinen ausgefüllten Honorarvertrag für folgenden Einsatz hochzuladen. Vorher können wir nicht fest mit dir planen und behalten uns vor den Einsatz anderweitig zu vergeben.
|
||||
|
||||
**{destination}**
|
||||
|
||||
# Weekly, for as long as an entry of the season checklist is not ticked off. It is not tied
|
||||
# to a deadline, so it must not name one.
|
||||
reminder_season_tasks:
|
||||
label: 'Erinnerung: Saisonunterlagen'
|
||||
placeholders: ['destination', 'tasks']
|
||||
subject: 'Reminder: Fehlende Saisonunterlagen'
|
||||
headline: 'Hallo aus Köln,'
|
||||
body: |-
|
||||
für deinen Saisoneinsatz **{destination}** fehlen uns noch folgende Unterlagen:
|
||||
|
||||
{tasks}
|
||||
|
||||
Im E&P-Team Portal findest du unter "Saisonunterlagen" die Vorlagen zum Herunterladen und kannst deine unterschriebenen Unterlagen direkt hochladen.
|
||||
|
||||
reminder_invoice_upload:
|
||||
label: 'Erinnerung: Honorarnote hochladen'
|
||||
placeholders: ['destination', 'invoiceUploadDeadlineDays']
|
||||
subject: 'Reminder: Fehlende Dokumente'
|
||||
headline: 'Hallo aus Köln,'
|
||||
body: |-
|
||||
vielen Dank für deinen Einsatz {destination}. Bitte lade deine Honorarnote innerhalb der nächsten {invoiceUploadDeadlineDays} Tage hoch, damit wir dein Honorar bearbeiten können. Solltest du deine Honorarnote zu spät hochladen, können wir sie eventuell nicht mehr annehmen.
|
||||
|
||||
# Goes out on the last day of the upload period, so it must not promise any time left.
|
||||
reminder_invoice_upload_final:
|
||||
label: 'Letzte Erinnerung: Honorarnote hochladen'
|
||||
placeholders: ['destination', 'invoiceUploadDeadlineDays']
|
||||
subject: 'Letzte Erinnerung: Deine Honorarnote fehlt'
|
||||
headline: 'Hallo aus Köln,'
|
||||
body: |-
|
||||
für deinen Einsatz {destination} liegt uns noch immer keine Honorarnote vor.
|
||||
|
||||
Heute endet die Frist von {invoiceUploadDeadlineDays} Tagen nach deinem Einsatz. **Bitte lade deine Honorarnote noch heute hoch** - danach können wir sie eventuell nicht mehr annehmen.
|
||||
|
||||
Bei Fragen melde dich gerne unter [email protected]
|
||||
|
||||
reminder_disposition:
|
||||
label: 'Erinnerung: Einsatz steht bevor'
|
||||
placeholders: ['destination', 'jobProfile', 'product', 'dateFrom', 'dateTo', 'diffInDays', 'invoiceUploadDeadlineDays']
|
||||
subject: 'Reminder: Dein Einsatz {destination}'
|
||||
headline: 'Hallo aus Köln,'
|
||||
body: |-
|
||||
dein Einsatz als {jobProfile}, {product} vom {dateFrom} bis {dateTo} beginnt **in {diffInDays} Tagen**.
|
||||
|
||||
Wir hoffen, dass du ich schon freust und wünschen dir viel Spaß und Erfolg! Finale Infos erhältst du, falls noch nicht geschehen, ein paar Tage vor deinem Einsatz von deinen zuständigen Haus- oder Reisemanager:innen.
|
||||
|
||||
Bitte denk daran, deine **ausgefüllte Honorarnote bis zu {invoiceUploadDeadlineDays} Tage nach deinem Einsatz** in E&P Team hochzuladen.
|
||||
|
||||
Liebe Grüße,
|
||||
dein Team Personalabteilung
|
||||
|
||||
# Descriptions shown next to the form fields. They describe the placeholder itself rather
|
||||
# than any one mail, so they are shared: a name means the same thing wherever it appears.
|
||||
# Daily, for as long as a work month has no signed Arbeitszeitnachweis on it. The month
|
||||
# is named rather than a deadline: there is none, the office simply wants it back.
|
||||
reminder_work_month_upload:
|
||||
label: 'Erinnerung: Arbeitszeitnachweis hochladen'
|
||||
placeholders: ['workMonth']
|
||||
subject: 'Reminder: Fehlender Arbeitszeitnachweis'
|
||||
headline: 'Hallo aus Köln,'
|
||||
body: |-
|
||||
bitte denke daran, deinen unterschriebenen Arbeitszeitnachweis hochzuladen. Ohne ihn können wir den Monat nicht abrechnen.
|
||||
|
||||
**{workMonth}**
|
||||
|
||||
Du findest ihn im Portal unter "Mein Profil - Arbeitszeiten": dort herunterladen, unterschreiben und wieder hochladen.
|
||||
|
||||
work_month_rejected:
|
||||
label: 'Arbeitszeitnachweis abgelehnt'
|
||||
placeholders: ['workMonth', 'comment']
|
||||
subject: 'Dein Arbeitszeitnachweis wurde abgelehnt'
|
||||
headline: 'Hallo aus Köln,'
|
||||
body: |-
|
||||
leider können wir deinen Arbeitszeitnachweis für **{workMonth}** so nicht annehmen.
|
||||
|
||||
Begründung: {comment}
|
||||
|
||||
Bitte lade ihn im Portal unter "Mein Profil - Arbeitszeiten" noch einmal hoch.
|
||||
|
||||
placeholders:
|
||||
bookingUrl: 'Buchungslink für den Team-Infocall, kommt aus der Konfiguration (APPLICATION_FORM_BOOKING_URL)'
|
||||
season: 'Kommende Saison in Kurzform, z. B. "26/27" - wird automatisch eingesetzt'
|
||||
firstName: 'Vorname der Person, die das Formular abgeschickt hat'
|
||||
lastName: 'Nachname der Person, die das Formular abgeschickt hat'
|
||||
formLabel: 'Name des Formulars, z. B. "Bewerbungsformular"'
|
||||
destination: 'Einsatz, z. B. "12.01.25 - 19.01.25 Skireise"'
|
||||
specialAgreements: 'Zusätzliche Absprachen zum Einsatz'
|
||||
contractUploadDeadlineDays: 'Frist zum Hochladen des Honorarvertrags in Tagen'
|
||||
reason: 'Begründung der Absage'
|
||||
documentType: 'Art des Dokuments, z. B. "Dein Honorarvertrag"'
|
||||
comment: 'Begründung der Ablehnung'
|
||||
invoiceUploadDeadlineDays: 'Frist zum Hochladen der Honorarnote in Tagen'
|
||||
jobProfile: 'Jobprofil des Einsatzes'
|
||||
product: 'Produkt der Reise'
|
||||
dateFrom: 'Beginn des Einsatzes'
|
||||
dateTo: 'Ende des Einsatzes'
|
||||
diffInDays: 'Tage bis zum Beginn des Einsatzes'
|
||||
tasks: 'Noch offene Punkte der Saison-Checkliste, kommagetrennt'
|
||||
workMonth: 'Monat der Arbeitszeiten, z. B. "März 2026"'
|
||||
@@ -7,8 +7,14 @@ doctrine:
|
||||
# either here or in the DATABASE_URL env var (see .env file)
|
||||
#server_version: '15'
|
||||
orm:
|
||||
auto_generate_proxy_classes: true
|
||||
enable_lazy_ghost_objects: true
|
||||
enable_native_lazy_objects: true
|
||||
# Keep implicit controller-argument entity resolution on (many actions type-hint
|
||||
# an entity against a matching {id}/{uuid} route param without #[MapEntity]).
|
||||
# Setting this explicitly silences the "default will flip to false" deprecation;
|
||||
# the separate "automapping is deprecated" notice stays until those actions are
|
||||
# migrated to #[MapEntity], which is its own task.
|
||||
controller_resolver:
|
||||
auto_mapping: true
|
||||
report_fields_where_declared: true
|
||||
validate_xml_mapping: true
|
||||
naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
|
||||
@@ -31,12 +37,17 @@ when@test:
|
||||
dbal:
|
||||
# "TEST_TOKEN" is typically set by ParaTest
|
||||
dbname_suffix: '_test%env(default::TEST_TOKEN)%'
|
||||
# schema:create otherwise emits MySQL 8's platform default (utf8mb4_0900_ai_ci),
|
||||
# while the migrations that built dev and production declare utf8mb4_unicode_ci.
|
||||
# Different collation means different string comparison and ORDER BY semantics,
|
||||
# so tests would sort and match differently than production.
|
||||
default_table_options:
|
||||
charset: utf8mb4
|
||||
collation: utf8mb4_unicode_ci
|
||||
|
||||
when@prod:
|
||||
doctrine:
|
||||
orm:
|
||||
auto_generate_proxy_classes: false
|
||||
proxy_dir: '%kernel.build_dir%/doctrine/orm/Proxies'
|
||||
query_cache_driver:
|
||||
type: pool
|
||||
pool: doctrine.system_cache_pool
|
||||
|
||||
@@ -10,3 +10,9 @@ flagception:
|
||||
datev_email:
|
||||
default: false
|
||||
env: FEATURE_DATEV_EMAIL
|
||||
myep_login:
|
||||
default: false
|
||||
env: FEATURE_MYEP_LOGIN
|
||||
upload_replacement:
|
||||
default: false
|
||||
env: FEATURE_UPLOAD_REPLACEMENT
|
||||
@@ -1,17 +1,6 @@
|
||||
# Read the documentation at https://github.com/thephpleague/flysystem-bundle/blob/master/docs/1-getting-started.md
|
||||
flysystem:
|
||||
storages:
|
||||
xml_export.storage:
|
||||
adapter: 'local'
|
||||
options:
|
||||
directory: '%env(resolve:XML_EXPORT_PATH)%'
|
||||
xml_dump.storage:
|
||||
adapter: 'local'
|
||||
options:
|
||||
local:
|
||||
directory: '%kernel.project_dir%/var/bpn'
|
||||
# adapter: 'sftp'
|
||||
# options:
|
||||
# host: '%env(SFTP_XML_EXPORT_HOST)%'
|
||||
# port: '%env(int:SFTP_XML_EXPORT_PORT)%'
|
||||
# username: '%env(SFTP_XML_EXPORT_USER)%'
|
||||
# password: '%env(SFTP_XML_EXPORT_PASSWORD)%'
|
||||
@@ -5,6 +5,11 @@ framework:
|
||||
http_method_override: false
|
||||
handle_all_throwables: true
|
||||
|
||||
# The OAuth2 redirect_uri handed to MyE&P is generated from the incoming request, so an
|
||||
# unvalidated Host header would let a crafted request point the authorization code
|
||||
# somewhere else. Regex patterns, matched against the host without the scheme or port.
|
||||
trusted_hosts: ['%env(APP_TRUSTED_HOSTS)%']
|
||||
|
||||
# Enables session support. Note that the session will ONLY be started if you read or write from it.
|
||||
# Remove or comment this section to explicitly disable session support.
|
||||
session:
|
||||
@@ -23,6 +28,22 @@ framework:
|
||||
router:
|
||||
default_uri: '%env(APP_BASE_URI)%'
|
||||
|
||||
# Every call to the sibling MyE&P application, both the token request and the API
|
||||
# itself. Paths are given relative to the base uri, so without a leading slash.
|
||||
http_client:
|
||||
scoped_clients:
|
||||
myep.client:
|
||||
base_uri: '%env(MYEP_API_BASE_URL)%'
|
||||
timeout: 10
|
||||
max_duration: 30
|
||||
headers:
|
||||
Accept: application/json
|
||||
retry_failed:
|
||||
max_retries: 2
|
||||
# 401 is deliberately absent: it needs the cached token dropped
|
||||
# before the retry, which App\MyEp\Api\ApiClient does itself.
|
||||
http_codes: [423, 425, 429, 500, 502, 503, 504]
|
||||
|
||||
when@test:
|
||||
framework:
|
||||
test: true
|
||||
|
||||
@@ -17,7 +17,13 @@ liip_imagine:
|
||||
format: jpeg
|
||||
jpeg_quality: 75
|
||||
filters:
|
||||
thumbnail: { size: [ 128, 128 ], mode: outbound }
|
||||
# Phone photos carry their orientation in EXIF rather than in the pixels, so
|
||||
# without this the table renders them sideways while every other filter set,
|
||||
# which has always had it, shows them upright.
|
||||
auto_rotate: ~
|
||||
# outbound crops to the box but never scales up, so a photo narrower than
|
||||
# 128px came out oblong and the rounded-full avatar with it.
|
||||
thumbnail: { size: [ 128, 128 ], mode: outbound, allow_upscale: true }
|
||||
profile:
|
||||
format: jpeg
|
||||
jpeg_quality: 75
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
framework:
|
||||
mailer:
|
||||
dsn: '%env(MAILER_DSN)%'
|
||||
# "main" has to stay first: it is the transport every mail without an explicit
|
||||
# X-Transport header goes out over, which is all of them apart from the teamer
|
||||
# mailing. Reordering this quietly moves the whole application to Mailjet.
|
||||
transports:
|
||||
main: '%env(MAILER_DSN)%'
|
||||
mailing: '%env(MAILING_MAILER_DSN)%'
|
||||
@@ -12,6 +12,29 @@ framework:
|
||||
retry_strategy:
|
||||
max_retries: 3
|
||||
multiplier: 2
|
||||
# Bulk teamer mailings, kept off "async" so that a mailing of several hundred
|
||||
# recipients cannot delay a password reset. Its own cron worker consumes this
|
||||
# queue and nothing else; the two never see each other's messages, because the
|
||||
# doctrine transport filters on the queue_name column. See docs/operations.md
|
||||
# for both cron entries.
|
||||
#
|
||||
# The retry delays are spelled out because the default is one second: with a
|
||||
# multiplier alone, all three retries of a mail are spent within seconds, so a
|
||||
# short hiccup at the mail provider would drop a whole mailing into "failed" at
|
||||
# once - the very thing sending one message per recipient is meant to avoid.
|
||||
# A minute, three and nine outlive any realistic blip and span several runs of
|
||||
# a worker that only lives for a few minutes at a time.
|
||||
mailing:
|
||||
dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
|
||||
options:
|
||||
queue_name: mailing
|
||||
use_notify: true
|
||||
check_delayed_interval: 60000
|
||||
retry_strategy:
|
||||
max_retries: 3
|
||||
delay: 60000
|
||||
multiplier: 3
|
||||
max_delay: 900000
|
||||
failed: 'doctrine://default?queue_name=failed'
|
||||
sync: 'sync://'
|
||||
|
||||
@@ -20,6 +43,9 @@ framework:
|
||||
Symfony\Component\Notifier\Message\ChatMessage: async
|
||||
Symfony\Component\Notifier\Message\SmsMessage: async
|
||||
|
||||
# only the fan-out, the mails it produces carry an X-Bus-Transport header
|
||||
App\Message\SendTeamerMailing: mailing
|
||||
|
||||
# Route your messages to the transports
|
||||
# 'App\Message\YourMessage': async
|
||||
|
||||
@@ -30,3 +56,13 @@ when@dev:
|
||||
Symfony\Component\Mailer\Messenger\SendEmailMessage: sync
|
||||
Symfony\Component\Notifier\Message\ChatMessage: sync
|
||||
Symfony\Component\Notifier\Message\SmsMessage: sync
|
||||
App\Message\SendTeamerMailing: sync
|
||||
|
||||
when@test:
|
||||
framework:
|
||||
messenger:
|
||||
routing:
|
||||
Symfony\Component\Mailer\Messenger\SendEmailMessage: sync
|
||||
Symfony\Component\Notifier\Message\ChatMessage: sync
|
||||
Symfony\Component\Notifier\Message\SmsMessage: sync
|
||||
App\Message\SendTeamerMailing: sync
|
||||
@@ -2,7 +2,10 @@ monolog:
|
||||
channels:
|
||||
- deprecation
|
||||
- bpn
|
||||
- saas
|
||||
- myep
|
||||
- audit
|
||||
- api
|
||||
|
||||
handlers:
|
||||
main:
|
||||
@@ -11,7 +14,7 @@ monolog:
|
||||
handler: exceptions
|
||||
excluded_http_codes: [404, 405]
|
||||
buffer_size: 50
|
||||
channels: ["!bpn", "!myep"]
|
||||
channels: ["!bpn", "!saas", "!myep", "!audit", "!api"]
|
||||
exceptions:
|
||||
type: rotating_file
|
||||
path: "%kernel.logs_dir%/framework.%kernel.environment%.log"
|
||||
@@ -23,20 +26,52 @@ monolog:
|
||||
path: '%kernel.logs_dir%/bpn.%kernel.environment%.log'
|
||||
max_files: 5
|
||||
level: info
|
||||
saas:
|
||||
channels: ["saas"]
|
||||
type: rotating_file
|
||||
path: '%kernel.logs_dir%/saas.%kernel.environment%.log'
|
||||
max_files: 5
|
||||
level: info
|
||||
myep:
|
||||
channels: [ "myep" ]
|
||||
channels: ["myep"]
|
||||
type: rotating_file
|
||||
path: '%kernel.logs_dir%/myep.%kernel.environment%.log'
|
||||
max_files: 5
|
||||
level: info
|
||||
audit:
|
||||
channels: [ "audit" ]
|
||||
type: rotating_file
|
||||
path: '%kernel.logs_dir%/audit.%kernel.environment%.log'
|
||||
max_files: 5
|
||||
level: info
|
||||
# Form submissions posted by the sibling websites. Own file because it is machine
|
||||
# traffic nobody watches between incidents, and it must stay out of the audit table.
|
||||
api:
|
||||
channels: [ "api" ]
|
||||
type: rotating_file
|
||||
path: '%kernel.logs_dir%/api.%kernel.environment%.log'
|
||||
max_files: 30
|
||||
level: info
|
||||
database:
|
||||
channels: ["myep"]
|
||||
channels: ["audit"]
|
||||
type: service
|
||||
id: App\Logging\DatabaseHandler
|
||||
console:
|
||||
type: console
|
||||
process_psr_3_messages: false
|
||||
channels: ["!event", "!bpn", "!doctrine", "!console"]
|
||||
channels: ["!event", "!bpn", "!saas", "!myep", "!api", "!doctrine", "!console"]
|
||||
|
||||
when@dev:
|
||||
monolog:
|
||||
handlers:
|
||||
# Single catch-all file so everything is visible in one place during
|
||||
# development. The handlers above are error-level or channel-bound,
|
||||
# which makes ordinary app output invisible locally.
|
||||
dev:
|
||||
type: stream
|
||||
path: '%kernel.logs_dir%/%kernel.environment%.log'
|
||||
level: debug
|
||||
channels: ["!event", "!doctrine"]
|
||||
|
||||
when@test:
|
||||
monolog:
|
||||
@@ -61,7 +96,7 @@ when@prod:
|
||||
handler: exceptions
|
||||
excluded_http_codes: [404, 405]
|
||||
buffer_size: 50
|
||||
channels: ["!bpn", "!myep"]
|
||||
channels: ["!bpn", "!saas", "!myep", "!audit", "!api"]
|
||||
exceptions:
|
||||
type: rotating_file
|
||||
path: "%kernel.logs_dir%/framework.%kernel.environment%.log"
|
||||
@@ -73,17 +108,35 @@ when@prod:
|
||||
path: '%kernel.logs_dir%/bpn.%kernel.environment%.log'
|
||||
max_files: 5
|
||||
level: info
|
||||
saas:
|
||||
channels: ["saas"]
|
||||
type: rotating_file
|
||||
path: '%kernel.logs_dir%/saas.%kernel.environment%.log'
|
||||
max_files: 5
|
||||
level: info
|
||||
myep:
|
||||
channels: [ "myep" ]
|
||||
channels: ["myep"]
|
||||
type: rotating_file
|
||||
path: '%kernel.logs_dir%/myep.%kernel.environment%.log'
|
||||
max_files: 5
|
||||
level: info
|
||||
audit:
|
||||
channels: [ "audit" ]
|
||||
type: rotating_file
|
||||
path: '%kernel.logs_dir%/audit.%kernel.environment%.log'
|
||||
max_files: 5
|
||||
level: info
|
||||
api:
|
||||
channels: [ "api" ]
|
||||
type: rotating_file
|
||||
path: '%kernel.logs_dir%/api.%kernel.environment%.log'
|
||||
max_files: 30
|
||||
level: info
|
||||
database:
|
||||
channels: ["myep"]
|
||||
channels: ["audit"]
|
||||
type: service
|
||||
id: App\Logging\DatabaseHandler
|
||||
console:
|
||||
type: console
|
||||
process_psr_3_messages: false
|
||||
channels: ["!event", "!doctrine", "!bpn", "!myep"]
|
||||
channels: ["!event", "!doctrine", "!bpn", "!saas", "!myep", "!audit", "!api"]
|
||||
@@ -8,12 +8,6 @@ nelmio_security:
|
||||
content_type:
|
||||
nosniff: true
|
||||
|
||||
# forces Microsoft's XSS-Protection with
|
||||
# its block mode
|
||||
xss_protection:
|
||||
enabled: true
|
||||
mode_block: true
|
||||
|
||||
# Send a full URL in the `Referer` header when performing a same-origin request,
|
||||
# only send the origin of the document to secure destination (HTTPS->HTTPS),
|
||||
# and send no header to a less secure destination (HTTPS->HTTP).
|
||||
|
||||
@@ -36,6 +36,38 @@ oneup_uploader:
|
||||
namer: app.upload_namer
|
||||
storage:
|
||||
directory: '%kernel.project_dir%/uploads/driver_license'
|
||||
# Belege backing a Honorarnote. The directory has to be named exactly after
|
||||
# Upload::TYPE_RECEIPT: UploadHandler::getUploadFilepath() interpolates the type into
|
||||
# uploads/<type>/<shard>/<filename> rather than looking the mapping up.
|
||||
receipt:
|
||||
frontend: dropzone
|
||||
use_orphanage: true
|
||||
namer: app.upload_namer
|
||||
storage:
|
||||
directory: '%kernel.project_dir%/uploads/receipt'
|
||||
# Saisonunterlagen. Like receipt above, both the mapping key and the directory have to
|
||||
# be exactly the Upload::TYPE_* value - out for what the office issues to one teamer,
|
||||
# in for what that teamer sends back signed.
|
||||
season_document_out:
|
||||
frontend: dropzone
|
||||
use_orphanage: true
|
||||
namer: app.upload_namer
|
||||
storage:
|
||||
directory: '%kernel.project_dir%/uploads/season_document_out'
|
||||
season_document_in:
|
||||
frontend: dropzone
|
||||
use_orphanage: true
|
||||
namer: app.upload_namer
|
||||
storage:
|
||||
directory: '%kernel.project_dir%/uploads/season_document_in'
|
||||
# Signed Arbeitszeitnachweise coming back from a teamer. Same rule again: the key and
|
||||
# the directory are exactly Upload::TYPE_WORK_MONTH.
|
||||
work_month:
|
||||
frontend: dropzone
|
||||
use_orphanage: true
|
||||
namer: app.upload_namer
|
||||
storage:
|
||||
directory: '%kernel.project_dir%/uploads/work_month'
|
||||
chunks:
|
||||
maxage: 86400
|
||||
storage:
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
framework:
|
||||
property_info:
|
||||
# Keep the current (7.x) default; enabling the constructor extractor is a
|
||||
# behaviour change to evaluate on its own, not part of the 6.4 -> 7.4 bump.
|
||||
with_constructor_extractor: false
|
||||
@@ -7,19 +7,38 @@ security:
|
||||
entity:
|
||||
class: App\Entity\User
|
||||
property: email
|
||||
# The sibling websites post as this one; it exists in configuration only. An API caller
|
||||
# must never be a row in the user table, where it would be a login to the UI as well.
|
||||
api_user_provider:
|
||||
memory:
|
||||
users:
|
||||
api: { roles: [ ROLE_API ] }
|
||||
n8n: { roles: [ ROLE_API_ASSIGNMENTS ] }
|
||||
|
||||
role_hierarchy:
|
||||
ROLE_ADMIN: [ ROLE_ADMINISTRATIVE ]
|
||||
ROLE_TEAM_ADMIN: [ ROLE_ADMINISTRATIVE ]
|
||||
ROLE_MANAGER: [ ROLE_ADMINISTRATIVE ]
|
||||
|
||||
firewalls:
|
||||
dev:
|
||||
pattern: ^/(_(profiler|wdt)|css|images|js)/
|
||||
security: false
|
||||
# Stateless and key-authenticated: a machine posts here inside a visitor's form
|
||||
# submission on another site, so there is no session, no login form and no redirect.
|
||||
# Must stay above "main", whose pattern is ^/.
|
||||
api:
|
||||
pattern: ^/api
|
||||
stateless: true
|
||||
provider: api_user_provider
|
||||
access_token:
|
||||
token_handler: App\Security\AccessToken\ApiKeyTokenHandler
|
||||
token_extractors:
|
||||
- App\Security\AccessToken\ApiKeyExtractor
|
||||
main:
|
||||
lazy: true
|
||||
provider: bpn_user_provider
|
||||
custom_authenticators:
|
||||
- App\Security\MyEpAuthenticator
|
||||
- App\Security\BpnAuthenticator
|
||||
user_checker: App\Security\UserChecker
|
||||
switch_user:
|
||||
@@ -28,6 +47,8 @@ security:
|
||||
path: app_security_logout
|
||||
target: app_security_login
|
||||
access_control:
|
||||
# The firewall authenticates, the controller requires ROLE_API
|
||||
- { path: '^/api', roles: PUBLIC_ACCESS, requires_channel: https }
|
||||
- { path: '^/_uploader', roles: ROLE_USER, requires_channel: https }
|
||||
- { path: '^/', roles: PUBLIC_ACCESS, requires_channel: https }
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
# Enabled for #[MapRequestPayload] on the api controllers - nothing else in this application
|
||||
# serializes. ALLOW_EXTRA_ATTRIBUTES stays at its default (true) on purpose: the TYPO3 form
|
||||
# payload grows keys without a release here, and rejecting an unknown one would turn every
|
||||
# later mapping change into a 400.
|
||||
framework:
|
||||
serializer:
|
||||
name_converter: ~
|
||||
@@ -14,4 +14,5 @@ when@test:
|
||||
intercept_redirects: false
|
||||
|
||||
framework:
|
||||
profiler: { collect: false }
|
||||
profiler:
|
||||
collect_serializer_data: true
|
||||
@@ -3,31 +3,70 @@ zenstruck_schedule:
|
||||
|
||||
mailer:
|
||||
service: mailer
|
||||
default_to: [email protected]
|
||||
default_from: info@ep-reisen.de
|
||||
subject_prefix: "[MyE&P-Team]"
|
||||
default_to: [email protected]
|
||||
default_from: noreply@ep-reisen.de
|
||||
subject_prefix: "[E&P-Team]"
|
||||
|
||||
schedule_extensions:
|
||||
email_on_failure:
|
||||
to: [email protected]
|
||||
enabled: true
|
||||
to: [email protected]
|
||||
|
||||
tasks:
|
||||
- task: app:bpn-import
|
||||
frequency: '0 3 * * *'
|
||||
description: "Imports dates, products and hotels from BusProNet"
|
||||
when@prod:
|
||||
zenstruck_schedule:
|
||||
tasks:
|
||||
# 03:20 rather than 03:00: MyE&P mirrors from BusProNet hourly overnight, on the
|
||||
# hour, so an import at 03:00 can read a half written export.
|
||||
- task: app:myep-import
|
||||
frequency: '20 3 * * *'
|
||||
description: "Imports travel dates, products and hotels from the MyE&P API"
|
||||
|
||||
- task: app:cron
|
||||
frequency: '0 1 * * *'
|
||||
description: "Executes several tasks triggered on a daily basis"
|
||||
- task: app:cron
|
||||
frequency: '0 1 * * *'
|
||||
description: "Executes several tasks triggered on a daily basis"
|
||||
|
||||
- task: app:teamer-status
|
||||
frequency: '30 1 * * *'
|
||||
description: "Updates status of teamers depending on their disposition"
|
||||
- task: app:teamer-status
|
||||
frequency: '30 1 * * *'
|
||||
description: "Updates status of teamers depending on their disposition"
|
||||
|
||||
- task: oneup:uploader:clear-orphans
|
||||
frequency: '30 * * * *'
|
||||
description: "Removes orphaned uploads"
|
||||
- task: app:saas:work-hours
|
||||
frequency: '0 4 10 * *'
|
||||
description: "Imports last month's working hours of season teamers from SaaS"
|
||||
|
||||
- task: app:cleanup:xml-dumps
|
||||
frequency: "0 2 * * *"
|
||||
description: "Removes outdated XML dumps of requests/responses to BPN API for debugging"
|
||||
- task: oneup:uploader:clear-orphans
|
||||
frequency: '30 * * * *'
|
||||
description: "Removes orphaned uploads"
|
||||
|
||||
- task: app:cleanup:xml-dumps
|
||||
frequency: '0 2 * * *'
|
||||
description: "Removes outdated XML dumps of requests/responses to BPN API for debugging"
|
||||
|
||||
when@staging:
|
||||
zenstruck_schedule:
|
||||
mailer:
|
||||
subject_prefix: "[E&P-Team STAGING]"
|
||||
|
||||
tasks:
|
||||
- task: app:myep-import
|
||||
frequency: '50 3 * * *'
|
||||
description: "Imports travel dates, products and hotels from the MyE&P API"
|
||||
|
||||
- task: app:cron
|
||||
frequency: '30 2 * * *'
|
||||
description: "Executes several tasks triggered on a daily basis"
|
||||
|
||||
- task: app:teamer-status
|
||||
frequency: '30 1 * * *'
|
||||
description: "Updates status of teamers depending on their disposition"
|
||||
|
||||
- task: app:saas:work-hours
|
||||
frequency: '0 4 10 * *'
|
||||
description: "Imports last month's working hours of season teamers from SaaS"
|
||||
|
||||
- task: oneup:uploader:clear-orphans
|
||||
frequency: '30 * * * *'
|
||||
description: "Removes orphaned uploads"
|
||||
|
||||
- task: app:cleanup:xml-dumps
|
||||
frequency: '0 2 * * *'
|
||||
description: "Removes outdated XML dumps of requests/responses to BPN API for debugging"
|
||||
@@ -0,0 +1,271 @@
|
||||
# Questionnaire a teamer fills in after a placement with bus escort, in addition to
|
||||
# teamer_feedback.yaml and on the same page (App\Service\Questionnaire\DispositionContext).
|
||||
#
|
||||
# Replaces the Google form "Bus-Feedback 25-26"; the question-by-question spec, with every decision
|
||||
# taken, is kept locally outside the repository. Questions whose answer the
|
||||
# disposition already knows (travel date, destination, Reiseleitung, first pickup) are not asked:
|
||||
# they are snapshotted into the response's context instead.
|
||||
#
|
||||
# Labels, help texts, section titles and option labels may be edited in place. Renaming or
|
||||
# removing a question or option key, or changing a type, needs a new version: copy this file to
|
||||
# bus_feedback.v1.yaml first, then bump `version` here. Submitted answers are keyed by it.
|
||||
#
|
||||
# Parsed and validated by App\Config\QuestionnaireCatalog; QuestionnaireCatalogTest builds it.
|
||||
#
|
||||
# Question fields: key, label, type (text | textarea | choice | multi | date | rating),
|
||||
# required (default false), help, widget (choice only: radio | dropdown, default radio),
|
||||
# options (choice and multi only), show_if ({question, equals | in | lte}, earlier questions only).
|
||||
|
||||
key: bus_feedback
|
||||
version: 1
|
||||
title: "Bus-Feedback"
|
||||
|
||||
sections:
|
||||
- key: bus_trip
|
||||
title: "Busfahrt"
|
||||
questions:
|
||||
- key: direction
|
||||
label: "Hin- oder Rückfahrt"
|
||||
type: choice
|
||||
required: true
|
||||
options:
|
||||
- { key: outbound, label: "Hinfahrt" }
|
||||
- { key: return, label: "Rückfahrt" }
|
||||
|
||||
- key: bus_company
|
||||
label: "Bus-Unternehmen:"
|
||||
type: choice
|
||||
widget: dropdown
|
||||
required: true
|
||||
options:
|
||||
- { key: boonk, label: "Boonk Reisen" }
|
||||
- { key: piccolonia, label: "Piccolonia" }
|
||||
- { key: heess, label: "Heeß" }
|
||||
- { key: finkbeiner, label: "Finkbeiner" }
|
||||
- { key: other, label: "Anderes Unternehmen" }
|
||||
|
||||
- key: bus_company_other
|
||||
label: "Name des Busunternehmens:"
|
||||
type: text
|
||||
required: true
|
||||
show_if: { question: bus_company, equals: other }
|
||||
|
||||
- key: bus_team
|
||||
title: "Busteam"
|
||||
questions:
|
||||
- key: driver_names
|
||||
label: "Busfahrer-Namen:"
|
||||
type: text
|
||||
required: true
|
||||
|
||||
- key: bus_first_pickup
|
||||
title: "1. Abfahrtsort"
|
||||
questions:
|
||||
- key: on_time_at_first_pickup
|
||||
label: "Bus pünktlich am 1. Abfahrtsort?"
|
||||
type: choice
|
||||
required: true
|
||||
options:
|
||||
- { key: "yes", label: "Ja" }
|
||||
- { key: "no", label: "Nein" }
|
||||
|
||||
- key: delay_at_first_pickup
|
||||
label: "Wieviel Verspätung hatte der Bus am 1. Abfahrtsort?"
|
||||
type: text
|
||||
required: true
|
||||
show_if: { question: on_time_at_first_pickup, equals: "no" }
|
||||
|
||||
- key: bus_rating
|
||||
title: "Bewertung Bus & Fahrer"
|
||||
questions:
|
||||
- key: overall_impression
|
||||
label: "Gesamteindruck des Busses"
|
||||
type: choice
|
||||
widget: dropdown
|
||||
required: true
|
||||
options:
|
||||
- { key: very_good, label: "Sehr gut" }
|
||||
- { key: good, label: "Gut" }
|
||||
- { key: ok, label: "OK" }
|
||||
- { key: not_so_good, label: "nicht so gut" }
|
||||
- { key: bad, label: "schlecht" }
|
||||
|
||||
- key: vehicle_age
|
||||
label: "Alter des Fahrzeugs"
|
||||
type: choice
|
||||
widget: dropdown
|
||||
required: true
|
||||
options:
|
||||
- { key: very_new, label: "Sehr neu" }
|
||||
- { key: new, label: "neu" }
|
||||
- { key: ok, label: "OK" }
|
||||
- { key: somewhat_old, label: "etwas älter" }
|
||||
- { key: very_old, label: "sehr alt" }
|
||||
|
||||
- key: seating_comfort
|
||||
label: "Sitzkomfort im Bus"
|
||||
type: choice
|
||||
widget: dropdown
|
||||
required: true
|
||||
options:
|
||||
- { key: very_good, label: "Sehr gut" }
|
||||
- { key: good, label: "Gut" }
|
||||
- { key: ok, label: "OK" }
|
||||
- { key: not_so_good, label: "nicht so gut" }
|
||||
- { key: bad, label: "schlecht" }
|
||||
|
||||
- key: driver_friendliness
|
||||
label: "Freundlichkeit der Fahrer"
|
||||
type: choice
|
||||
widget: dropdown
|
||||
required: true
|
||||
options:
|
||||
- { key: very_friendly, label: "Sehr freundlich" }
|
||||
- { key: friendly, label: "freundlich" }
|
||||
- { key: ok, label: "OK" }
|
||||
- { key: not_so_friendly, label: "nicht so freundlich" }
|
||||
- { key: unfriendly, label: "unfreundlich" }
|
||||
|
||||
- key: breaks
|
||||
label: "Pausenzeiten"
|
||||
type: choice
|
||||
widget: dropdown
|
||||
required: true
|
||||
options:
|
||||
- { key: very_good, label: "Sehr gut" }
|
||||
- { key: good, label: "Gut" }
|
||||
- { key: ok, label: "OK" }
|
||||
- { key: not_so_good, label: "nicht so gut" }
|
||||
- { key: unacceptable, label: "geht gar nicht" }
|
||||
|
||||
- key: temperature
|
||||
label: "Temperatur im Bus"
|
||||
type: choice
|
||||
widget: dropdown
|
||||
required: true
|
||||
options:
|
||||
- { key: very_good, label: "Sehr gut" }
|
||||
- { key: good, label: "Gut" }
|
||||
- { key: ok, label: "OK" }
|
||||
- { key: not_so_good, label: "nicht so gut" }
|
||||
- { key: unacceptable, label: "geht gar nicht" }
|
||||
|
||||
- key: guest_behaviour
|
||||
label: "Verhalten der Gäste im Bus"
|
||||
type: choice
|
||||
widget: dropdown
|
||||
required: true
|
||||
options:
|
||||
- { key: very_good, label: "Sehr gut" }
|
||||
- { key: good, label: "Gut" }
|
||||
- { key: ok, label: "OK" }
|
||||
- { key: not_so_good, label: "nicht so gut" }
|
||||
- { key: unacceptable, label: "geht gar nicht" }
|
||||
|
||||
- key: bus_pickups
|
||||
title: "Ladestellen"
|
||||
questions:
|
||||
- key: drivers_knew_pickups
|
||||
label: "Waren den Fahrern alle Abfahrtsorte bekannt?"
|
||||
type: choice
|
||||
required: true
|
||||
options:
|
||||
- { key: "yes", label: "Ja" }
|
||||
- { key: "no", label: "Nein" }
|
||||
|
||||
- key: unknown_pickups
|
||||
label: "Welche Abfahrtsorte waren nicht bekannt? Gab es dadurch Probleme?"
|
||||
type: textarea
|
||||
required: true
|
||||
show_if: { question: drivers_knew_pickups, equals: "no" }
|
||||
|
||||
- key: bus_destination
|
||||
title: "Zielkenntnis"
|
||||
questions:
|
||||
- key: drivers_knew_destination
|
||||
label: "War den Busfahrern der Zielort und die Ausladestelle bekannt?"
|
||||
type: choice
|
||||
required: true
|
||||
options:
|
||||
- { key: "yes", label: "Ja" }
|
||||
- { key: "no", label: "Nein" }
|
||||
|
||||
- key: bus_equipment
|
||||
title: "Bus, Technik & Allgemeines"
|
||||
questions:
|
||||
- key: video
|
||||
label: "Video/DVD-Gerät"
|
||||
type: choice
|
||||
widget: dropdown
|
||||
required: true
|
||||
options:
|
||||
- { key: working, label: "funktionstüchtig" }
|
||||
- { key: broken, label: "defekt" }
|
||||
- { key: missing, label: "nicht vorhanden" }
|
||||
|
||||
- key: wc
|
||||
label: "WC"
|
||||
type: choice
|
||||
widget: dropdown
|
||||
required: true
|
||||
options:
|
||||
- { key: working, label: "funktionstüchtig" }
|
||||
- { key: broken, label: "defekt" }
|
||||
- { key: closed, label: "nicht geöffnet" }
|
||||
|
||||
- key: luggage
|
||||
label: "Gepäck"
|
||||
type: choice
|
||||
widget: dropdown
|
||||
required: true
|
||||
options:
|
||||
- { key: trailer, label: "Anhänger" }
|
||||
- { key: ski_box, label: "Skikoffer" }
|
||||
- { key: trailer_and_ski_box, label: "Anhänger und Skikoffer" }
|
||||
- { key: none, label: "Kein Anhänger oder Skikoffer" }
|
||||
|
||||
- key: smoking
|
||||
label: "Rauchen im Bus"
|
||||
type: choice
|
||||
widget: dropdown
|
||||
required: true
|
||||
options:
|
||||
- { key: driver, label: "Busfahrer hat im Bus geraucht" }
|
||||
- { key: driver_and_guests, label: "Busfahrer und Gäste haben im Bus geraucht" }
|
||||
- { key: nobody, label: "Es wurde nicht geraucht" }
|
||||
|
||||
- key: snow_chains
|
||||
label: "Schneeketten"
|
||||
type: choice
|
||||
widget: dropdown
|
||||
required: true
|
||||
options:
|
||||
- { key: fitted, label: "Fahrer hat Ketten aufgezogen" }
|
||||
- { key: not_needed, label: "Ketten wurden nicht gebraucht" }
|
||||
- { key: not_fitted, label: "Ketten nicht aufgezogen (trotz starkem Schneefall)" }
|
||||
|
||||
- key: bus_damage
|
||||
title: "Schäden"
|
||||
questions:
|
||||
- key: bus_damage
|
||||
label: "Entstanden während der Fahrt Schäden am Bus?"
|
||||
type: choice
|
||||
widget: dropdown
|
||||
required: true
|
||||
options:
|
||||
- { key: none, label: "Keine Schäden" }
|
||||
- { key: settled_with_driver, label: "Bereits mit Fahrer abgeklärt" }
|
||||
- { key: claim_expected, label: "Da wird vom Busunternehmen noch was kommen" }
|
||||
|
||||
- key: damage_description
|
||||
label: "Was ist passiert?"
|
||||
type: textarea
|
||||
required: true
|
||||
show_if: { question: bus_damage, equals: claim_expected }
|
||||
|
||||
- key: bus_remarks
|
||||
title: "Anmerkungen zur Busfahrt"
|
||||
questions:
|
||||
- key: bus_remarks
|
||||
label: "Das ging gar nicht / ist besonders hervorzuheben:"
|
||||
type: textarea
|
||||
@@ -0,0 +1,137 @@
|
||||
# Questionnaire every teamer fills in after a placement (Disposition, not SeasonAssignment).
|
||||
#
|
||||
# Replaces the Google form "RL-Feedback 25-26"; the question-by-question spec, with every decision
|
||||
# taken, is kept locally outside the repository. A teamer who escorted the bus
|
||||
# also gets bus_feedback.yaml, on the same page. Questions whose answer the disposition already
|
||||
# knows (name, job profile, house, arrival day) are not asked: they are snapshotted into the
|
||||
# response's context instead.
|
||||
#
|
||||
# Labels, help texts, section titles and option labels may be edited in place. Renaming or
|
||||
# removing a question or option key, or changing a type, needs a new version: copy this file to
|
||||
# teamer_feedback.v1.yaml first, then bump `version` here. Submitted answers are keyed by it.
|
||||
#
|
||||
# Parsed and validated by App\Config\QuestionnaireCatalog; QuestionnaireCatalogTest builds it.
|
||||
#
|
||||
# Question fields: key, label, type (text | textarea | choice | multi | date | rating),
|
||||
# required (default false), help, widget (choice only: radio | dropdown, default radio),
|
||||
# options (choice and multi only), show_if ({question, equals | in | lte}, earlier questions only).
|
||||
|
||||
key: teamer_feedback
|
||||
version: 1
|
||||
title: "Wochenteamer:innen-Feedback"
|
||||
description: |-
|
||||
Nur durch euer Feedback können wir besser werden! 🙂
|
||||
|
||||
Bei Bewertunsskalen: 1= schlecht, 5 = sehr gut
|
||||
|
||||
sections:
|
||||
- key: organisation
|
||||
title: "Organisation & Ablauf"
|
||||
questions:
|
||||
- key: office_briefing_rating
|
||||
label: "Wie gut hast du dich vorab vom Büro gebrieft gefühlt?"
|
||||
type: rating
|
||||
required: true
|
||||
|
||||
- key: office_briefing_low_reason
|
||||
label: "Wenn 1 - 3 Sterne Bewertung, warum?"
|
||||
type: textarea
|
||||
required: true
|
||||
show_if: { question: office_briefing_rating, lte: 3 }
|
||||
|
||||
- key: team_week_seminar
|
||||
label: "War das Wochenteam-Seminar hilfreich für dich? Warst du bzw. wurdest du gut vorbereitet?"
|
||||
type: choice
|
||||
widget: dropdown
|
||||
required: true
|
||||
options:
|
||||
- { key: very_helpful, label: "Ja, die Schulung war sehr hilfreich und ich fühlte mich bestens vorbereitet." }
|
||||
- { key: mostly_helpful, label: "Ja, größtenteils – einige Themen hätte man noch vertiefen können." }
|
||||
- { key: partly_helpful, label: "Es ging – die Schulung war teilweise hilfreich, aber nicht ausreichend für alle Aufgaben." }
|
||||
- { key: rather_not_helpful, label: "Eher nein – ich fühlte mich in vielen Bereichen unvorbereitet." }
|
||||
- { key: not_helpful, label: "Nein, die Schulung war kaum hilfreich und hat mich nicht auf den Arbeitsalltag vorbereitet." }
|
||||
- { key: not_attended, label: "Ich habe nicht teilgenommen." }
|
||||
|
||||
- key: onsite_organisation_rating
|
||||
label: "Wie gut lief die Reiseorganisation vor Ort?"
|
||||
type: rating
|
||||
required: true
|
||||
|
||||
- key: had_organisational_problems
|
||||
label: "Gab es organisatorische Probleme?"
|
||||
type: choice
|
||||
required: true
|
||||
options:
|
||||
- { key: "yes", label: "Ja" }
|
||||
- { key: "no", label: "Nein" }
|
||||
|
||||
- key: organisational_problems
|
||||
label: "Wenn ja, welche?"
|
||||
type: text
|
||||
show_if: { question: had_organisational_problems, equals: "yes" }
|
||||
|
||||
- key: communication
|
||||
title: "Kommunikation & Zusammenarbeit"
|
||||
questions:
|
||||
- key: teamer_collaboration_rating
|
||||
label: "Wie lief die Kommunikation & Zusammenarbeit mit den E&P Teamer:innen vor Ort während der Reise?"
|
||||
type: rating
|
||||
required: true
|
||||
|
||||
- key: support_wished
|
||||
label: "Welche Unterstützung hättest du dir zusätzlich gewünscht?"
|
||||
type: textarea
|
||||
|
||||
- key: house_manager_problem_handling
|
||||
label: "Wie ist deine Hausleitung mit Problemen vor Ort umgegangen?"
|
||||
type: choice
|
||||
widget: dropdown
|
||||
required: true
|
||||
options:
|
||||
- { key: very_good, label: "Sehr gut – Probleme wurden schnell, fair und lösungsorientiert behandelt." }
|
||||
- { key: good, label: "Gut – die meisten Anliegen wurden ernst genommen und bearbeitet." }
|
||||
- { key: mixed, label: "Teils gut/teils schlecht – manche Probleme wurden gelöst, andere nicht." }
|
||||
- { key: rather_poor, label: "Eher schlecht – es wurde wenig unternommen oder nicht auf Augenhöhe kommuniziert." }
|
||||
- { key: very_poor, label: "Sehr schlecht – Probleme wurden ignoriert oder verschlimmert." }
|
||||
|
||||
- key: felt_overwhelmed
|
||||
label: "Gab es Momente, in denen du dich überfordert gefühlt hast?"
|
||||
type: choice
|
||||
required: true
|
||||
options:
|
||||
- { key: "yes", label: "Ja" }
|
||||
- { key: "no", label: "Nein" }
|
||||
|
||||
- key: overwhelmed_reason
|
||||
label: "Wenn ja, wodurch?"
|
||||
type: textarea
|
||||
show_if: { question: felt_overwhelmed, equals: "yes" }
|
||||
|
||||
- key: improvement
|
||||
title: "Verbesserung"
|
||||
questions:
|
||||
- key: wished_differently
|
||||
label: "Was hättest du dir anders oder besser gewünscht?"
|
||||
type: textarea
|
||||
|
||||
- key: would_work_again
|
||||
label: "Würdest du wieder als Reiseleitung für E&P arbeiten?"
|
||||
type: choice
|
||||
required: true
|
||||
options:
|
||||
- { key: "yes", label: "Ja" }
|
||||
- { key: "no", label: "Nein" }
|
||||
|
||||
- key: would_not_work_again_reason
|
||||
label: "Wenn nein, warum nicht?"
|
||||
type: textarea
|
||||
show_if: { question: would_work_again, equals: "no" }
|
||||
|
||||
- key: other
|
||||
title: "Sonstiges"
|
||||
questions:
|
||||
- key: anything_else
|
||||
label: |-
|
||||
Du hast nun schon vieles beantwortet,
|
||||
aber nirgendwo konntest du folgende Aspekte loswerden:
|
||||
type: textarea
|
||||
@@ -1,4 +1,4 @@
|
||||
when@dev:
|
||||
_errors:
|
||||
resource: '@FrameworkBundle/Resources/config/routing/errors.xml'
|
||||
resource: '@FrameworkBundle/Resources/config/routing/errors.php'
|
||||
prefix: /_error
|
||||
@@ -1,8 +1,8 @@
|
||||
when@dev:
|
||||
web_profiler_wdt:
|
||||
resource: '@WebProfilerBundle/Resources/config/routing/wdt.xml'
|
||||
resource: '@WebProfilerBundle/Resources/config/routing/wdt.php'
|
||||
prefix: /_wdt
|
||||
|
||||
web_profiler_profiler:
|
||||
resource: '@WebProfilerBundle/Resources/config/routing/profiler.xml'
|
||||
resource: '@WebProfilerBundle/Resources/config/routing/profiler.php'
|
||||
prefix: /_profiler
|
||||
+339
-29
@@ -1,34 +1,173 @@
|
||||
# yaml-language-server: $schema=../vendor/symfony/dependency-injection/Loader/schema/services.schema.json
|
||||
parameters:
|
||||
bpn_crm_id_admin: '%env(int:APP_BPN_CRM_ID_ADMIN)%'
|
||||
bpn_crm_id_team_admin: '%env(int:APP_BPN_CRM_ID_TEAM_ADMIN)%'
|
||||
bpn_crm_id_manager: '%env(int:APP_BPN_CRM_ID_MANAGER)%'
|
||||
bpn_crm_id_teamer: '%env(int:APP_BPN_CRM_ID_TEAMER)%'
|
||||
bpn_default_hotel_code: '%env(default::APP_BPN_DEFAULT_HOTEL_CODE)%'
|
||||
|
||||
# BusPro CRM selection id => hotel code. A person holding a selected attribute from this
|
||||
# list is a house manager for that house. Matched by id and not by the "Hausleitung XXX"
|
||||
# label, so renaming a selection in BusPro cannot silently grant or revoke a house.
|
||||
# The commented entries are BusPro selections whose code is not a house in the "houses"
|
||||
# parameter below: granting them would let someone in who then sees no assignments and
|
||||
# no dispositions at all. Uncomment one once its house exists. The reverse gap is fine -
|
||||
# not every house has a selection.
|
||||
bpn_crm_house_manager_ids:
|
||||
1299: 'SSL'
|
||||
1300: 'SST'
|
||||
1301: 'MVK'
|
||||
# 1302: 'ASB'
|
||||
1303: 'LPJ'
|
||||
1304: 'DKS'
|
||||
1305: 'DGS'
|
||||
1306: 'DPW'
|
||||
1307: 'DKI'
|
||||
1308: 'DWW'
|
||||
1309: 'PMV'
|
||||
# 1352: 'ASG'
|
||||
1371: 'ASC'
|
||||
1373: 'PCJ'
|
||||
1374: 'SBW'
|
||||
# 1375: 'UCH'
|
||||
# 1376: 'SZO'
|
||||
1377: 'KHH'
|
||||
1459: 'SVS'
|
||||
1461: 'SHM'
|
||||
1462: 'AGR'
|
||||
|
||||
# Shared key the sibling websites authenticate their form submissions with. Empty here on
|
||||
# purpose - the real value belongs in the environment of each stage, and an empty key
|
||||
# rejects every request instead of letting them through.
|
||||
application_form_api_key: '%env(string:APPLICATION_FORM_API_KEY)%'
|
||||
|
||||
# Shared key n8n reads the assignments feed with (GET /api/assignments). Same rules as above.
|
||||
n8n_api_key: '%env(string:N8N_API_KEY)%'
|
||||
|
||||
# What the n8n assignments feed covers: assignments in these houses (matched against the first
|
||||
# three characters of destination.hotelCode) for these job profiles - guide, course and
|
||||
# service-team work. Job profile ids are database ids, so check them against production.
|
||||
assignment_export.house_codes: [ DPW, DGS, DWW, DKI, DKS ]
|
||||
assignment_export.job_profile_ids:
|
||||
- 5 # Reiseleitung
|
||||
- 9 # Reiseleitung Ski
|
||||
- 10 # Reiseleitung Snowboard
|
||||
- 3 # Kursleitung Ski Anfänger:innen
|
||||
- 14 # Kursleitung Ski Fortgeschrittene
|
||||
- 18 # Kursleitung Ski Anfänger:innen + Fortgeschrittene
|
||||
- 36 # Kursleitung Ski Kinder Anfänger:innen & Kinderbetreuung
|
||||
- 37 # Kursleitung Ski Kinder Fortgeschrittene
|
||||
- 38 # Kursleitung Ski Kinder
|
||||
- 4 # Kursleitung Snowboard Anfänger:innen
|
||||
- 15 # Kursleitung Snowboard Fortgeschrittene
|
||||
- 19 # Kursleitung Snowboard Anfänger:innen + Fortgeschrittene
|
||||
- 47 # Kursleitung Snowboard Kinder/Jugendliche Anfänger:innen
|
||||
- 20 # Reise- und Kursleitung Ski Anfänger:innen
|
||||
- 21 # Reise- und Kursleitung Ski Fortgeschrittene
|
||||
- 22 # Reise- und Kursleitung Ski Anfänger:innen + Fortgeschrittene
|
||||
- 23 # Reise- und Kursleitung Snowboard Anfänger:innen
|
||||
- 24 # Reise- und Kursleitung Snowboard Fortgeschrittene
|
||||
- 25 # Reise- und Kursleitung Snowboard Anfänger:innen + Fortgeschrittene
|
||||
- 6 # Serviceteam 40h (ST40)
|
||||
- 13 # Serviceteam 25h (ST25)
|
||||
- 16 # Serviceteam 10h (ST10)
|
||||
|
||||
# Booking link the application form receipt mail invites the applicant to. A deployment
|
||||
# value rather than part of the wording, so a new booking system does not need an edit in
|
||||
# every stage's mail text.
|
||||
application_form_booking_url: '%env(string:APPLICATION_FORM_BOOKING_URL)%'
|
||||
|
||||
default_email_from: '%env(APP_DEFAULT_EMAIL_FROM)%'
|
||||
default_email_to: '%env(APP_DEFAULT_EMAIL_TO)%'
|
||||
|
||||
# Per deployment and not per environment: staging and production run the same compiled
|
||||
# environment and differ only in their .env.local. The csv processor turns an unset
|
||||
# variable into an empty list, which switches the filtering off - so production is
|
||||
# unaffected without anything having to be remembered at deploy time.
|
||||
mail_recipient_whitelist: '%env(csv:APP_MAIL_RECIPIENT_WHITELIST)%'
|
||||
|
||||
teamer_inactive_period: '-2 years'
|
||||
|
||||
destinations:
|
||||
- Schwendi
|
||||
- Waldschlössli
|
||||
- Spinabad
|
||||
- Schweizerhaus
|
||||
- Weißfluh
|
||||
- Victoria
|
||||
- Klein Tirol
|
||||
- Jenatsch
|
||||
- Lederer
|
||||
- Steinachhof
|
||||
- Val Thorens
|
||||
- Les Deux Alpes
|
||||
- Ranalt
|
||||
- Jolimont
|
||||
- Christiler
|
||||
- Heuberghaus
|
||||
- Silvana
|
||||
- Rotbach
|
||||
- Mitterlengau
|
||||
# The stand-in PDF served instead of real uploads while the upload_replacement feature is
|
||||
# on. Relative to the project dir. Which file is a fixed fact of the repo; whether it is
|
||||
# used at all is the feature flag's job alone.
|
||||
upload_replacement_file: 'assets/pdf/chicken.pdf'
|
||||
|
||||
# The contract upload period, counted from the creation of the disposition. This is the
|
||||
# single definition: the reminder mail, the "Honorarvertrag fällig" flag on the teamer
|
||||
# dashboard, the admin list of overdue contracts and the wording of the mails all read it,
|
||||
# so they cannot mark the same disposition due on three different days.
|
||||
contract_upload_deadline_days: 5
|
||||
|
||||
# The invoice upload period, counted from the end of the assignment. The teamer is
|
||||
# reminded the day after the assignment ends and again on the last day of this period.
|
||||
invoice_upload_deadline_days: 14
|
||||
|
||||
# Messenger transport the mails of a teamer mailing are queued on. An X-Bus-Transport
|
||||
# header wins over the routing in messenger.yaml (see SendersLocator::getSenders),
|
||||
# so this is what keeps a mailing off the shared queue - and what has to be switched
|
||||
# back to "sync" further down, or a mailing would need a running worker to arrive
|
||||
# in dev and test while every other mail is sent right away.
|
||||
mailing_bus_transport: 'mailing'
|
||||
|
||||
# Dates (MM-DD) on which teamers must re-confirm their personal data.
|
||||
personal_data_check_deadlines:
|
||||
- '04-01'
|
||||
- '10-01'
|
||||
|
||||
# The season window as MM-DD. It wraps the turn of the year and is named after the
|
||||
# calendar year it starts in ("Saison 2024/25"). The teamer info modal buckets a
|
||||
# teamer's dispositions by the season their assignment starts in; one whose
|
||||
# assignment starts outside this window is counted as "außerhalb der Saison".
|
||||
season_start: '11-01'
|
||||
season_end: '04-01'
|
||||
|
||||
# The teamer questionnaires after a placement (config/questionnaires/teamer_feedback.yaml, plus
|
||||
# bus_feedback.yaml for a bus escort) are offered for placements that ended within this many
|
||||
# days. Without a window every past placement would show up as pending on the teamers' dashboards.
|
||||
questionnaire_window_days: 30
|
||||
|
||||
# How far ahead of the destination's first travel day a season assignment's checklist
|
||||
# entries fall due. A relative date expression, applied to destination.dateFrom, and the
|
||||
# single definition of both deadlines: the first is this offset, the second defaults to
|
||||
# the first. Per-entry overrides are stored on the task row; this is what they override.
|
||||
season_task_deadline_offset: '-1 month'
|
||||
|
||||
# What the staffing matrix generates (App\Service\Assignment\AssignmentPlanner), keyed by
|
||||
# App\Enum\StaffingRole / StaffingFee value. Ids and not names, so renaming a job profile
|
||||
# or fee in the admin cannot silently change what the drafts get. A missing or deleted id
|
||||
# makes App\Service\Assignment\StaffingResolver refuse rather than skip.
|
||||
auto_assignment_job_profiles:
|
||||
guide: 5 # Reiseleitung
|
||||
ski_beginner: 3 # Kursleitung Ski Anfänger:innen
|
||||
ski_advanced: 14 # Kursleitung Ski Fortgeschrittene
|
||||
guide_ski_beginner: 20 # Reise- und Kursleitung Ski Anfänger:innen
|
||||
guide_ski_advanced: 21 # Reise- und Kursleitung Ski Fortgeschrittene
|
||||
auto_assignment_fees:
|
||||
guiding: 3 # Skigebietsguiding (pro Einheit)
|
||||
course: 4 # Ski-/Snowboardkurs (pro Stunde)
|
||||
|
||||
# Houses, hotel code: name. The name is used both as a label and to match
|
||||
# destination.hotel, the code to match destination.hotelCode.
|
||||
houses:
|
||||
AGR: 'Rotbach'
|
||||
ASC: 'Christiler'
|
||||
DGS: 'Spinabad'
|
||||
DKI: 'Schwendi'
|
||||
DKS: 'Schweizerhaus'
|
||||
DPW: 'Waldschlössli'
|
||||
DWW: 'Weißfluh'
|
||||
KHH: 'Heuberghaus'
|
||||
L2A: 'Les Deux Alpes'
|
||||
LPJ: 'Jenatsch'
|
||||
MVK: 'Klein Tirol'
|
||||
PCJ: 'Jolimont'
|
||||
PMV: 'Victoria'
|
||||
SBW: 'Val Thorens'
|
||||
SHM: 'Mitterlengau'
|
||||
SRR: 'Ranalt'
|
||||
SSL: 'Lederer'
|
||||
SST: 'Steinachhof'
|
||||
SVS: 'Silvana'
|
||||
|
||||
services:
|
||||
_defaults:
|
||||
@@ -36,11 +175,34 @@ services:
|
||||
autoconfigure: true
|
||||
bind:
|
||||
$tempDir: '%kernel.project_dir%/temp'
|
||||
$logger: '@monolog.logger.myep'
|
||||
$destinations: '%destinations%'
|
||||
$xmlExport: '@xml_export.storage'
|
||||
# Application audit channel, not a MyE&P one: this is what feeds the
|
||||
# log table via App\Logging\DatabaseHandler. Unbinding a service from
|
||||
# it drops its records from that table.
|
||||
$logger: '@monolog.logger.audit'
|
||||
$houses: '%houses%'
|
||||
$xmlDump: '@xml_dump.storage'
|
||||
$teamerInactivePeriod: '%teamer_inactive_period%'
|
||||
$contractUploadDeadlineDays: '%contract_upload_deadline_days%'
|
||||
$invoiceUploadDeadlineDays: '%invoice_upload_deadline_days%'
|
||||
# Only the path enters the container - the file itself is parsed on demand by
|
||||
# App\Config\EmailTextCatalog, since its contents matter on few requests.
|
||||
$emailTextsFile: '%kernel.project_dir%/config/email_texts.yaml'
|
||||
# Likewise parsed on demand, by App\Config\QuestionnaireCatalog.
|
||||
$questionnaireDir: '%kernel.project_dir%/config/questionnaires'
|
||||
$questionnaireWindowDays: '%questionnaire_window_days%'
|
||||
$mailingBusTransport: '%mailing_bus_transport%'
|
||||
$seasonStart: '%season_start%'
|
||||
$seasonEnd: '%season_end%'
|
||||
$seasonTaskDeadlineOffset: '%season_task_deadline_offset%'
|
||||
$autoAssignmentJobProfiles: '%auto_assignment_job_profiles%'
|
||||
$autoAssignmentFees: '%auto_assignment_fees%'
|
||||
|
||||
# Tagged by interface rather than listed by hand: order between collectors is
|
||||
# irrelevant, so the only thing an explicit list could add here is the chance to
|
||||
# forget one, which would silently stop a metric being recorded.
|
||||
_instanceof:
|
||||
App\Service\Statistics\Collector\StatisticsCollectorInterface:
|
||||
tags: ['app.statistics_collector']
|
||||
|
||||
App\:
|
||||
resource: '../src/'
|
||||
@@ -57,7 +219,7 @@ services:
|
||||
- name: liip_imagine.cache.resolver
|
||||
resolver: format_extension
|
||||
|
||||
App\Command\BpnImportCommand:
|
||||
App\Command\MyEpImportCommand:
|
||||
arguments:
|
||||
$connection: '@doctrine.dbal.default_connection'
|
||||
|
||||
@@ -79,14 +241,61 @@ services:
|
||||
bpn_api_port: '%env(APP_BPN_PORT)%'
|
||||
debug: '%env(bool:APP_BPN_DEBUG)%'
|
||||
|
||||
App\Service\Teamer\TeamerProvisioner:
|
||||
arguments:
|
||||
$logger: '@monolog.logger.bpn'
|
||||
# The same selection the login reads a teamer's role from - this is the one place that
|
||||
# writes it, so it must be the same id.
|
||||
$teamerSelectionId: '%bpn_crm_id_teamer%'
|
||||
|
||||
App\BusProNet\ResponseParser:
|
||||
arguments:
|
||||
$options:
|
||||
bpn_crm_id_admin: '%bpn_crm_id_admin%'
|
||||
bpn_crm_id_team_admin: '%bpn_crm_id_team_admin%'
|
||||
bpn_crm_id_manager: '%bpn_crm_id_manager%'
|
||||
bpn_crm_id_teamer: '%bpn_crm_id_teamer%'
|
||||
bpn_crm_house_manager_ids: '%bpn_crm_house_manager_ids%'
|
||||
bpn_default_hotel_code: '%bpn_default_hotel_code%'
|
||||
|
||||
App\Saas\ApiClient:
|
||||
arguments:
|
||||
$logger: '@monolog.logger.saas'
|
||||
$options:
|
||||
base_url: '%env(APP_SAAS_BASE_URL)%'
|
||||
api_key: '%env(APP_SAAS_API_KEY)%'
|
||||
|
||||
App\Saas\DataProvider\EmployeeDataProvider:
|
||||
arguments:
|
||||
$logger: '@monolog.logger.saas'
|
||||
# The roster is slow and barely changes; six hours means one fetch per run and a
|
||||
# new starter is picked up the same day.
|
||||
$ttl: 21600
|
||||
|
||||
App\MyEp\Api\AccessTokenProvider:
|
||||
arguments:
|
||||
$logger: '@monolog.logger.myep'
|
||||
$cache: '@cache.app'
|
||||
$options:
|
||||
client_id: '%env(MYEP_API_CLIENT_ID)%'
|
||||
client_secret: '%env(MYEP_API_CLIENT_SECRET)%'
|
||||
|
||||
App\MyEp\Api\ApiClient:
|
||||
arguments:
|
||||
$logger: '@monolog.logger.myep'
|
||||
|
||||
App\MyEp\Api\ResponseMapper:
|
||||
arguments:
|
||||
$logger: '@monolog.logger.myep'
|
||||
|
||||
App\MyEp\Cache\StaleWhileErrorCache:
|
||||
arguments:
|
||||
$logger: '@monolog.logger.myep'
|
||||
$cache: '@cache.app'
|
||||
|
||||
App\MyEp\Import\TravelExportClient:
|
||||
arguments:
|
||||
$logger: '@monolog.logger.myep'
|
||||
|
||||
App\Twig\AppRuntime:
|
||||
arguments:
|
||||
$intlExtension: '@twig.extension.intl'
|
||||
@@ -98,12 +307,32 @@ services:
|
||||
tags:
|
||||
- name: monolog.processor
|
||||
|
||||
# Every SaaS url carries the api key as a path segment, so the request lines Symfony's
|
||||
# http client writes - and the exception messages that repeat those urls - are redacted
|
||||
# on the way into the log rather than suppressed.
|
||||
App\Logging\RedactSaasCredentialsProcessor:
|
||||
arguments:
|
||||
$apiKey: '%env(APP_SAAS_API_KEY)%'
|
||||
tags:
|
||||
- name: monolog.processor
|
||||
|
||||
App\Logging\RequestInfoProcessor:
|
||||
arguments:
|
||||
- '@request_stack'
|
||||
tags:
|
||||
- name: monolog.processor
|
||||
|
||||
App\Menu\MenuBuilder:
|
||||
arguments:
|
||||
$factory: '@knp_menu.factory'
|
||||
tags:
|
||||
- name: knp_menu.menu_builder
|
||||
method: createMainMenu
|
||||
alias: default_main
|
||||
- name: knp_menu.menu_builder
|
||||
method: createTeamerMenu
|
||||
alias: default_teamer
|
||||
|
||||
App\Menu\AdminMenuBuilder:
|
||||
arguments:
|
||||
$factory: '@knp_menu.factory'
|
||||
@@ -146,14 +375,21 @@ services:
|
||||
arguments:
|
||||
$orphanageManager: '@oneup_uploader.orphanage_manager'
|
||||
$projectDir: '%kernel.project_dir%'
|
||||
$environment: '%kernel.environment%'
|
||||
$uploadReplacementFile: '%env(default::UPLOAD_REPLACEMENT_FILE)%'
|
||||
$uploadReplacementFile: '%upload_replacement_file%'
|
||||
|
||||
App\Service\Pdf\Sanitizer:
|
||||
arguments:
|
||||
$binGs: '%env(BIN_GS)%'
|
||||
$binConvert: '%env(BIN_CONVERT)%'
|
||||
|
||||
App\EventListener\MaintenanceModeListener:
|
||||
arguments:
|
||||
$options:
|
||||
enabled: '%env(bool:MAINTENANCE_MODE_ENABLED)%'
|
||||
whitelisted_ips: '%env(csv:MAINTENANCE_MODE_IP_WHITELIST)%'
|
||||
whitelisted_routes: '%env(csv:MAINTENANCE_MODE_ROUTE_WHITELIST)%'
|
||||
whitelisted_paths: '%env(csv:MAINTENANCE_MODE_PATH_WHITELIST)%'
|
||||
|
||||
App\EventListener\UploadSessionListener:
|
||||
tags:
|
||||
- name: kernel.event_listener
|
||||
@@ -171,15 +407,27 @@ services:
|
||||
$datevEmailRecipient: '%env(DATEV_EMAIL_RECIPIENT)%'
|
||||
$datevEmailSender: '%env(DATEV_EMAIL_SENDER)%'
|
||||
|
||||
App\EventListener\StatisticsChangeSetListener:
|
||||
arguments:
|
||||
$collectors: !tagged_iterator app.statistics_collector
|
||||
|
||||
App\RequiredTeamerCheck\RequiredTeamerCheckRegistry:
|
||||
arguments:
|
||||
# Presentation order only. Each check must point at a page that clears
|
||||
# that check alone, so a teamer can always work through them whatever
|
||||
# the order — never make correctness depend on this list.
|
||||
$checks:
|
||||
- '@App\RequiredTeamerCheck\DriverLicenseRequiredCheck'
|
||||
- '@App\RequiredTeamerCheck\FinancialDataRequiredCheck'
|
||||
- '@App\RequiredTeamerCheck\PersonalDataVerificationRequiredCheck'
|
||||
- '@App\RequiredTeamerCheck\MealPreferenceRequiredCheck'
|
||||
|
||||
App\RequiredTeamerCheck\PersonalDataVerificationRequiredCheck:
|
||||
arguments:
|
||||
$dataVerificationDeadlines: '%env(csv:PERSONAL_DATA_CHECK_DEADLINES)%'
|
||||
$deadlines: !service
|
||||
class: App\RequiredTeamerCheck\RecurringDeadlines
|
||||
arguments:
|
||||
$deadlines: '%personal_data_check_deadlines%'
|
||||
|
||||
app.upload_namer:
|
||||
class: App\Service\Upload\UploadNamer
|
||||
@@ -200,8 +448,70 @@ services:
|
||||
$options:
|
||||
project_dir: '%kernel.project_dir%'
|
||||
|
||||
App\Service\Pdf\WorkMonthRenderer:
|
||||
arguments:
|
||||
$options:
|
||||
project_dir: '%kernel.project_dir%'
|
||||
|
||||
App\Email\Mailer:
|
||||
arguments:
|
||||
$defaults:
|
||||
from: '%default_email_from%'
|
||||
to: '%default_email_to%'
|
||||
|
||||
# Its own log channel, not the audit one bound by default: the audit channel writes a row
|
||||
# to the log table through App\Logging\DatabaseHandler, which records what staff did here.
|
||||
App\Email\EmailTextRenderer:
|
||||
arguments:
|
||||
$bookingUrl: '%application_form_booking_url%'
|
||||
|
||||
App\Security\AccessToken\ApiKeyTokenHandler:
|
||||
arguments:
|
||||
# user identifier in security.yaml's api_user_provider => key
|
||||
$apiKeys:
|
||||
api: '%application_form_api_key%'
|
||||
n8n: '%n8n_api_key%'
|
||||
$logger: '@monolog.logger.api'
|
||||
|
||||
App\Controller\Api\ApplicationFormController:
|
||||
arguments:
|
||||
$logger: '@monolog.logger.api'
|
||||
tags: [ 'controller.service_arguments' ]
|
||||
|
||||
App\Controller\Api\Assignment\ListController:
|
||||
arguments:
|
||||
$houseCodes: '%assignment_export.house_codes%'
|
||||
$jobProfileIds: '%assignment_export.job_profile_ids%'
|
||||
$logger: '@monolog.logger.api'
|
||||
tags: [ 'controller.service_arguments' ]
|
||||
|
||||
App\EventListener\EmailRecipientWhitelistSubscriber:
|
||||
arguments:
|
||||
$allowedRecipients: '%mail_recipient_whitelist%'
|
||||
# Not the audit channel this argument is bound to by default: a mail dropped by a
|
||||
# staging guard is a technical event and has no place in the application log table.
|
||||
$logger: '@logger'
|
||||
|
||||
App\Security\OAuth2\MyEpClient:
|
||||
arguments:
|
||||
$options:
|
||||
myep_oauth2_client_id: '%env(MYEP_OAUTH2_CLIENT_ID)%'
|
||||
myep_oauth2_client_secret: '%env(MYEP_OAUTH2_CLIENT_SECRET)%'
|
||||
myep_oauth2_url_authorize: '%env(MYEP_OAUTH2_URL_AUTHORIZE)%'
|
||||
myep_oauth2_url_access_token: '%env(MYEP_OAUTH2_URL_ACCESS_TOKEN)%'
|
||||
myep_oauth2_url_resource_owner_details: '%env(MYEP_OAUTH2_URL_RESOURCE_OWNER_DETAILS)%'
|
||||
myep_oauth2_scopes: '%env(csv:MYEP_OAUTH2_SCOPES)%'
|
||||
|
||||
# The mails of a mailing are sent right away here, like every other mail, instead of
|
||||
# waiting for a worker on the dedicated queue. The mailer transport is deliberately not
|
||||
# switched: a mailing still goes out over MAILING_MAILER_DSN, so the path being exercised
|
||||
# locally is the one that runs in production.
|
||||
when@dev:
|
||||
parameters:
|
||||
mailing_bus_transport: 'sync'
|
||||
|
||||
when@test:
|
||||
parameters:
|
||||
# a job profile id ListControllerTest inserts itself - db_test has no job profiles
|
||||
assignment_export.job_profile_ids: [ 900001 ]
|
||||
mailing_bus_transport: 'sync'
|
||||
+20
-16
@@ -22,7 +22,7 @@ set('keep_releases', 3);
|
||||
// Rsync options, mainly files/dirs to exclude
|
||||
$rsyncOptions = [
|
||||
'exclude' => [
|
||||
'.cursor',
|
||||
'.claude',
|
||||
'.idea',
|
||||
'.vscode',
|
||||
'.DS_Store',
|
||||
@@ -38,6 +38,7 @@ $rsyncOptions = [
|
||||
'.env.test.local',
|
||||
'.gitignore',
|
||||
'.nvmrc',
|
||||
'.browserslistrc',
|
||||
'.php-cs-fixer.cache',
|
||||
'.php-cs-fixer.dist.php',
|
||||
'.phpunit.result.cache',
|
||||
@@ -46,11 +47,12 @@ $rsyncOptions = [
|
||||
'package.json',
|
||||
'package-lock.json',
|
||||
'*.http',
|
||||
'http-client*.json',
|
||||
'postcss.config.js',
|
||||
'phpunit.xml.dist',
|
||||
'public/.htaccess',
|
||||
'tailwind.config.js',
|
||||
'webpack.config.js',
|
||||
'webpack.config.mjs',
|
||||
'rapidapi.paw',
|
||||
'bin/.phpunit',
|
||||
'temp/*',
|
||||
@@ -70,31 +72,33 @@ $rsyncOptions = [
|
||||
];
|
||||
|
||||
host('prod')
|
||||
->setHostname('185.243.135.29')
|
||||
->setRemoteUser('p704161')
|
||||
->setHostname('dedi10193.your-server.de')
|
||||
->setRemoteUser('myepteam')
|
||||
->setForwardAgent(true)
|
||||
->setSshMultiplexing(true)
|
||||
->setDeployPath('/home/www/p704161/html/myep-team')
|
||||
->set('writable_mode', 'chmod')
|
||||
->set('http_user', 'p704161')
|
||||
->set('bin/php', '/usr/local/bin/php')
|
||||
->setDeployPath('/usr/www/users/myepteam/prod')
|
||||
->set('bin/php', '/usr/bin/php')
|
||||
->set('http_user', 'myepteam')
|
||||
->set('rsync_src', __DIR__)
|
||||
->set('rsync', $rsyncOptions)
|
||||
->set('cachetool_args', '--web=SymfonyHttpClient --web-path={{current_path}}/public/ --web-url=https://myep-team.ep-reisen.de')
|
||||
->set('cachetool_args', '--web=SymfonyHttpClient --web-path={{current_path}}/public/ --web-url=https://team.ep-reisen.de')
|
||||
;
|
||||
|
||||
host('staging')
|
||||
->setHostname('185.243.135.29')
|
||||
->setRemoteUser('p704161')
|
||||
->setHostname('dedi10193.your-server.de')
|
||||
->setRemoteUser('myepteam')
|
||||
->setForwardAgent(true)
|
||||
->setSshMultiplexing(true)
|
||||
->setDeployPath('/home/www/p704161/html/myep-team-staging')
|
||||
->set('writable_mode', 'chmod')
|
||||
->set('http_user', 'p704161')
|
||||
->set('bin/php', '/usr/local/bin/php')
|
||||
->setDeployPath('/usr/www/users/myepteam/staging')
|
||||
->set('bin/php', '/usr/bin/php')
|
||||
->set('http_user', 'myepteam')
|
||||
->set('rsync_src', __DIR__)
|
||||
->set('rsync', $rsyncOptions)
|
||||
->set('cachetool_args', '--web=SymfonyHttpClient --web-path={{current_path}}/public/ --web-url=https://myep-team.ep-reisen.net')
|
||||
->set('cachetool_args', '--web=SymfonyHttpClient --web-path={{current_path}}/public/ --web-url=https://team.ep-reisen.net --web-basic-auth=team:staging')
|
||||
->add('shared_files', [
|
||||
'public/.htaccess',
|
||||
'public/.htpasswd',
|
||||
])
|
||||
;
|
||||
|
||||
task('deploy', [
|
||||
|
||||
@@ -1,187 +0,0 @@
|
||||
# Plan: Cancellation Statistics Feature
|
||||
|
||||
## Objective
|
||||
|
||||
Enable stakeholders to see how often dispositions and applications have been canceled by either the teamer or the office, per hotel and for a given date range.
|
||||
|
||||
---
|
||||
|
||||
## Current State Analysis
|
||||
|
||||
### Dispositions - Already Tracked
|
||||
|
||||
The `Disposition` entity has comprehensive cancellation tracking:
|
||||
|
||||
| Field | Type | Purpose |
|
||||
|-------|------|---------|
|
||||
| `status` | string | `'called_off'` when cancelled |
|
||||
| `calledOffBy` | string (nullable) | `'teamer'` or `'office'` |
|
||||
| `calledOffReason` | text (nullable) | Reason for cancellation |
|
||||
| `updatedAt` | datetime | Timestamp of last change |
|
||||
|
||||
**Constants:**
|
||||
```php
|
||||
public const STATUS_CALLED_OFF = 'called_off';
|
||||
public const CALLED_OFF_BY_TEAMER = 'teamer';
|
||||
public const CALLED_OFF_BY_OFFICE = 'office';
|
||||
```
|
||||
|
||||
**Cancellation entry points:**
|
||||
1. Individual disposition cancellation via `Administrative/Disposition/CallOffController`
|
||||
- Form requires `calledOffBy` and `calledOffReason`
|
||||
- Dispatches `DispositionCalledOffEvent`
|
||||
|
||||
2. Full assignment cancellation via `Administrative/Assignment/CallOffController`
|
||||
- Sets all dispositions to `calledOffBy = 'office'`
|
||||
- Dispatches `AssignmentCalledOffEvent`
|
||||
|
||||
### Applications - Missing Tracking
|
||||
|
||||
The `Application` entity lacks cancellation tracking:
|
||||
|
||||
| Current Field | Issue |
|
||||
|---------------|-------|
|
||||
| `status` | Only `'rejected'` - no distinction between teamer withdrawal and office rejection |
|
||||
| - | No `cancelledBy` field |
|
||||
| - | No `cancellationReason` field |
|
||||
|
||||
**Current rejection mechanisms:**
|
||||
1. Manual rejection by office (no tracking of who)
|
||||
2. Automatic rejection by `RejectApplicationListener` when disposition slots fill
|
||||
3. Automatic removal by `InvalidateApplicationsListener` when teamer has overlapping confirmed dispositions
|
||||
|
||||
---
|
||||
|
||||
## Pending Decision
|
||||
|
||||
**Question for stakeholders:** Should automatic rejections be included in statistics?
|
||||
|
||||
| Option | Description | Impact |
|
||||
|--------|-------------|--------|
|
||||
| **Only user-initiated** | Count only manual cancellations by teamer or office | Simpler model, clearer accountability |
|
||||
| **Include automatic** | Also track system-initiated rejections | Full picture of lost applications, requires `'system'` category |
|
||||
|
||||
**Status:** Awaiting stakeholder response
|
||||
|
||||
---
|
||||
|
||||
## Proposed Changes
|
||||
|
||||
### Application Entity
|
||||
|
||||
**Option A: If only user-initiated cancellations count**
|
||||
|
||||
Add new status and fields:
|
||||
```php
|
||||
public const STATUS_WITHDRAWN = 'withdrawn'; // teamer-initiated
|
||||
public const STATUS_REJECTED = 'rejected'; // office-initiated (existing)
|
||||
|
||||
private ?string $cancellationReason = null;
|
||||
```
|
||||
|
||||
**Option B: If automatic rejections should be tracked**
|
||||
|
||||
Add new status, fields, and constant:
|
||||
```php
|
||||
public const STATUS_WITHDRAWN = 'withdrawn'; // teamer-initiated
|
||||
public const STATUS_REJECTED = 'rejected'; // office or system initiated
|
||||
|
||||
public const CANCELLED_BY_TEAMER = 'teamer';
|
||||
public const CANCELLED_BY_OFFICE = 'office';
|
||||
public const CANCELLED_BY_SYSTEM = 'system';
|
||||
|
||||
private ?string $cancelledBy = null;
|
||||
private ?string $cancellationReason = null;
|
||||
```
|
||||
|
||||
### Database Migration
|
||||
|
||||
Add columns to `application` table:
|
||||
- `cancellation_reason` (LONGTEXT, nullable)
|
||||
- Possibly `cancelled_by` (VARCHAR(64), nullable) if Option B
|
||||
|
||||
### Repository Methods
|
||||
|
||||
Add to `ApplicationRepository`:
|
||||
```php
|
||||
/**
|
||||
* @return array<string, array{teamer: int, office: int}>
|
||||
*/
|
||||
public function getCancellationStatsByHotel(
|
||||
\DateTimeImmutable $startDate,
|
||||
\DateTimeImmutable $endDate
|
||||
): array;
|
||||
```
|
||||
|
||||
Add to `DispositionRepository`:
|
||||
```php
|
||||
/**
|
||||
* @return array<string, array{teamer: int, office: int}>
|
||||
*/
|
||||
public function getCancellationStatsByHotel(
|
||||
\DateTimeImmutable $startDate,
|
||||
\DateTimeImmutable $endDate
|
||||
): array;
|
||||
```
|
||||
|
||||
**Date filtering:** Based on assignment travel dates (confirmed in discussion).
|
||||
|
||||
### Update Listeners (if Option B)
|
||||
|
||||
Modify `RejectApplicationListener` and `InvalidateApplicationsListener` to set `cancelledBy = 'system'`.
|
||||
|
||||
### UI Changes
|
||||
|
||||
Add withdrawal functionality for teamers (if not already present) that sets `STATUS_WITHDRAWN`.
|
||||
|
||||
---
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
1. [ ] Finalize decision on automatic rejection tracking
|
||||
2. [ ] Add new constants and fields to `Application` entity
|
||||
3. [ ] Create database migration
|
||||
4. [ ] Update `ApplicationRepository` with statistics methods
|
||||
5. [ ] Update `DispositionRepository` with statistics methods
|
||||
6. [ ] Update listeners if tracking automatic rejections
|
||||
7. [ ] Add/update controllers for teamer withdrawal flow
|
||||
8. [ ] Write tests for new repository methods
|
||||
9. [ ] Run php-cs-fixer
|
||||
|
||||
---
|
||||
|
||||
## Related Files
|
||||
|
||||
### Entities
|
||||
- `src/Entity/Application.php`
|
||||
- `src/Entity/Disposition.php`
|
||||
- `src/Entity/Assignment.php`
|
||||
|
||||
### Repositories
|
||||
- `src/Repository/ApplicationRepository.php`
|
||||
- `src/Repository/DispositionRepository.php`
|
||||
|
||||
### Controllers
|
||||
- `src/Controller/Administrative/Disposition/CallOffController.php`
|
||||
- `src/Controller/Administrative/Assignment/CallOffController.php`
|
||||
- `src/Controller/Teamer/Disposition/DetailController.php`
|
||||
|
||||
### Listeners
|
||||
- `src/EventListener/InvalidateApplicationsListener.php`
|
||||
- `src/EventListener/RejectApplicationListener.php`
|
||||
|
||||
### Events
|
||||
- `src/Event/DispositionCalledOffEvent.php`
|
||||
- `src/Event/AssignmentCalledOffEvent.php`
|
||||
|
||||
### Forms
|
||||
- `src/Form/DispositionCallOffType.php`
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
- Filter statistics by **assignment travel dates**, not cancellation timestamp
|
||||
- Disposition tracking already works - only need repository query methods
|
||||
- Application tracking requires entity changes and migration
|
||||
- Consider whether teamer self-service withdrawal exists or needs to be added
|
||||
@@ -0,0 +1,140 @@
|
||||
# Operations: messenger workers and cron
|
||||
|
||||
Reference for how queued work actually gets executed in production. Describes the setup as
|
||||
it is — not a plan.
|
||||
|
||||
The governing fact, from which most of the rest follows:
|
||||
|
||||
> **Nothing in this repository starts a worker.** Both workers are crontab entries on the
|
||||
> host, outside version control. `deploy.php` only ever *stops* workers, and even that is
|
||||
> largely ineffective (see below). If you change how a queue is consumed, change it here
|
||||
> too, or the next person has to reverse-engineer it from the config.
|
||||
|
||||
---
|
||||
|
||||
## Transports
|
||||
|
||||
Defined in `config/packages/messenger.yaml`, all backed by the same Doctrine table
|
||||
(`MESSENGER_TRANSPORT_DSN=doctrine://default?auto_setup=0`):
|
||||
|
||||
| Transport | `queue_name` | Carries | Consumed by |
|
||||
|-----------|--------------|---------|-------------|
|
||||
| `async` | `default` | all ordinary mail, chat and SMS notifications | the async cron worker |
|
||||
| `mailing` | `mailing` | bulk teamer mailings only | the mailing cron worker |
|
||||
| `failed` | `failed` | anything that exhausted its retries | nobody — inspected by hand |
|
||||
| `sync` | — | dev and test only | the request itself |
|
||||
|
||||
The two live workers are isolated **by the `queue_name` column**, not by how they are
|
||||
started: the Doctrine transport's `get()` filters on it, so neither worker can see the
|
||||
other's messages. Ordinary mail therefore never queues behind a mailing of several hundred
|
||||
recipients.
|
||||
|
||||
Overlapping runs of the same worker are harmless. `Connection::get()` selects
|
||||
`FOR UPDATE SKIP LOCKED` inside a transaction and stamps `delivered_at` before committing,
|
||||
so two consumers can never be handed the same row. No `flock` is needed; an overlap only
|
||||
costs a second process.
|
||||
|
||||
## The cron entries
|
||||
|
||||
> **The async line below is a placeholder.** Read the real one off the host
|
||||
> (`crontab -l` as `p704161`) and transcribe it verbatim, including any differences in
|
||||
> flags, php path or logging — those differences are information.
|
||||
|
||||
```cron
|
||||
# ordinary mail, notifications
|
||||
*/15 * * * * cd /home/www/p704161/html/myep-team/current && /usr/local/bin/php bin/console messenger:consume async --time-limit=... --memory-limit=... --quiet >> /home/www/p704161/html/myep-team/shared/var/log/cron-messenger-async.log 2>&1
|
||||
|
||||
# bulk teamer mailings
|
||||
*/15 * * * * cd /home/www/p704161/html/myep-team/current && /usr/local/bin/php bin/console messenger:consume mailing --time-limit=300 --memory-limit=128M --quiet >> /home/www/p704161/html/myep-team/shared/var/log/cron-messenger-mailing.log 2>&1
|
||||
```
|
||||
|
||||
Why each part is the way it is:
|
||||
|
||||
- **`/usr/local/bin/php`, not bare `php`.** `deploy.php` sets `bin/php` to this path
|
||||
explicitly for both hosts, because the host's default CLI is not the right one. Cron's
|
||||
`PATH` is typically only `/usr/bin:/bin`. `vendor/` is rsynced from a developer machine
|
||||
and there is no `deploy:vendors`, so the CLI must match the PHP the dependencies were
|
||||
installed against (8.4). Deployments run `composer check-platform-reqs` locally before
|
||||
rsync; because `config.platform.php` is pinned in `composer.json`, this only verifies
|
||||
that the locked dependencies resolve against the PHP and extension set we target — it
|
||||
does not probe the staging or production runtime. Keeping both FPM and
|
||||
`/usr/local/bin/php` on staging and production at PHP 8.4 is a manual precondition of
|
||||
every deploy.
|
||||
- **`--time-limit=300`** on the mailing worker. An 800-recipient mailing needs roughly
|
||||
200–360 seconds of worker time — the fan-out renders 800 Twig bodies and inserts 800
|
||||
rows, then the mails go out at about 3–5 per second over a reused SMTP connection. So a
|
||||
mailing usually drains in one window and occasionally spills into the next; worst case
|
||||
end to end is about half an hour. Raise it toward 840 for lower latency at the cost of an
|
||||
effectively always-on process, lower it for a lighter footprint. Anything under 900
|
||||
guarantees two runs cannot overlap.
|
||||
- **Stopping mid-mailing is safe.** The time limit is only checked *between* messages
|
||||
(`StopWorkerOnTimeLimitListener` listens on `WorkerRunningEvent`), so it never truncates
|
||||
a send or a half-finished fan-out. Unhandled rows keep `delivered_at = NULL` and the next
|
||||
run picks them up.
|
||||
- **`--memory-limit` is a recycle trigger, not a safety net.** It too is only checked
|
||||
between messages, so it cannot protect the fan-out, which is the memory peak. That is
|
||||
down to the CLI `memory_limit` ini — keep it at 256M or more.
|
||||
- **`--quiet` plus a redirect.** `messenger:consume` prints a banner on every run and
|
||||
unredirected cron output is mailed to the crontab user, which would be 96 mails a day.
|
||||
Nothing is lost: errors still reach `var/log/framework.prod.log` through the prod monolog
|
||||
handler. Redirect into the **shared** `var/log`, which is in `shared_dirs` and survives
|
||||
deploys — never anywhere under `var/cache`, which is per-release by design.
|
||||
- **No `APP_ENV=prod` on the line.** `bin/console` loads Dotenv relative to the project
|
||||
dir, and `APP_ENV` lives in the shared `.env.local`. A real environment variable takes
|
||||
precedence over that file, so putting it in the crontab would create a second source of
|
||||
truth and would need a different line for staging, which runs on the same host under the
|
||||
same user.
|
||||
|
||||
## Deploys do not restart workers
|
||||
|
||||
`deploy.php` ends with `deploy:stop-workers`, which runs `messenger:stop-workers`. That
|
||||
writes a restart timestamp into a cache pool under `%kernel.cache_dir%`, i.e. into the
|
||||
**new release**. A worker that is already running was started from the previous release and
|
||||
stays pinned to that release's `var/cache` for its whole life, so it never sees the flag.
|
||||
|
||||
**`--time-limit` is therefore what actually bounds a worker's life and what makes new code
|
||||
take effect** — up to five minutes after a deploy for the mailing worker. This is not worth
|
||||
"fixing" by moving `var/cache` into `shared_dirs`; that would break cache warming and
|
||||
opcache invalidation far worse than the problem it solves.
|
||||
|
||||
Related: `keep_releases: 3`. A worker must not outlive three deploys, or it will be running
|
||||
out of a directory that has been deleted. At a five-minute lifetime this cannot happen.
|
||||
|
||||
## When something goes wrong
|
||||
|
||||
```bash
|
||||
bin/console messenger:stats # queue depths, including failed
|
||||
bin/console messenger:failed:show # what failed and why
|
||||
bin/console messenger:failed:show <id> -vv # the full exception
|
||||
bin/console messenger:failed:retry # requeue, interactively
|
||||
```
|
||||
|
||||
Nothing watches the `failed` transport, so a failed mailing stays silent until somebody
|
||||
looks. Check it after any large mailing.
|
||||
|
||||
The scheduler's `email_on_failure` does not help here, and covers less than it appears to:
|
||||
a task only counts as failed when its command exits non-zero, and `App\Command\CronCommand`
|
||||
always returns `Command::SUCCESS` — even where it renders `$io->error()` for a failed log
|
||||
flush. So none of the five services it orchestrates can currently raise an alert.
|
||||
|
||||
Three failure modes worth knowing because they are quiet rather than loud:
|
||||
|
||||
- **`.env.local` missing from the deploy path.** `APP_ENV` falls back to `dev` and
|
||||
`MAILING_MAILER_DSN` to `null://null`. The worker then consumes every message and
|
||||
delivers nothing, reporting success and recording no failure. The quietest failure in the
|
||||
whole system.
|
||||
- **No worker consuming `mailing`.** Mailings queue up indefinitely while the admin is told
|
||||
they are on their way. `messenger:stats` shows it immediately.
|
||||
- **Wrong PHP CLI.** A version mismatch against the rsynced `vendor/` fails at parse time,
|
||||
inside a cron job nobody reads. This is why the log redirect exists.
|
||||
|
||||
## Scheduled tasks are a different mechanism
|
||||
|
||||
`config/packages/zenstruck_schedule.yaml` holds the daily jobs (`app:cron`,
|
||||
`app:myep-import`, `app:teamer-status`, …), driven by a separate `schedule:run` cron entry.
|
||||
|
||||
**Do not put `messenger:consume` in there.** The bundle runs tasks in-process and
|
||||
sequentially, so a consume task holds the entire schedule for its duration — every later
|
||||
task waits, and the next `schedule:run` starts concurrently because no task declares
|
||||
`withoutOverlapping`. This was tried and reverted (`dc796849`, "remove messenger from
|
||||
scheduler config to be run separately"). Workers belong in their own crontab lines.
|
||||
@@ -0,0 +1,358 @@
|
||||
# Statistics: event collection and evaluation
|
||||
|
||||
Reference for how statistics are collected, what is stored, and how to read or extend it.
|
||||
Describes the behaviour as implemented — not a plan. Evaluation screens for these metrics do
|
||||
not exist yet; this is what they will be built on.
|
||||
|
||||
The governing rule, from which the rest follows:
|
||||
|
||||
> **Columns are for what you `GROUP BY`. The payload is for what you read back.**
|
||||
>
|
||||
> A value that will end up in a `GROUP BY` needs its own indexed column. Everything else —
|
||||
> old and new values, reasons, scopes, flags — lives in the JSON `payload`. That split is
|
||||
> what lets a new metric ship without a migration, and it is the only rule that has to hold
|
||||
> as the table grows.
|
||||
|
||||
Two corollaries that surprise people:
|
||||
|
||||
- **Rows are never updated or deleted.** A statistics event is a record of something that
|
||||
happened, not a view of current state. If the answer can be recomputed from live data at
|
||||
any time, it does not belong here — see *What does not belong* below.
|
||||
- **Nothing here is a foreign key.** `assignment_id` and friends are plain integers, so
|
||||
statistics outlive the rows they describe. Applications are hard deleted; their history is
|
||||
not.
|
||||
|
||||
---
|
||||
|
||||
## Why the table exists
|
||||
|
||||
Most of the metrics could not be answered from live data:
|
||||
|
||||
- `Assignment::$jobProfile` holds only the current value. Every edit destroyed the previous
|
||||
one permanently.
|
||||
- Applications are hard deleted (no soft delete), so any count over them silently shrank
|
||||
over time. Dispositions are hard deleted too, and leave even less behind — no `createdAt`,
|
||||
no trace of the teamer they were on.
|
||||
- Call-offs persisted, but not *when* they happened — `updatedAt` is overwritten by any
|
||||
later edit — nor at what scope.
|
||||
|
||||
The `log` table is not an alternative. It is a monolog dump (`App\Logging\DatabaseHandler`)
|
||||
with free-form `context`/`extra`, excluded from migrations by
|
||||
`schema_filter: ~^(?!log)~` in `config/packages/doctrine.yaml`. It is an audit trail for
|
||||
humans to read, not a schema to aggregate over.
|
||||
|
||||
---
|
||||
|
||||
## The table
|
||||
|
||||
`statistics_event`, created by `migrations/Version20260826000000.php`, mapped by
|
||||
`src/Entity/StatisticsEvent.php`.
|
||||
|
||||
| Column | Type | Purpose |
|
||||
|--------|------|---------|
|
||||
| `id` | int, PK | |
|
||||
| `name` | varchar(64) | the metric, `enumType: StatisticsEventName` |
|
||||
| `occurred_at` | datetime | when it happened |
|
||||
| `actor_id` | int, null | `User::getId()` of whoever acted |
|
||||
| `actor_label` | varchar(180), null | their `getUserIdentifier()` |
|
||||
| `actor_role` | varchar(32), null | `enumType: StatisticsActorRole` |
|
||||
| `assignment_id` | int, null | dimension |
|
||||
| `disposition_id` | int, null | dimension |
|
||||
| `application_id` | int, null | dimension |
|
||||
| `teamer_id` | int, null | dimension |
|
||||
| `destination_id` | int, null | dimension |
|
||||
| `job_profile_id` | int, null | dimension |
|
||||
| `hotel_code` | varchar(8), null | **normalized** code, frozen at write time |
|
||||
| `payload` | json | everything metric-specific |
|
||||
|
||||
Indexed on `(name, occurred_at)`, `(name, hotel_code)`, `(assignment_id)`, `(teamer_id)`.
|
||||
|
||||
### Two traps in the columns
|
||||
|
||||
**`hotel_code` is normalized here and raw in `feedback`.** This table stores `ALB`;
|
||||
`feedback.hotel_code` stores `SERALB` and normalizes at query time. Same column name,
|
||||
opposite conventions — comparing or joining them directly returns nothing, silently. The
|
||||
`SER`-stripping rule lives in `Destination::getHotelCodeNormalized()`
|
||||
(`src/Entity/Destination.php`), and is reimplemented in DQL in `DispositionRepository` and
|
||||
in raw SQL in `FeedbackRepository`. Reuse the entity method in PHP; do not add a fourth copy.
|
||||
|
||||
**Dimensions are frozen at write time, deliberately.** A later correction to a destination's
|
||||
hotel code does not rewrite past periods. This is the same pattern `Feedback::fromAssignment()`
|
||||
(`src/Entity/Feedback.php`) already uses.
|
||||
|
||||
---
|
||||
|
||||
## The five metrics
|
||||
|
||||
Defined in `src/Enum/StatisticsEventName.php`. Adding a case here plus one collector that
|
||||
records it is the entire cost of a new metric.
|
||||
|
||||
| Name | Recorded when | Payload |
|
||||
|------|---------------|---------|
|
||||
| `assignment.job_profile_changed` | the job profile column changes on any write path | `from`, `to` (each `{id, name}` or null) |
|
||||
| `assignment.called_off` | an assignment's status becomes `called_off` | `dispositions_affected`, `previous_status` |
|
||||
| `disposition.called_off` | a disposition's status becomes `called_off` | `called_off_by`, `scope`, `reason`, `previous_status` |
|
||||
| `disposition.deleted` | a disposition is hard deleted, on any delete path | `previous_status`, `remarks`, `called_off_by` |
|
||||
| `application.created` | an application is persisted, by any path | — |
|
||||
|
||||
Which dimensions each one fills:
|
||||
|
||||
| Metric | assignment | destination | job_profile | hotel_code | teamer | disposition | application |
|
||||
|--------|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|
||||
| `assignment.job_profile_changed` | ✓ | ✓ | ✓ | ✓ | — | — | — |
|
||||
| `assignment.called_off` | ✓ | ✓ | ✓ | ✓ | — | — | — |
|
||||
| `disposition.called_off` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | — |
|
||||
| `disposition.deleted` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | — |
|
||||
| `application.created` | ✓ | ✓ | ✓ | ✓ | ✓ | — | ✓ |
|
||||
|
||||
`disposition.deleted` carries `remarks` because a hard delete destroys the entity outright:
|
||||
the justification an admin types on the delete form (`DeleteController`) lives nowhere else
|
||||
afterwards. `called_off_by` is set only when the placement had already been called off before
|
||||
it was deleted.
|
||||
|
||||
### Call-offs: three different "who" and "how far"
|
||||
|
||||
This is the part most likely to be misread when building a chart.
|
||||
|
||||
- **`actor_role`** — who *clicked*, from the security token. Always `admin` for a call-off;
|
||||
only administrative users can perform one.
|
||||
- **`payload.called_off_by`** — whose *decision* it was, `teamer` or `office`, as declared by
|
||||
an admin on the call-off form (`src/Form/DispositionCallOffType.php`). There is no
|
||||
teamer-initiated call-off route; a teamer's decision is recorded on their behalf.
|
||||
- **`payload.scope`** — how far it *reached*, `assignment` or `disposition`
|
||||
(`src/Enum/CallOffScope.php`). The office can cancel one placement just as a
|
||||
whole trip can fall away, so `called_off_by` does not imply this.
|
||||
|
||||
Cancelling a trip cascades onto every disposition on it in one flush, so **one office
|
||||
decision produces one `assignment.called_off` row plus N `disposition.called_off` rows**.
|
||||
Counting the disposition rows therefore counts *affected placements*, not decisions. To count
|
||||
decisions:
|
||||
|
||||
```sql
|
||||
-- decisions
|
||||
SELECT COUNT(*) FROM statistics_event WHERE name = 'assignment.called_off';
|
||||
SELECT COUNT(*) FROM statistics_event
|
||||
WHERE name = 'disposition.called_off'
|
||||
AND JSON_UNQUOTE(JSON_EXTRACT(payload,'$.scope')) = 'disposition';
|
||||
|
||||
-- affected placements
|
||||
SELECT COUNT(*) FROM statistics_event WHERE name = 'disposition.called_off';
|
||||
```
|
||||
|
||||
`assignment.called_off` carries `dispositions_affected` so a cancelled trip's cost reads off
|
||||
a single row. It is frequently `0` — a trip cancelled before anyone was staffed is still a
|
||||
decision worth counting.
|
||||
|
||||
---
|
||||
|
||||
## How events are recorded
|
||||
|
||||
One mechanism, writing through `App\Service\Statistics\StatisticsRecorder`.
|
||||
|
||||
**Doctrine changesets** — `src/EventListener/StatisticsChangeSetListener.php`, on `onFlush`
|
||||
and `postFlush`. Used for state changes. `onFlush` is the only place the *previous* value
|
||||
still exists, and it catches every write path — edit form, duplication, imports, console —
|
||||
rather than the one route someone remembered to hook. Recording happens in `postFlush`, after
|
||||
commit, so a rolled-back flush leaves no phantom statistics: the batch belongs to one flush,
|
||||
is cleared at the start of the next, and goes away with the service reset that a worker does
|
||||
between messages.
|
||||
|
||||
The listener itself knows nothing about any metric. It owns only the timing, and delegates
|
||||
to **collectors** — implementations of
|
||||
`App\Service\Statistics\Collector\StatisticsCollectorInterface`, autoconfigured by interface
|
||||
so a new one needs no wiring and cannot be silently forgotten.
|
||||
|
||||
A collector receives the whole flush (`StatisticsFlush`), not one entity at a time. That is
|
||||
deliberate: `CallOffCollector` has to see which *assignments* are becoming `called_off`
|
||||
before it can tell a cascaded disposition from a teamer dropping out, and it counts the
|
||||
cascade to fill `dispositions_affected`. Neither is expressible from a single entity's
|
||||
changeset, and that distinction exists only inside the flush.
|
||||
|
||||
**Group collectors by concern, not by metric.** Both call-off metrics live in one collector
|
||||
because they can only be told apart together; a job profile change shares nothing with them
|
||||
and lives in its own. Order between collectors is irrelevant and nothing may depend on it.
|
||||
|
||||
Collectors see **insertions** as well as updates, so a metric about something being created
|
||||
is a collector too. `ApplicationCreatedCollector` reads `insertionsOf(Application::class)`
|
||||
rather than subscribing to `ApplicationCreatedEvent`, which is what an earlier version did.
|
||||
Reading the flush counts every path that persists an application; subscribing counted only
|
||||
the paths that remember to dispatch, and would have silently missed an admin applying on
|
||||
someone's behalf, an import, or a fixture.
|
||||
|
||||
A newly inserted entity has no id during `onFlush`. That is fine — Doctrine assigns it during
|
||||
the commit, and recording happens afterwards in `postFlush`.
|
||||
|
||||
Collectors see **deletions** too, through `deletionsOf()`. `DispositionDeletedCollector`
|
||||
reads them for the same reason: every delete path is counted, and a hard delete leaves
|
||||
nothing to reconstruct afterwards. Deletions carry one wrinkle the other two do not.
|
||||
Recording still happens in `postFlush`, but by then Doctrine has removed the entity from the
|
||||
identity map and **nulled its generated id** (`UnitOfWork::executeDeletions()`), so
|
||||
`StatisticsRecorder` can no longer resolve the dimension columns from it. A deletion
|
||||
collector therefore snapshots its dimensions itself, during `onFlush`, via
|
||||
`StatisticsDimensions::forDisposition()`, and passes them on the `CollectedStatisticsEvent`;
|
||||
the listener writes that array verbatim instead of resolving from the subject. Anything a
|
||||
deletion metric needs in its payload — status, the form `remarks` — has to be read the same
|
||||
way, off the still-attached entity, before the flush commits.
|
||||
|
||||
> **There is deliberately only one mechanism.** If a future metric is not a persisted state
|
||||
> change at all — an email sent, a login, a document downloaded — call `StatisticsRecorder`
|
||||
> directly from wherever that happens. Do not reintroduce a second listener layer for it.
|
||||
|
||||
**Writes go through raw DBAL**, not the ORM, so recording never touches the UnitOfWork — a
|
||||
listener running inside a flush would otherwise trigger a nested flush. Failures are caught
|
||||
and reported to the audit logger: statistics are a side effect and must never break the
|
||||
business action that produced them.
|
||||
|
||||
---
|
||||
|
||||
## Reading the data
|
||||
|
||||
`src/Repository/StatisticsEventRepository.php`.
|
||||
|
||||
```php
|
||||
public function countGroupedBy(
|
||||
StatisticsEventName $name,
|
||||
string $dimension, // whitelisted
|
||||
?\DateTimeImmutable $dateFrom = null,
|
||||
?\DateTimeImmutable $dateTo = null,
|
||||
StatisticsDateBasis $dateBasis = StatisticsDateBasis::SEASON,
|
||||
): array
|
||||
```
|
||||
|
||||
`getCountGroupedByQuery()` returns the same query unexecuted, which is how its semantics are
|
||||
pinned in tests without a database.
|
||||
|
||||
### A period means the season, not when the row was written
|
||||
|
||||
`StatisticsDateBasis::SEASON` (the default) filters on the destination's date range through
|
||||
`src/Repository/Filter/SeasonPeriodFilter.php`, which is shared with
|
||||
`DispositionRepository`. This matches every other statistics screen and
|
||||
`StatisticsFilterType`. Change what a season means there, once, not per screen.
|
||||
|
||||
`StatisticsDateBasis::OCCURRENCE` filters `occurred_at` instead. Reach for it deliberately,
|
||||
for genuine time series such as call-offs per month.
|
||||
|
||||
The two routinely disagree, because an application for the coming winter arrives months
|
||||
before the season it belongs to. Picking the wrong one does not fail — it quietly answers a
|
||||
different question from the screen next to it.
|
||||
|
||||
`SEASON` reaches the assignment through an arbitrary join and therefore excludes events with
|
||||
no `assignment_id`. All five metrics set it.
|
||||
|
||||
### Crossing with live data
|
||||
|
||||
The plain-integer dimension columns cost nothing in queryability — Doctrine's arbitrary join
|
||||
works:
|
||||
|
||||
```
|
||||
SELECT destination.hotel AS hotel, COUNT(event.id) AS applications
|
||||
FROM App\Entity\StatisticsEvent event
|
||||
JOIN App\Entity\Assignment assignment WITH assignment.id = event.assignmentId
|
||||
JOIN assignment.destination destination
|
||||
WHERE event.name = :name
|
||||
GROUP BY hotel
|
||||
```
|
||||
|
||||
Group by `hotel_code`, though, not by the hotel *name*: they are not one-to-one. One
|
||||
normalized code can span several destinations with different names.
|
||||
|
||||
---
|
||||
|
||||
## Backfilling
|
||||
|
||||
`bin/console app:statistics:backfill` seeds what current data still contains. Idempotent —
|
||||
it skips subjects already recorded — and supports `--dry-run`. It is never run automatically:
|
||||
nothing in `CronCommand`, the scheduler, or any deploy script invokes it.
|
||||
|
||||
It writes only to `statistics_event`, so it is safe to run against production at any time.
|
||||
|
||||
| Metric | Backfillable | `occurred_at` from |
|
||||
|--------|--------------|--------------------|
|
||||
| `application.created` | yes | `createdAt` — exact |
|
||||
| `disposition.called_off` | yes | `updatedAt` — **approximate** |
|
||||
| `assignment.called_off` | yes | `updatedAt` — **approximate** |
|
||||
| `assignment.job_profile_changed` | **no** | history does not exist |
|
||||
| `disposition.deleted` | **no** | the disposition is gone, nothing left to seed |
|
||||
|
||||
Every seeded row carries `"backfilled": true`; the approximate ones also carry
|
||||
`"approximate_date": true`. So the choice is per chart, never table-wide:
|
||||
|
||||
```sql
|
||||
WHERE JSON_EXTRACT(payload,'$.backfilled') IS NULL -- organically recorded only
|
||||
```
|
||||
|
||||
Three limits on seeded rows, all of which matter when charting:
|
||||
|
||||
- **Dates from `updatedAt` are a proxy**, since any later edit overwrites it. Exclude those
|
||||
rows from time series; keep them for totals.
|
||||
- **`actor_role` is `system`.** The command runs on the CLI with no security token, so the
|
||||
original actor is not recoverable.
|
||||
- **Frozen dimensions are frozen at *backfill* time**, not at the time of the event, and
|
||||
`scope` is left unset because it cannot be established retroactively.
|
||||
|
||||
---
|
||||
|
||||
## Extending it
|
||||
|
||||
The cost depends entirely on whether the new metric needs a new *dimension* or just a new
|
||||
*name*. Almost all need only a name.
|
||||
|
||||
**No migration — a new kind of event.** Add a case to `StatisticsEventName`, then one
|
||||
`StatisticsCollectorInterface` implementation that records it. Metric-specific detail goes in
|
||||
`payload`. `StatisticsChangeSetListener` is never edited. This covers most requests —
|
||||
application rejections, contract upload delays, feedback submitted, any status transition,
|
||||
anything created or deleted.
|
||||
|
||||
Reuse an existing collector when the new metric is part of a concern already covered —
|
||||
especially when it needs to see the same entities in the same flush. Write a new one when it
|
||||
is genuinely independent.
|
||||
|
||||
**One migration — a new dimension to slice by.** Only when you need to group by something no
|
||||
column carries (a season, a country, a fee band). Add the column and an index, fill it in
|
||||
`StatisticsRecorder`, and accept `NULL` on older rows. Resist when the dimension is already
|
||||
reachable through a join.
|
||||
|
||||
**Migration plus backfill — history for something not yet recorded.** Extend
|
||||
`StatisticsBackfillCommand` if current data still holds the answer. If it has been
|
||||
overwritten, as with job profiles, the metric simply starts the day it ships.
|
||||
|
||||
---
|
||||
|
||||
## What does not belong here
|
||||
|
||||
**Anything recomputable from live data that cannot be lost.** Feedback statistics
|
||||
(`src/Controller/Administrative/Statistics/`) are the worked example: `Feedback` already
|
||||
freezes its own dimensions at creation, is never deleted, and its aggregations average a
|
||||
per-question `ratings` array. It is already an append-only record; copying it into
|
||||
`statistics_event` would duplicate every row and lose the structure the charts need. This
|
||||
table counts occurrences — it has no shape for averaging structured arrays.
|
||||
|
||||
**Current state.** "How many applications are open right now" is a question for the
|
||||
`application` table. This table answers "how many were ever received", which is a different
|
||||
and equally valid question — just not the same one.
|
||||
|
||||
**An audit trail for humans.** That is the `log` table and `/admin/log`.
|
||||
|
||||
---
|
||||
|
||||
## Where things live
|
||||
|
||||
| Concern | Path |
|
||||
|---------|------|
|
||||
| Entity | `src/Entity/StatisticsEvent.php` |
|
||||
| Repository | `src/Repository/StatisticsEventRepository.php` |
|
||||
| Enums | `src/Enum/Statistics*.php` |
|
||||
| Write path | `src/Service/Statistics/StatisticsRecorder.php` |
|
||||
| Dimension columns resolved from an entity | `src/Service/Statistics/StatisticsDimensions.php` |
|
||||
| Flush orchestration | `src/EventListener/StatisticsChangeSetListener.php` |
|
||||
| Collectors (one per concern) | `src/Service/Statistics/Collector/` |
|
||||
| Flush context passed to collectors | `src/Service/Statistics/StatisticsFlush.php` |
|
||||
| What a period means, shared with the disposition statistics | `src/Repository/Filter/SeasonPeriodFilter.php` |
|
||||
| Backfill | `src/Command/StatisticsBackfillCommand.php` |
|
||||
| Migration | `migrations/Version20260826000000.php` |
|
||||
| Tests | `tests/EventListener/`, `tests/Repository/`, `tests/Service/Statistics/` |
|
||||
|
||||
Existing statistics screens, for the filter/handler pattern a future evaluation UI should
|
||||
reuse: `src/Controller/Administrative/Statistics/`,
|
||||
`src/Service/Common/StatisticsFilterHandler.php`, `src/Model/StatisticsFilterDto.php`,
|
||||
`src/Form/StatisticsFilterType.php`.
|
||||
@@ -0,0 +1,430 @@
|
||||
# User Roles: Assignment, Approval and Revocation
|
||||
|
||||
Reference for how a user account comes into existence, how it gains and loses roles, and
|
||||
how it gets blocked. Describes the behaviour as implemented — not a plan.
|
||||
|
||||
The governing rules, from which the rest follows:
|
||||
|
||||
> **BusPro is the source of truth for roles and hotel codes.** Both are synced on every
|
||||
> login, so anything it no longer reports is withdrawn.
|
||||
>
|
||||
> **The CRM may nominate, but never grant, an administrative role.** Only a super admin
|
||||
> turns a nomination into a privilege. The CRM's word alone is enough to take away access,
|
||||
> never to hand it out - and an administrator's word alone is enough for neither.
|
||||
|
||||
Two corollaries that surprise people:
|
||||
|
||||
- **A role cannot be handed out by hand.** The edit form approves what BusPro claims; it
|
||||
cannot add a role BusPro is silent about, nor remove one it reports. Same for hotel codes,
|
||||
which are display-only there.
|
||||
- **Every role stands on its own.** `ROLE_TEAMER` is not a base role others build on, and a
|
||||
Reisemanager is not a superset of a Hausleitung - somebody claiming both is nominated for
|
||||
both, and approved for each separately.
|
||||
|
||||
---
|
||||
|
||||
## Role catalogue
|
||||
|
||||
Defined in `User::ROLES` (`src/Entity/User.php`), these four are the only roles a human can
|
||||
assign:
|
||||
|
||||
| Role | Label | Granted by | Revoked by | Hierarchy |
|
||||
|------|-------|-----------|------------|-----------|
|
||||
| `ROLE_TEAM_ADMIN` | Admin | super admin, approving a CRM claim | the CRM, automatically | ⇒ `ROLE_ADMINISTRATIVE` |
|
||||
| `ROLE_MANAGER` | Reisemanager | super admin, approving a CRM claim | the CRM, automatically | ⇒ `ROLE_ADMINISTRATIVE` |
|
||||
| `ROLE_HOUSE_MANAGER` | Hausleitung | super admin, approving a CRM claim | the CRM, automatically | — |
|
||||
| `ROLE_TEAMER` | Teamer | the CRM, automatically | the CRM, automatically | — |
|
||||
|
||||
### A note on the `TEAM_` prefix
|
||||
|
||||
`ROLE_TEAM_ADMIN`, `ROLE_TEAM_ADMIN_PENDING` and `ROLE_TEAM_SUPER_ADMIN` were renamed from
|
||||
`ROLE_ADMIN`, `ROLE_ADMIN_PENDING` and `ROLE_SUPER_ADMIN`. The MyE&P identity provider is
|
||||
shared with a sibling portal that uses `ROLE_ADMIN` for a different privilege, so the plain
|
||||
name was ambiguous across the estate; the prefix makes it unambiguously *this* application's
|
||||
admin. The German label is unchanged — it still reads "Admin" everywhere in the UI.
|
||||
|
||||
Two consequences worth remembering:
|
||||
|
||||
- **`ROLE_ADMINISTRATIVE` is a different role and was not renamed.** It is granted only by
|
||||
the hierarchy, never stored, and it shares the old `ROLE_ADMIN` prefix — so any
|
||||
search-and-replace over role names must match on a word boundary
|
||||
(`ROLE_TEAM_ADMIN(?![A-Z_])`) or it will corrupt ~100 call sites silently.
|
||||
- **`ELIGIBLE_ROLES` in `MyEpAuthenticator` is a wire contract**, not an internal name: it is
|
||||
compared directly against the IdP's `roles` claim. It only works while MyE&P emits
|
||||
`ROLE_TEAM_ADMIN`, so the two sides have to move together.
|
||||
|
||||
`User::PENDING_ROLES` holds a marker for each of the three administrative roles, keyed by
|
||||
the role it stands for:
|
||||
|
||||
| Marker | Meaning |
|
||||
|--------|---------|
|
||||
| `ROLE_TEAM_ADMIN_PENDING` | the CRM claims this person is an admin, nobody has confirmed it |
|
||||
| `ROLE_MANAGER_PENDING` | likewise for Reisemanager |
|
||||
| `ROLE_HOUSE_MANAGER_PENDING` | likewise for Hausleitung |
|
||||
|
||||
Markers **grant nothing**. They appear in `getRoles()` and therefore in the security token,
|
||||
but no `access_control` rule, `role_hierarchy` entry or voter references them. Their only
|
||||
effects are cosmetic (rendered as "Admin (nicht freigeschaltet)") and organisational (they
|
||||
put the user on the approval list). `ROLE_TEAMER` has no marker: it needs no approval.
|
||||
|
||||
Two further roles are synthesized by `User::getRoles()` and never stored: `ROLE_USER` for
|
||||
everybody, and `ROLE_TEAM_SUPER_ADMIN` when the separate `superAdmin` boolean column is set. A
|
||||
validation callback (`User::validateSuperAdmin()`) refuses `superAdmin` without
|
||||
`ROLE_TEAM_ADMIN` alongside it — super admin is an elevation, never a standalone grant. The sync
|
||||
enforces the same rule from the other side: revoking `ROLE_TEAM_ADMIN` clears the flag, or the one
|
||||
role that outranks every check in the application would outlive the role it depends on.
|
||||
|
||||
### Storage and accessors
|
||||
|
||||
Everything except `superAdmin` lives in the single `roles` JSON column. Three accessors
|
||||
slice it, and picking the right one matters:
|
||||
|
||||
| Accessor | Returns |
|
||||
|----------|---------|
|
||||
| `getRoles()` | the column **plus** synthesized `ROLE_USER` / `ROLE_TEAM_SUPER_ADMIN` — what Symfony authorises against |
|
||||
| `getAssignedRoles()` | only the four real roles from the column — what the sync works on |
|
||||
| `getPendingRoles()` | only the markers |
|
||||
| `getNominatedRoles()` | the roles behind those markers, as `role => label` — what an approver acts on |
|
||||
|
||||
---
|
||||
|
||||
## Where roles come from: the CRM mapping
|
||||
|
||||
`ResponseParser::createCrmAttributesResponse()` walks the `selektionsmerkmale` of the
|
||||
`SelektionCRM` response and sets four booleans plus the hotel codes:
|
||||
|
||||
| CRM attribute | Recognised by | Sets |
|
||||
|---------------|---------------|------|
|
||||
| admin | attribute id `%bpn_crm_id_team_admin%`, selected | `isAdmin` |
|
||||
| Reisemanager | attribute id `%bpn_crm_id_manager%`, selected | `isManager` |
|
||||
| teamer | attribute id `%bpn_crm_id_teamer%`, selected | `isTeamer` |
|
||||
| Hausleitung | attribute id listed in `%bpn_crm_house_manager_ids%`, selected | `isHouseManager` + the hotel code that id maps to |
|
||||
|
||||
`bpn_crm_house_manager_ids` (`config/services.yaml`) maps a BusPro selection id to a hotel
|
||||
code. Entries whose code is not a house in the `houses` parameter are kept commented out:
|
||||
such a house manager could log in but would see no assignments and no dispositions at all.
|
||||
|
||||
`UserDataHandler::collectClaimedRoles()` turns those four booleans into plain role names -
|
||||
what the CRM says. Two things are made of that list: `toPendingRoles()` produces one marker
|
||||
per claimed administrative role (the roles are independent, so a Reisemanager who is also a
|
||||
Hausleitung gets both), and `collectRoles()` produces the markers plus a real `ROLE_TEAMER`,
|
||||
which is what a brand new account starts with.
|
||||
|
||||
### The shape of a BusPro response (important)
|
||||
|
||||
**BusPro always returns the full attribute tree.** Membership is expressed by the `auswahl`
|
||||
flag on each `<selektion>`, so a role somebody does not hold arrives as `auswahl="False"`,
|
||||
never as a missing element, and a person who holds nothing at all still receives every
|
||||
group. Sample payloads for both cases live in `tests/Resources/crm_attributes_granted.xml`
|
||||
and `crm_attributes_revoked.xml`, and `ResponseParserTest` asserts against them.
|
||||
|
||||
This is what makes the demotion path below safe: an **empty group set cannot occur in a real
|
||||
response**, so it is a reliable signal that the response is degraded rather than that the
|
||||
roles were revoked.
|
||||
|
||||
> Note what that check does *not* cover. The three ids are compared with `===`, and a wrong
|
||||
> or unset one produces a perfectly well-formed response in which nobody holds anything —
|
||||
> every user logging in would be demoted, one at a time. The attribute ids are therefore
|
||||
> deployment-critical configuration, not a detail:
|
||||
>
|
||||
> | Parameter | Attribute |
|
||||
> |-----------|-----------|
|
||||
> | `APP_BPN_CRM_ID_TEAM_ADMIN` | the team-admin selection — **not** the old portal-wide `Admin` (1292) |
|
||||
> | `APP_BPN_CRM_ID_MANAGER` | `Manager` |
|
||||
> | `APP_BPN_CRM_ID_TEAMER` | `E&P Teamer - allg. Merkmal` |
|
||||
>
|
||||
> Matching is by id and never by label, so `Preisrechner Admin` does not trip the admin flag.
|
||||
>
|
||||
> `APP_BPN_CRM_ID_TEAM_ADMIN` was renamed from `APP_BPN_CRM_ID_ADMIN` with the
|
||||
> `ROLE_TEAM_ADMIN` rename, and its **value has to change too**. The old value 1292 is the
|
||||
> portal-wide admin selection, which still means `ROLE_ADMIN` in the sibling portal — keeping
|
||||
> it would have left this app granting its admin off the very selection the rename was meant
|
||||
> to stop sharing. It ships as `0` — a valid int that matches no attribute, so the container
|
||||
> boots but nobody is granted the role — and must be set to the new selection's id.
|
||||
>
|
||||
> **This is a hard cutover.** `revokeUnclaimedRoles()` withdraws any granted role the CRM no
|
||||
> longer claims, and `revokeSuperAdminWithoutRoleAdmin()` takes the super admin flag down with
|
||||
> `ROLE_TEAM_ADMIN`. So the new selection must exist **and already be assigned to every admin**
|
||||
> in BusPro before this is deployed; otherwise each of them is demoted on their next login and
|
||||
> needs a super admin to re-approve. Admins who also hold `ROLE_TEAMER` degrade to teamer
|
||||
> access; an admin without it is blocked outright by `disableForRevokedCrmRoles()`.
|
||||
>
|
||||
> `bpn_crm_house_manager_ids` (`config/services.yaml`) is deployment-critical for the same
|
||||
> reason, and more sharply so: since roles are synced, an id missing from that map does not
|
||||
> merely fail to nominate a Hausleitung, it **revokes** the role from everyone holding it, one
|
||||
> login at a time, with re-approval manual per user. Entries whose hotel code is not a house
|
||||
> in the `houses` parameter are deliberately commented out there — a person holding only such
|
||||
> a Hausleitung claims nothing at all and is blocked (see 4) rather than left with a role that
|
||||
> shows them no data.
|
||||
>
|
||||
> `APP_BPN_DEFAULT_HOTEL_CODE` is a testing affordance: when set, **every** admin also
|
||||
> becomes a house manager for that hotel. It must stay empty outside local development.
|
||||
|
||||
---
|
||||
|
||||
## Lifecycle
|
||||
|
||||
### 1. First login
|
||||
|
||||
`BpnAuthenticator::getOrCreateLocalUser()` → `UserDataHandler::createLocalUser()` writes
|
||||
`collectRoles()` verbatim, together with the hotel codes from the Hausleitung attributes.
|
||||
|
||||
A CRM admin who is not also a teamer therefore starts with `['ROLE_TEAM_ADMIN_PENDING']` and no
|
||||
privileges at all: they can authenticate, but `UserChecker` refuses the session until a
|
||||
super admin approves them.
|
||||
|
||||
### 2. Every subsequent login — the sync
|
||||
|
||||
`UserDataHandler::updateLocalUser()` refreshes name, email and the teamer record, and hands
|
||||
the roles to **`syncRoles()`**, which is the whole policy in four steps:
|
||||
|
||||
1. **revoke** every granted role the CRM no longer claims. This is what makes BusPro the
|
||||
source of truth, and it applies to `ROLE_TEAMER` as much as to the administrative roles.
|
||||
2. **clear the super admin flag** when `ROLE_TEAM_ADMIN` was among them — `ROLE_TEAM_SUPER_ADMIN` is
|
||||
synthesized from a separate column and would otherwise survive its own precondition.
|
||||
3. **`refreshPendingRoles()`** recomputes the marker set from the current claims. A marker
|
||||
whose real role is already granted is dropped — an approved role is never marked again.
|
||||
It runs *after* the revocation, so a role just revoked is not immediately marked again.
|
||||
4. **`grantTeamerRole()`** adds `ROLE_TEAMER` when the CRM claims it. Grant-only in itself;
|
||||
withdrawing it is step 1's business.
|
||||
|
||||
**`syncHotelCodes()`** then replaces the hotel codes with the ones the CRM reports, so a
|
||||
Hausleitung who moves house is not left seeing the old one.
|
||||
|
||||
Nothing on this path can raise a privilege: step 3 only ever produces markers.
|
||||
|
||||
The same two methods run on the MyE&P SSO path (`MyEpAuthenticator`), against the eligible
|
||||
roles it reports — one policy, two identity sources.
|
||||
|
||||
### 3. Approval — turning a marker into a role
|
||||
|
||||
`/admin/system/user/edit/{id}`, `UserType`, gated by `UserVoter::EDIT`: super admin only,
|
||||
never yourself, never while impersonating.
|
||||
|
||||
The page keeps three things apart, because they follow three different rules:
|
||||
|
||||
| Block | What it is |
|
||||
|-------|-----------|
|
||||
| **Aus BusPro** | information, not a form: granted roles, houses, last login. Synced on every login and editable nowhere in this application |
|
||||
| **Freischaltung** | one action per nomination — a button, a confirmation dialog, its own route (`ApproveRoleController`). Only shown when the user carries a marker |
|
||||
| **Account** | the actual form (`UserType`): super admin, block, block reasons |
|
||||
|
||||
None of this is a disabled form field. Roles and hotel codes are simply not fields, so there
|
||||
is nothing to submit and nothing that looks editable but is not.
|
||||
|
||||
**Approval is its own act**, deliberately not a checkbox on the form: it grants a privilege,
|
||||
so it is confirmed on its own, logged on its own, and cannot happen as a side effect of
|
||||
saving an unrelated setting. `UserDataHandler::approveRole()` refuses any role the user has
|
||||
no marker for, so a hand-crafted URL cannot grant one the CRM never claimed, and the check
|
||||
runs again on submit to catch a sync that revoked the claim while the dialog was open.
|
||||
|
||||
A denial is not recorded anywhere: as long as the CRM keeps claiming the role, the
|
||||
nomination is back on the next login.
|
||||
|
||||
**Super admin** is only offered to somebody who already holds `ROLE_TEAM_ADMIN` — approve first,
|
||||
elevate afterwards. The one exception is a flag that outlived its role, which stays editable
|
||||
so the account can be saved at all while `User::validateSuperAdmin()` is violated; the sync
|
||||
clears it (see 2), so it should never occur in practice.
|
||||
|
||||
The user list marks nominations with their own badge — it is the only place an approver would
|
||||
look for them.
|
||||
|
||||
### 4. Losing everything — the block
|
||||
|
||||
Revocation of an *individual* role is step 1 of the sync above. This section is the stronger
|
||||
case: the CRM claims **nothing at all**, which is not a demotion but an exit.
|
||||
|
||||
Evaluated on every login, in `BpnAuthenticator::getOrCreateLocalUser()`, before the local
|
||||
user is even loaded. If `collectClaimedRoles()` is empty, the CRM grants this person nothing
|
||||
in this application, so they are not a user of it:
|
||||
|
||||
| Situation | Outcome |
|
||||
|-----------|---------|
|
||||
| no local account | login refused, **no account created** |
|
||||
| existing account | `disableForRevokedCrmRoles()` blocks it, and the user is still returned so `UserChecker` can state the reason |
|
||||
| account already blocked | left completely alone — an existing block may be disciplinary and must never be overwritten |
|
||||
|
||||
`disableForRevokedCrmRoles()` sets `disabledAt`, a public `disabledReason` ("Für deinen
|
||||
Account liegt in BusPro keine Berechtigung mehr vor.") and an internal one, and drops all
|
||||
pending markers — they no longer reflect the CRM. **Granted roles are kept**, so the account
|
||||
stays reviewable.
|
||||
|
||||
Two preconditions guard this branch, because "no roles" is otherwise indistinguishable from
|
||||
"the CRM told us nothing". They now protect the sync as well: a degraded response that got
|
||||
past them would not merely block one account, it would strip the roles of every user logging
|
||||
in.
|
||||
|
||||
1. the response must be a `CrmAttributesResponse` — BusPro answers with a notification
|
||||
record on its own errors;
|
||||
2. it must carry at least one attribute group. Since a real response always carries the
|
||||
full tree (see above), an empty group set means an empty payload or a changed schema —
|
||||
which would otherwise block every user who logs in. That case refuses the single login,
|
||||
logs a warning, and leaves the account untouched.
|
||||
|
||||
A misconfigured attribute id defeats both checks, as noted above. There is no signal inside
|
||||
the response that distinguishes it from a genuine revocation.
|
||||
|
||||
**Regaining a CRM role does not unblock anything.** The next login skips the demotion branch,
|
||||
refreshes markers as usual, and `UserChecker` still refuses on `disabledAt`. Unblocking is
|
||||
always a human decision.
|
||||
|
||||
### 5. Blocking and unblocking by hand
|
||||
|
||||
Two surfaces, with deliberately different authority — the super admin rule is about
|
||||
administrative users, teamers are an admin's business:
|
||||
|
||||
| Surface | Who | Notes |
|
||||
|---------|-----|-------|
|
||||
| `/admin/teamer/disable-user/{uuid}` and `/administrative/teamer/enable-user/{uuid}` | `ROLE_TEAM_ADMIN` | teamers; public reason mandatory, internal optional |
|
||||
| "Account gesperrt" checkbox on the user edit form | super admin (`UserVoter`) | everyone else; both reasons optional |
|
||||
|
||||
Both go through `User::setDisabled()`, which is a no-op when the state is unchanged — saving
|
||||
an unrelated edit never resets the block timestamp — and clears both reasons on unblocking.
|
||||
`UserType` additionally clears them in a `POST_SUBMIT` listener, so unticking the box without
|
||||
emptying the prefilled textareas cannot leave a stale reason behind.
|
||||
|
||||
---
|
||||
|
||||
## The login gate
|
||||
|
||||
`UserChecker::checkPreAuth()` runs after BusPro has accepted the credentials, in this order:
|
||||
|
||||
| Condition | Message |
|
||||
|-----------|---------|
|
||||
| `disabledAt` set | `Dein Account wurde gesperrt: «disabledReason»` |
|
||||
| no role from `User::ROLES`, but markers present | `Dein Account wurde noch nicht freigeschaltet.` |
|
||||
| no role from `User::ROLES` at all | `Keine gültige Rolle zugewiesen.` |
|
||||
|
||||
Only then does `onAuthenticationSuccess()` stamp `lastLoginAt` and redirect to
|
||||
`User::getDefaultRoute()` — admin, manager, house manager or teamer area, in that order of
|
||||
precedence.
|
||||
|
||||
Note that a refused login never updates `lastLoginAt`, which is what the teamer list's
|
||||
`includeInactive` filter keys on.
|
||||
|
||||
---
|
||||
|
||||
## Who is listed where
|
||||
|
||||
| List | Contains |
|
||||
|------|----------|
|
||||
| `/admin/system/user` (`UserRepository::getAdministrativeUsers()`) | holders of an administrative role, holders of a marker, and blocked accounts **without** a teamer record |
|
||||
| teamer list (`TeamerRepository`) | teamers; blocked ones are badged, and stale ones need the `includeInactive` filter |
|
||||
|
||||
The blocked-accounts clause exists because a CRM demotion can leave a user with no role that
|
||||
would list them — a candidate awaiting approval loses their markers and would otherwise
|
||||
become invisible with no page to be unblocked from. Blocked teamers are excluded because
|
||||
they already have one.
|
||||
|
||||
---
|
||||
|
||||
## Accountzustände
|
||||
|
||||
An account is in exactly one of three states. **Block and deletion are independent flags**,
|
||||
so both can be set at once — a disciplinary block has to survive a deletion and the restore
|
||||
that follows it.
|
||||
|
||||
| Zustand | Spalte | Anmeldung | Listen, Formulare, Mailings | Gesetzt von |
|
||||
|---------|--------|-----------|------------------------------|-------------|
|
||||
| aktiv | – | ja | enthalten | – |
|
||||
| gesperrt | `user.disabled_at` | abgelehnt, Grund wird angezeigt | **weiterhin enthalten** | Admin (`DisableUserController`), CRM (`disableForRevokedCrmRoles()`) |
|
||||
| gelöscht | `teamer.deleted_at` + `user.deleted_at` | abgelehnt | ausgeschlossen | ausschließlich `AccountDeletionHandler` |
|
||||
|
||||
A blocked teamer stays in the list on purpose: that list is the only place the block can be
|
||||
lifted from. A deleted one is removed from every forward-looking process instead, which is
|
||||
what makes the deletion legally meaningful.
|
||||
|
||||
### Why two columns
|
||||
|
||||
Both `Teamer` and `User` carry their own `deleted_at`. Teamers without a user exist, users
|
||||
without a teamer exist, and there is no Doctrine SQL filter — every query filters by hand on
|
||||
the alias it already has (`teamer` on the teamer side, `user` on the login and mail side). A
|
||||
one-sided flag would force a join into roughly ten queries, each an opportunity to drop rows
|
||||
through the wrong join type.
|
||||
|
||||
`src/Service/Teamer/AccountDeletionHandler.php` is the **only** place that writes either
|
||||
flag, which is what keeps the two from drifting apart. Do not call `setDeleted()` on a Teamer
|
||||
or User anywhere else.
|
||||
|
||||
### What a deletion excludes
|
||||
|
||||
`TeamerRepository::getListQuery()` (behind `includeDeleted`) and `getAutocompletionData()`,
|
||||
`UserRepository::getAdministrativeUsers()` / `getUsersByRoleAndHotelCode()` /
|
||||
`getAutocompletionData()`, `AvailabilityRepository::getListQuery()` (custom branch) and
|
||||
`getSelectableForTeamer()`, every teamer-facing handler in `EmailNotificationSubscriber`, the
|
||||
cron reminders in `UploadReminderService` and `DispositionReminderService`, new applications
|
||||
(`ApplicationValidator`) and impersonation (`ImpersonationVoter`).
|
||||
|
||||
### What stays visible
|
||||
|
||||
Everything already recorded: dispositions, applications, uploads, feedback, and the contracts
|
||||
and invoices rendered from them. `AssignmentRepository`, `DispositionRepository`,
|
||||
`ApplicationRepository`, `UploadRepository`, `FeedbackRepository`, the dashboards and all of
|
||||
`src/Service/Pdf/` deliberately do **not** filter. Deleted teamers are marked in the UI with
|
||||
`templates/_partials/_teamer_deleted_badge.html.twig`; PDFs are left untouched so that a
|
||||
document regenerated after a deletion is identical to the one issued before it.
|
||||
|
||||
### Login and CRM
|
||||
|
||||
`UserChecker` refuses a deleted account before it checks the block, so the deletion message
|
||||
wins. `BpnAuthenticator::getOrCreateLocalUser()` returns a deleted user untouched before any
|
||||
sync branch runs: nothing is written back, no role is granted or revoked, `lastLoginAt` is not
|
||||
bumped. `UserDataHandler::findLocalUser()` still matches a deleted account **by email** —
|
||||
excluding it there would make the caller take the person for unknown and create a second
|
||||
account, resurrecting them under a new row.
|
||||
|
||||
Symfony's `ContextListener` does not re-run the user checker when restoring a session from
|
||||
its cookie, so `DeletedUserSubscriber` ends the session of anyone deleted while logged in.
|
||||
|
||||
---
|
||||
|
||||
## Deliberate decisions and known edges
|
||||
|
||||
- **Markers grant nothing but are stored in `roles`.** Convenient (one column, one query),
|
||||
but it means `getRoles()` contains strings that are not roles in any meaningful sense.
|
||||
Always intersect against `User::ROLES` when asking "may this user do anything at all" —
|
||||
`UserChecker` does exactly that.
|
||||
- **`ROLE_TEAMER` is granted but never withdrawn automatically.** See 2. A strict mirror
|
||||
would strip manual grants on the next login.
|
||||
- **A denied nomination reappears.** See 3. There is no "rejected" state.
|
||||
- **Sole-super-admin lockout.** A super admin who is not a teamer and loses their CRM admin
|
||||
attribute is auto-blocked, and only another super admin can unblock them. With no second
|
||||
super admin there is no route back through the UI.
|
||||
- **`getUsersByRoleAndHotelCode()` matches granted roles only**, so notification recipients
|
||||
(e.g. feedback reminders) never include people who are merely nominated. That is intended.
|
||||
|
||||
---
|
||||
|
||||
## File map
|
||||
|
||||
| Concern | File |
|
||||
|---------|------|
|
||||
| role constants, accessors, `setDisabled()` | `src/Entity/User.php` |
|
||||
| CRM → roles mapping | `src/BusProNet/ResponseParser.php`, `src/BusProNet/Model/CrmAttributesResponse.php` |
|
||||
| create / update / demote | `src/BusProNet/UserDataHandler.php` |
|
||||
| login flow and the demotion branch | `src/Security/BpnAuthenticator.php` |
|
||||
| login gate | `src/Security/UserChecker.php` |
|
||||
| who may edit a user | `src/Security/Voter/UserVoter.php` |
|
||||
| who may impersonate | `src/Security/Voter/ImpersonationVoter.php` |
|
||||
| approval / block form | `src/Form/UserType.php`, `templates/admin/system/user/_form.html.twig` |
|
||||
| teamer block / unblock | `src/Controller/Admin/Teamer/DisableUserController.php`, `src/Form/DisableUserType.php` |
|
||||
| soft delete / restore | `src/Service/Teamer/AccountDeletionHandler.php`, `src/Entity/Traits/SoftDeletableEntity.php` |
|
||||
| deletion by an admin | `src/Controller/Admin/Teamer/DeleteAccountController.php` |
|
||||
| deletion by the teamer | `src/Controller/Teamer/Profile/DeleteAccountController.php`, `src/Form/DeleteAccountType.php` |
|
||||
| session of a deleted user | `src/EventListener/DeletedUserSubscriber.php` |
|
||||
| listing | `src/Repository/UserRepository.php` |
|
||||
| hierarchy, firewall, impersonation | `config/packages/security.yaml` |
|
||||
| CRM attribute ids | `config/services.yaml` |
|
||||
|
||||
## Tests
|
||||
|
||||
| File | Covers |
|
||||
|------|--------|
|
||||
| `tests/BusProNet/UserDataHandlerTest.php` | CRM → marker mapping, marker refresh, teamer role grant, demotion incl. the untouched disciplinary block |
|
||||
| `tests/Security/BpnAuthenticatorTest.php` | the three demotion outcomes, including the empty-payload safeguard |
|
||||
| `tests/Entity/UserTest.php` | `setDisabled()` block / unblock / no-op, block and deletion as independent states, OneToOne inverse-side sync |
|
||||
| `tests/Service/Teamer/AccountDeletionHandlerTest.php` | deletion from either side, missing counterpart, idempotence, roles and block left untouched, restore |
|
||||
| `tests/Security/UserCheckerTest.php` | deleted refused, deletion message wins over the block message |
|
||||
| `tests/EventListener/EmailNotificationSubscriberTest.php` | no mail to a deleted teamer, per-teamer exclusion on an assignment call-off |
|
||||
| `tests/Entity/Traits/SoftDeletableEntityTest.php` | nullable `deletedAt`, `setDeleted()` / `setRestored()` |
|
||||
| `tests/Form/UserTypeTest.php` | approval clears the marker, block with reasons, unblock clears them even when the fields are still filled |
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"prod": {
|
||||
"saas_base_url": "https://desktop.saas.de/rest"
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20240110122028 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE feedback CHANGE destination_name destination_name VARCHAR(255) DEFAULT NULL, CHANGE assignment_date assignment_date DATE DEFAULT NULL COMMENT \'(DC2Type:date_immutable)\', CHANGE job_profile_name job_profile_name VARCHAR(255) DEFAULT NULL');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE feedback CHANGE destination_name destination_name VARCHAR(255) NOT NULL, CHANGE job_profile_name job_profile_name VARCHAR(255) NOT NULL, CHANGE assignment_date assignment_date DATE NOT NULL COMMENT \'(DC2Type:date_immutable)\'');
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20240110164517 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE application ADD pickup INT DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE disposition ADD pickup INT DEFAULT NULL');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE disposition DROP pickup');
|
||||
$this->addSql('ALTER TABLE application DROP pickup');
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20240128125138 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE upload ADD global TINYINT(1) NOT NULL');
|
||||
}
|
||||
|
||||
public function postUp(Schema $schema): void
|
||||
{
|
||||
$this->connection->executeQuery('UPDATE upload SET global=0');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE upload DROP global');
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20240131173953 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE assignment_upload DROP FOREIGN KEY FK_2F9CC602D19302F8');
|
||||
$this->addSql('ALTER TABLE assignment_upload DROP FOREIGN KEY FK_2F9CC602CCCFBA31');
|
||||
$this->addSql('DROP TABLE assignment_upload');
|
||||
$this->addSql('ALTER TABLE upload DROP global');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('CREATE TABLE assignment_upload (assignment_id INT NOT NULL, upload_id INT NOT NULL, INDEX IDX_2F9CC602CCCFBA31 (upload_id), INDEX IDX_2F9CC602D19302F8 (assignment_id), PRIMARY KEY(assignment_id, upload_id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB COMMENT = \'\' ');
|
||||
$this->addSql('ALTER TABLE assignment_upload ADD CONSTRAINT FK_2F9CC602D19302F8 FOREIGN KEY (assignment_id) REFERENCES assignment (id) ON DELETE CASCADE');
|
||||
$this->addSql('ALTER TABLE assignment_upload ADD CONSTRAINT FK_2F9CC602CCCFBA31 FOREIGN KEY (upload_id) REFERENCES upload (id) ON DELETE CASCADE');
|
||||
$this->addSql('ALTER TABLE upload ADD global TINYINT(1) NOT NULL');
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20240131185423 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE assignment ADD job_profile_info LONGTEXT DEFAULT NULL');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE assignment DROP job_profile_info');
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20240214134047 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE feedback ADD hotel VARCHAR(255) DEFAULT NULL, ADD hotel_code VARCHAR(32) DEFAULT NULL, ADD hotel_bus_pro_id INT DEFAULT NULL, ADD assignment_date_to DATE DEFAULT NULL COMMENT \'(DC2Type:date_immutable)\', CHANGE assignment_date assignment_date_from DATE DEFAULT NULL COMMENT \'(DC2Type:date_immutable)\'');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE feedback ADD assignment_date DATE DEFAULT NULL COMMENT \'(DC2Type:date_immutable)\', DROP hotel, DROP hotel_code, DROP hotel_bus_pro_id, DROP assignment_date_from, DROP assignment_date_to');
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20240404071550 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE contact ADD phone VARCHAR(255) NOT NULL, ADD category VARCHAR(64) NOT NULL');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE contact DROP phone, DROP category');
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20240404091244 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE disposition ADD special_agreements LONGTEXT DEFAULT NULL');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE disposition DROP special_agreements');
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20240417160406 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE user ADD super_admin TINYINT(1) NOT NULL');
|
||||
}
|
||||
|
||||
public function postUp(Schema $schema): void
|
||||
{
|
||||
$this->connection->executeQuery('UPDATE user SET super_admin = 0');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE user DROP super_admin');
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20240515131333 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE destination ADD created_by VARCHAR(255) DEFAULT NULL, ADD updated_by VARCHAR(255) DEFAULT NULL, CHANGE bus_pro_id bus_pro_id INT DEFAULT NULL, CHANGE code code VARCHAR(32) DEFAULT NULL');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE destination DROP created_by, DROP updated_by, CHANGE bus_pro_id bus_pro_id INT NOT NULL, CHANGE code code VARCHAR(32) NOT NULL');
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20240516075149 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE user ADD mute_notifications TINYINT(1) NOT NULL');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE user DROP mute_notifications');
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20240623151419 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE teamer ADD viewed TINYINT(1) NOT NULL');
|
||||
}
|
||||
|
||||
public function postUp(Schema $schema): void
|
||||
{
|
||||
$this->connection->executeQuery('UPDATE teamer SET viewed = 1');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE teamer DROP viewed');
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20240623161718 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('CREATE TABLE news (id INT AUTO_INCREMENT NOT NULL, status SMALLINT NOT NULL, message LONGTEXT NOT NULL, created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', updated_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', created_by VARCHAR(255) DEFAULT NULL, updated_by VARCHAR(255) DEFAULT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('DROP TABLE news');
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20240623164458 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->connection->executeQuery('update assignment set status=? where status=?', ['published', 'open']);
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->connection->executeQuery('update assignment set status=? where status=?', ['open', 'published']);
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use App\Entity\Assignment;
|
||||
use App\Repository\AssignmentRepository;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20240716063200 extends AbstractMigration implements ContainerAwareInterface
|
||||
{
|
||||
private ContainerInterface $container;
|
||||
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function setContainer(?ContainerInterface $container): void
|
||||
{
|
||||
$this->container = $container;
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE assignment ADD staffing_status VARCHAR(64) NOT NULL');
|
||||
}
|
||||
|
||||
public function postUp(Schema $schema): void
|
||||
{
|
||||
$entityManager = $this->container->get('doctrine.orm.entity_manager');
|
||||
$assignmentRepository = $entityManager->getRepository(Assignment::class);
|
||||
|
||||
$assignments = $assignmentRepository->findAll();
|
||||
|
||||
foreach ($assignments as $assignment) {
|
||||
if (Assignment::STATUS_DRAFT === $assignment->getStatus() || null !== $assignment->getDeletedAt()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$assignment->updateStaffingStatus();
|
||||
}
|
||||
|
||||
$entityManager->flush();
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE assignment DROP staffing_status');
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use App\Entity\Assignment;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20240718105318 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->connection->executeQuery('UPDATE assignment SET status=? WHERE deleted_at is NOT NULL', [Assignment::STATUS_DELETED]);
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20241028164059 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE teamer CHANGE date_of_birth date_of_birth DATE DEFAULT NULL COMMENT \'(DC2Type:date_immutable)\'');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE teamer CHANGE date_of_birth date_of_birth DATE NOT NULL COMMENT \'(DC2Type:date_immutable)\'');
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20241030091854 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('CREATE TABLE rememberme_token (series VARCHAR(88) NOT NULL, value VARCHAR(88) NOT NULL, lastUsed DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', class VARCHAR(100) NOT NULL, username VARCHAR(200) NOT NULL, PRIMARY KEY(series)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('DROP TABLE rememberme_token');
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20250107163840 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE user ADD hotel_codes JSON NOT NULL COMMENT \'(DC2Type:json)\'');
|
||||
}
|
||||
|
||||
public function postUp(Schema $schema): void
|
||||
{
|
||||
$this->connection->executeQuery('UPDATE user SET hotel_codes="[]" WHERE hotel_code IS NULL');
|
||||
|
||||
$rows = $this->connection->fetchAllAssociative('SELECT id, hotel_code FROM user WHERE hotel_code IS NOT NULL');
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$this->connection->update('user', [
|
||||
'hotel_codes' => json_encode([$row['hotel_code']]),
|
||||
], [
|
||||
'id' => $row['id'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE user DROP hotel_codes');
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20250110161832 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE user DROP hotel_code');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE user ADD hotel_code VARCHAR(32) DEFAULT NULL');
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20250131164533 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('DROP TABLE rememberme_token');
|
||||
$this->addSql('ALTER TABLE upload ADD downloaded_at DATE DEFAULT NULL COMMENT \'(DC2Type:date_immutable)\', ADD downloaded_by VARCHAR(255) DEFAULT NULL');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('CREATE TABLE rememberme_token (series VARCHAR(88) CHARACTER SET utf8mb4 NOT NULL COLLATE `utf8mb4_unicode_ci`, value VARCHAR(88) CHARACTER SET utf8mb4 NOT NULL COLLATE `utf8mb4_unicode_ci`, lastUsed DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', class VARCHAR(100) CHARACTER SET utf8mb4 NOT NULL COLLATE `utf8mb4_unicode_ci`, username VARCHAR(200) CHARACTER SET utf8mb4 NOT NULL COLLATE `utf8mb4_unicode_ci`, PRIMARY KEY(series)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB COMMENT = \'\' ');
|
||||
$this->addSql('ALTER TABLE upload DROP downloaded_at, DROP downloaded_by');
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20250210162751 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE teamer ADD remarks_internal LONGTEXT DEFAULT NULL');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE teamer DROP remarks_internal');
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20250228133252 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE assignment ADD cost_center VARCHAR(255) DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE destination ADD cost_unit VARCHAR(255) DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE upload ADD approved_at DATE DEFAULT NULL COMMENT \'(DC2Type:date_immutable)\', ADD approved_by VARCHAR(255) DEFAULT NULL');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE upload DROP approved_at, DROP approved_by');
|
||||
$this->addSql('ALTER TABLE assignment DROP cost_center');
|
||||
$this->addSql('ALTER TABLE destination DROP cost_unit');
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20250318105943 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE assignment DROP cost_center');
|
||||
$this->addSql('ALTER TABLE job_profile ADD cost_unit VARCHAR(255) DEFAULT NULL');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE job_profile DROP cost_unit');
|
||||
$this->addSql('ALTER TABLE assignment ADD cost_center VARCHAR(255) DEFAULT NULL');
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20250430080623 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE job_profile ADD category VARCHAR(255) DEFAULT NULL
|
||||
SQL);
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE job_profile DROP category
|
||||
SQL);
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20250722125647 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE disposition ADD called_off_by VARCHAR(64) DEFAULT NULL, ADD called_off_reason LONGTEXT DEFAULT NULL');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE disposition DROP called_off_by, DROP called_off_reason');
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20250722142834 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE user ADD disabled_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', ADD disabled_reason LONGTEXT DEFAULT NULL, ADD disabled_reason_internal LONGTEXT DEFAULT NULL');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE user DROP disabled_at, DROP disabled_reason, DROP disabled_reason_internal');
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20250930161953 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE disposition ADD synced_with_bus_pro_net TINYINT(1) NOT NULL');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE disposition DROP synced_with_bus_pro_net');
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20251215135758 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Add allow_overlapping_applications column to teamer table';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE teamer ADD allow_overlapping_applications TINYINT(1) NOT NULL DEFAULT 0');
|
||||
}
|
||||
|
||||
public function postUp(Schema $schema): void
|
||||
{
|
||||
$this->connection->executeStatement('UPDATE teamer SET allow_overlapping_applications = 0');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE teamer DROP allow_overlapping_applications');
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20251216135542 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE feedback ADD assignment_id INT DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE feedback ADD CONSTRAINT FK_D2294458D19302F8 FOREIGN KEY (assignment_id) REFERENCES assignment (id)');
|
||||
$this->addSql('CREATE INDEX IDX_D2294458D19302F8 ON feedback (assignment_id)');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE feedback DROP FOREIGN KEY FK_D2294458D19302F8');
|
||||
$this->addSql('DROP INDEX IDX_D2294458D19302F8 ON feedback');
|
||||
$this->addSql('ALTER TABLE feedback DROP assignment_id');
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20251216162601 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE feedback ADD job_profile_category VARCHAR(32) DEFAULT NULL');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE feedback DROP job_profile_category');
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20251218100000 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Split course category into course_ski/course_board and add event_team category based on job profile names';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// Split course category: profiles with "Ski" in name become course_ski
|
||||
$this->addSql("
|
||||
UPDATE job_profile
|
||||
SET category = 'course_ski'
|
||||
WHERE category = 'course'
|
||||
AND name LIKE '%Ski%'
|
||||
");
|
||||
|
||||
// Split course category: profiles with "Board" or "Snowboard" in name become course_board
|
||||
$this->addSql("
|
||||
UPDATE job_profile
|
||||
SET category = 'course_board'
|
||||
WHERE category = 'course'
|
||||
AND (name LIKE '%Board%' OR name LIKE '%Snowboard%')
|
||||
");
|
||||
|
||||
// New event_team category for profiles containing "Event" in name
|
||||
$this->addSql("
|
||||
UPDATE job_profile
|
||||
SET category = 'event_team'
|
||||
WHERE name LIKE '%Event%'
|
||||
");
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// Revert course_ski and course_board back to course
|
||||
$this->addSql("
|
||||
UPDATE job_profile
|
||||
SET category = 'course'
|
||||
WHERE category IN ('course_ski', 'course_board')
|
||||
");
|
||||
|
||||
// Note: Cannot reliably revert event_team as we don't know original category
|
||||
$this->addSql("
|
||||
UPDATE job_profile
|
||||
SET category = 'service'
|
||||
WHERE category = 'event_team'
|
||||
");
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20251218100001 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Backfill assignment_id and job_profile_category on feedback table from existing disposition data';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// Backfill assignment_id from disposition's feedback relation
|
||||
$this->addSql('
|
||||
UPDATE feedback f
|
||||
INNER JOIN disposition d ON d.feedback_id = f.id
|
||||
SET f.assignment_id = d.assignment_id
|
||||
WHERE f.assignment_id IS NULL
|
||||
');
|
||||
|
||||
// Backfill job_profile_category from assignment's job profile
|
||||
$this->addSql('
|
||||
UPDATE feedback f
|
||||
INNER JOIN assignment a ON a.id = f.assignment_id
|
||||
INNER JOIN job_profile jp ON jp.id = a.job_profile_id
|
||||
SET f.job_profile_category = jp.category
|
||||
WHERE f.job_profile_category IS NULL
|
||||
AND f.assignment_id IS NOT NULL
|
||||
');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// Data migration - setting to NULL on rollback
|
||||
$this->addSql('UPDATE feedback SET assignment_id = NULL, job_profile_category = NULL');
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20251223131716 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Add feedback_set_id relation to feedback table';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE feedback ADD feedback_set_id INT DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE feedback ADD CONSTRAINT FK_D22944582DB73956 FOREIGN KEY (feedback_set_id) REFERENCES feedback_set (id) ON DELETE SET NULL');
|
||||
$this->addSql('CREATE INDEX IDX_D22944582DB73956 ON feedback (feedback_set_id)');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE feedback DROP FOREIGN KEY FK_D22944582DB73956');
|
||||
$this->addSql('DROP INDEX IDX_D22944582DB73956 ON feedback');
|
||||
$this->addSql('ALTER TABLE feedback DROP feedback_set_id');
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20260209104255 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE teamer CHANGE size size VARCHAR(3) DEFAULT NULL');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE teamer CHANGE size size VARCHAR(2) DEFAULT NULL');
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20260218173855 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql("ALTER TABLE teamer ADD data_verified_at DATETIME DEFAULT NULL COMMENT '(DC2Type:datetime_immutable)'");
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE teamer DROP data_verified_at');
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20260227111223 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE teamer ADD driver_license_upload_id INT DEFAULT NULL, ADD driver_license_declaration TINYINT(1) DEFAULT NULL, ADD driver_license_verified TINYINT(1) NOT NULL DEFAULT 0, ADD driver_license_status VARCHAR(64) NOT NULL DEFAULT \'none\', ADD driver_license_review_comment LONGTEXT DEFAULT NULL, ADD driver_license_reviewed_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', ADD driver_license_reviewed_by VARCHAR(8) DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE teamer ADD CONSTRAINT FK_15B751AE1C882DBD FOREIGN KEY (driver_license_upload_id) REFERENCES upload (id)');
|
||||
$this->addSql('CREATE UNIQUE INDEX UNIQ_15B751AE1C882DBD ON teamer (driver_license_upload_id)');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE teamer DROP FOREIGN KEY FK_15B751AE1C882DBD');
|
||||
$this->addSql('DROP INDEX UNIQ_15B751AE1C882DBD ON teamer');
|
||||
$this->addSql('ALTER TABLE teamer DROP driver_license_upload_id, DROP driver_license_declaration, DROP driver_license_verified, DROP driver_license_status, DROP driver_license_review_comment, DROP driver_license_reviewed_at, DROP driver_license_reviewed_by');
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ use Doctrine\Migrations\AbstractMigration;
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20231212155746 extends AbstractMigration
|
||||
final class Version20260824124850 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
@@ -20,12 +20,12 @@ final class Version20231212155746 extends AbstractMigration
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE feedback ADD author VARCHAR(255) NOT NULL');
|
||||
$this->addSql('ALTER TABLE teamer ADD all_season TINYINT(1) NOT NULL');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE feedback DROP author');
|
||||
$this->addSql('ALTER TABLE teamer DROP all_season');
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ use Doctrine\Migrations\AbstractMigration;
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20231208094506 extends AbstractMigration
|
||||
final class Version20260901090029 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
@@ -20,30 +20,32 @@ final class Version20231208094506 extends AbstractMigration
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('CREATE TABLE application (id INT AUTO_INCREMENT NOT NULL, assignment_id INT DEFAULT NULL, teamer_id INT DEFAULT NULL, uuid VARCHAR(36) NOT NULL, requests LONGTEXT DEFAULT NULL, status VARCHAR(64) NOT NULL, comment LONGTEXT DEFAULT NULL, comment_visible TINYINT(1) NOT NULL, created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', updated_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', UNIQUE INDEX UNIQ_A45BDDC1D17F50A6 (uuid), INDEX IDX_A45BDDC1D19302F8 (assignment_id), INDEX IDX_A45BDDC14302FF75 (teamer_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||
$this->addSql('CREATE TABLE assignment (id INT AUTO_INCREMENT NOT NULL, destination_id INT DEFAULT NULL, job_profile_id INT DEFAULT NULL, contact_id INT DEFAULT NULL, owner_id INT DEFAULT NULL, uuid VARCHAR(36) NOT NULL, available_dispositions INT DEFAULT NULL, show_available_dispositions TINYINT(1) NOT NULL, date_from DATE DEFAULT NULL COMMENT \'(DC2Type:date_immutable)\', date_to DATE DEFAULT NULL COMMENT \'(DC2Type:date_immutable)\', pickup_date DATE DEFAULT NULL COMMENT \'(DC2Type:date_immutable)\', pickup INT DEFAULT NULL, benefits LONGTEXT DEFAULT NULL, remarks LONGTEXT DEFAULT NULL, status VARCHAR(64) NOT NULL, created_by VARCHAR(255) DEFAULT NULL, updated_by VARCHAR(255) DEFAULT NULL, created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', updated_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', deleted_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', UNIQUE INDEX UNIQ_30C544BAD17F50A6 (uuid), INDEX IDX_30C544BA816C6140 (destination_id), INDEX IDX_30C544BAA68D82A5 (job_profile_id), INDEX IDX_30C544BAE7A1254A (contact_id), INDEX IDX_30C544BA7E3C61F9 (owner_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||
$this->addSql('CREATE TABLE application (id INT AUTO_INCREMENT NOT NULL, assignment_id INT DEFAULT NULL, teamer_id INT DEFAULT NULL, uuid VARCHAR(36) NOT NULL, requests LONGTEXT DEFAULT NULL, pickup INT DEFAULT NULL, status VARCHAR(64) NOT NULL, comment LONGTEXT DEFAULT NULL, comment_visible TINYINT(1) NOT NULL, created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', updated_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', UNIQUE INDEX UNIQ_A45BDDC1D17F50A6 (uuid), INDEX IDX_A45BDDC1D19302F8 (assignment_id), INDEX IDX_A45BDDC14302FF75 (teamer_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||
$this->addSql('CREATE TABLE assignment (id INT AUTO_INCREMENT NOT NULL, destination_id INT DEFAULT NULL, job_profile_id INT DEFAULT NULL, contact_id INT DEFAULT NULL, owner_id INT DEFAULT NULL, uuid VARCHAR(36) NOT NULL, job_profile_info LONGTEXT DEFAULT NULL, available_dispositions INT DEFAULT NULL, show_available_dispositions TINYINT(1) NOT NULL, skip_formalities TINYINT(1) NOT NULL, pickup_date DATE DEFAULT NULL COMMENT \'(DC2Type:date_immutable)\', pickup INT DEFAULT NULL, benefits LONGTEXT DEFAULT NULL, remarks LONGTEXT DEFAULT NULL, status VARCHAR(64) NOT NULL, staffing_status VARCHAR(64) NOT NULL, created_by VARCHAR(255) DEFAULT NULL, updated_by VARCHAR(255) DEFAULT NULL, created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', updated_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', deleted_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', UNIQUE INDEX UNIQ_30C544BAD17F50A6 (uuid), INDEX IDX_30C544BA816C6140 (destination_id), INDEX IDX_30C544BAA68D82A5 (job_profile_id), INDEX IDX_30C544BAE7A1254A (contact_id), INDEX IDX_30C544BA7E3C61F9 (owner_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||
$this->addSql('CREATE TABLE assignment_fee (assignment_id INT NOT NULL, fee_id INT NOT NULL, INDEX IDX_4E2AE50AD19302F8 (assignment_id), INDEX IDX_4E2AE50AAB45AECA (fee_id), PRIMARY KEY(assignment_id, fee_id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||
$this->addSql('CREATE TABLE assignment_upload (assignment_id INT NOT NULL, upload_id INT NOT NULL, INDEX IDX_2F9CC602D19302F8 (assignment_id), INDEX IDX_2F9CC602CCCFBA31 (upload_id), PRIMARY KEY(assignment_id, upload_id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||
$this->addSql('CREATE TABLE availability (id INT AUTO_INCREMENT NOT NULL, owner_id INT DEFAULT NULL, date_from DATE NOT NULL COMMENT \'(DC2Type:date_immutable)\', date_to DATE NOT NULL COMMENT \'(DC2Type:date_immutable)\', description LONGTEXT DEFAULT NULL, created_by VARCHAR(255) DEFAULT NULL, updated_by VARCHAR(255) DEFAULT NULL, created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', updated_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', deleted_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', INDEX IDX_3FB7A2BF7E3C61F9 (owner_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||
$this->addSql('CREATE TABLE contact (id INT AUTO_INCREMENT NOT NULL, photo_id INT DEFAULT NULL, name VARCHAR(255) NOT NULL, email VARCHAR(255) NOT NULL, destination VARCHAR(255) DEFAULT NULL, UNIQUE INDEX UNIQ_4C62E6387E9E4C8C (photo_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||
$this->addSql('CREATE TABLE destination (id INT AUTO_INCREMENT NOT NULL, bus_pro_id INT NOT NULL, code VARCHAR(32) NOT NULL, date_from DATE NOT NULL COMMENT \'(DC2Type:date_immutable)\', date_to DATE NOT NULL COMMENT \'(DC2Type:date_immutable)\', product VARCHAR(255) NOT NULL, hotel VARCHAR(255) NOT NULL, hotel_code VARCHAR(32) NOT NULL, hotel_bus_pro_id INT NOT NULL, pickups JSON NOT NULL COMMENT \'(DC2Type:json)\', country VARCHAR(2) DEFAULT NULL, created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', updated_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', deleted_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||
$this->addSql('CREATE TABLE disposition (id INT AUTO_INCREMENT NOT NULL, assignment_id INT DEFAULT NULL, teamer_id INT DEFAULT NULL, feedback_id INT DEFAULT NULL, uuid VARCHAR(36) NOT NULL, status VARCHAR(64) NOT NULL, remarks LONGTEXT DEFAULT NULL, created_by VARCHAR(255) DEFAULT NULL, updated_by VARCHAR(255) DEFAULT NULL, created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', updated_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', UNIQUE INDEX UNIQ_4C58BF60D17F50A6 (uuid), INDEX IDX_4C58BF60D19302F8 (assignment_id), INDEX IDX_4C58BF604302FF75 (teamer_id), UNIQUE INDEX UNIQ_4C58BF60D249A887 (feedback_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||
$this->addSql('CREATE TABLE contact (id INT AUTO_INCREMENT NOT NULL, photo_id INT DEFAULT NULL, name VARCHAR(255) NOT NULL, email VARCHAR(255) NOT NULL, phone VARCHAR(255) NOT NULL, destination VARCHAR(255) DEFAULT NULL, category VARCHAR(64) NOT NULL, UNIQUE INDEX UNIQ_4C62E6387E9E4C8C (photo_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||
$this->addSql('CREATE TABLE destination (id INT AUTO_INCREMENT NOT NULL, bus_pro_id INT DEFAULT NULL, code VARCHAR(32) DEFAULT NULL, date_from DATE NOT NULL COMMENT \'(DC2Type:date_immutable)\', date_to DATE NOT NULL COMMENT \'(DC2Type:date_immutable)\', product VARCHAR(255) NOT NULL, hotel VARCHAR(255) NOT NULL, hotel_code VARCHAR(32) NOT NULL, hotel_bus_pro_id INT NOT NULL, cost_unit VARCHAR(255) DEFAULT NULL, pickups JSON NOT NULL, country VARCHAR(2) DEFAULT NULL, created_by VARCHAR(255) DEFAULT NULL, updated_by VARCHAR(255) DEFAULT NULL, created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', updated_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', deleted_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||
$this->addSql('CREATE TABLE disposition (id INT AUTO_INCREMENT NOT NULL, assignment_id INT DEFAULT NULL, teamer_id INT DEFAULT NULL, feedback_id INT DEFAULT NULL, uuid VARCHAR(36) NOT NULL, status VARCHAR(64) NOT NULL, pickup INT DEFAULT NULL, remarks LONGTEXT DEFAULT NULL, special_agreements LONGTEXT DEFAULT NULL, called_off_by VARCHAR(64) DEFAULT NULL, called_off_reason LONGTEXT DEFAULT NULL, synced_with_bus_pro_net TINYINT(1) NOT NULL, created_by VARCHAR(255) DEFAULT NULL, updated_by VARCHAR(255) DEFAULT NULL, created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', updated_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', UNIQUE INDEX UNIQ_4C58BF60D17F50A6 (uuid), INDEX IDX_4C58BF60D19302F8 (assignment_id), INDEX IDX_4C58BF604302FF75 (teamer_id), UNIQUE INDEX UNIQ_4C58BF60D249A887 (feedback_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||
$this->addSql('CREATE TABLE email_text (id INT AUTO_INCREMENT NOT NULL, template_key VARCHAR(64) NOT NULL, subject VARCHAR(255) NOT NULL, headline VARCHAR(255) DEFAULT NULL, body LONGTEXT NOT NULL, created_by VARCHAR(255) DEFAULT NULL, updated_by VARCHAR(255) DEFAULT NULL, created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', updated_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', UNIQUE INDEX uniq_email_text_template_key (template_key), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||
$this->addSql('CREATE TABLE faq (id INT AUTO_INCREMENT NOT NULL, question LONGTEXT NOT NULL, answer LONGTEXT NOT NULL, sorting INT NOT NULL, created_by VARCHAR(255) DEFAULT NULL, updated_by VARCHAR(255) DEFAULT NULL, created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', updated_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||
$this->addSql('CREATE TABLE fee (id INT AUTO_INCREMENT NOT NULL, value INT NOT NULL, name VARCHAR(255) NOT NULL, name_internal VARCHAR(255) NOT NULL, created_by VARCHAR(255) DEFAULT NULL, updated_by VARCHAR(255) DEFAULT NULL, created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', updated_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', deleted_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||
$this->addSql('CREATE TABLE feedback (id INT AUTO_INCREMENT NOT NULL, teamer_id INT DEFAULT NULL, uuid VARCHAR(36) NOT NULL, status VARCHAR(32) NOT NULL, destination_name VARCHAR(255) NOT NULL, job_profile_name VARCHAR(255) NOT NULL, assignment_date DATE NOT NULL COMMENT \'(DC2Type:date_immutable)\', ratings JSON NOT NULL COMMENT \'(DC2Type:json)\', average_rating INT DEFAULT NULL, comment LONGTEXT DEFAULT NULL, comment_internal LONGTEXT DEFAULT NULL, created_by VARCHAR(255) DEFAULT NULL, updated_by VARCHAR(255) DEFAULT NULL, created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', updated_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', UNIQUE INDEX UNIQ_D2294458D17F50A6 (uuid), INDEX IDX_D22944584302FF75 (teamer_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||
$this->addSql('CREATE TABLE feedback_set (id INT AUTO_INCREMENT NOT NULL, name VARCHAR(255) NOT NULL, ratings JSON NOT NULL COMMENT \'(DC2Type:json)\', created_by VARCHAR(255) DEFAULT NULL, updated_by VARCHAR(255) DEFAULT NULL, created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', updated_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||
$this->addSql('CREATE TABLE job_profile (id INT AUTO_INCREMENT NOT NULL, feedback_set_id INT DEFAULT NULL, uuid VARCHAR(36) NOT NULL, name VARCHAR(255) NOT NULL, required_licenses JSON NOT NULL COMMENT \'(DC2Type:json)\', created_by VARCHAR(255) DEFAULT NULL, updated_by VARCHAR(255) DEFAULT NULL, created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', updated_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', deleted_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', UNIQUE INDEX UNIQ_1BB81D94D17F50A6 (uuid), INDEX IDX_1BB81D942DB73956 (feedback_set_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||
$this->addSql('CREATE TABLE feedback (id INT AUTO_INCREMENT NOT NULL, teamer_id INT DEFAULT NULL, assignment_id INT DEFAULT NULL, feedback_set_id INT DEFAULT NULL, uuid VARCHAR(36) NOT NULL, status VARCHAR(32) NOT NULL, destination_name VARCHAR(255) DEFAULT NULL, job_profile_name VARCHAR(255) DEFAULT NULL, job_profile_category VARCHAR(32) DEFAULT NULL, hotel VARCHAR(255) DEFAULT NULL, hotel_code VARCHAR(32) DEFAULT NULL, hotel_bus_pro_id INT DEFAULT NULL, assignment_date_from DATE DEFAULT NULL COMMENT \'(DC2Type:date_immutable)\', assignment_date_to DATE DEFAULT NULL COMMENT \'(DC2Type:date_immutable)\', ratings JSON NOT NULL, average_rating INT DEFAULT NULL, comment LONGTEXT DEFAULT NULL, comment_internal LONGTEXT DEFAULT NULL, author VARCHAR(255) NOT NULL, created_by VARCHAR(255) DEFAULT NULL, updated_by VARCHAR(255) DEFAULT NULL, created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', updated_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', UNIQUE INDEX UNIQ_D2294458D17F50A6 (uuid), INDEX IDX_D22944584302FF75 (teamer_id), INDEX IDX_D2294458D19302F8 (assignment_id), INDEX IDX_D22944582DB73956 (feedback_set_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||
$this->addSql('CREATE TABLE feedback_set (id INT AUTO_INCREMENT NOT NULL, name VARCHAR(255) NOT NULL, ratings JSON NOT NULL, created_by VARCHAR(255) DEFAULT NULL, updated_by VARCHAR(255) DEFAULT NULL, created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', updated_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||
$this->addSql('CREATE TABLE job_profile (id INT AUTO_INCREMENT NOT NULL, feedback_set_id INT DEFAULT NULL, uuid VARCHAR(36) NOT NULL, name VARCHAR(255) NOT NULL, category VARCHAR(255) DEFAULT NULL, required_licenses JSON NOT NULL, cost_unit VARCHAR(255) DEFAULT NULL, created_by VARCHAR(255) DEFAULT NULL, updated_by VARCHAR(255) DEFAULT NULL, created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', updated_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', deleted_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', UNIQUE INDEX UNIQ_1BB81D94D17F50A6 (uuid), INDEX IDX_1BB81D942DB73956 (feedback_set_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||
$this->addSql('CREATE TABLE job_profile_training (job_profile_id INT NOT NULL, training_id INT NOT NULL, INDEX IDX_F19E0BB8A68D82A5 (job_profile_id), INDEX IDX_F19E0BB8BEFD98D1 (training_id), PRIMARY KEY(job_profile_id, training_id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||
$this->addSql('CREATE TABLE license (id INT AUTO_INCREMENT NOT NULL, certificate_id INT DEFAULT NULL, teamer_id INT DEFAULT NULL, uuid VARCHAR(36) NOT NULL, type VARCHAR(64) NOT NULL, date DATE NOT NULL COMMENT \'(DC2Type:date_immutable)\', created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', updated_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', UNIQUE INDEX UNIQ_5768F419D17F50A6 (uuid), UNIQUE INDEX UNIQ_5768F41999223FFD (certificate_id), INDEX IDX_5768F4194302FF75 (teamer_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||
$this->addSql('CREATE TABLE teamer (id INT AUTO_INCREMENT NOT NULL, photo_id INT DEFAULT NULL, uuid VARCHAR(36) NOT NULL, first_name VARCHAR(255) NOT NULL, last_name VARCHAR(255) NOT NULL, gender VARCHAR(1) NOT NULL, date_of_birth DATE NOT NULL COMMENT \'(DC2Type:date_immutable)\', academic_title VARCHAR(255) DEFAULT NULL, salutation VARCHAR(255) DEFAULT NULL, nationality VARCHAR(255) DEFAULT NULL, tax_id VARCHAR(255) DEFAULT NULL, health_insurance_company VARCHAR(255) DEFAULT NULL, status VARCHAR(32) NOT NULL, remarks LONGTEXT DEFAULT NULL, pickups JSON NOT NULL COMMENT \'(DC2Type:json)\', language VARCHAR(255) DEFAULT NULL, size VARCHAR(2) DEFAULT NULL, crm_selections JSON DEFAULT NULL COMMENT \'(DC2Type:json)\', created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', updated_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', address_street VARCHAR(255) DEFAULT NULL, address_post_code VARCHAR(255) DEFAULT NULL, address_city VARCHAR(255) DEFAULT NULL, address_country VARCHAR(255) DEFAULT NULL, communication_phone VARCHAR(255) DEFAULT NULL, communication_mobile VARCHAR(255) DEFAULT NULL, communication_email VARCHAR(255) DEFAULT NULL, bank_account_iban VARCHAR(255) DEFAULT NULL, bank_account_bic VARCHAR(255) DEFAULT NULL, bank_account_bank VARCHAR(255) DEFAULT NULL, bank_account_holder VARCHAR(255) DEFAULT NULL, UNIQUE INDEX UNIQ_15B751AED17F50A6 (uuid), UNIQUE INDEX UNIQ_15B751AE7E9E4C8C (photo_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||
$this->addSql('CREATE TABLE news (id INT AUTO_INCREMENT NOT NULL, status SMALLINT NOT NULL, message LONGTEXT NOT NULL, created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', updated_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', created_by VARCHAR(255) DEFAULT NULL, updated_by VARCHAR(255) DEFAULT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||
$this->addSql('CREATE TABLE statistics_event (id INT AUTO_INCREMENT NOT NULL, name VARCHAR(64) NOT NULL, occurred_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', actor_id INT DEFAULT NULL, actor_label VARCHAR(180) DEFAULT NULL, actor_role VARCHAR(32) DEFAULT NULL, assignment_id INT DEFAULT NULL, disposition_id INT DEFAULT NULL, application_id INT DEFAULT NULL, teamer_id INT DEFAULT NULL, destination_id INT DEFAULT NULL, job_profile_id INT DEFAULT NULL, hotel_code VARCHAR(8) DEFAULT NULL, payload JSON NOT NULL, INDEX IDX_A343D13C5E237E0687C03D1B (name, occurred_at), INDEX IDX_A343D13C5E237E06C0D0E610 (name, hotel_code), INDEX IDX_A343D13CD19302F8 (assignment_id), INDEX IDX_A343D13C4302FF75 (teamer_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||
$this->addSql('CREATE TABLE teamer (id INT AUTO_INCREMENT NOT NULL, photo_id INT DEFAULT NULL, driver_license_upload_id INT DEFAULT NULL, uuid VARCHAR(36) NOT NULL, first_name VARCHAR(255) NOT NULL, last_name VARCHAR(255) NOT NULL, gender VARCHAR(1) NOT NULL, date_of_birth DATE DEFAULT NULL COMMENT \'(DC2Type:date_immutable)\', academic_title VARCHAR(255) DEFAULT NULL, salutation VARCHAR(255) DEFAULT NULL, nationality VARCHAR(255) DEFAULT NULL, tax_id VARCHAR(255) DEFAULT NULL, health_insurance_company VARCHAR(255) DEFAULT NULL, status VARCHAR(32) NOT NULL, remarks LONGTEXT DEFAULT NULL, remarks_internal LONGTEXT DEFAULT NULL, pickups JSON NOT NULL, language VARCHAR(255) DEFAULT NULL, size VARCHAR(3) DEFAULT NULL, meal_preference VARCHAR(255) DEFAULT NULL, crm_selections JSON DEFAULT NULL, viewed TINYINT(1) NOT NULL, allow_overlapping_applications TINYINT(1) NOT NULL, data_verified_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', driver_license_declaration TINYINT(1) DEFAULT NULL, driver_license_verified TINYINT(1) NOT NULL, driver_license_status VARCHAR(64) NOT NULL, driver_license_review_comment LONGTEXT DEFAULT NULL, driver_license_reviewed_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', driver_license_reviewed_by VARCHAR(8) DEFAULT NULL, created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', updated_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', deleted_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', address_street VARCHAR(255) DEFAULT NULL, address_post_code VARCHAR(255) DEFAULT NULL, address_city VARCHAR(255) DEFAULT NULL, address_country VARCHAR(255) DEFAULT NULL, communication_phone VARCHAR(255) DEFAULT NULL, communication_mobile VARCHAR(255) DEFAULT NULL, communication_email VARCHAR(255) DEFAULT NULL, bank_account_iban VARCHAR(255) DEFAULT NULL, bank_account_bic VARCHAR(255) DEFAULT NULL, bank_account_bank VARCHAR(255) DEFAULT NULL, bank_account_holder VARCHAR(255) DEFAULT NULL, UNIQUE INDEX UNIQ_15B751AED17F50A6 (uuid), UNIQUE INDEX UNIQ_15B751AE7E9E4C8C (photo_id), UNIQUE INDEX UNIQ_15B751AE1C882DBD (driver_license_upload_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||
$this->addSql('CREATE TABLE teamer_availability (teamer_id INT NOT NULL, availability_id INT NOT NULL, INDEX IDX_9CF7958B4302FF75 (teamer_id), INDEX IDX_9CF7958B61778466 (availability_id), PRIMARY KEY(teamer_id, availability_id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||
$this->addSql('CREATE TABLE teamer_job_profile (teamer_id INT NOT NULL, job_profile_id INT NOT NULL, INDEX IDX_E78F5DF64302FF75 (teamer_id), INDEX IDX_E78F5DF6A68D82A5 (job_profile_id), PRIMARY KEY(teamer_id, job_profile_id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||
$this->addSql('CREATE TABLE teamer_assignment (teamer_id INT NOT NULL, assignment_id INT NOT NULL, INDEX IDX_BE0D5AE14302FF75 (teamer_id), INDEX IDX_BE0D5AE1D19302F8 (assignment_id), PRIMARY KEY(teamer_id, assignment_id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||
$this->addSql('CREATE TABLE training (id INT AUTO_INCREMENT NOT NULL, name VARCHAR(255) NOT NULL, created_by VARCHAR(255) DEFAULT NULL, updated_by VARCHAR(255) DEFAULT NULL, created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', updated_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', deleted_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||
$this->addSql('CREATE TABLE training_attendance (id INT AUTO_INCREMENT NOT NULL, training_id INT DEFAULT NULL, certificate_id INT DEFAULT NULL, teamer_id INT DEFAULT NULL, uuid VARCHAR(36) NOT NULL, date DATE NOT NULL COMMENT \'(DC2Type:date_immutable)\', created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', updated_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', UNIQUE INDEX UNIQ_D75DB7F7D17F50A6 (uuid), INDEX IDX_D75DB7F7BEFD98D1 (training_id), UNIQUE INDEX UNIQ_D75DB7F799223FFD (certificate_id), INDEX IDX_D75DB7F74302FF75 (teamer_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||
$this->addSql('CREATE TABLE upload (id INT AUTO_INCREMENT NOT NULL, owner_id INT DEFAULT NULL, disposition_id INT DEFAULT NULL, uuid VARCHAR(36) NOT NULL, type VARCHAR(64) NOT NULL, status VARCHAR(64) DEFAULT NULL, filename VARCHAR(255) NOT NULL, original_filename VARCHAR(255) NOT NULL, display_name VARCHAR(255) DEFAULT NULL, size INT NOT NULL, mime_type VARCHAR(255) NOT NULL, comment LONGTEXT DEFAULT NULL, created_by VARCHAR(255) DEFAULT NULL, updated_by VARCHAR(255) DEFAULT NULL, created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', updated_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', UNIQUE INDEX UNIQ_17BDE61FD17F50A6 (uuid), INDEX IDX_17BDE61F7E3C61F9 (owner_id), INDEX IDX_17BDE61F287B65ED (disposition_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||
$this->addSql('CREATE TABLE user (id INT AUTO_INCREMENT NOT NULL, teamer_id INT DEFAULT NULL, contact_id INT DEFAULT NULL, uuid VARCHAR(36) NOT NULL, bus_pro_address_id INT NOT NULL, bus_pro_person_id INT NOT NULL, first_name VARCHAR(255) NOT NULL, last_name VARCHAR(255) NOT NULL, email VARCHAR(255) NOT NULL, roles JSON NOT NULL COMMENT \'(DC2Type:json)\', last_login_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', hotel_code VARCHAR(32) DEFAULT NULL, created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', updated_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', UNIQUE INDEX UNIQ_8D93D649D17F50A6 (uuid), UNIQUE INDEX UNIQ_8D93D6494302FF75 (teamer_id), UNIQUE INDEX UNIQ_8D93D649E7A1254A (contact_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||
$this->addSql('CREATE TABLE messenger_messages (id BIGINT AUTO_INCREMENT NOT NULL, body LONGTEXT NOT NULL, headers LONGTEXT NOT NULL, queue_name VARCHAR(190) NOT NULL, created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', available_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', delivered_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', INDEX IDX_75EA56E0FB7336F0 (queue_name), INDEX IDX_75EA56E0E3BD61CE (available_at), INDEX IDX_75EA56E016BA31DB (delivered_at), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||
$this->addSql('CREATE TABLE upload (id INT AUTO_INCREMENT NOT NULL, owner_id INT DEFAULT NULL, disposition_id INT DEFAULT NULL, uuid VARCHAR(36) NOT NULL, type VARCHAR(64) NOT NULL, status VARCHAR(64) DEFAULT NULL, filename VARCHAR(255) NOT NULL, original_filename VARCHAR(255) NOT NULL, display_name VARCHAR(255) DEFAULT NULL, size INT NOT NULL, mime_type VARCHAR(255) NOT NULL, comment LONGTEXT DEFAULT NULL, downloaded_at DATE DEFAULT NULL COMMENT \'(DC2Type:date_immutable)\', downloaded_by VARCHAR(255) DEFAULT NULL, approved_at DATE DEFAULT NULL COMMENT \'(DC2Type:date_immutable)\', approved_by VARCHAR(255) DEFAULT NULL, created_by VARCHAR(255) DEFAULT NULL, updated_by VARCHAR(255) DEFAULT NULL, created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', updated_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', UNIQUE INDEX UNIQ_17BDE61FD17F50A6 (uuid), INDEX IDX_17BDE61F7E3C61F9 (owner_id), INDEX IDX_17BDE61F287B65ED (disposition_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||
$this->addSql('CREATE TABLE user (id INT AUTO_INCREMENT NOT NULL, teamer_id INT DEFAULT NULL, contact_id INT DEFAULT NULL, uuid VARCHAR(36) NOT NULL, bus_pro_address_id INT NOT NULL, bus_pro_person_id INT NOT NULL, first_name VARCHAR(255) NOT NULL, last_name VARCHAR(255) NOT NULL, email VARCHAR(255) NOT NULL, roles JSON NOT NULL, super_admin TINYINT(1) NOT NULL, last_login_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', hotel_codes JSON NOT NULL, mute_notifications TINYINT(1) NOT NULL, disabled_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', disabled_reason LONGTEXT DEFAULT NULL, disabled_reason_internal LONGTEXT DEFAULT NULL, created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', updated_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', deleted_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', UNIQUE INDEX UNIQ_8D93D649D17F50A6 (uuid), UNIQUE INDEX UNIQ_8D93D6494302FF75 (teamer_id), UNIQUE INDEX UNIQ_8D93D649E7A1254A (contact_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||
$this->addSql('CREATE TABLE messenger_messages (id BIGINT AUTO_INCREMENT NOT NULL, body LONGTEXT NOT NULL, headers LONGTEXT NOT NULL, queue_name VARCHAR(190) NOT NULL, created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', available_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', delivered_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', INDEX IDX_75EA56E0FB7336F0E3BD61CE16BA31DBBF396750 (queue_name, available_at, delivered_at, id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||
$this->addSql('ALTER TABLE application ADD CONSTRAINT FK_A45BDDC1D19302F8 FOREIGN KEY (assignment_id) REFERENCES assignment (id)');
|
||||
$this->addSql('ALTER TABLE application ADD CONSTRAINT FK_A45BDDC14302FF75 FOREIGN KEY (teamer_id) REFERENCES teamer (id)');
|
||||
$this->addSql('ALTER TABLE assignment ADD CONSTRAINT FK_30C544BA816C6140 FOREIGN KEY (destination_id) REFERENCES destination (id)');
|
||||
@@ -52,20 +54,21 @@ final class Version20231208094506 extends AbstractMigration
|
||||
$this->addSql('ALTER TABLE assignment ADD CONSTRAINT FK_30C544BA7E3C61F9 FOREIGN KEY (owner_id) REFERENCES user (id)');
|
||||
$this->addSql('ALTER TABLE assignment_fee ADD CONSTRAINT FK_4E2AE50AD19302F8 FOREIGN KEY (assignment_id) REFERENCES assignment (id) ON DELETE CASCADE');
|
||||
$this->addSql('ALTER TABLE assignment_fee ADD CONSTRAINT FK_4E2AE50AAB45AECA FOREIGN KEY (fee_id) REFERENCES fee (id) ON DELETE CASCADE');
|
||||
$this->addSql('ALTER TABLE assignment_upload ADD CONSTRAINT FK_2F9CC602D19302F8 FOREIGN KEY (assignment_id) REFERENCES assignment (id) ON DELETE CASCADE');
|
||||
$this->addSql('ALTER TABLE assignment_upload ADD CONSTRAINT FK_2F9CC602CCCFBA31 FOREIGN KEY (upload_id) REFERENCES upload (id) ON DELETE CASCADE');
|
||||
$this->addSql('ALTER TABLE availability ADD CONSTRAINT FK_3FB7A2BF7E3C61F9 FOREIGN KEY (owner_id) REFERENCES teamer (id)');
|
||||
$this->addSql('ALTER TABLE contact ADD CONSTRAINT FK_4C62E6387E9E4C8C FOREIGN KEY (photo_id) REFERENCES upload (id)');
|
||||
$this->addSql('ALTER TABLE disposition ADD CONSTRAINT FK_4C58BF60D19302F8 FOREIGN KEY (assignment_id) REFERENCES assignment (id)');
|
||||
$this->addSql('ALTER TABLE disposition ADD CONSTRAINT FK_4C58BF604302FF75 FOREIGN KEY (teamer_id) REFERENCES teamer (id)');
|
||||
$this->addSql('ALTER TABLE disposition ADD CONSTRAINT FK_4C58BF60D249A887 FOREIGN KEY (feedback_id) REFERENCES feedback (id) ON DELETE SET NULL');
|
||||
$this->addSql('ALTER TABLE feedback ADD CONSTRAINT FK_D22944584302FF75 FOREIGN KEY (teamer_id) REFERENCES teamer (id)');
|
||||
$this->addSql('ALTER TABLE feedback ADD CONSTRAINT FK_D2294458D19302F8 FOREIGN KEY (assignment_id) REFERENCES assignment (id)');
|
||||
$this->addSql('ALTER TABLE feedback ADD CONSTRAINT FK_D22944582DB73956 FOREIGN KEY (feedback_set_id) REFERENCES feedback_set (id) ON DELETE SET NULL');
|
||||
$this->addSql('ALTER TABLE job_profile ADD CONSTRAINT FK_1BB81D942DB73956 FOREIGN KEY (feedback_set_id) REFERENCES feedback_set (id) ON DELETE SET NULL');
|
||||
$this->addSql('ALTER TABLE job_profile_training ADD CONSTRAINT FK_F19E0BB8A68D82A5 FOREIGN KEY (job_profile_id) REFERENCES job_profile (id) ON DELETE CASCADE');
|
||||
$this->addSql('ALTER TABLE job_profile_training ADD CONSTRAINT FK_F19E0BB8BEFD98D1 FOREIGN KEY (training_id) REFERENCES training (id) ON DELETE CASCADE');
|
||||
$this->addSql('ALTER TABLE license ADD CONSTRAINT FK_5768F41999223FFD FOREIGN KEY (certificate_id) REFERENCES upload (id)');
|
||||
$this->addSql('ALTER TABLE license ADD CONSTRAINT FK_5768F4194302FF75 FOREIGN KEY (teamer_id) REFERENCES teamer (id)');
|
||||
$this->addSql('ALTER TABLE teamer ADD CONSTRAINT FK_15B751AE7E9E4C8C FOREIGN KEY (photo_id) REFERENCES upload (id)');
|
||||
$this->addSql('ALTER TABLE teamer ADD CONSTRAINT FK_15B751AE1C882DBD FOREIGN KEY (driver_license_upload_id) REFERENCES upload (id)');
|
||||
$this->addSql('ALTER TABLE teamer_availability ADD CONSTRAINT FK_9CF7958B4302FF75 FOREIGN KEY (teamer_id) REFERENCES teamer (id) ON DELETE CASCADE');
|
||||
$this->addSql('ALTER TABLE teamer_availability ADD CONSTRAINT FK_9CF7958B61778466 FOREIGN KEY (availability_id) REFERENCES availability (id) ON DELETE CASCADE');
|
||||
$this->addSql('ALTER TABLE teamer_job_profile ADD CONSTRAINT FK_E78F5DF64302FF75 FOREIGN KEY (teamer_id) REFERENCES teamer (id) ON DELETE CASCADE');
|
||||
@@ -92,20 +95,21 @@ final class Version20231208094506 extends AbstractMigration
|
||||
$this->addSql('ALTER TABLE assignment DROP FOREIGN KEY FK_30C544BA7E3C61F9');
|
||||
$this->addSql('ALTER TABLE assignment_fee DROP FOREIGN KEY FK_4E2AE50AD19302F8');
|
||||
$this->addSql('ALTER TABLE assignment_fee DROP FOREIGN KEY FK_4E2AE50AAB45AECA');
|
||||
$this->addSql('ALTER TABLE assignment_upload DROP FOREIGN KEY FK_2F9CC602D19302F8');
|
||||
$this->addSql('ALTER TABLE assignment_upload DROP FOREIGN KEY FK_2F9CC602CCCFBA31');
|
||||
$this->addSql('ALTER TABLE availability DROP FOREIGN KEY FK_3FB7A2BF7E3C61F9');
|
||||
$this->addSql('ALTER TABLE contact DROP FOREIGN KEY FK_4C62E6387E9E4C8C');
|
||||
$this->addSql('ALTER TABLE disposition DROP FOREIGN KEY FK_4C58BF60D19302F8');
|
||||
$this->addSql('ALTER TABLE disposition DROP FOREIGN KEY FK_4C58BF604302FF75');
|
||||
$this->addSql('ALTER TABLE disposition DROP FOREIGN KEY FK_4C58BF60D249A887');
|
||||
$this->addSql('ALTER TABLE feedback DROP FOREIGN KEY FK_D22944584302FF75');
|
||||
$this->addSql('ALTER TABLE feedback DROP FOREIGN KEY FK_D2294458D19302F8');
|
||||
$this->addSql('ALTER TABLE feedback DROP FOREIGN KEY FK_D22944582DB73956');
|
||||
$this->addSql('ALTER TABLE job_profile DROP FOREIGN KEY FK_1BB81D942DB73956');
|
||||
$this->addSql('ALTER TABLE job_profile_training DROP FOREIGN KEY FK_F19E0BB8A68D82A5');
|
||||
$this->addSql('ALTER TABLE job_profile_training DROP FOREIGN KEY FK_F19E0BB8BEFD98D1');
|
||||
$this->addSql('ALTER TABLE license DROP FOREIGN KEY FK_5768F41999223FFD');
|
||||
$this->addSql('ALTER TABLE license DROP FOREIGN KEY FK_5768F4194302FF75');
|
||||
$this->addSql('ALTER TABLE teamer DROP FOREIGN KEY FK_15B751AE7E9E4C8C');
|
||||
$this->addSql('ALTER TABLE teamer DROP FOREIGN KEY FK_15B751AE1C882DBD');
|
||||
$this->addSql('ALTER TABLE teamer_availability DROP FOREIGN KEY FK_9CF7958B4302FF75');
|
||||
$this->addSql('ALTER TABLE teamer_availability DROP FOREIGN KEY FK_9CF7958B61778466');
|
||||
$this->addSql('ALTER TABLE teamer_job_profile DROP FOREIGN KEY FK_E78F5DF64302FF75');
|
||||
@@ -122,11 +126,11 @@ final class Version20231208094506 extends AbstractMigration
|
||||
$this->addSql('DROP TABLE application');
|
||||
$this->addSql('DROP TABLE assignment');
|
||||
$this->addSql('DROP TABLE assignment_fee');
|
||||
$this->addSql('DROP TABLE assignment_upload');
|
||||
$this->addSql('DROP TABLE availability');
|
||||
$this->addSql('DROP TABLE contact');
|
||||
$this->addSql('DROP TABLE destination');
|
||||
$this->addSql('DROP TABLE disposition');
|
||||
$this->addSql('DROP TABLE email_text');
|
||||
$this->addSql('DROP TABLE faq');
|
||||
$this->addSql('DROP TABLE fee');
|
||||
$this->addSql('DROP TABLE feedback');
|
||||
@@ -134,6 +138,8 @@ final class Version20231208094506 extends AbstractMigration
|
||||
$this->addSql('DROP TABLE job_profile');
|
||||
$this->addSql('DROP TABLE job_profile_training');
|
||||
$this->addSql('DROP TABLE license');
|
||||
$this->addSql('DROP TABLE news');
|
||||
$this->addSql('DROP TABLE statistics_event');
|
||||
$this->addSql('DROP TABLE teamer');
|
||||
$this->addSql('DROP TABLE teamer_availability');
|
||||
$this->addSql('DROP TABLE teamer_job_profile');
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Adds the season_assignment table: the placement of an all-season teamer at a destination.
|
||||
*
|
||||
* Written by hand rather than taken from doctrine:migrations:diff - the diff against this
|
||||
* database also carries a large amount of pre-existing drift (datetime and JSON column
|
||||
* declarations across nearly every table) that has nothing to do with this feature.
|
||||
*/
|
||||
final class Version20260909124625 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Add season_assignment table';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql('CREATE TABLE season_assignment (id INT AUTO_INCREMENT NOT NULL, uuid VARCHAR(36) NOT NULL, remarks LONGTEXT DEFAULT NULL, created_by VARCHAR(255) DEFAULT NULL, updated_by VARCHAR(255) DEFAULT NULL, created_at DATETIME NOT NULL, updated_at DATETIME DEFAULT NULL, deleted_at DATETIME DEFAULT NULL, teamer_id INT DEFAULT NULL, destination_id INT DEFAULT NULL, job_profile_id INT DEFAULT NULL, UNIQUE INDEX UNIQ_FB891DD17F50A6 (uuid), INDEX IDX_FB891D4302FF75 (teamer_id), INDEX IDX_FB891D816C6140 (destination_id), INDEX IDX_FB891DA68D82A5 (job_profile_id), PRIMARY KEY (id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||
$this->addSql('ALTER TABLE season_assignment ADD CONSTRAINT FK_FB891D4302FF75 FOREIGN KEY (teamer_id) REFERENCES teamer (id)');
|
||||
$this->addSql('ALTER TABLE season_assignment ADD CONSTRAINT FK_FB891D816C6140 FOREIGN KEY (destination_id) REFERENCES destination (id)');
|
||||
$this->addSql('ALTER TABLE season_assignment ADD CONSTRAINT FK_FB891DA68D82A5 FOREIGN KEY (job_profile_id) REFERENCES job_profile (id)');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE season_assignment DROP FOREIGN KEY FK_FB891D4302FF75');
|
||||
$this->addSql('ALTER TABLE season_assignment DROP FOREIGN KEY FK_FB891D816C6140');
|
||||
$this->addSql('ALTER TABLE season_assignment DROP FOREIGN KEY FK_FB891DA68D82A5');
|
||||
$this->addSql('DROP TABLE season_assignment');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Adds the season assignment checklist: one row per checklist entry per assignment, the binding
|
||||
* of an entry to a document from the global library, and the two links a season assignment needs
|
||||
* to carry its own files.
|
||||
*
|
||||
* Written by hand rather than taken from doctrine:migrations:diff, like Version20260909124625 -
|
||||
* the diff against this database also carries the pre-existing drift in datetime and JSON column
|
||||
* declarations, which has nothing to do with this feature.
|
||||
*/
|
||||
final class Version20260915103000 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Add season assignment tasks, task document mapping and season uploads';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql('CREATE TABLE season_assignment_task (id INT AUTO_INCREMENT NOT NULL, uuid VARCHAR(36) NOT NULL, task_key VARCHAR(64) NOT NULL, done TINYINT(1) NOT NULL, done_at DATETIME DEFAULT NULL, done_by VARCHAR(255) DEFAULT NULL, uploaded TINYINT(1) NOT NULL, deadline_override1 DATE DEFAULT NULL, deadline_override2 DATE DEFAULT NULL, created_by VARCHAR(255) DEFAULT NULL, updated_by VARCHAR(255) DEFAULT NULL, created_at DATETIME NOT NULL, updated_at DATETIME DEFAULT NULL, season_assignment_id INT NOT NULL, UNIQUE INDEX UNIQ_BEEB9D8DD17F50A6 (uuid), UNIQUE INDEX season_assignment_task_unique (season_assignment_id, task_key), INDEX IDX_BEEB9D8DB291FD85 (season_assignment_id), PRIMARY KEY (id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||
$this->addSql('CREATE TABLE season_task_document (id INT AUTO_INCREMENT NOT NULL, uuid VARCHAR(36) NOT NULL, task_key VARCHAR(64) NOT NULL, created_by VARCHAR(255) DEFAULT NULL, updated_by VARCHAR(255) DEFAULT NULL, created_at DATETIME NOT NULL, updated_at DATETIME DEFAULT NULL, document_id INT NOT NULL, UNIQUE INDEX UNIQ_FBAB9F47D17F50A6 (uuid), UNIQUE INDEX UNIQ_FBAB9F47E512532E (task_key), INDEX IDX_FBAB9F47C33F7837 (document_id), PRIMARY KEY (id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||
$this->addSql('ALTER TABLE season_assignment_task ADD CONSTRAINT FK_BEEB9D8DB291FD85 FOREIGN KEY (season_assignment_id) REFERENCES season_assignment (id)');
|
||||
$this->addSql('ALTER TABLE season_task_document ADD CONSTRAINT FK_FBAB9F47C33F7837 FOREIGN KEY (document_id) REFERENCES upload (id)');
|
||||
$this->addSql('ALTER TABLE season_assignment ADD done_teamer TINYINT(1) NOT NULL');
|
||||
$this->addSql('ALTER TABLE upload ADD season_assignment_id INT DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE upload ADD CONSTRAINT FK_17BDE61FB291FD85 FOREIGN KEY (season_assignment_id) REFERENCES season_assignment (id)');
|
||||
$this->addSql('CREATE INDEX IDX_17BDE61FB291FD85 ON upload (season_assignment_id)');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE upload DROP FOREIGN KEY FK_17BDE61FB291FD85');
|
||||
$this->addSql('DROP INDEX IDX_17BDE61FB291FD85 ON upload');
|
||||
$this->addSql('ALTER TABLE upload DROP season_assignment_id');
|
||||
$this->addSql('ALTER TABLE season_assignment DROP done_teamer');
|
||||
$this->addSql('ALTER TABLE season_assignment_task DROP FOREIGN KEY FK_BEEB9D8DB291FD85');
|
||||
$this->addSql('ALTER TABLE season_task_document DROP FOREIGN KEY FK_FBAB9F47C33F7837');
|
||||
$this->addSql('DROP TABLE season_assignment_task');
|
||||
$this->addSql('DROP TABLE season_task_document');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Replaces the season assignment's Destination relation with its own period and house.
|
||||
*
|
||||
* A Destination is one travel with one departure window; an all-season placement runs a whole
|
||||
* season at one house, so the relation only ever meant "borrow three scalars from an arbitrary
|
||||
* travel row". The house is now a key of the configured "houses" map, which is keyed by the
|
||||
* normalized hotel code - hence the SER stripping in the backfill, mirroring
|
||||
* App\Entity\Destination::getHotelCodeNormalized().
|
||||
*
|
||||
* Written by hand rather than taken from doctrine:migrations:diff, like Version20260909124625 -
|
||||
* the diff against this database also carries the pre-existing drift in datetime and JSON column
|
||||
* declarations, which has nothing to do with this feature.
|
||||
*/
|
||||
final class Version20260916160000 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Move season assignment period and house onto the assignment itself';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// nullable first, so the existing rows can be backfilled before the columns are tightened
|
||||
$this->addSql('ALTER TABLE season_assignment ADD date_from DATE DEFAULT NULL, ADD date_to DATE DEFAULT NULL, ADD hotel_code VARCHAR(32) DEFAULT NULL');
|
||||
|
||||
$this->addSql('UPDATE season_assignment sa JOIN destination d ON d.id = sa.destination_id SET sa.date_from = d.date_from, sa.date_to = d.date_to, sa.hotel_code = CASE WHEN d.hotel_code LIKE \'SER%\' THEN SUBSTRING(d.hotel_code, 4, 3) ELSE SUBSTRING(d.hotel_code, 1, 3) END');
|
||||
|
||||
$this->addSql('ALTER TABLE season_assignment CHANGE date_from date_from DATE NOT NULL, CHANGE date_to date_to DATE NOT NULL, CHANGE hotel_code hotel_code VARCHAR(32) NOT NULL');
|
||||
|
||||
$this->addSql('ALTER TABLE season_assignment DROP FOREIGN KEY FK_FB891D816C6140');
|
||||
$this->addSql('DROP INDEX IDX_FB891D816C6140 ON season_assignment');
|
||||
$this->addSql('ALTER TABLE season_assignment DROP destination_id');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE season_assignment ADD destination_id INT DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE season_assignment ADD CONSTRAINT FK_FB891D816C6140 FOREIGN KEY (destination_id) REFERENCES destination (id)');
|
||||
$this->addSql('CREATE INDEX IDX_FB891D816C6140 ON season_assignment (destination_id)');
|
||||
|
||||
// The destination a placement was cut from cannot be recovered from a period and a house
|
||||
// code, so the relation comes back empty.
|
||||
$this->addSql('ALTER TABLE season_assignment DROP date_from, DROP date_to, DROP hotel_code');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Drops the season assignment's done_teamer flag.
|
||||
*
|
||||
* It was a note the office set by hand and it gated nothing - neither the weekly teamer digest
|
||||
* nor the office deadline summary ever read it. Both stages it was meant to express follow from
|
||||
* the checklist rows, so App\Service\Season\SeasonChecklistBuilder derives them instead.
|
||||
*
|
||||
* Written by hand rather than taken from doctrine:migrations:diff, like Version20260916160000:
|
||||
* the diff against this database also carries the pre-existing drift in datetime and JSON column
|
||||
* declarations, which has nothing to do with this change.
|
||||
*/
|
||||
final class Version20260917120000 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Drop the informative done_teamer flag from season assignments';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE season_assignment DROP done_teamer');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// The hand-set notes themselves cannot be recovered, so the column comes back unset.
|
||||
$this->addSql('ALTER TABLE season_assignment ADD done_teamer TINYINT(1) NOT NULL');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Renames the checklist entry's uploaded flag to office_done.
|
||||
*
|
||||
* The flag is the office's own step on an entry, and that step differs per entry - filing a
|
||||
* returned contract is not the same act as registering someone with the municipality. Named
|
||||
* after a file arriving, it read as a second kind of "done" everywhere else, next to the done
|
||||
* flag that actually closes the entry.
|
||||
*
|
||||
* A rename, not a new column: the ticks themselves stay.
|
||||
*/
|
||||
final class Version20260917123000 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Rename season_assignment_task.uploaded to office_done';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE season_assignment_task CHANGE uploaded office_done TINYINT(1) NOT NULL');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE season_assignment_task CHANGE office_done uploaded TINYINT(1) NOT NULL');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Copies the library document onto the checklist row.
|
||||
*
|
||||
* The library holds what the next assignments get and is replaced from season to season, so
|
||||
* resolving it on read rewrote what an assignment that already exists - a finished one included -
|
||||
* hands out. The row carries its own reference from now on.
|
||||
*
|
||||
* The backfill maps every existing row to what it resolves to today, so nothing changes for the
|
||||
* rows that are already out there. ON DELETE SET NULL keeps a row readable when the library file
|
||||
* itself is deleted: "kein Dokument hinterlegt" is a state every view already handles.
|
||||
*/
|
||||
final class Version20260917140000 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Give each season checklist row its own document reference';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE season_assignment_task ADD document_id INT DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE season_assignment_task ADD CONSTRAINT FK_BEEB9D8DC33F7837 FOREIGN KEY (document_id) REFERENCES upload (id) ON DELETE SET NULL');
|
||||
$this->addSql('CREATE INDEX IDX_BEEB9D8DC33F7837 ON season_assignment_task (document_id)');
|
||||
|
||||
$this->addSql('UPDATE season_assignment_task t JOIN season_task_document d ON d.task_key = t.task_key SET t.document_id = d.document_id');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE season_assignment_task DROP FOREIGN KEY FK_BEEB9D8DC33F7837');
|
||||
$this->addSql('DROP INDEX IDX_BEEB9D8DC33F7837 ON season_assignment_task');
|
||||
$this->addSql('ALTER TABLE season_assignment_task DROP document_id');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Work months imported from the SaaS time tracking, and the signed Arbeitszeitnachweis that
|
||||
* belongs to each of them.
|
||||
*
|
||||
* Hand-trimmed: doctrine:migrations:diff also produced a long tail of datetime, JSON and
|
||||
* TINYINT redeclarations across almost every table. That is the long-standing drift between
|
||||
* the dev database and the mapping, not anything this feature changed, so none of it is here.
|
||||
*/
|
||||
final class Version20260921122743 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Adds teamer_work_month, the SaaS employee id on teamer and the work month relation on upload';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql('CREATE TABLE teamer_work_month (id INT AUTO_INCREMENT NOT NULL, uuid VARCHAR(36) NOT NULL, year SMALLINT NOT NULL, month SMALLINT NOT NULL, days JSON NOT NULL, total_minutes INT NOT NULL, worked_days INT NOT NULL, absence_days INT NOT NULL, source_ist_minutes INT NOT NULL, status VARCHAR(255) NOT NULL, submitted_at DATETIME DEFAULT NULL, reviewed_at DATETIME DEFAULT NULL, reviewed_by VARCHAR(8) DEFAULT NULL, review_comment LONGTEXT DEFAULT NULL, synced_at DATETIME NOT NULL, warnings JSON DEFAULT NULL, mismatch_detected_at DATETIME DEFAULT NULL, mismatch_total_minutes INT DEFAULT NULL, created_at DATETIME NOT NULL, updated_at DATETIME DEFAULT NULL, teamer_id INT NOT NULL, UNIQUE INDEX UNIQ_28857572D17F50A6 (uuid), UNIQUE INDEX teamer_work_month_unique (teamer_id, year, month), INDEX IDX_288575724302FF75 (teamer_id), PRIMARY KEY (id)) DEFAULT CHARACTER SET utf8mb4');
|
||||
$this->addSql('ALTER TABLE teamer_work_month ADD CONSTRAINT FK_288575724302FF75 FOREIGN KEY (teamer_id) REFERENCES teamer (id)');
|
||||
|
||||
$this->addSql('ALTER TABLE teamer ADD saas_employee_uuid VARCHAR(36) DEFAULT NULL');
|
||||
|
||||
$this->addSql('ALTER TABLE upload ADD teamer_work_month_id INT DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE upload ADD CONSTRAINT FK_17BDE61FFEAE833A FOREIGN KEY (teamer_work_month_id) REFERENCES teamer_work_month (id)');
|
||||
$this->addSql('CREATE INDEX IDX_17BDE61FFEAE833A ON upload (teamer_work_month_id)');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE upload DROP FOREIGN KEY FK_17BDE61FFEAE833A');
|
||||
$this->addSql('DROP INDEX IDX_17BDE61FFEAE833A ON upload');
|
||||
$this->addSql('ALTER TABLE upload DROP teamer_work_month_id');
|
||||
|
||||
$this->addSql('ALTER TABLE teamer DROP saas_employee_uuid');
|
||||
|
||||
$this->addSql('ALTER TABLE teamer_work_month DROP FOREIGN KEY FK_288575724302FF75');
|
||||
$this->addSql('DROP TABLE teamer_work_month');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* The monthly target and the month's total break time, both shown in the head of the
|
||||
* Arbeitszeitnachweis next to the hours actually worked.
|
||||
*
|
||||
* Hand-trimmed, like Version20260921122743: doctrine:migrations:diff sweeps in the
|
||||
* long-standing datetime and JSON drift of the dev database, none of which belongs here.
|
||||
*
|
||||
* The per-day start, end and break times that go with these live in the existing "days" JSON
|
||||
* column, so they need no schema change. Rows imported before this migration simply carry
|
||||
* neither, and are refreshed by the next import of a month that is not yet signed.
|
||||
*/
|
||||
final class Version20260921160000 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Adds the monthly target and total break time to teamer_work_month';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE teamer_work_month ADD target_minutes INT DEFAULT 0 NOT NULL, ADD break_minutes INT DEFAULT 0 NOT NULL');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE teamer_work_month DROP target_minutes, DROP break_minutes');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* The employee's department, shown in the head of the Arbeitszeitnachweis.
|
||||
*
|
||||
* Hand-trimmed like its two predecessors: doctrine:migrations:diff sweeps in the long-standing
|
||||
* datetime and JSON drift of the dev database, none of which belongs here.
|
||||
*
|
||||
* Nullable, and not only because existing rows have none: a month made up entirely of holiday
|
||||
* carries no booking to read a department from.
|
||||
*/
|
||||
final class Version20260921170000 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Adds the department to teamer_work_month';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE teamer_work_month ADD department VARCHAR(255) DEFAULT NULL');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE teamer_work_month DROP department');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Form submissions posted by the sibling websites (TYPO3 ApplicationApiFinisher).
|
||||
*
|
||||
* Generated with doctrine:migrations:diff and reduced to these two statements: the diff also
|
||||
* swept in the long-standing dev drift in the datetime and json column declarations, plus
|
||||
* tables that only exist on another branch.
|
||||
*/
|
||||
final class Version20260923062331 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Add application_form_submission for form submissions posted by the websites';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql('CREATE TABLE application_form_submission (id INT AUTO_INCREMENT NOT NULL, uuid VARCHAR(36) NOT NULL, form VARCHAR(64) NOT NULL, form_label VARCHAR(255) NOT NULL, submitted_at DATETIME NOT NULL, page_uid INT DEFAULT NULL, page_url VARCHAR(2048) DEFAULT NULL, first_name VARCHAR(255) DEFAULT NULL, last_name VARCHAR(255) DEFAULT NULL, email VARCHAR(255) DEFAULT NULL, phone VARCHAR(64) DEFAULT NULL, mobile VARCHAR(64) DEFAULT NULL, birth_date VARCHAR(64) DEFAULT NULL, street VARCHAR(255) DEFAULT NULL, zip_code VARCHAR(32) DEFAULT NULL, city VARCHAR(255) DEFAULT NULL, details JSON NOT NULL, payload JSON NOT NULL, created_at DATETIME NOT NULL, updated_at DATETIME DEFAULT NULL, UNIQUE INDEX UNIQ_F47CE995D17F50A6 (uuid), INDEX IDX_F47CE995B5265E72 (form, submitted_at), PRIMARY KEY (id)) DEFAULT CHARACTER SET utf8mb4');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql('DROP TABLE application_form_submission');
|
||||
}
|
||||
}
|
||||
Loaded 100 of 910 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user