Compare commits

...

14 Commits

Author SHA1 Message Date
cfe7baa4ae feat : Ajout de pinia, création de la table weight et reception mise en place du système de step pour les receptions (WIP) 2026-01-12 18:07:58 +01:00
03638d988b fix : correction du useApi et ajout des autres types de requête 2026-01-12 14:05:12 +01:00
889e8d6a09 feat : Ajout d'un layout default.vue + Ajout de la couleur primary dans la conf tailwind.config.ts +Ajout d'un composable pour gérer les appels API (GET, POST) 2026-01-12 11:58:46 +01:00
14960d5e87 feat : Ajout d'un CHANGELOG.md 2026-01-07 15:05:40 +01:00
ecb6f25159 feat : Ajout d'un template de merge request 2026-01-07 15:05:28 +01:00
566e7f132a feat : Ajout d'un commit linter 2026-01-07 15:04:56 +01:00
a2d20dafb1 Feat : Ajout de la conf xdebug et des commandes utiles dans le README.md 2026-01-07 08:29:30 +01:00
6fc72d180a Fix : Config docker et xdebug 2026-01-06 16:49:32 +01:00
c082224c7d Fix : Config docker exposition du port 3000 pour le front 2026-01-06 15:01:07 +01:00
74a0120883 Fix : Config php unit et gitignore 2026-01-06 14:43:17 +01:00
b0f4004d11 Fix : Suppression des logs 2026-01-06 14:42:00 +01:00
c759194b83 Fix : Makefile pour l'initialisation du projet 2026-01-06 14:35:28 +01:00
5f0703811f Fix : Ajout php unit et correction de l'installation du projet (bdd local) 2026-01-06 14:00:05 +01:00
8ea211835f First commit 2026-01-06 10:50:33 +01:00
97 changed files with 27193 additions and 1 deletions

17
.editorconfig Normal file
View File

@@ -0,0 +1,17 @@
# editorconfig.org
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
[{compose.yaml,compose.*.yaml}]
indent_size = 2
[*.md]
trim_trailing_whitespace = false

41
.env Normal file
View File

@@ -0,0 +1,41 @@
# In all environments, the following files are loaded if they exist,
# the latter taking precedence over the former:
#
# * .env contains default values for the environment variables needed by the app
# * .env.local uncommitted file with local overrides
# * .env.$APP_ENV committed environment-specific defaults
# * .env.$APP_ENV.local uncommitted environment-specific overrides
#
# Real environment variables win over .env files.
#
# DO NOT DEFINE PRODUCTION SECRETS IN THIS FILE NOR IN ANY OTHER COMMITTED FILES.
# https://symfony.com/doc/current/configuration/secrets.html
#
# Run "composer dump-env prod" to compile .env files for production use (requires symfony/flex >=1.2).
# https://symfony.com/doc/current/best_practices.html#use-environment-variables-for-infrastructure-configuration
###> symfony/framework-bundle ###
APP_ENV=dev
APP_SECRET=
APP_SHARE_DIR=var/share
###< symfony/framework-bundle ###
###> symfony/routing ###
# Configure how to generate URLs in non-HTTP contexts, such as CLI commands.
# See https://symfony.com/doc/current/routing.html#generating-urls-in-commands
DEFAULT_URI=http://localhost
###< symfony/routing ###
###> doctrine/doctrine-bundle ###
# Format described at https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url
# IMPORTANT: You MUST configure your server version, either here or in config/packages/doctrine.yaml
#
# DATABASE_URL="sqlite:///%kernel.project_dir%/var/data_%kernel.environment%.db"
# DATABASE_URL="mysql://app:!ChangeMe!@127.0.0.1:3306/app?serverVersion=8.0.32&charset=utf8mb4"
# DATABASE_URL="mysql://app:!ChangeMe!@127.0.0.1:3306/app?serverVersion=10.11.2-MariaDB&charset=utf8mb4"
#DATABASE_URL="postgresql://app:!ChangeMe!@127.0.0.1:5432/app?serverVersion=16&charset=utf8"
###< doctrine/doctrine-bundle ###
###> nelmio/cors-bundle ###
CORS_ALLOW_ORIGIN='^https?://(localhost|127\.0\.0\.1)(:[0-9]+)?$'
###< nelmio/cors-bundle ###

3
.env.test Normal file
View File

@@ -0,0 +1,3 @@
# define your env variables for the test env here
KERNEL_CLASS='App\Kernel'
APP_SECRET='$ecretf0rt3st'

20
.gitattributes vendored Normal file
View File

@@ -0,0 +1,20 @@
# Force LF in repo
* text=auto eol=lf
# (optionnel) scripts
*.sh text eol=lf
*.bash text eol=lf
# (optionnel) configs
*.yml text eol=lf
*.yaml text eol=lf
*.env text eol=lf
Dockerfile text eol=lf
# (optionnel) frontend
*.ts text eol=lf
*.tsx text eol=lf
*.js text eol=lf
*.vue text eol=lf
*.css text eol=lf
*.scss text eol=lf

View File

@@ -0,0 +1,23 @@
---
name: "Merge Request"
about: "Template de MR"
title: "[#NUMERO_TICKET] TITRE TICKET"
ref: "main"
---
| Numéro du ticket | Titre du ticket |
|------------------|-----------------|
| | |
## Description de la PR
## Modification du .env
## Check list
- [ ] Pas de régression
- [ ] TU/TI/TF rédigée
- [ ] TU/TI/TF OK
- [ ] CHANGELOG modifié

25
.gitignore vendored Normal file
View File

@@ -0,0 +1,25 @@
###> symfony/framework-bundle ###
/.env.local
/.env.local.php
/.env.*.local
/config/secrets/prod/prod.decrypt.private.php
/public/bundles/
/var/
/vendor/
/LOG/
###< symfony/framework-bundle ###
###> friendsofphp/php-cs-fixer ###
/.php-cs-fixer.php
/.php-cs-fixer.cache
###< friendsofphp/php-cs-fixer ###
###> phpunit/phpunit ###
/phpunit.xml
/.phpunit.cache/
###< phpunit/phpunit ###
###> docker ###
docker/.env.docker.local
###< docker ###

9
.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,9 @@
# Default ignored files
/shelf/
# Ignored default folder with query files
/queries/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
# Editor-based HTTP Client requests
/httpRequests/

5
.idea/codeStyles/codeStyleConfig.xml generated Normal file
View File

@@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
</state>
</component>

12
.idea/dataSources.xml generated Normal file
View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="ferme" uuid="f407a514-c6b4-4b26-9555-445a85892502">
<driver-ref>postgresql</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.postgresql.Driver</jdbc-driver>
<jdbc-url>jdbc:postgresql://localhost:5432/ferme</jdbc-url>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
</component>
</project>

146
.idea/ferme.iml generated Normal file
View File

@@ -0,0 +1,146 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" packagePrefix="App\" />
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" packagePrefix="App\Tests\" />
<excludeFolder url="file://$MODULE_DIR$/public/bundles" />
<excludeFolder url="file://$MODULE_DIR$/var" />
<excludeFolder url="file://$MODULE_DIR$/vendor/api-platform/doctrine-common" />
<excludeFolder url="file://$MODULE_DIR$/vendor/api-platform/doctrine-orm" />
<excludeFolder url="file://$MODULE_DIR$/vendor/api-platform/documentation" />
<excludeFolder url="file://$MODULE_DIR$/vendor/api-platform/http-cache" />
<excludeFolder url="file://$MODULE_DIR$/vendor/api-platform/hydra" />
<excludeFolder url="file://$MODULE_DIR$/vendor/api-platform/json-schema" />
<excludeFolder url="file://$MODULE_DIR$/vendor/api-platform/jsonld" />
<excludeFolder url="file://$MODULE_DIR$/vendor/api-platform/metadata" />
<excludeFolder url="file://$MODULE_DIR$/vendor/api-platform/openapi" />
<excludeFolder url="file://$MODULE_DIR$/vendor/api-platform/serializer" />
<excludeFolder url="file://$MODULE_DIR$/vendor/api-platform/state" />
<excludeFolder url="file://$MODULE_DIR$/vendor/api-platform/symfony" />
<excludeFolder url="file://$MODULE_DIR$/vendor/api-platform/validator" />
<excludeFolder url="file://$MODULE_DIR$/vendor/clue/ndjson-react" />
<excludeFolder url="file://$MODULE_DIR$/vendor/composer" />
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/collections" />
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/common" />
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/dbal" />
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/deprecations" />
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/doctrine-bundle" />
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/doctrine-migrations-bundle" />
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/event-manager" />
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/inflector" />
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/instantiator" />
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/lexer" />
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/migrations" />
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/orm" />
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/persistence" />
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/sql-formatter" />
<excludeFolder url="file://$MODULE_DIR$/vendor/evenement/evenement" />
<excludeFolder url="file://$MODULE_DIR$/vendor/fidry/cpu-core-counter" />
<excludeFolder url="file://$MODULE_DIR$/vendor/friendsofphp/php-cs-fixer" />
<excludeFolder url="file://$MODULE_DIR$/vendor/nelmio/cors-bundle" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpdocumentor/reflection-common" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpdocumentor/reflection-docblock" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpdocumentor/type-resolver" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpstan/phpdoc-parser" />
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/cache" />
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/clock" />
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/container" />
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/event-dispatcher" />
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/link" />
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/log" />
<excludeFolder url="file://$MODULE_DIR$/vendor/react/cache" />
<excludeFolder url="file://$MODULE_DIR$/vendor/react/child-process" />
<excludeFolder url="file://$MODULE_DIR$/vendor/react/dns" />
<excludeFolder url="file://$MODULE_DIR$/vendor/react/event-loop" />
<excludeFolder url="file://$MODULE_DIR$/vendor/react/promise" />
<excludeFolder url="file://$MODULE_DIR$/vendor/react/socket" />
<excludeFolder url="file://$MODULE_DIR$/vendor/react/stream" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/diff" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/asset" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/cache" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/cache-contracts" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/clock" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/config" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/console" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/dependency-injection" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/deprecation-contracts" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/doctrine-bridge" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/dotenv" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/error-handler" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/event-dispatcher" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/event-dispatcher-contracts" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/expression-language" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/filesystem" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/finder" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/flex" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/framework-bundle" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/http-foundation" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/http-kernel" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/options-resolver" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/password-hasher" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-intl-grapheme" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-intl-normalizer" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-mbstring" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-php85" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-uuid" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/process" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/property-access" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/property-info" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/routing" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/runtime" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/security-bundle" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/security-core" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/security-csrf" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/security-http" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/serializer" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/service-contracts" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/stopwatch" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/string" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/translation-contracts" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/twig-bridge" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/twig-bundle" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/type-info" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/uid" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/validator" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/var-dumper" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/var-exporter" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/web-link" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/yaml" />
<excludeFolder url="file://$MODULE_DIR$/vendor/twig/twig" />
<excludeFolder url="file://$MODULE_DIR$/vendor/webmozart/assert" />
<excludeFolder url="file://$MODULE_DIR$/vendor/willdurand/negotiation" />
<excludeFolder url="file://$MODULE_DIR$/vendor/myclabs/deep-copy" />
<excludeFolder url="file://$MODULE_DIR$/vendor/nikic/php-parser" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phar-io/manifest" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phar-io/version" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpunit/php-code-coverage" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpunit/php-file-iterator" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpunit/php-invoker" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpunit/php-text-template" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpunit/php-timer" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpunit/phpunit" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/cli-parser" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/comparator" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/complexity" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/environment" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/exporter" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/global-state" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/lines-of-code" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/object-enumerator" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/object-reflector" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/recursion-context" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/type" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/version" />
<excludeFolder url="file://$MODULE_DIR$/vendor/staabm/side-effects-detector" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/browser-kit" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/css-selector" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/dom-crawler" />
<excludeFolder url="file://$MODULE_DIR$/vendor/theseer/tokenizer" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/http-client" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/http-client-contracts" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

8
.idea/laravel-idea.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="InertiaPackage">
<option name="directoryPaths">
<list />
</option>
</component>
</project>

12
.idea/material_theme_project_new.xml generated Normal file
View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="MaterialThemeProjectNewConfig">
<option name="metadata">
<MTProjectMetadataState>
<option name="migrated" value="true" />
<option name="pristineConfig" value="false" />
<option name="userId" value="-70fca0d0:19b8da49b68:-7ffe" />
</MTProjectMetadataState>
</option>
</component>
</project>

8
.idea/modules.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/ferme.iml" filepath="$PROJECT_DIR$/.idea/ferme.iml" />
</modules>
</component>
</project>

162
.idea/php.xml generated Normal file
View File

@@ -0,0 +1,162 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="MessDetectorOptionsConfiguration">
<option name="transferred" value="true" />
</component>
<component name="PHPCSFixerOptionsConfiguration">
<option name="transferred" value="true" />
</component>
<component name="PHPCodeSnifferOptionsConfiguration">
<option name="highlightLevel" value="WARNING" />
<option name="transferred" value="true" />
</component>
<component name="PhpIncludePathManager">
<include_path>
<path value="$PROJECT_DIR$/vendor/symfony/framework-bundle" />
<path value="$PROJECT_DIR$/vendor/symfony/uid" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-intl-normalizer" />
<path value="$PROJECT_DIR$/vendor/symfony/cache-contracts" />
<path value="$PROJECT_DIR$/vendor/symfony/finder" />
<path value="$PROJECT_DIR$/vendor/symfony/asset" />
<path value="$PROJECT_DIR$/vendor/symfony/var-exporter" />
<path value="$PROJECT_DIR$/vendor/symfony/runtime" />
<path value="$PROJECT_DIR$/vendor/symfony/error-handler" />
<path value="$PROJECT_DIR$/vendor/symfony/translation-contracts" />
<path value="$PROJECT_DIR$/vendor/symfony/clock" />
<path value="$PROJECT_DIR$/vendor/symfony/twig-bridge" />
<path value="$PROJECT_DIR$/vendor/symfony/event-dispatcher-contracts" />
<path value="$PROJECT_DIR$/vendor/theseer/tokenizer" />
<path value="$PROJECT_DIR$/vendor/symfony/doctrine-bridge" />
<path value="$PROJECT_DIR$/vendor/symfony/type-info" />
<path value="$PROJECT_DIR$/vendor/doctrine/doctrine-migrations-bundle" />
<path value="$PROJECT_DIR$/vendor/doctrine/deprecations" />
<path value="$PROJECT_DIR$/vendor/doctrine/migrations" />
<path value="$PROJECT_DIR$/vendor/doctrine/event-manager" />
<path value="$PROJECT_DIR$/vendor/doctrine/doctrine-bundle" />
<path value="$PROJECT_DIR$/vendor/doctrine/lexer" />
<path value="$PROJECT_DIR$/vendor/doctrine/orm" />
<path value="$PROJECT_DIR$/vendor/doctrine/inflector" />
<path value="$PROJECT_DIR$/vendor/doctrine/sql-formatter" />
<path value="$PROJECT_DIR$/vendor/doctrine/common" />
<path value="$PROJECT_DIR$/vendor/doctrine/collections" />
<path value="$PROJECT_DIR$/vendor/doctrine/instantiator" />
<path value="$PROJECT_DIR$/vendor/doctrine/persistence" />
<path value="$PROJECT_DIR$/vendor/sebastian/lines-of-code" />
<path value="$PROJECT_DIR$/vendor/sebastian/type" />
<path value="$PROJECT_DIR$/vendor/doctrine/dbal" />
<path value="$PROJECT_DIR$/vendor/evenement/evenement" />
<path value="$PROJECT_DIR$/vendor/sebastian/diff" />
<path value="$PROJECT_DIR$/vendor/sebastian/cli-parser" />
<path value="$PROJECT_DIR$/vendor/sebastian/object-reflector" />
<path value="$PROJECT_DIR$/vendor/sebastian/environment" />
<path value="$PROJECT_DIR$/vendor/sebastian/complexity" />
<path value="$PROJECT_DIR$/vendor/sebastian/comparator" />
<path value="$PROJECT_DIR$/vendor/sebastian/recursion-context" />
<path value="$PROJECT_DIR$/vendor/sebastian/exporter" />
<path value="$PROJECT_DIR$/vendor/sebastian/global-state" />
<path value="$PROJECT_DIR$/vendor/webmozart/assert" />
<path value="$PROJECT_DIR$/vendor/sebastian/object-enumerator" />
<path value="$PROJECT_DIR$/vendor/sebastian/version" />
<path value="$PROJECT_DIR$/vendor/api-platform/jsonld" />
<path value="$PROJECT_DIR$/vendor/api-platform/hydra" />
<path value="$PROJECT_DIR$/vendor/willdurand/negotiation" />
<path value="$PROJECT_DIR$/vendor/api-platform/http-cache" />
<path value="$PROJECT_DIR$/vendor/api-platform/serializer" />
<path value="$PROJECT_DIR$/vendor/api-platform/doctrine-orm" />
<path value="$PROJECT_DIR$/vendor/api-platform/validator" />
<path value="$PROJECT_DIR$/vendor/api-platform/state" />
<path value="$PROJECT_DIR$/vendor/api-platform/metadata" />
<path value="$PROJECT_DIR$/vendor/api-platform/symfony" />
<path value="$PROJECT_DIR$/vendor/api-platform/openapi" />
<path value="$PROJECT_DIR$/vendor/api-platform/documentation" />
<path value="$PROJECT_DIR$/vendor/friendsofphp/php-cs-fixer" />
<path value="$PROJECT_DIR$/vendor/phpdocumentor/type-resolver" />
<path value="$PROJECT_DIR$/vendor/api-platform/json-schema" />
<path value="$PROJECT_DIR$/vendor/api-platform/doctrine-common" />
<path value="$PROJECT_DIR$/vendor/phpdocumentor/reflection-common" />
<path value="$PROJECT_DIR$/vendor/phpdocumentor/reflection-docblock" />
<path value="$PROJECT_DIR$/vendor/composer" />
<path value="$PROJECT_DIR$/vendor/psr/log" />
<path value="$PROJECT_DIR$/vendor/psr/link" />
<path value="$PROJECT_DIR$/vendor/psr/cache" />
<path value="$PROJECT_DIR$/vendor/psr/clock" />
<path value="$PROJECT_DIR$/vendor/clue/ndjson-react" />
<path value="$PROJECT_DIR$/vendor/psr/event-dispatcher" />
<path value="$PROJECT_DIR$/vendor/psr/container" />
<path value="$PROJECT_DIR$/vendor/nikic/php-parser" />
<path value="$PROJECT_DIR$/vendor/react/promise" />
<path value="$PROJECT_DIR$/vendor/twig/twig" />
<path value="$PROJECT_DIR$/vendor/fidry/cpu-core-counter" />
<path value="$PROJECT_DIR$/vendor/react/stream" />
<path value="$PROJECT_DIR$/vendor/react/child-process" />
<path value="$PROJECT_DIR$/vendor/react/cache" />
<path value="$PROJECT_DIR$/vendor/react/event-loop" />
<path value="$PROJECT_DIR$/vendor/nelmio/cors-bundle" />
<path value="$PROJECT_DIR$/vendor/staabm/side-effects-detector" />
<path value="$PROJECT_DIR$/vendor/react/socket" />
<path value="$PROJECT_DIR$/vendor/react/dns" />
<path value="$PROJECT_DIR$/vendor/phar-io/version" />
<path value="$PROJECT_DIR$/vendor/phpstan/phpdoc-parser" />
<path value="$PROJECT_DIR$/vendor/myclabs/deep-copy" />
<path value="$PROJECT_DIR$/vendor/phar-io/manifest" />
<path value="$PROJECT_DIR$/vendor/phpunit/php-code-coverage" />
<path value="$PROJECT_DIR$/vendor/phpunit/php-text-template" />
<path value="$PROJECT_DIR$/vendor/phpunit/php-invoker" />
<path value="$PROJECT_DIR$/vendor/phpunit/php-timer" />
<path value="$PROJECT_DIR$/vendor/symfony/flex" />
<path value="$PROJECT_DIR$/vendor/symfony/validator" />
<path value="$PROJECT_DIR$/vendor/phpunit/phpunit" />
<path value="$PROJECT_DIR$/vendor/phpunit/php-file-iterator" />
<path value="$PROJECT_DIR$/vendor/symfony/security-csrf" />
<path value="$PROJECT_DIR$/vendor/symfony/dependency-injection" />
<path value="$PROJECT_DIR$/vendor/symfony/security-bundle" />
<path value="$PROJECT_DIR$/vendor/symfony/deprecation-contracts" />
<path value="$PROJECT_DIR$/vendor/symfony/password-hasher" />
<path value="$PROJECT_DIR$/vendor/symfony/http-kernel" />
<path value="$PROJECT_DIR$/vendor/symfony/console" />
<path value="$PROJECT_DIR$/vendor/symfony/filesystem" />
<path value="$PROJECT_DIR$/vendor/symfony/cache" />
<path value="$PROJECT_DIR$/vendor/symfony/web-link" />
<path value="$PROJECT_DIR$/vendor/symfony/serializer" />
<path value="$PROJECT_DIR$/vendor/symfony/css-selector" />
<path value="$PROJECT_DIR$/vendor/symfony/twig-bundle" />
<path value="$PROJECT_DIR$/vendor/symfony/process" />
<path value="$PROJECT_DIR$/vendor/symfony/security-http" />
<path value="$PROJECT_DIR$/vendor/symfony/config" />
<path value="$PROJECT_DIR$/vendor/symfony/event-dispatcher" />
<path value="$PROJECT_DIR$/vendor/symfony/browser-kit" />
<path value="$PROJECT_DIR$/vendor/symfony/options-resolver" />
<path value="$PROJECT_DIR$/vendor/symfony/service-contracts" />
<path value="$PROJECT_DIR$/vendor/symfony/yaml" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-mbstring" />
<path value="$PROJECT_DIR$/vendor/symfony/string" />
<path value="$PROJECT_DIR$/vendor/symfony/property-access" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-uuid" />
<path value="$PROJECT_DIR$/vendor/symfony/property-info" />
<path value="$PROJECT_DIR$/vendor/symfony/dom-crawler" />
<path value="$PROJECT_DIR$/vendor/symfony/var-dumper" />
<path value="$PROJECT_DIR$/vendor/symfony/expression-language" />
<path value="$PROJECT_DIR$/vendor/symfony/http-foundation" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-php85" />
<path value="$PROJECT_DIR$/vendor/symfony/routing" />
<path value="$PROJECT_DIR$/vendor/symfony/security-core" />
<path value="$PROJECT_DIR$/vendor/symfony/dotenv" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-intl-grapheme" />
<path value="$PROJECT_DIR$/vendor/symfony/stopwatch" />
<path value="$PROJECT_DIR$/vendor/symfony/http-client-contracts" />
<path value="$PROJECT_DIR$/vendor/symfony/http-client" />
</include_path>
</component>
<component name="PhpProjectSharedConfiguration" php_language_level="8.4" />
<component name="PhpStanOptionsConfiguration">
<option name="transferred" value="true" />
</component>
<component name="PhpUnit">
<phpunit_settings>
<PhpUnitSettings custom_loader_path="$PROJECT_DIR$/vendor/autoload.php" />
</phpunit_settings>
</component>
<component name="PsalmOptionsConfiguration">
<option name="transferred" value="true" />
</component>
</project>

6
.idea/symfony2.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Symfony2PluginSettings">
<option name="pluginEnabled" value="true" />
</component>
</project>

6
.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

291
.idea/workspace.xml generated Normal file
View File

@@ -0,0 +1,291 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AutoImportSettings">
<option name="autoReloadType" value="SELECTIVE" />
</component>
<component name="ChangeListManager">
<list default="true" id="7c107abe-5995-4428-8429-b146aaca8386" name="Changes" comment="Feat">
<change afterPath="$PROJECT_DIR$/frontend/components/reception/reception-form.vue" afterDir="false" />
<change afterPath="$PROJECT_DIR$/frontend/components/reception/reception-weight.vue" afterDir="false" />
<change afterPath="$PROJECT_DIR$/frontend/services/dto/weight-data.ts" afterDir="false" />
<change afterPath="$PROJECT_DIR$/frontend/services/reception.ts" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/Dto/PontBasculeReading.php" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/Exception/PontBasculeException.php" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/Service/PontBasculePayloadDecoder.php" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/Service/PontBasculeService.php" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/State/ReceptionWeighingProvider.php" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/composer.json" beforeDir="false" afterPath="$PROJECT_DIR$/composer.json" afterDir="false" />
<change beforePath="$PROJECT_DIR$/composer.lock" beforeDir="false" afterPath="$PROJECT_DIR$/composer.lock" afterDir="false" />
<change beforePath="$PROJECT_DIR$/config/packages/api_platform.yaml" beforeDir="false" afterPath="$PROJECT_DIR$/config/packages/api_platform.yaml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/config/reference.php" beforeDir="false" afterPath="$PROJECT_DIR$/config/reference.php" afterDir="false" />
<change beforePath="$PROJECT_DIR$/config/services.yaml" beforeDir="false" afterPath="$PROJECT_DIR$/config/services.yaml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/docker/php/config/vhost.conf" beforeDir="false" afterPath="$PROJECT_DIR$/docker/php/config/vhost.conf" afterDir="false" />
<change beforePath="$PROJECT_DIR$/frontend/composables/useApi.ts" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/composables/useApi.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/frontend/nuxt.config.ts" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/nuxt.config.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/frontend/package-lock.json" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/package-lock.json" afterDir="false" />
<change beforePath="$PROJECT_DIR$/frontend/package.json" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/package.json" afterDir="false" />
<change beforePath="$PROJECT_DIR$/frontend/pages/index.vue" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/pages/index.vue" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="ComposerSettings" synchronizationState="SYNCHRONIZE">
<pharConfigPath>$PROJECT_DIR$/composer.json</pharConfigPath>
<execution />
</component>
<component name="EmbeddingIndexingInfo">
<option name="cachedIndexableFilesCount" value="115" />
<option name="fileBasedEmbeddingIndicesEnabled" value="true" />
</component>
<component name="FileTemplateManagerImpl">
<option name="RECENT_TEMPLATES">
<list>
<option value="TypeScript File" />
<option value="Vue Composition API Component" />
</list>
</option>
</component>
<component name="Git.Settings">
<option name="RECENT_BRANCH_BY_REPOSITORY">
<map>
<entry key="$PROJECT_DIR$" value="feat/connexion-pont-bascule" />
</map>
</option>
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
</component>
<component name="McpProjectServerCommands">
<commands />
<urls />
</component>
<component name="PhpServers">
<servers>
<server host="localhost" id="36c0c232-9151-4654-a36c-e0f5fd99da91" name="ferme-docker" port="8080" use_path_mappings="true">
<path_mappings>
<mapping local-root="$PROJECT_DIR$" remote-root="/var/www/html" />
</path_mappings>
</server>
</servers>
</component>
<component name="PhpWorkspaceProjectConfiguration">
<include_path>
<path value="$PROJECT_DIR$/vendor/symfony/framework-bundle" />
<path value="$PROJECT_DIR$/vendor/symfony/uid" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-intl-normalizer" />
<path value="$PROJECT_DIR$/vendor/symfony/cache-contracts" />
<path value="$PROJECT_DIR$/vendor/symfony/finder" />
<path value="$PROJECT_DIR$/vendor/symfony/asset" />
<path value="$PROJECT_DIR$/vendor/symfony/var-exporter" />
<path value="$PROJECT_DIR$/vendor/symfony/runtime" />
<path value="$PROJECT_DIR$/vendor/symfony/error-handler" />
<path value="$PROJECT_DIR$/vendor/symfony/translation-contracts" />
<path value="$PROJECT_DIR$/vendor/symfony/clock" />
<path value="$PROJECT_DIR$/vendor/symfony/twig-bridge" />
<path value="$PROJECT_DIR$/vendor/symfony/event-dispatcher-contracts" />
<path value="$PROJECT_DIR$/vendor/theseer/tokenizer" />
<path value="$PROJECT_DIR$/vendor/symfony/doctrine-bridge" />
<path value="$PROJECT_DIR$/vendor/symfony/type-info" />
<path value="$PROJECT_DIR$/vendor/doctrine/doctrine-migrations-bundle" />
<path value="$PROJECT_DIR$/vendor/doctrine/deprecations" />
<path value="$PROJECT_DIR$/vendor/doctrine/migrations" />
<path value="$PROJECT_DIR$/vendor/doctrine/event-manager" />
<path value="$PROJECT_DIR$/vendor/doctrine/doctrine-bundle" />
<path value="$PROJECT_DIR$/vendor/doctrine/lexer" />
<path value="$PROJECT_DIR$/vendor/doctrine/orm" />
<path value="$PROJECT_DIR$/vendor/doctrine/inflector" />
<path value="$PROJECT_DIR$/vendor/doctrine/sql-formatter" />
<path value="$PROJECT_DIR$/vendor/doctrine/common" />
<path value="$PROJECT_DIR$/vendor/doctrine/collections" />
<path value="$PROJECT_DIR$/vendor/doctrine/instantiator" />
<path value="$PROJECT_DIR$/vendor/doctrine/persistence" />
<path value="$PROJECT_DIR$/vendor/sebastian/lines-of-code" />
<path value="$PROJECT_DIR$/vendor/sebastian/type" />
<path value="$PROJECT_DIR$/vendor/doctrine/dbal" />
<path value="$PROJECT_DIR$/vendor/evenement/evenement" />
<path value="$PROJECT_DIR$/vendor/sebastian/diff" />
<path value="$PROJECT_DIR$/vendor/sebastian/cli-parser" />
<path value="$PROJECT_DIR$/vendor/sebastian/object-reflector" />
<path value="$PROJECT_DIR$/vendor/sebastian/environment" />
<path value="$PROJECT_DIR$/vendor/sebastian/complexity" />
<path value="$PROJECT_DIR$/vendor/sebastian/comparator" />
<path value="$PROJECT_DIR$/vendor/sebastian/recursion-context" />
<path value="$PROJECT_DIR$/vendor/sebastian/exporter" />
<path value="$PROJECT_DIR$/vendor/sebastian/global-state" />
<path value="$PROJECT_DIR$/vendor/webmozart/assert" />
<path value="$PROJECT_DIR$/vendor/sebastian/object-enumerator" />
<path value="$PROJECT_DIR$/vendor/sebastian/version" />
<path value="$PROJECT_DIR$/vendor/api-platform/jsonld" />
<path value="$PROJECT_DIR$/vendor/api-platform/hydra" />
<path value="$PROJECT_DIR$/vendor/willdurand/negotiation" />
<path value="$PROJECT_DIR$/vendor/api-platform/http-cache" />
<path value="$PROJECT_DIR$/vendor/api-platform/serializer" />
<path value="$PROJECT_DIR$/vendor/api-platform/doctrine-orm" />
<path value="$PROJECT_DIR$/vendor/api-platform/validator" />
<path value="$PROJECT_DIR$/vendor/api-platform/state" />
<path value="$PROJECT_DIR$/vendor/api-platform/metadata" />
<path value="$PROJECT_DIR$/vendor/api-platform/symfony" />
<path value="$PROJECT_DIR$/vendor/api-platform/openapi" />
<path value="$PROJECT_DIR$/vendor/api-platform/documentation" />
<path value="$PROJECT_DIR$/vendor/friendsofphp/php-cs-fixer" />
<path value="$PROJECT_DIR$/vendor/phpdocumentor/type-resolver" />
<path value="$PROJECT_DIR$/vendor/api-platform/json-schema" />
<path value="$PROJECT_DIR$/vendor/api-platform/doctrine-common" />
<path value="$PROJECT_DIR$/vendor/phpdocumentor/reflection-common" />
<path value="$PROJECT_DIR$/vendor/phpdocumentor/reflection-docblock" />
<path value="$PROJECT_DIR$/vendor/composer" />
<path value="$PROJECT_DIR$/vendor/psr/log" />
<path value="$PROJECT_DIR$/vendor/psr/link" />
<path value="$PROJECT_DIR$/vendor/psr/cache" />
<path value="$PROJECT_DIR$/vendor/psr/clock" />
<path value="$PROJECT_DIR$/vendor/clue/ndjson-react" />
<path value="$PROJECT_DIR$/vendor/psr/event-dispatcher" />
<path value="$PROJECT_DIR$/vendor/psr/container" />
<path value="$PROJECT_DIR$/vendor/nikic/php-parser" />
<path value="$PROJECT_DIR$/vendor/react/promise" />
<path value="$PROJECT_DIR$/vendor/twig/twig" />
<path value="$PROJECT_DIR$/vendor/fidry/cpu-core-counter" />
<path value="$PROJECT_DIR$/vendor/react/stream" />
<path value="$PROJECT_DIR$/vendor/react/child-process" />
<path value="$PROJECT_DIR$/vendor/react/cache" />
<path value="$PROJECT_DIR$/vendor/react/event-loop" />
<path value="$PROJECT_DIR$/vendor/nelmio/cors-bundle" />
<path value="$PROJECT_DIR$/vendor/staabm/side-effects-detector" />
<path value="$PROJECT_DIR$/vendor/react/socket" />
<path value="$PROJECT_DIR$/vendor/react/dns" />
<path value="$PROJECT_DIR$/vendor/phar-io/version" />
<path value="$PROJECT_DIR$/vendor/phpstan/phpdoc-parser" />
<path value="$PROJECT_DIR$/vendor/myclabs/deep-copy" />
<path value="$PROJECT_DIR$/vendor/phar-io/manifest" />
<path value="$PROJECT_DIR$/vendor/phpunit/php-code-coverage" />
<path value="$PROJECT_DIR$/vendor/phpunit/php-text-template" />
<path value="$PROJECT_DIR$/vendor/phpunit/php-invoker" />
<path value="$PROJECT_DIR$/vendor/phpunit/php-timer" />
<path value="$PROJECT_DIR$/vendor/symfony/flex" />
<path value="$PROJECT_DIR$/vendor/symfony/validator" />
<path value="$PROJECT_DIR$/vendor/phpunit/phpunit" />
<path value="$PROJECT_DIR$/vendor/phpunit/php-file-iterator" />
<path value="$PROJECT_DIR$/vendor/symfony/security-csrf" />
<path value="$PROJECT_DIR$/vendor/symfony/dependency-injection" />
<path value="$PROJECT_DIR$/vendor/symfony/security-bundle" />
<path value="$PROJECT_DIR$/vendor/symfony/deprecation-contracts" />
<path value="$PROJECT_DIR$/vendor/symfony/password-hasher" />
<path value="$PROJECT_DIR$/vendor/symfony/http-kernel" />
<path value="$PROJECT_DIR$/vendor/symfony/console" />
<path value="$PROJECT_DIR$/vendor/symfony/filesystem" />
<path value="$PROJECT_DIR$/vendor/symfony/cache" />
<path value="$PROJECT_DIR$/vendor/symfony/web-link" />
<path value="$PROJECT_DIR$/vendor/symfony/serializer" />
<path value="$PROJECT_DIR$/vendor/symfony/css-selector" />
<path value="$PROJECT_DIR$/vendor/symfony/twig-bundle" />
<path value="$PROJECT_DIR$/vendor/symfony/process" />
<path value="$PROJECT_DIR$/vendor/symfony/security-http" />
<path value="$PROJECT_DIR$/vendor/symfony/config" />
<path value="$PROJECT_DIR$/vendor/symfony/event-dispatcher" />
<path value="$PROJECT_DIR$/vendor/symfony/browser-kit" />
<path value="$PROJECT_DIR$/vendor/symfony/options-resolver" />
<path value="$PROJECT_DIR$/vendor/symfony/service-contracts" />
<path value="$PROJECT_DIR$/vendor/symfony/yaml" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-mbstring" />
<path value="$PROJECT_DIR$/vendor/symfony/string" />
<path value="$PROJECT_DIR$/vendor/symfony/property-access" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-uuid" />
<path value="$PROJECT_DIR$/vendor/symfony/property-info" />
<path value="$PROJECT_DIR$/vendor/symfony/dom-crawler" />
<path value="$PROJECT_DIR$/vendor/symfony/var-dumper" />
<path value="$PROJECT_DIR$/vendor/symfony/expression-language" />
<path value="$PROJECT_DIR$/vendor/symfony/http-foundation" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-php85" />
<path value="$PROJECT_DIR$/vendor/symfony/routing" />
<path value="$PROJECT_DIR$/vendor/symfony/security-core" />
<path value="$PROJECT_DIR$/vendor/symfony/dotenv" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-intl-grapheme" />
<path value="$PROJECT_DIR$/vendor/symfony/stopwatch" />
<path value="$PROJECT_DIR$/vendor/symfony/http-client-contracts" />
<path value="$PROJECT_DIR$/vendor/symfony/http-client" />
</include_path>
</component>
<component name="ProjectColorInfo">{
&quot;customColor&quot;: &quot;&quot;,
&quot;associatedIndex&quot;: 5
}</component>
<component name="ProjectId" id="381AhnCm9yPeOiWgMObKHhtgv2C" />
<component name="ProjectViewState">
<option name="autoscrollFromSource" value="true" />
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent"><![CDATA[{
"keyToString": {
"RunOnceActivity.MCP Project settings loaded": "true",
"RunOnceActivity.ShowReadmeOnStart": "true",
"RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true",
"RunOnceActivity.git.unshallow": "true",
"RunOnceActivity.typescript.service.memoryLimit.init": "true",
"git-widget-placeholder": "feat/203-reception-parcours-pesee-multi-etapas",
"node.js.detected.package.eslint": "true",
"node.js.detected.package.tslint": "true",
"node.js.selected.package.eslint": "(autodetect)",
"node.js.selected.package.tslint": "(autodetect)",
"nodejs_package_manager_path": "npm",
"settings.editor.selected.configurable": "settings.php.debug.servers",
"vue.rearranger.settings.migration": "true"
},
"keyToStringList": {
"vue.recent.templates": [
"Vue Composition API Component"
]
}
}]]></component>
<component name="RecentsManager">
<key name="MoveFile.RECENT_KEYS">
<recent name="\\wsl.localhost\Ubuntu-24.04\home\tristan\workspace\ferme\frontend\pages\reception" />
<recent name="\\wsl.localhost\Ubuntu-24.04\home\tristan\workspace\ferme\frontend\pages" />
</key>
</component>
<component name="SharedIndexes">
<attachedChunks>
<set>
<option value="bundled-php-predefined-a98d8de5180a-0e0d91225499-com.jetbrains.php.sharedIndexes-PS-253.29346.257" />
</set>
</attachedChunks>
</component>
<component name="TaskManager">
<task active="true" id="Default" summary="Default task">
<changelist id="7c107abe-5995-4428-8429-b146aaca8386" name="Changes" comment="" />
<created>1767956826164</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1767956826164</updated>
<workItem from="1767956827666" duration="7866000" />
<workItem from="1768201706520" duration="13383000" />
</task>
<servers />
</component>
<component name="TypeScriptGeneratedFilesManager">
<option name="version" value="3" />
</component>
<component name="Vcs.Log.Tabs.Properties">
<option name="TAB_STATES">
<map>
<entry key="MAIN">
<value>
<State />
</value>
</entry>
</map>
</option>
</component>
<component name="XDebuggerManager">
<breakpoint-manager>
<breakpoints>
<line-breakpoint enabled="true" type="php">
<url>file://$PROJECT_DIR$/src/State/ReceptionWeighingProvider.php</url>
<line>28</line>
<option name="timeStamp" value="6" />
</line-breakpoint>
</breakpoints>
</breakpoint-manager>
</component>
</project>

1
.nvmrc Normal file
View File

@@ -0,0 +1 @@
24.12.0

56
.php-cs-fixer.dist.php Normal file
View File

@@ -0,0 +1,56 @@
<?php
declare(strict_types=1);
use PhpCsFixer\Config;
use PhpCsFixer\Finder;
$finder = Finder::create()
->in('src')
->notName('Kernel.php')
;
$rules = [
'@Symfony' => true,
'@PSR12' => true,
'@PHP84Migration' => true,
'@PER-CS' => true,
'@PhpCsFixer' => true,
'strict_param' => true,
'strict_comparison' => true,
'no_useless_else' => true,
'no_useless_return' => true,
'binary_operator_spaces' => [
'operators' => [
'=' => 'align_single_space_minimal',
'||' => 'align_single_space_minimal',
'=>' => 'align_single_space_minimal',
],
],
'global_namespace_import' => [
'import_classes' => true,
'import_constants' => true,
'import_functions' => true,
],
'modernize_strpos' => true, // needs PHP 8+ or polyfill
'no_superfluous_phpdoc_tags' => true,
'echo_tag_syntax' => true,
'semicolon_after_instruction' => true,
'combine_consecutive_unsets' => true,
'ternary_to_null_coalescing' => true,
'declare_strict_types' => true,
'operator_linebreak' => [
'position' => 'beginning',
],
'no_unused_imports' => true,
'single_line_throw' => false,
'php_unit_test_class_requires_covers' => false,
];
$config = new Config();
return $config
->setRiskyAllowed(true)
->setRules($rules)
->setFinder($finder)
;

35
AGENTS.md Normal file
View File

@@ -0,0 +1,35 @@
# AGENTS.md
Project overview
- Symfony 8 + API Platform 4 backend, Nuxt 3 frontend in `frontend/`.
- Apache vhost serves API under `/api` and frontend from `frontend/dist`.
- API base URL on frontend uses `NUXT_PUBLIC_API_BASE` (see `frontend/.env`).
Backend conventions
- Use English for code identifiers/messages; keep “pont-bascule” as domain term.
- API Platform operations are defined on Doctrine entities.
- Reception entity is in `src/Entity/Reception.php`, with custom weigh endpoint `/receptions/weigh`.
- Reception fields: `dsd`, `weight`, `date_reception`, `license_plate`, `current_step` (default 0), `is_valid` (default false).
- `date_reception` is set by the UI, stored as `DateTimeImmutable`.
- Weight entity (`src/Entity/Weight.php`) is 11 with Reception, weights stored as `int` (kg), dates nullable.
- Custom exception: `App\Exception\PontBasculeException` with French messages, mapped to 500 in provider.
- Parsing of pont-bascule payload is in `src/Service/PontBasculePayloadDecoder.php`.
- `config/reference.php` is auto-generated; keep it.
Frontend conventions
- Nuxt SSR disabled; Tailwind used.
- Layout in `frontend/layouts/default.vue`: max width `1050px`, header full width.
- Tailwind custom color palette is `primary` (e.g. `bg-primary-500`).
- API composable in `frontend/composables/useApi.ts` with `get/post/put/patch/delete` and default JSON/PATCH content types.
- Pinia store: `frontend/stores/reception.ts` is the source of truth for the current reception.
- Reception step UI uses store state (`currentStep`) in `frontend/pages/reception/[[id]].vue`.
- Active nav styles in header use `NuxtLink` with `custom` slot.
Environment & routing
- Frontend dev server: `npm run dev` in `frontend/`.
- API base for local dev: `http://localhost:8080/api` (set in `frontend/.env` via `NUXT_PUBLIC_API_BASE`).
- CORS handled by Nelmio; `.env` includes `CORS_ALLOW_ORIGIN` regex for localhost.
Notes
- Do not add a GET that creates resources; use POST + PATCH.
- Keep endpoints in plural (API Platform convention).

15
CHANGELOG.md Normal file
View File

@@ -0,0 +1,15 @@
# Changelog
Liste des évolutions du projet Ferme
## [0.0.0]
### Parameters
Ajouter dans le fichier .env
- DEFAULT_URI
- DATABASE_URL
### Added
### Changed
### Fixed

View File

@@ -2,7 +2,7 @@
## Installation du projet
### Windows
Pour windows, il faut installer le WSL2, Ubuntu et nvm.
Pour windows, il faut installer le WSL2, Ubuntu, docker et nvm.
Il suffit de suivre cette [doc](https://wiki.malio.fr/bookstack/books/environnement-de-dev/chapter/windows)
### Linux
@@ -15,12 +15,47 @@ Une fois les prérequis installés, il suffit de cloner le projet et de lancer l
make start
make install
```
Dans le cas ou le `make start` plante à cause du port de la bdd, il faut modifier **POSTGRES_PORT** dans le fichier .env.docker.local, remplacer le par un port disponible.
### Configuration xdebug
Pour configurer xdebug, il faut ajouter un serveur sur phpstorm. <br>
Pour cela, il faut aller dans **Settings > PHP > Servers** <br>
* Name : ferme-docker
* Host : localhost
* Port : 8080
* Path : File/Directory -> l'endroit où est stocké votre projet et le path -> /var/www/html
Pour que xdebug fonctionne sur windows, il faut modifier la variable **XDEBUG_CLIENT_HOST** par votre ip local
## Utilisation du projet
### Backend
L'api est disponible sur http://localhost:8080/api
Pour la bdd toutes les infos sont dans le fichier **docker/.env.docker.local**
Vous pouvez modifier le port si nécessaire.
La bdd est déja pré-configuré dans PhpStorm, il suffit de rentrer les infos du .env.docker.local pour se connecter.
C'est un bdd local dans le docker.
### Frontend
Pour le frontend, il suffit de taper la commande suivante qui va lancer le serveur de dev
```bash
make dev-nuxt
```
Le front sera accessible sur http://localhost:3000
## Commandes utiles
Pour restart le container
```bash
make restart
```
Pour lancer les TU
```bash
make test
```
Pour accéder au container et lance des commandes
```bash
make shell
```
Pour clear le cache Symfony
```bash
make cache-clear
```

21
bin/console Executable file
View File

@@ -0,0 +1,21 @@
#!/usr/bin/env php
<?php
use App\Kernel;
use Symfony\Bundle\FrameworkBundle\Console\Application;
if (!is_dir(dirname(__DIR__).'/vendor')) {
throw new LogicException('Dependencies are missing. Try running "composer install".');
}
if (!is_file(dirname(__DIR__).'/vendor/autoload_runtime.php')) {
throw new LogicException('Symfony Runtime is missing. Try running "composer require symfony/runtime".');
}
require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
return function (array $context) {
$kernel = new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
return new Application($kernel);
};

4
bin/phpunit Executable file
View File

@@ -0,0 +1,4 @@
#!/usr/bin/env php
<?php
require dirname(__DIR__).'/vendor/phpunit/phpunit/phpunit';

31
commit-msg Normal file
View File

@@ -0,0 +1,31 @@
#!/usr/bin/env bash
set -euo pipefail
MSG_FILE="${1}"
FIRST_LINE="$(head -n 1 "$MSG_FILE" | tr -d '\r')"
# Autoriser commits auto-générés par git
if [[ "$FIRST_LINE" =~ ^Merge\ ]]; then
exit 0
fi
# Types autorisés (MINUSCULES uniquement)
# Optionnel: scope => feat(auth) : ...
REGEX='^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)(\([a-z0-9._-]+\))?\ :\ .+'
if [[ ! "$FIRST_LINE" =~ $REGEX ]]; then
echo "❌ Message de commit invalide."
echo ""
echo "➡️ Format attendu : <type>(<scope optionnel>) : <message>"
echo "➡️ Types autorisés (minuscules uniquement) :"
echo " build, chore, ci, docs, feat, fix, perf, refactor, revert, style, test"
echo ""
echo "✅ Exemples :"
echo " feat : add login page"
echo " fix(auth) : prevent null token crash"
echo " docs : update README"
echo ""
echo "❌ Exemple refusé :"
echo " Feat : add login page"
exit 1
fi

92
composer.json Normal file
View File

@@ -0,0 +1,92 @@
{
"type": "project",
"license": "proprietary",
"minimum-stability": "stable",
"prefer-stable": true,
"require": {
"php": ">=8.4",
"ext-ctype": "*",
"ext-iconv": "*",
"api-platform/doctrine-orm": "^4.2",
"api-platform/symfony": "^4.2",
"doctrine/doctrine-bundle": "^3.2",
"doctrine/doctrine-migrations-bundle": "^4.0",
"doctrine/orm": "^3.6",
"nelmio/cors-bundle": "^2.6",
"phpdocumentor/reflection-docblock": "^5.6",
"phpstan/phpdoc-parser": "^2.3",
"symfony/asset": "8.0.*",
"symfony/console": "8.0.*",
"symfony/dotenv": "8.0.*",
"symfony/expression-language": "8.0.*",
"symfony/flex": "^2",
"symfony/framework-bundle": "8.0.*",
"symfony/http-client": "8.0.*",
"symfony/property-access": "8.0.*",
"symfony/property-info": "8.0.*",
"symfony/runtime": "8.0.*",
"symfony/security-bundle": "8.0.*",
"symfony/serializer": "8.0.*",
"symfony/twig-bundle": "8.0.*",
"symfony/validator": "8.0.*",
"symfony/yaml": "8.0.*"
},
"config": {
"allow-plugins": {
"php-http/discovery": true,
"symfony/flex": true,
"symfony/runtime": true
},
"bump-after-update": true,
"sort-packages": true
},
"autoload": {
"psr-4": {
"App\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"App\\Tests\\": "tests/"
}
},
"replace": {
"symfony/polyfill-ctype": "*",
"symfony/polyfill-iconv": "*",
"symfony/polyfill-php72": "*",
"symfony/polyfill-php73": "*",
"symfony/polyfill-php74": "*",
"symfony/polyfill-php80": "*",
"symfony/polyfill-php81": "*",
"symfony/polyfill-php82": "*",
"symfony/polyfill-php83": "*",
"symfony/polyfill-php84": "*"
},
"scripts": {
"auto-scripts": {
"cache:clear": "symfony-cmd",
"assets:install %PUBLIC_DIR%": "symfony-cmd"
},
"post-install-cmd": [
"@auto-scripts"
],
"post-update-cmd": [
"@auto-scripts"
]
},
"conflict": {
"symfony/symfony": "*"
},
"extra": {
"symfony": {
"allow-contrib": false,
"require": "8.0.*"
}
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^3.92",
"phpunit/phpunit": "^12.5",
"symfony/browser-kit": "8.0.*",
"symfony/css-selector": "8.0.*"
}
}

10395
composer.lock generated Normal file

File diff suppressed because it is too large Load Diff

11
config/bundles.php Normal file
View File

@@ -0,0 +1,11 @@
<?php
return [
Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true],
Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true],
Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true],
Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true],
Nelmio\CorsBundle\NelmioCorsBundle::class => ['all' => true],
ApiPlatform\Symfony\Bundle\ApiPlatformBundle::class => ['all' => true],
];

View File

@@ -0,0 +1,12 @@
api_platform:
title: Hello API Platform
version: 1.0.0
defaults:
stateless: true
cache_headers:
vary: ['Content-Type', 'Authorization', 'Origin']
formats:
json: ['application/json']
jsonld: ['application/ld+json']
patch_formats:
json: ['application/merge-patch+json']

View File

@@ -0,0 +1,19 @@
framework:
cache:
# Unique name of your app: used to compute stable namespaces for cache keys.
#prefix_seed: your_vendor_name/app_name
# The "app" cache stores to the filesystem by default.
# The data in this cache should persist between deploys.
# Other options include:
# Redis
#app: cache.adapter.redis
#default_redis_provider: redis://localhost
# APCu (not recommended with heavy random-write workloads as memory fragmentation can cause perf issues)
#app: cache.adapter.apcu
# Namespaced pools use the above "app" backend by default
#pools:
#my.dedicated.cache: null

View File

@@ -0,0 +1,48 @@
doctrine:
dbal:
url: '%env(resolve:DATABASE_URL)%'
# IMPORTANT: You MUST configure your server version,
# either here or in the DATABASE_URL env var (see .env file)
#server_version: '16'
profiling_collect_backtrace: '%kernel.debug%'
orm:
validate_xml_mapping: true
naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
identity_generation_preferences:
Doctrine\DBAL\Platforms\PostgreSQLPlatform: identity
auto_mapping: true
mappings:
App:
type: attribute
is_bundle: false
dir: '%kernel.project_dir%/src/Entity'
prefix: 'App\Entity'
alias: App
controller_resolver:
auto_mapping: false
when@test:
doctrine:
dbal:
# "TEST_TOKEN" is typically set by ParaTest
dbname_suffix: '_test%env(default::TEST_TOKEN)%'
when@prod:
doctrine:
orm:
query_cache_driver:
type: pool
pool: doctrine.system_cache_pool
result_cache_driver:
type: pool
pool: doctrine.result_cache_pool
framework:
cache:
pools:
doctrine.result_cache_pool:
adapter: cache.app
doctrine.system_cache_pool:
adapter: cache.system

View File

@@ -0,0 +1,6 @@
doctrine_migrations:
migrations_paths:
# namespace is arbitrary but should be different from App\Migrations
# as migrations classes should NOT be autoloaded
'DoctrineMigrations': '%kernel.project_dir%/migrations'
enable_profiler: false

View File

@@ -0,0 +1,15 @@
# see https://symfony.com/doc/current/reference/configuration/framework.html
framework:
secret: '%env(APP_SECRET)%'
# Note that the session will be started ONLY if you read or write from it.
session: true
#esi: true
#fragments: true
when@test:
framework:
test: true
session:
storage_factory_id: session.storage.factory.mock_file

View File

@@ -0,0 +1,10 @@
nelmio_cors:
defaults:
origin_regex: true
allow_origin: ['%env(CORS_ALLOW_ORIGIN)%']
allow_methods: ['GET', 'OPTIONS', 'POST', 'PUT', 'PATCH', 'DELETE']
allow_headers: ['Content-Type', 'Authorization']
expose_headers: ['Link']
max_age: 3600
paths:
'^/': null

View File

@@ -0,0 +1,3 @@
framework:
property_info:
with_constructor_extractor: true

View File

@@ -0,0 +1,10 @@
framework:
router:
# Configure how to generate URLs in non-HTTP contexts, such as CLI commands.
# See https://symfony.com/doc/current/routing.html#generating-urls-in-commands
default_uri: '%env(DEFAULT_URI)%'
when@prod:
framework:
router:
strict_requirements: null

View File

@@ -0,0 +1,39 @@
security:
# https://symfony.com/doc/current/security.html#registering-the-user-hashing-passwords
password_hashers:
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
# https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider
providers:
users_in_memory: { memory: null }
firewalls:
dev:
# Ensure dev tools and static assets are always allowed
pattern: ^/(_profiler|_wdt|assets|build)/
security: false
main:
lazy: true
provider: users_in_memory
# Activate different ways to authenticate:
# https://symfony.com/doc/current/security.html#the-firewall
# https://symfony.com/doc/current/security/impersonating_user.html
# switch_user: true
# Note: Only the *first* matching rule is applied
access_control:
# - { path: ^/admin, roles: ROLE_ADMIN }
# - { path: ^/profile, roles: ROLE_USER }
when@test:
security:
password_hashers:
# Password hashers are resource-intensive by design to ensure security.
# In tests, it's safe to reduce their cost to improve performance.
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface:
algorithm: auto
cost: 4 # Lowest possible value for bcrypt
time_cost: 3 # Lowest possible value for argon
memory_cost: 10 # Lowest possible value for argon

View File

@@ -0,0 +1,6 @@
twig:
file_name_pattern: '*.twig'
when@test:
twig:
strict_variables: true

View File

@@ -0,0 +1,11 @@
framework:
validation:
# Enables validator auto-mapping support.
# For instance, basic validation constraints will be inferred from Doctrine's metadata.
#auto_mapping:
# App\Entity\: []
when@test:
framework:
validation:
not_compromised_password: false

5
config/preload.php Normal file
View File

@@ -0,0 +1,5 @@
<?php
if (file_exists(dirname(__DIR__).'/var/cache/prod/App_KernelProdContainer.preload.php')) {
require dirname(__DIR__).'/var/cache/prod/App_KernelProdContainer.preload.php';
}

1662
config/reference.php Normal file

File diff suppressed because it is too large Load Diff

11
config/routes.yaml Normal file
View File

@@ -0,0 +1,11 @@
# yaml-language-server: $schema=../vendor/symfony/routing/Loader/schema/routing.schema.json
# This file is the entry point to configure the routes of your app.
# Methods with the #[Route] attribute are automatically imported.
# See also https://symfony.com/doc/current/routing.html
# To list all registered routes, run the following command:
# bin/console debug:router
controllers:
resource: routing.controllers

View File

@@ -0,0 +1,4 @@
api_platform:
resource: .
type: api_platform
prefix: /

View File

@@ -0,0 +1,4 @@
when@dev:
_errors:
resource: '@FrameworkBundle/Resources/config/routing/errors.php'
prefix: /_error

View File

@@ -0,0 +1,3 @@
_security_logout:
resource: security.route_loader.logout
type: service

28
config/services.yaml Normal file
View File

@@ -0,0 +1,28 @@
# yaml-language-server: $schema=../vendor/symfony/dependency-injection/Loader/schema/services.schema.json
# This file is the entry point to configure your own services.
# Files in the packages/ subdirectory configure your dependencies.
# See also https://symfony.com/doc/current/service_container/import.html
# Put parameters here that don't need to change on each machine where the app is deployed
# https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration
parameters:
services:
# default configuration for services in *this* file
_defaults:
autowire: true # Automatically injects dependencies in your services.
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
# makes classes in src/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
App\:
resource: '../src/'
App\Service\PontBasculeService:
arguments:
$endpoint: '%env(PONT_BASCULE_URL)%'
$bypass: '%env(bool:PONT_BASCULE_BYPASS)%'
# add more service definitions when explicit configuration is needed
# please note that last definitions always *replace* previous ones

47
docker-compose.yml Normal file
View File

@@ -0,0 +1,47 @@
services:
web:
container_name: php-${DOCKER_APP_NAME}-apache
build:
context: ./docker/php
dockerfile: Dockerfile
args:
DOCKER_PHP_VERSION: ${DOCKER_PHP_VERSION}
DOCKER_NODE_VERSION: ${DOCKER_NODE_VERSION}
CURRENT_UID: ${CURRENT_UID}
CURRENT_GID: ${CURRENT_GID}
environment:
PHP_IDE_CONFIG: serverName=${DOCKER_APP_NAME}-docker
XDEBUG_CLIENT_HOST: ${XDEBUG_CLIENT_HOST:-host.docker.internal}
XDEBUG_CONFIG: client_host=${XDEBUG_CLIENT_HOST:-host.docker.internal} client_port=9003
DATABASE_URL: "postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}?serverVersion=16&charset=utf8"
volumes:
- ./:/var/www/html
- ~/.cache:/var/www/.cache # Pour la cache de composer
- ~/.config:/var/www/.config # Pour la config de yarn
- ~/.composer:/var/www/.composer # Pour la config de composer
- ./docker/php/config/php.ini:/usr/local/etc/php/php.ini
- ./docker/php/config/vhost.conf:/etc/apache2/sites-available/000-default.conf
- ./docker/php/config/docker-php-ext-xdebug.ini:/usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini
- ./LOG:/var/www/html/LOG
- ./LOG/logs_apache:/var/log/apache2/
extra_hosts:
- "host.docker.internal:host-gateway"
depends_on:
- db
ports:
- "8080:80"
- "3000:3000"
restart: unless-stopped
db:
image: postgres:16-alpine
environment:
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- pg_data:/var/lib/postgresql/data
ports:
- "${POSTGRES_PORT:-5432}:5432"
restart: unless-stopped
volumes:
pg_data:

9
docker/.env.docker Normal file
View File

@@ -0,0 +1,9 @@
DOCKER_APP_NAME=ferme
DOCKER_PHP_VERSION=8.4.6
DOCKER_NODE_VERSION=24.12.0
APP_USER=www-data
POSTGRES_DB=ferme
POSTGRES_USER=root
POSTGRES_PASSWORD=root
POSTGRES_PORT=5432
XDEBUG_CLIENT_HOST=host.docker.internal

128
docker/php/Dockerfile Normal file
View File

@@ -0,0 +1,128 @@
ARG DOCKER_PHP_VERSION
FROM php:${DOCKER_PHP_VERSION}-apache-bullseye
ARG DOCKER_NODE_VERSION
ENV DOCKER_NODE_VERSION="${DOCKER_NODE_VERSION}"
# Installer les dépendances et extensions PHP nécessaires
RUN apt-get update && apt-get install -y \
libicu-dev \
libpq-dev \
libpng-dev \
libzip-dev \
libxml2-dev \
ca-certificates \
gnupg \
libbz2-dev \
libgmp-dev \
libldap2-dev \
libonig-dev \
libsodium-dev \
libxslt1-dev \
unixodbc-dev \
libsqlite3-dev \
zlib1g-dev \
libssl-dev \
libc-client-dev \
libkrb5-dev \
freetds-dev \
vim \
tcpdump \
dnsutils \
wget \
git \
unzip \
&& docker-php-ext-install -j$(nproc) \
intl \
zip \
bcmath \
bz2 \
calendar \
exif \
gd \
gettext \
gmp \
ldap \
# mysqli \
pcntl \
pdo_pgsql \
# pdo_mysql \
# pdo_sqlite \
# pdo_sqlsrv \
soap \
sockets \
sysvsem \
xsl
# Installation de node
RUN wget -qO- "https://nodejs.org/dist/v${DOCKER_NODE_VERSION}/node-v${DOCKER_NODE_VERSION}-linux-x64.tar.xz" | tar xJC /tmp/ && \
cp -r /tmp/node-v${DOCKER_NODE_VERSION}-linux-x64/bin /usr/ && \
cp -r /tmp/node-v${DOCKER_NODE_VERSION}-linux-x64/include /usr/ && \
cp -r /tmp/node-v${DOCKER_NODE_VERSION}-linux-x64/lib /usr/ && \
cp -r /tmp/node-v${DOCKER_NODE_VERSION}-linux-x64/share /usr/ && \
npm install --global yarn
# installation/activation d'extensions php
RUN pecl install xdebug
RUN docker-php-ext-enable xdebug && \
docker-php-ext-install zip && \
docker-php-ext-install gd && \
docker-php-ext-install soap && \
docker-php-ext-configure intl && \
docker-php-ext-install intl
# Configuration spéciale pour quelques extensions
# RUN docker-php-ext-configure pdo_odbc --with-pdo-odbc=unixODBC,/usr && \
# docker-php-ext-install pdo_odbc \
RUN docker-php-ext-enable opcache
# Configurer Oracle OCI8 (nécessite le SDK Oracle, à installer manuellement ou à lier via les dépendances)
#RUN apt-get update && apt-get -y install wget unzip libaio1 && \
# wget https://download.oracle.com/otn_software/linux/instantclient/2340000/instantclient-basic-linux.x64-23.4.0.24.05.zip && \
# unzip -o instantclient-basic-linux.x64-23.4.0.24.05.zip -d /usr/local && \
# wget https://download.oracle.com/otn_software/linux/instantclient/2340000/instantclient-sdk-linux.x64-23.4.0.24.05.zip && \
# unzip -o instantclient-sdk-linux.x64-23.4.0.24.05.zip -d /usr/local
#
#RUN echo 'instantclient,/usr/local/instantclient_23_4' | pecl install oci8-3.4.0 \
# && docker-php-ext-enable oci8
#
#ENV ORACLE_BASE /usr/local/instantclient_23_4
#ENV LD_LIBRARY_PATH /usr/local/instantclient_23_4
#ENV TNS_ADMIN /usr/local/instantclient_23_4
#ENV ORACLE_HOME /usr/local/instantclient_23_4
# Configuration pour utiliser Kerberos avec IMAP (si nécessaire)
# RUN docker-php-ext-configure imap --with-kerberos --with-imap-ssl \
# && docker-php-ext-install imap
# installation de composer
RUN rm -rf /var/cache/apk/* && rm -rf /tmp/* && \
curl --insecure https://getcomposer.org/composer.phar -o /usr/bin/composer && chmod +x /usr/bin/composer
# Création de la structure du projet
RUN mkdir /var/www/html/LOG
# Activation du module pour Apache2 proxy_http et rewrite
RUN a2enmod proxy_http && \
a2enmod rewrite
###> User ###
ARG CURRENT_UID
ARG CURRENT_GID
# mapping du user host avec www-data
RUN usermod -o -u ${CURRENT_UID} www-data && groupmod -o -g ${CURRENT_GID} www-data
RUN chown www-data:www-data -R /var/www/*
RUN chown www-data:www-data -R /var/www/.*
###< User ###
RUN rm -rf \
/var/lib/apt/lists/* \
/tmp/* \
/var/tmp/*
WORKDIR /var/www/html
EXPOSE 80

View File

@@ -0,0 +1,9 @@
zend_extension = /usr/local/lib/php/extensions/no-debug-non-zts-20240924/xdebug.so
xdebug.mode=debug
xdebug.idekey=PHPSTORM
xdebug.start_with_request=yes
xdebug.discover_client_host=1
xdebug.client_port=9003
xdebug.log="/var/www/html/LOG/xdebug.log"
xdebug.log_level=0
xdebug.connect_timeout_ms=2

View File

@@ -0,0 +1,4 @@
[Date]
; Defines the default timezone used by the date functions
; http://php.net/date.timezone
date.timezone = Europe/Paris

View File

@@ -0,0 +1,32 @@
<VirtualHost *:80>
DocumentRoot /var/www/html
AliasMatch "^/api(/.*)?" "/var/www/html/public$1"
<Directory /var/www/html/public>
Options FollowSymLinks
AllowOverride All
Require all granted
RewriteEngine On
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^ /api/index.php [L]
</Directory>
AliasMatch "^/(?!api)(.*)$" "/var/www/html/frontend/dist/$1"
<Directory /var/www/html/frontend/dist>
AllowOverride All
Order allow,deny
Allow from All
RewriteEngine on
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [L]
RewriteRule ^ index.html [L]
</Directory>
ErrorLog "${APACHE_LOG_DIR}/error.log"
CustomLog "${APACHE_LOG_DIR}/access.log" combined
</VirtualHost>

24
frontend/.gitignore vendored Normal file
View File

@@ -0,0 +1,24 @@
# Nuxt dev/build outputs
.output
.data
.nuxt
.nitro
.cache
dist
# Node dependencies
node_modules
# Logs
logs
*.log
# Misc
.DS_Store
.fleet
.idea
# Local env files
.env
.env.*
!.env.example

75
frontend/README.md Normal file
View File

@@ -0,0 +1,75 @@
# Nuxt Minimal Starter
Look at the [Nuxt documentation](https://nuxt.com/docs/getting-started/introduction) to learn more.
## Setup
Make sure to install dependencies:
```bash
# npm
npm install
# pnpm
pnpm install
# yarn
yarn install
# bun
bun install
```
## Development Server
Start the development server on `http://localhost:3000`:
```bash
# npm
npm run dev
# pnpm
pnpm dev
# yarn
yarn dev
# bun
bun run dev
```
## Production
Build the application for production:
```bash
# npm
npm run build
# pnpm
pnpm build
# yarn
yarn build
# bun
bun run build
```
Locally preview production build:
```bash
# npm
npm run preview
# pnpm
pnpm preview
# yarn
yarn preview
# bun
bun run preview
```
Check out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information.

5
frontend/app.vue Normal file
View File

@@ -0,0 +1,5 @@
<template>
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
</template>

View File

View File

@@ -0,0 +1,36 @@
<template>
<h1>Formulaire</h1>
<button
@click="validate"
class="text-xl uppercase bg-primary-500 text-white h-[50px] w-[272px]"
>Valider
</button>
</template>
<script setup lang="ts">
import { useReceptionStore } from '~/stores/reception'
const receptionStore = useReceptionStore()
const isLoading = ref<boolean>(false)
const errorMessage = ref<string | null>(null)
async function validate() {
if (!receptionStore.current) {
errorMessage.value = 'Réception introuvable.'
return
}
isLoading.value = true
try {
const nextStep = receptionStore.current.currentStep + 1
await receptionStore.updateReception(receptionStore.current.id, {
currentStep: nextStep
})
} catch (error) {
errorMessage.value = error.error ?? 'Erreur inconnue.'
} finally {
isLoading.value = false
}
}
</script>

View File

@@ -0,0 +1,41 @@
<template>
<div v-if="weightData">
<p>{{ weightData.weight }} kg</p>
<p>DSD : {{ weightData.dsd }}</p>
</div>
<button
class="text-xl uppercase bg-primary-500 text-white h-[50px] w-[272px]"
@click="getReceptionWeight"
>Peser</button>
</template>
<script setup lang="ts">
import { getWeight } from '~/services/reception'
import type { WeightData } from '~/services/dto/weight-data'
import { useReceptionStore } from '~/stores/reception'
const isLoading = ref(false)
const weightData = ref<WeightData | null>(null)
const errorMessage = ref<string | null>(null)
const receptionStore = useReceptionStore()
async function getReceptionWeight() {
isLoading.value = true
try {
weightData.value = await getWeight()
if (receptionStore.current) {
const nextStep = receptionStore.current.currentStep + 1
await receptionStore.updateReception(receptionStore.current.id, {
dsd: weightData.value?.dsd ?? null,
weight: weightData.value?.weight ?? null,
currentStep: nextStep
})
}
} catch (error) {
errorMessage.value = error.error
} finally {
isLoading.value = false
}
}
</script>

View File

@@ -0,0 +1,55 @@
import type { FetchOptions } from 'ofetch'
import { $fetch, FetchError } from 'ofetch'
export type AnyObject = Record<string, unknown>
export type ApiClient = {
get<T>(url: string, query?: AnyObject, options?: FetchOptions<'json'>): Promise<T>
post<T>(url: string, body?: AnyObject, options?: FetchOptions<'json'>): Promise<T>
put<T>(url: string, body?: AnyObject, options?: FetchOptions<'json'>): Promise<T>
patch<T>(url: string, body?: AnyObject, options?: FetchOptions<'json'>): Promise<T>
delete<T>(url: string, query?: AnyObject, options?: FetchOptions<'json'>): Promise<T>
}
export const useApi = (): ApiClient => {
const config = useRuntimeConfig()
const baseURL = config.public.apiBase ?? '/api'
const client = $fetch.create({ baseURL })
const request = <T>(
method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE',
url: string,
options: FetchOptions<'json'> = {}
) => {
const needsJsonBody = method === 'POST' || method === 'PUT'
const needsMergePatch = method === 'PATCH'
const headers = new Headers(options.headers as HeadersInit | undefined)
if (needsMergePatch && !headers.has('Content-Type')) {
headers.set('Content-Type', 'application/merge-patch+json')
} else if (needsJsonBody && !headers.has('Content-Type')) {
headers.set('Content-Type', 'application/json')
}
return client<T>(url, { ...options, method, headers })
}
return {
get<T>(url: string, query: AnyObject = {}, options: FetchOptions<'json'> = {}) {
return request<T>('GET', url, { ...options, query })
},
post<T>(url: string, body: AnyObject = {}, options: FetchOptions<'json'> = {}) {
return request<T>('POST', url, { ...options, body })
},
put<T>(url: string, body: AnyObject = {}, options: FetchOptions<'json'> = {}) {
return request<T>('PUT', url, { ...options, body })
},
patch<T>(url: string, body: AnyObject = {}, options: FetchOptions<'json'> = {}) {
return request<T>('PATCH', url, { ...options, body })
},
delete<T>(url: string, query: AnyObject = {}, options: FetchOptions<'json'> = {}) {
return request<T>('DELETE', url, { ...options, query })
}
}
}

View File

@@ -0,0 +1,38 @@
<template>
<div class="min-h-screen bg-white text-neutral-900">
<header class="w-full border-b border-neutral-200 bg-primary-500">
<div class="flex w-full items-center px-6 py-4">
<NuxtLink to="/" class="flex items-center gap-3">
<span
class="flex items-center justify-center bg-white text-xl font-bold uppercase text-primary-500 p-4"
>
LOGO
</span>
</NuxtLink>
<nav class="mx-8 flex gap-8 text-2xl font-bold uppercase text-white">
<NuxtLink to="/" custom v-slot="{ href, navigate, isExactActive }">
<a
:href="href"
@click="navigate"
:class="isExactActive ? 'opacity-100' : 'opacity-50'"
>
Accueil
</a>
</NuxtLink>
<NuxtLink to="/reception" custom v-slot="{ href, navigate, isActive }">
<a
:href="href"
@click="navigate"
:class="isActive ? 'opacity-100' : 'opacity-50'"
>
Reception
</a>
</NuxtLink>
</nav>
</div>
</header>
<main class="mx-auto w-full max-w-[1050px] px-6 pt-[90px] pb-0">
<slot/>
</main>
</div>
</template>

14
frontend/nuxt.config.ts Normal file
View File

@@ -0,0 +1,14 @@
export default defineNuxtConfig({
compatibilityDate: '2025-07-15',
devtools: { enabled: true },
ssr: false,
modules: ['@nuxtjs/tailwindcss', '@pinia/nuxt'],
runtimeConfig: {
public: {
apiBase: process.env.NUXT_PUBLIC_API_BASE
}
},
typescript: {
strict: true
}
})

11947
frontend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

23
frontend/package.json Normal file
View File

@@ -0,0 +1,23 @@
{
"name": "frontend",
"type": "module",
"private": true,
"scripts": {
"build": "nuxt build",
"dev": "nuxt dev",
"generate": "nuxt generate",
"preview": "nuxt preview",
"postinstall": "nuxt prepare",
"build:dist": "nuxt generate && rm -rf dist && cp -R .output/public dist"
},
"dependencies": {
"@pinia/nuxt": "^0.11.3",
"nuxt": "^4.2.2",
"pinia": "^3.0.4",
"vue": "^3.5.26",
"vue-router": "^4.6.4"
},
"devDependencies": {
"@nuxtjs/tailwindcss": "^6.14.0"
}
}

21
frontend/pages/index.vue Normal file
View File

@@ -0,0 +1,21 @@
<template>
<div class="">
<h1 class="text-3xl font-bold">Liste des receptions</h1>
<ul>
<li v-for="reception in receptionList" :key="reception.id">
<NuxtLink :to="`/reception/${reception.id}`">Réception numéro {{ reception.id}}</NuxtLink>
</li>
</ul>
</div>
</template>
<script setup lang="ts">
import type {ReceptionData} from "~/services/dto/reception-data";
import {getReceptionList} from "~/services/reception";
const receptionList = ref<ReceptionData[]>()
onMounted(async () => {
receptionList.value = await getReceptionList()
})
</script>

View File

@@ -0,0 +1,42 @@
<template>
<div v-if="errorMessage" class="text-red-600">{{ errorMessage }}</div>
<div v-if="isLoading" class="text-neutral-600">Chargement...</div>
<div v-else>
<ReceptionForm v-if="storeReception?.currentStep === 0"/>
<ReceptionWeight v-if="storeReception?.currentStep === 1"/>
<div v-if="storeReception?.currentStep === 2">Décharger</div>
<ReceptionWeight v-if="storeReception?.currentStep === 3"/>
</div>
</template>
<script setup lang="ts">
import { createReception, getReception } from '~/services/reception'
import type { ReceptionData } from '~/services/dto/reception-data'
import { useReceptionStore } from '~/stores/reception'
import { storeToRefs } from 'pinia'
const route = useRoute()
const router = useRouter()
const isLoading = ref<boolean>(false)
const errorMessage = ref<string | null>(null)
const receptionStore = useReceptionStore()
const { current: storeReception } = storeToRefs(receptionStore)
onMounted(async () => {
isLoading.value = true
const raw = route.params.id
const idStr = Array.isArray(raw) ? raw[0] : raw
const id = idStr ? Number(idStr) : null
try {
const result = id === null ? await createReception() : await getReception(id)
if (result) {
receptionStore.setCurrent(result as ReceptionData)
}
} catch (error) {
errorMessage.value = error.error ?? 'Erreur inconnue.'
}
isLoading.value = false
})
</script>

BIN
frontend/public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@@ -0,0 +1,2 @@
User-Agent: *
Disallow:

View File

@@ -0,0 +1,9 @@
export interface ReceptionData {
id: number
dsd: number | null
licensePlate: string | null
weight: number | null
receptionDate: string
currentStep: number
isValid: boolean
}

View File

@@ -0,0 +1,5 @@
export interface WeightData {
weight: number | null
dsd: number | null
receptionDate: string
}

View File

@@ -0,0 +1,50 @@
import { useApi } from '~/composables/useApi'
import type { ReceptionData } from '~/services/dto/reception-data'
import type { WeightData } from '~/services/dto/weight-data'
const api = useApi()
export async function getReceptionList() {
try {
return await api.get<ReceptionData>(`receptions`)
} catch (error) {
console.error(error.message, error)
return error
}
}
export async function getReception(id: number) {
try {
return await api.get<ReceptionData>(`receptions/${id}`)
} catch (error) {
console.error(error.message, error)
return error
}
}
export async function createReception(payload: Partial<ReceptionData> = {}) {
try {
return await api.post<ReceptionData>('receptions', payload)
} catch (error) {
console.error(error.message, error)
return error
}
}
export async function updateReception(id: number, payload: Partial<ReceptionData>) {
try {
return await api.patch<ReceptionData>(`receptions/${id}`, payload)
} catch (error) {
console.error(error.message, error)
return error
}
}
export async function getWeight(): Promise<WeightData> {
try {
return await api.get<WeightData>('receptions/weigh')
} catch (error) {
console.error(error.message, error)
return error
}
}

View File

@@ -0,0 +1,72 @@
import { defineStore } from 'pinia'
import type { ReceptionData } from '~/services/dto/reception-data'
import { createReception, getReception, updateReception } from '~/services/reception'
const isReceptionData = (value: unknown): value is ReceptionData => {
return Boolean(value && typeof value === 'object' && 'id' in value)
}
export const useReceptionStore = defineStore('reception', {
state: () => ({
current: null as ReceptionData | null,
isLoading: false,
errorMessage: null as string | null
}),
actions: {
setCurrent(reception: ReceptionData | null) {
this.current = reception
},
clearError() {
this.errorMessage = null
},
async loadReception(id: number) {
this.isLoading = true
this.errorMessage = null
try {
const result = await getReception(id)
if (!isReceptionData(result)) {
this.errorMessage = 'Réception introuvable.'
this.current = null
return null
}
this.current = result
return result
} finally {
this.isLoading = false
}
},
async createReception() {
this.isLoading = true
this.errorMessage = null
try {
const result = await createReception()
if (!isReceptionData(result)) {
this.errorMessage = 'Impossible de créer la réception.'
return null
}
this.current = result
return result
} finally {
this.isLoading = false
}
},
async updateReception(id: number, payload: Partial<ReceptionData>) {
this.isLoading = true
this.errorMessage = null
try {
const result = await updateReception(id, payload)
if (!isReceptionData(result)) {
this.errorMessage = 'Impossible de mettre à jour la réception.'
return null
}
this.current = result
return result
} finally {
this.isLoading = false
}
}
}
})

View File

@@ -0,0 +1,22 @@
import type { Config } from 'tailwindcss'
export default <Partial<Config>>{
theme: {
extend: {
colors: {
primary: {
50: '#f6f9ea',
100: '#eaf2cf',
200: '#d6e3a4',
300: '#c1d47a',
400: '#afc85a',
500: '#9ebb43',
600: '#7e9735',
700: '#607228',
800: '#414d1a',
900: '#24290d'
}
}
}
}
}

18
frontend/tsconfig.json Normal file
View File

@@ -0,0 +1,18 @@
{
// https://nuxt.com/docs/guide/concepts/typescript
"files": [],
"references": [
{
"path": "./.nuxt/tsconfig.app.json"
},
{
"path": "./.nuxt/tsconfig.server.json"
},
{
"path": "./.nuxt/tsconfig.shared.json"
},
{
"path": "./.nuxt/tsconfig.node.json"
}
]
}

110
makefile Normal file
View File

@@ -0,0 +1,110 @@
# Permet d'utiliser un .env.docker.local pour override
ENV_DEFAULT = docker/.env.docker
ENV_LOCAL = docker/.env.docker.local
ENV_FILE := $(if $(wildcard $(ENV_LOCAL)),$(ENV_LOCAL),$(ENV_DEFAULT))
# Permet d'avoir les variables du fichier .env.docker.local
include $(ENV_DEFAULT)
-include $(ENV_LOCAL)
PHP_CONTAINER = php-$(DOCKER_APP_NAME)-apache
SYMFONY_CONSOLE = $(EXEC_PHP) php bin/console
DOCKER_COMPOSE = docker compose --env-file $(ENV_FILE)
DOCKER = docker
EXEC_PHP = $(DOCKER) exec -t -u $(APP_USER) $(PHP_CONTAINER)
EXEC_PHP_CS_FIXER = $(EXEC_PHP) php vendor/bin/php-cs-fixer
EXEC_PHP_ROOT = $(DOCKER) exec -t -u root $(PHP_CONTAINER)
EXEC_PHP_INTERACTIVE = $(DOCKER) exec -it -u $(APP_USER) $(PHP_CONTAINER)
EXEC_PHP_INTERACTIVE_ROOT = $(DOCKER) exec -it -u root $(PHP_CONTAINER)
FILES =
#========================================================================================
env-init:
@mkdir -p docker
@cp --update=none $(ENV_DEFAULT) $(ENV_LOCAL)
# Lance le container
start: env-init
@echo "**** START CONTAINERS ****"
@cp --update=none docker/.env.docker docker/.env.docker.local
CURRENT_UID=$(shell id -u) CURRENT_GID=$(shell id -g) $(DOCKER_COMPOSE) up -d
# Éteint le container
stop:
$(DOCKER_COMPOSE) stop
restart: env-init
$(DOCKER_COMPOSE) down
CURRENT_UID=$(shell id -u) CURRENT_GID=$(shell id -g) $(DOCKER_COMPOSE) up -d
install: copy-git-hook composer-install cache-clear node-use build-nuxtJS
# Supprime tout est réinstalle tout (Attention ça supprime la bdd aussi)
reset: delete_built_dir remove_orphans build-without-cache start wait install
composer-install:
$(EXEC_PHP) composer install
build-nuxtJS:
# $(EXEC_PHP) cp -n frontend/.env.dist frontend/.env.local
$(EXEC_PHP) sh -lc "cd frontend && npm install && npm run build:dist"
dev-nuxt:
$(EXEC_PHP) sh -c "cd frontend && npm run dev"
delete_built_dir:
CURRENT_UID=$(shell id -u) CURRENT_GID=$(shell id -g) $(DOCKER_COMPOSE) up -d
$(DOCKER) exec -u root $(PHP_CONTAINER) rm -rf vendor/
$(DOCKER) exec -u root $(PHP_CONTAINER) rm -rf frontend/node_modules
remove_orphans:
$(DOCKER_COMPOSE) kill
$(DOCKER_COMPOSE) down --volumes --remove-orphans
build-without-cache:
$(DOCKER_COMPOSE) build \
--build-arg="DOCKER_PHP_VERSION=$(DOCKER_PHP_VERSION)" \
--build-arg="DOCKER_NODE_VERSION=$(DOCKER_NODE_VERSION)" \
--build-arg="CURRENT_UID=$(shell id -u)" \
--build-arg="CURRENT_GID=$(shell id -g)" \
--no-cache
# Attention, supprime votre bdd local
db-reset:
$(DOCKER_COMPOSE) down -v
$(DOCKER_COMPOSE) up -d
# Restart la bdd
db-restart:
$(DOCKER_COMPOSE) down
$(DOCKER_COMPOSE) up -d
cache-clear:
$(SYMFONY_CONSOLE) cache:clear
copy-git-hook:
$(EXEC_PHP) cp pre-commit .git/hooks/
$(EXEC_PHP) cp commit-msg .git/hooks/
$(EXEC_PHP) chmod a+x .git/hooks/pre-commit
$(EXEC_PHP) chmod a+x .git/hooks/commit-msg
shell:
$(EXEC_PHP_INTERACTIVE) bash
# Force la version node
node-use:
bash -lc 'source "$$HOME/.nvm/nvm.sh" && nvm install && nvm use'
# Utilisé par le pre-commit pour fix les fichiers modifiés
php-cs-fixer-allow-risky:
@echo "Fixing files: $(FILES)"
$(EXEC_PHP_CS_FIXER) fix --config=.php-cs-fixer.dist.php --allow-risky=yes $(FILES)
test:
$(EXEC_PHP) php -d memory_limit="512M" vendor/bin/phpunit $(FILES)
wait:
sleep 10

0
migrations/.gitignore vendored Normal file
View File

View File

@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20260112000100 extends AbstractMigration
{
public function getDescription(): string
{
return 'Create reception table';
}
public function up(Schema $schema): void
{
$this->addSql('CREATE TABLE reception (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, dsd INT DEFAULT NULL, weight DOUBLE PRECISION DEFAULT NULL, weighed_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, PRIMARY KEY(id))');
}
public function down(Schema $schema): void
{
$this->addSql('DROP TABLE reception');
}
}

View File

@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20260112000200 extends AbstractMigration
{
public function getDescription(): string
{
return 'Create weight table and link to reception';
}
public function up(Schema $schema): void
{
$this->addSql('CREATE TABLE weight (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, reception_id INT NOT NULL, gross_weight INT DEFAULT NULL, tare_weight INT DEFAULT NULL, gross_weighed_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, tare_weighed_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, PRIMARY KEY(id))');
$this->addSql('CREATE UNIQUE INDEX UNIQ_7B4E3B2304A72F3F ON weight (reception_id)');
$this->addSql('ALTER TABLE weight ADD CONSTRAINT FK_7B4E3B2304A72F3F FOREIGN KEY (reception_id) REFERENCES reception (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
}
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE weight DROP CONSTRAINT FK_7B4E3B2304A72F3F');
$this->addSql('DROP TABLE weight');
}
}

View File

@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20260112000300 extends AbstractMigration
{
public function getDescription(): string
{
return 'Rename weighed_at to date_reception in reception table';
}
public function up(Schema $schema): void
{
$this->addSql('ALTER TABLE reception RENAME COLUMN weighed_at TO date_reception');
}
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE reception RENAME COLUMN date_reception TO weighed_at');
}
}

View File

@@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20260112000400 extends AbstractMigration
{
public function getDescription(): string
{
return 'Add license plate, current step, and validity fields to reception';
}
public function up(Schema $schema): void
{
$this->addSql('ALTER TABLE reception ADD license_plate VARCHAR(20) DEFAULT NULL');
$this->addSql('ALTER TABLE reception ADD current_step INT DEFAULT 0 NOT NULL');
$this->addSql('ALTER TABLE reception ADD is_valid BOOLEAN DEFAULT FALSE NOT NULL');
}
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE reception DROP license_plate');
$this->addSql('ALTER TABLE reception DROP current_step');
$this->addSql('ALTER TABLE reception DROP is_valid');
}
}

44
phpunit.dist.xml Normal file
View File

@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- https://phpunit.readthedocs.io/en/latest/configuration.html -->
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
colors="true"
failOnDeprecation="true"
failOnNotice="true"
failOnWarning="true"
bootstrap="tests/bootstrap.php"
cacheDirectory=".phpunit.cache"
>
<php>
<ini name="display_errors" value="1" />
<ini name="error_reporting" value="-1" />
<server name="APP_ENV" value="test" force="true" />
<server name="SHELL_VERBOSITY" value="-1" />
</php>
<testsuites>
<testsuite name="Project Test Suite">
<directory>tests</directory>
</testsuite>
</testsuites>
<source ignoreSuppressionOfDeprecations="true"
ignoreIndirectDeprecations="true"
restrictNotices="true"
restrictWarnings="true"
>
<include>
<directory>src</directory>
</include>
<deprecationTrigger>
<method>Doctrine\Deprecations\Deprecation::trigger</method>
<method>Doctrine\Deprecations\Deprecation::delegateTriggerToBackend</method>
<function>trigger_deprecation</function>
</deprecationTrigger>
</source>
<extensions>
</extensions>
</phpunit>

38
pre-commit Normal file
View File

@@ -0,0 +1,38 @@
#!/bin/sh
echo "######### Pre-commit hook start #############"
echo "--- php-cs-fixer pre commit hook start ---"
FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.php$')
# Vérifier s'il y a des fichiers PHP modifiés
if [ -n "$FILES" ]; then
echo "Running PHP CS Fixer on staged PHP files..."
# Convertir la liste des fichiers en une chaîne séparée par des espaces
FILES_LIST=""
for FILE in $FILES; do
FILES_LIST="$FILES_LIST $FILE"
done
# Exécuter la cible make pour PHP CS Fixer
make php-cs-fixer-allow-risky FILES="$FILES_LIST"
# Ajouter les fichiers corrigés au commit
git add $FILES
else
echo "No PHP files to fix."
fi
echo "--- php-cs-fixer pre commit hook finish---"
echo "--- phpunit pre commit hook start ---"
make test
PHPUNIT_RESULT=$?
if [ $PHPUNIT_RESULT -ne 0 ]; then
echo "PHPUnit tests failed. Aborting commit."
exit 1
fi
echo "--- phpunit pre commit hook finished ---"
echo "All checks passed. Proceeding with commit."
exit 0

9
public/index.php Normal file
View File

@@ -0,0 +1,9 @@
<?php
use App\Kernel;
require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
return function (array $context) {
return new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
};

0
src/ApiResource/.gitignore vendored Normal file
View File

0
src/Controller/.gitignore vendored Normal file
View File

View File

@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace App\Dto;
use DateTimeImmutable;
final readonly class PontBasculeReading
{
public function __construct(
private ?int $dsd,
private ?float $weight,
private ?DateTimeImmutable $datetime = null,
) {}
public function getDsd(): ?int
{
return $this->dsd;
}
public function getWeight(): ?float
{
return $this->weight;
}
public function getDatetime(): ?DateTimeImmutable
{
return $this->datetime;
}
}

0
src/Entity/.gitignore vendored Normal file
View File

199
src/Entity/Reception.php Normal file
View File

@@ -0,0 +1,199 @@
<?php
declare(strict_types=1);
namespace App\Entity;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Patch;
use ApiPlatform\Metadata\Post;
use ApiPlatform\OpenApi\Model\Operation as OpenApiOperation;
use App\State\ReceptionWeighingProvider;
use DateTimeImmutable;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Attribute\Groups;
#[ORM\Entity]
#[ORM\HasLifecycleCallbacks]
#[ORM\Table(name: 'reception')]
#[ApiResource(
operations: [
new Get(
normalizationContext: ['groups' => ['reception:read']],
),
new GetCollection(
normalizationContext: ['groups' => ['reception:read']],
),
new Post(
normalizationContext: ['groups' => ['reception:read']],
denormalizationContext: ['groups' => ['reception:write']],
),
new Patch(
normalizationContext: ['groups' => ['reception:read']],
denormalizationContext: ['groups' => ['reception:write']],
),
new Get(
uriTemplate: '/receptions/weigh',
openapi: new OpenApiOperation(
summary: 'Fetch the current weight reading',
description: 'Queries the pont-bascule and returns the weight data.',
),
normalizationContext: ['groups' => ['reception:read']],
provider: ReceptionWeighingProvider::class,
),
],
)]
class Reception
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
#[Groups(['reception:read'])]
private ?int $id = null;
#[ORM\Column(nullable: true)]
#[Groups(['reception:read', 'reception:write'])]
private ?int $dsd = null;
#[ORM\Column(type: 'float', nullable: true)]
#[Groups(['reception:read', 'reception:write'])]
private ?float $weight = null;
#[ORM\Column(length: 20, nullable: true)]
#[Groups(['reception:read', 'reception:write'])]
private ?string $licensePlate = null;
#[ORM\Column(options: ['default' => 0])]
#[Groups(['reception:read', 'reception:write'])]
private int $currentStep = 0;
#[ORM\Column(options: ['default' => false])]
#[Groups(['reception:read', 'reception:write'])]
private bool $isValid = false;
#[ORM\Column(name: 'date_reception', type: 'datetime_immutable')]
#[Groups(['reception:read'])]
private ?DateTimeImmutable $receptionDate = null;
#[ORM\OneToOne(targetEntity: Weight::class, mappedBy: 'reception', cascade: ['persist', 'remove'])]
private ?Weight $weightEntry = null;
public function __construct(
?int $dsd = null,
?float $weight = null,
?DateTimeImmutable $receptionDate = null,
) {
$this->dsd = $dsd;
$this->weight = $weight;
$this->receptionDate = $receptionDate;
}
public function getId(): ?int
{
return $this->id;
}
#[Groups(['reception:read'])]
public function getDsd(): ?int
{
return $this->dsd;
}
public function setDsd(?int $dsd): self
{
$this->dsd = $dsd;
return $this;
}
#[Groups(['reception:read'])]
public function getWeight(): ?float
{
return $this->weight;
}
public function setWeight(?float $weight): self
{
$this->weight = $weight;
return $this;
}
#[Groups(['reception:read'])]
public function getLicensePlate(): ?string
{
return $this->licensePlate;
}
public function setLicensePlate(?string $licensePlate): self
{
$this->licensePlate = $licensePlate;
return $this;
}
#[Groups(['reception:read'])]
public function getCurrentStep(): int
{
return $this->currentStep;
}
public function setCurrentStep(int $currentStep): self
{
$this->currentStep = $currentStep;
return $this;
}
#[Groups(['reception:read'])]
public function isValid(): bool
{
return $this->isValid;
}
public function setIsValid(bool $isValid): self
{
$this->isValid = $isValid;
return $this;
}
#[Groups(['reception:read'])]
public function getReceptionDate(): ?DateTimeImmutable
{
return $this->receptionDate;
}
public function setReceptionDate(?DateTimeImmutable $receptionDate): self
{
$this->receptionDate = $receptionDate;
return $this;
}
public function getWeightEntry(): ?Weight
{
return $this->weightEntry;
}
public function setWeightEntry(?Weight $weightEntry): self
{
$this->weightEntry = $weightEntry;
if (null !== $weightEntry && $weightEntry->getReception() !== $this) {
$weightEntry->setReception($this);
}
return $this;
}
#[ORM\PrePersist]
public function initializeReceptionDate(): void
{
if (null === $this->receptionDate) {
$this->receptionDate = new DateTimeImmutable();
}
}
}

103
src/Entity/Weight.php Normal file
View File

@@ -0,0 +1,103 @@
<?php
declare(strict_types=1);
namespace App\Entity;
use DateTimeImmutable;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
#[ORM\Table(name: 'weight')]
class Weight
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\OneToOne(inversedBy: 'weightEntry')]
#[ORM\JoinColumn(nullable: false)]
private ?Reception $reception = null;
#[ORM\Column(nullable: true)]
private ?int $grossWeight = null;
#[ORM\Column(nullable: true)]
private ?int $tareWeight = null;
#[ORM\Column(type: 'datetime_immutable', nullable: true)]
private ?DateTimeImmutable $grossWeighedAt = null;
#[ORM\Column(type: 'datetime_immutable', nullable: true)]
private ?DateTimeImmutable $tareWeighedAt = null;
public function getId(): ?int
{
return $this->id;
}
public function getReception(): ?Reception
{
return $this->reception;
}
public function setReception(?Reception $reception): self
{
$this->reception = $reception;
if (null !== $reception && $reception->getWeightEntry() !== $this) {
$reception->setWeightEntry($this);
}
return $this;
}
public function getGrossWeight(): ?int
{
return $this->grossWeight;
}
public function setGrossWeight(?int $grossWeight): self
{
$this->grossWeight = $grossWeight;
return $this;
}
public function getTareWeight(): ?int
{
return $this->tareWeight;
}
public function setTareWeight(?int $tareWeight): self
{
$this->tareWeight = $tareWeight;
return $this;
}
public function getGrossWeighedAt(): ?DateTimeImmutable
{
return $this->grossWeighedAt;
}
public function setGrossWeighedAt(?DateTimeImmutable $grossWeighedAt): self
{
$this->grossWeighedAt = $grossWeighedAt;
return $this;
}
public function getTareWeighedAt(): ?DateTimeImmutable
{
return $this->tareWeighedAt;
}
public function setTareWeighedAt(?DateTimeImmutable $tareWeighedAt): self
{
$this->tareWeighedAt = $tareWeighedAt;
return $this;
}
}

View File

@@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace App\Exception;
use RuntimeException;
final class PontBasculeException extends RuntimeException
{
public static function transportFailure(string $details): self
{
return new self('Erreur lors de la communication avec le pont bascule: '.$details, 500);
}
public static function invalidPayload(): self
{
return new self('Réponse invalide du pont bascule.', 500);
}
public static function missingPayloadField(string $field): self
{
return new self('Réponse incomplète du pont bascule: champ "'.$field.'" manquant.', 500);
}
public static function unreadableValues(): self
{
return new self('Impossible de lire les valeurs de pesée du pont bascule.', 500);
}
}

11
src/Kernel.php Normal file
View File

@@ -0,0 +1,11 @@
<?php
namespace App;
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
use Symfony\Component\HttpKernel\Kernel as BaseKernel;
class Kernel extends BaseKernel
{
use MicroKernelTrait;
}

0
src/Repository/.gitignore vendored Normal file
View File

View File

@@ -0,0 +1,68 @@
<?php
declare(strict_types=1);
namespace App\Service;
use App\Dto\PontBasculeReading;
use App\Exception\PontBasculeException;
final class PontBasculePayloadDecoder
{
public function decode(string $body): PontBasculeReading
{
// Payload is JSON with a "response_ascii" string containing STX (0x02) segments.
$payload = json_decode($body, true);
if (!is_array($payload)) {
throw PontBasculeException::invalidPayload();
}
$ascii = $payload['response_ascii'] ?? null;
if (!is_string($ascii)) {
throw PontBasculeException::missingPayloadField('response_ascii');
}
$dsd = null;
$net = null;
// Each segment starts with a 2-digit code followed by the numeric value.
$segments = preg_split('/\\x02/', $ascii) ?: [];
foreach ($segments as $segment) {
$segment = trim($segment);
if ('' === $segment) {
continue;
}
if (!preg_match('/^(\d{2})(\d+)(?:\.kg)?/', $segment, $matches)) {
continue;
}
$code = $matches[1];
$value = $matches[2];
// Code 99 holds the DSD value.
if ('99' === $code) {
$dsd = (int) ltrim($value, '0');
if (0 === $dsd && '' !== $value) {
$dsd = 0;
}
continue;
}
// Code 03 is the net weight; other codes are ignored for now.
if ('03' === $code) {
$net = (float) ltrim($value, '0');
if (0.0 === $net && '' !== $value) {
$net = 0.0;
}
}
}
if (null === $dsd && null === $net) {
throw PontBasculeException::unreadableValues();
}
return new PontBasculeReading($dsd, $net);
}
}

View File

@@ -0,0 +1,51 @@
<?php
declare(strict_types=1);
namespace App\Service;
use App\Dto\PontBasculeReading;
use App\Exception\PontBasculeException;
use DateTimeImmutable;
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;
final class PontBasculeService
{
public function __construct(
private readonly HttpClientInterface $httpClient,
private readonly PontBasculePayloadDecoder $payloadDecoder,
private readonly string $endpoint,
private readonly bool $bypass,
) {}
/**
* @TODO Voir pour que le pont-bascule retourne la date
*/
public function fetch(): PontBasculeReading
{
if ($this->bypass) {
$body = $this->getBypassPayload();
} else {
try {
$response = $this->httpClient->request('POST', $this->endpoint);
$body = $response->getContent(false);
} catch (TransportExceptionInterface $exception) {
throw PontBasculeException::transportFailure($exception->getMessage());
}
}
$reading = $this->payloadDecoder->decode($body);
return new PontBasculeReading(
$reading->getDsd(),
$reading->getWeight(),
new DateTimeImmutable(),
);
}
private function getBypassPayload(): string
{
return '{"ok":true,"busy":false,"mode":"serial","port":"/dev/ttyUSB0","baudrate":9600,"request_hex":"01 10 39 39 4D 0D 0A","response_hex":"01 02 30 34 30 32 30 30 02 30 31 30 30 31 34 32 30 2E 6B 67 20 02 30 32 30 30 30 30 30 30 2E 6B 67 20 02 30 33 30 30 31 34 32 30 2E 6B 67 20 02 39 39 30 30 31 32 31 0D 0A","response_ascii":"\u0001\u0002040200\u000201001420.kg \u000202000000.kg \u000203001420.kg \u00029900121"}';
}
}

View File

@@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
namespace App\State;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProviderInterface;
use App\Entity\Reception;
use App\Exception\PontBasculeException;
use App\Service\PontBasculeService;
use Symfony\Component\HttpKernel\Exception\HttpException;
final readonly class ReceptionWeighingProvider implements ProviderInterface
{
public function __construct(
private PontBasculeService $pontBasculeService,
) {}
public function provide(Operation $operation, array $uriVariables = [], array $context = []): ?Reception
{
try {
$result = $this->pontBasculeService->fetch();
} catch (PontBasculeException $exception) {
throw new HttpException(500, $exception->getMessage(), $exception);
}
return new Reception(
dsd: $result->getDsd(),
weight: $result->getWeight(),
receptionDate: $result->getDatetime(),
);
}
}

208
symfony.lock Normal file
View File

@@ -0,0 +1,208 @@
{
"api-platform/symfony": {
"version": "4.2",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "4.0",
"ref": "e9952e9f393c2d048f10a78f272cd35e807d972b"
},
"files": [
"config/packages/api_platform.yaml",
"config/routes/api_platform.yaml",
"src/ApiResource/.gitignore"
]
},
"doctrine/deprecations": {
"version": "1.1",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "1.0",
"ref": "87424683adc81d7dc305eefec1fced883084aab9"
}
},
"doctrine/doctrine-bundle": {
"version": "3.2",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "3.0",
"ref": "18ee08e513ba0303fd09a01fc1c934870af06ffa"
},
"files": [
"config/packages/doctrine.yaml",
"src/Entity/.gitignore",
"src/Repository/.gitignore"
]
},
"doctrine/doctrine-migrations-bundle": {
"version": "4.0",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "3.1",
"ref": "1d01ec03c6ecbd67c3375c5478c9a423ae5d6a33"
},
"files": [
"config/packages/doctrine_migrations.yaml",
"migrations/.gitignore"
]
},
"friendsofphp/php-cs-fixer": {
"version": "3.92",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "3.0",
"ref": "be2103eb4a20942e28a6dd87736669b757132435"
},
"files": [
".php-cs-fixer.dist.php"
]
},
"nelmio/cors-bundle": {
"version": "2.6",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "1.5",
"ref": "6bea22e6c564fba3a1391615cada1437d0bde39c"
},
"files": [
"config/packages/nelmio_cors.yaml"
]
},
"phpunit/phpunit": {
"version": "12.5",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "11.1",
"ref": "1117deb12541f35793eec9fff7494d7aa12283fc"
},
"files": [
".env.test",
"phpunit.dist.xml",
"tests/bootstrap.php",
"bin/phpunit"
]
},
"symfony/console": {
"version": "8.0",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "5.3",
"ref": "1781ff40d8a17d87cf53f8d4cf0c8346ed2bb461"
},
"files": [
"bin/console"
]
},
"symfony/flex": {
"version": "2.10",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "2.4",
"ref": "52e9754527a15e2b79d9a610f98185a1fe46622a"
},
"files": [
".env",
".env.dev"
]
},
"symfony/framework-bundle": {
"version": "8.0",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "7.4",
"ref": "09f6e081c763a206802674ce0cb34a022f0ffc6d"
},
"files": [
"config/packages/cache.yaml",
"config/packages/framework.yaml",
"config/preload.php",
"config/routes/framework.yaml",
"config/services.yaml",
"public/index.php",
"src/Controller/.gitignore",
"src/Kernel.php",
".editorconfig"
]
},
"symfony/property-info": {
"version": "8.0",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "7.3",
"ref": "dae70df71978ae9226ae915ffd5fad817f5ca1f7"
},
"files": [
"config/packages/property_info.yaml"
]
},
"symfony/routing": {
"version": "8.0",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "7.4",
"ref": "bc94c4fd86f393f3ab3947c18b830ea343e51ded"
},
"files": [
"config/packages/routing.yaml",
"config/routes.yaml"
]
},
"symfony/security-bundle": {
"version": "8.0",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "7.4",
"ref": "c42fee7802181cdd50f61b8622715829f5d2335c"
},
"files": [
"config/packages/security.yaml",
"config/routes/security.yaml"
]
},
"symfony/twig-bundle": {
"version": "8.0",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "6.4",
"ref": "cab5fd2a13a45c266d45a7d9337e28dee6272877"
},
"files": [
"config/packages/twig.yaml",
"templates/base.html.twig"
]
},
"symfony/uid": {
"version": "8.0",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "7.0",
"ref": "0df5844274d871b37fc3816c57a768ffc60a43a5"
}
},
"symfony/validator": {
"version": "8.0",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "7.0",
"ref": "8c1c4e28d26a124b0bb273f537ca8ce443472bfd"
},
"files": [
"config/packages/validator.yaml"
]
}
}

16
templates/base.html.twig Normal file
View File

@@ -0,0 +1,16 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>{% block title %}Welcome!{% endblock %}</title>
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 128 128%22><text y=%221.2em%22 font-size=%2296%22>⚫️</text><text y=%221.3em%22 x=%220.2em%22 font-size=%2276%22 fill=%22%23fff%22>sf</text></svg>">
{% block stylesheets %}
{% endblock %}
{% block javascripts %}
{% endblock %}
</head>
<body>
{% block body %}{% endblock %}
</body>
</html>

15
tests/bootstrap.php Normal file
View File

@@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
use Symfony\Component\Dotenv\Dotenv;
require dirname(__DIR__).'/vendor/autoload.php';
if (method_exists(Dotenv::class, 'bootEnv')) {
new Dotenv()->bootEnv(dirname(__DIR__).'/.env');
}
if ($_SERVER['APP_DEBUG']) {
umask(0o000);
}