Compare commits

..

9 Commits

Author SHA1 Message Date
92354d9060 feat : Ajout de tests unitaires 2026-02-06 11:03:14 +01:00
33563addbe feat : Ajout de la sélection des bovins étape 3 d'une réception 2026-02-05 09:08:30 +01:00
81c2a5802b feat : Ajout de la sélection des bovins étape 3 d'une réception (WIP) 2026-02-04 16:11:08 +01:00
015a8c49fb feat : Ajout de la sélection des bovins étape 3 d'une réception (WIP) 2026-02-04 13:04:23 +01:00
505f67d475 feat : Ajout de la sélection des bovins étape 3 d'une réception (WIP) 2026-02-03 16:06:52 +01:00
13e8698673 fix : correction des migrations et de la commande make pour lancer les migrations
All checks were successful
Auto Tag Develop / tag (push) Successful in 4s
Build Release Artefact / build (push) Successful in 1m8s
2026-02-03 13:11:30 +01:00
a34bdbfe8d Modification de la conf docker (!8)
All checks were successful
Auto Tag Develop / tag (push) Successful in 4s
Build Release Artefact / build (push) Successful in 1m13s
| 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é

Reviewed-on: #8
Co-authored-by: sroy <sebastien@yuno.malio.fr>
Co-committed-by: sroy <sebastien@yuno.malio.fr>
2026-02-03 10:33:53 +00:00
d8e1cdc72c Merge remote-tracking branch 'origin/develop' into develop
All checks were successful
Auto Tag Develop / tag (push) Successful in 5s
Build Release Artefact / build (push) Successful in 1m13s
# Conflicts:
#	.idea/workspace.xml
2026-02-03 09:30:13 +01:00
2c54a8c950 feat : ajout du bundle symfony make 2026-02-03 09:29:35 +01:00
40 changed files with 1445 additions and 119 deletions

6
.idea/copilot.data.migration.agent.xml generated Normal file
View File

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

6
.idea/copilot.data.migration.ask.xml generated Normal file
View File

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

View File

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

6
.idea/copilot.data.migration.edit.xml generated Normal file
View File

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

2
.idea/dataSources.xml generated
View File

@@ -5,7 +5,7 @@
<driver-ref>postgresql</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.postgresql.Driver</jdbc-driver>
<jdbc-url>jdbc:postgresql://localhost:5433/ferme</jdbc-url>
<jdbc-url>jdbc:postgresql://localhost:5432/ferme</jdbc-url>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
<data-source source="LOCAL" name="Ferme recette" uuid="ae622167-c834-4e7b-87a5-c1721036f5dc">

View File

@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourcePerFileMappings">
<file url="file://$APPLICATION_CONFIG_DIR$/consoles/db/ae622167-c834-4e7b-87a5-c1721036f5dc/console.sql" value="ae622167-c834-4e7b-87a5-c1721036f5dc" />
<file url="file://$APPLICATION_CONFIG_DIR$/consoles/db/f407a514-c6b4-4b26-9555-445a85892502/console.sql" value="f407a514-c6b4-4b26-9555-445a85892502" />
</component>
</project>

1
.idea/ferme.iml generated
View File

@@ -154,6 +154,7 @@
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/web-profiler-bundle" />
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/data-fixtures" />
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/doctrine-fixtures-bundle" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/maker-bundle" />
<excludePattern pattern="reference.php" />
</content>
<orderEntry type="inheritedJdk" />

View File

@@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="PhpCSFixerValidationInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
</profile>
</component>

23
.idea/php.xml generated
View File

@@ -4,12 +4,24 @@
<option name="transferred" value="true" />
</component>
<component name="PHPCSFixerOptionsConfiguration">
<option name="codingStandard" value="Custom" />
<option name="rulesetPath" value="$PROJECT_DIR$/.php-cs-fixer.dist.php" />
<option name="transferred" value="true" />
</component>
<component name="PHPCodeSnifferOptionsConfiguration">
<option name="highlightLevel" value="WARNING" />
<option name="transferred" value="true" />
</component>
<component name="PhpCSFixer">
<phpcsfixer_settings>
<PhpCSFixerConfiguration tool_path="$PROJECT_DIR$/vendor/bin/php-cs-fixer" />
</phpcsfixer_settings>
</component>
<component name="PhpCodeSniffer">
<phpcs_settings>
<phpcs_by_interpreter asDefaultInterpreter="true" interpreter_id="8475dcce-5d1d-4a2c-9e2f-7454868f1931" timeout="30000" />
</phpcs_settings>
</component>
<component name="PhpIncludePathManager">
<include_path>
<path value="$PROJECT_DIR$/vendor/psr/log" />
@@ -160,9 +172,15 @@
<path value="$PROJECT_DIR$/vendor/malio/ednotif-bundle" />
<path value="$PROJECT_DIR$/vendor/doctrine/doctrine-fixtures-bundle" />
<path value="$PROJECT_DIR$/vendor/doctrine/data-fixtures" />
<path value="$PROJECT_DIR$/vendor/symfony/maker-bundle" />
</include_path>
</component>
<component name="PhpProjectSharedConfiguration" php_language_level="8.4" />
<component name="PhpStan">
<PhpStan_settings>
<phpstan_by_interpreter asDefaultInterpreter="true" interpreter_id="8475dcce-5d1d-4a2c-9e2f-7454868f1931" timeout="60000" />
</PhpStan_settings>
</component>
<component name="PhpStanOptionsConfiguration">
<option name="transferred" value="true" />
</component>
@@ -171,6 +189,11 @@
<PhpUnitSettings custom_loader_path="$PROJECT_DIR$/vendor/autoload.php" />
</phpunit_settings>
</component>
<component name="Psalm">
<Psalm_settings>
<psalm_fixer_by_interpreter asDefaultInterpreter="true" interpreter_id="8475dcce-5d1d-4a2c-9e2f-7454868f1931" timeout="60000" />
</Psalm_settings>
</component>
<component name="PsalmOptionsConfiguration">
<option name="transferred" value="true" />
</component>

87
.idea/workspace.xml generated
View File

@@ -4,14 +4,15 @@
<option name="autoReloadType" value="SELECTIVE" />
</component>
<component name="ChangeListManager">
<list default="true" id="7c107abe-5995-4428-8429-b146aaca8386" name="Changes" comment="feat : mise à jour du bon de réception">
<list default="true" id="7c107abe-5995-4428-8429-b146aaca8386" name="Changes" comment="feat : Ajout de la sélection des bovins étape 3 d'une réception (WIP)">
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/config/reference.php" beforeDir="false" afterPath="$PROJECT_DIR$/config/reference.php" afterDir="false" />
<change beforePath="$PROJECT_DIR$/frontend/components/reception/reception-bovine-received.vue" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/components/reception/reception-bovine-received.vue" afterDir="false" />
<change beforePath="$PROJECT_DIR$/frontend/components/reception/reception-form.vue" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/components/reception/reception-form.vue" afterDir="false" />
<change beforePath="$PROJECT_DIR$/frontend/components/reception/reception-product-received.vue" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/components/reception/reception-product-received.vue" afterDir="false" />
<change beforePath="$PROJECT_DIR$/frontend/components/reception/reception-weight.vue" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/components/reception/reception-weight.vue" afterDir="false" />
<change beforePath="$PROJECT_DIR$/frontend/composables/usePdfPrinter.ts" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/composables/usePdfPrinter.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/Command/SeedCommand.php" beforeDir="false" afterPath="$PROJECT_DIR$/src/Command/SeedCommand.php" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/DataFixtures/ReferenceFixtures.php" beforeDir="false" afterPath="$PROJECT_DIR$/src/DataFixtures/ReferenceFixtures.php" afterDir="false" />
<change beforePath="$PROJECT_DIR$/frontend/services/dto/reception-data.ts" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/services/dto/reception-data.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/Entity/Reception.php" beforeDir="false" afterPath="$PROJECT_DIR$/src/Entity/Reception.php" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/Entity/ReceptionBovine.php" beforeDir="false" afterPath="$PROJECT_DIR$/src/Entity/ReceptionBovine.php" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
@@ -22,6 +23,11 @@
<pharConfigPath>$PROJECT_DIR$/composer.json</pharConfigPath>
<execution />
</component>
<component name="CopilotPersistence">
<persistenceIdMap>
<entry key="_//wsl.localhost/Ubuntu-24.04/home/kevin/Stage/Ferme" value="381AhnCm9yPeOiWgMObKHhtgv2C" />
</persistenceIdMap>
</component>
<component name="EmbeddingIndexingInfo">
<option name="cachedIndexableFilesCount" value="151" />
<option name="fileBasedEmbeddingIndicesEnabled" value="true" />
@@ -29,15 +35,15 @@
<component name="FileTemplateManagerImpl">
<option name="RECENT_TEMPLATES">
<list>
<option value="TypeScript File" />
<option value="Vue Composition API Component" />
<option value="TypeScript File" />
</list>
</option>
</component>
<component name="Git.Settings">
<option name="RECENT_BRANCH_BY_REPOSITORY">
<map>
<entry key="$PROJECT_DIR$" value="feat/poc-identification-bovin" />
<entry key="$PROJECT_DIR$" value="fix/makefile" />
</map>
</option>
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
@@ -55,7 +61,7 @@
</server>
</servers>
</component>
<component name="PhpWorkspaceProjectConfiguration">
<component name="PhpWorkspaceProjectConfiguration" interpreter_name="C:/php-8.4.3/php.exe">
<include_path>
<path value="$PROJECT_DIR$/vendor/psr/log" />
<path value="$PROJECT_DIR$/vendor/psr/event-dispatcher" />
@@ -205,6 +211,7 @@
<path value="$PROJECT_DIR$/vendor/malio/ednotif-bundle" />
<path value="$PROJECT_DIR$/vendor/doctrine/doctrine-fixtures-bundle" />
<path value="$PROJECT_DIR$/vendor/doctrine/data-fixtures" />
<path value="$PROJECT_DIR$/vendor/symfony/maker-bundle" />
</include_path>
</component>
<component name="ProjectColorInfo">{
@@ -224,13 +231,15 @@
&quot;RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252&quot;: &quot;true&quot;,
&quot;RunOnceActivity.git.unshallow&quot;: &quot;true&quot;,
&quot;RunOnceActivity.typescript.service.memoryLimit.init&quot;: &quot;true&quot;,
&quot;git-widget-placeholder&quot;: &quot;develop&quot;,
&quot;git-widget-placeholder&quot;: &quot;feat/256-reception-etape-3-bovin&quot;,
&quot;last_opened_file_path&quot;: &quot;/home/sroy/Documents/test/Ferme&quot;,
&quot;node.js.detected.package.eslint&quot;: &quot;true&quot;,
&quot;node.js.detected.package.tslint&quot;: &quot;true&quot;,
&quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;,
&quot;node.js.selected.package.tslint&quot;: &quot;(autodetect)&quot;,
&quot;nodejs_package_manager_path&quot;: &quot;npm&quot;,
&quot;settings.editor.selected.configurable&quot;: &quot;preferences.pluginManager&quot;,
&quot;settings.editor.selected.configurable&quot;: &quot;configurable.tailwindcss&quot;,
&quot;ts.external.directory.path&quot;: &quot;/opt/phpstorm/plugins/javascript-plugin/jsLanguageServicesImpl/external&quot;,
&quot;vue.rearranger.settings.migration&quot;: &quot;true&quot;
},
&quot;keyToStringList&quot;: {
@@ -282,6 +291,15 @@
<workItem from="1769756623432" duration="21592000" />
<workItem from="1770015653091" duration="73000" />
<workItem from="1770040138216" duration="6492000" />
<workItem from="1770050834470" duration="1873000" />
<workItem from="1770054381680" duration="1292000" />
<workItem from="1770055690365" duration="370000" />
<workItem from="1770056515646" duration="21000" />
<workItem from="1770102495553" duration="2280000" />
<workItem from="1770195604082" duration="90000" />
<workItem from="1770195718952" duration="215000" />
<workItem from="1770195959162" duration="18915000" />
<workItem from="1770274844804" duration="3940000" />
</task>
<task id="LOCAL-00001" summary="feat : Ajout de pinia, création de la table weight et reception mise en place du système de step pour les receptions (WIP)">
<option name="closed" value="true" />
@@ -651,7 +669,31 @@
<option name="project" value="LOCAL" />
<updated>1769782099473</updated>
</task>
<option name="localTasksCounter" value="47" />
<task id="LOCAL-00047" summary="feat : Ajout de la sélection des bovins étape 3 d'une réception (WIP)">
<option name="closed" value="true" />
<created>1770131226364</created>
<option name="number" value="00047" />
<option name="presentableId" value="LOCAL-00047" />
<option name="project" value="LOCAL" />
<updated>1770131226364</updated>
</task>
<task id="LOCAL-00048" summary="feat : Ajout de la sélection des bovins étape 3 d'une réception (WIP)">
<option name="closed" value="true" />
<created>1770206668867</created>
<option name="number" value="00048" />
<option name="presentableId" value="LOCAL-00048" />
<option name="project" value="LOCAL" />
<updated>1770206668867</updated>
</task>
<task id="LOCAL-00049" summary="feat : Ajout de la sélection des bovins étape 3 d'une réception (WIP)">
<option name="closed" value="true" />
<created>1770217875423</created>
<option name="number" value="00049" />
<option name="presentableId" value="LOCAL-00049" />
<option name="project" value="LOCAL" />
<updated>1770217875423</updated>
</task>
<option name="localTasksCounter" value="50" />
<servers />
</component>
<component name="TypeScriptGeneratedFilesManager">
@@ -701,7 +743,6 @@
</option>
</component>
<component name="VcsManagerConfiguration">
<MESSAGE value="ci : ajout du script et de la doc déploiement" />
<MESSAGE value="fix : correction du path URI pour la création d'un poids dans une réception" />
<MESSAGE value="feat : Ajout du bundle Monolog pour la gestion des logs" />
<MESSAGE value="fix : affiche plus détail dans les logs en recette/prod" />
@@ -726,10 +767,30 @@
<MESSAGE value="feat : ajout de colonne pour les Supplier, Address et modification du numéro de réception" />
<MESSAGE value="feat : ajout de colonne pour les Supplier, Address. Modification du numéro de réception et ajout de fixtures" />
<MESSAGE value="feat : mise à jour du bon de réception" />
<option name="LAST_COMMIT_MESSAGE" value="feat : mise à jour du bon de réception" />
<MESSAGE value="feat : Ajout de la sélection des bovins étape 3 d'une réception (WIP)" />
<option name="LAST_COMMIT_MESSAGE" value="feat : Ajout de la sélection des bovins étape 3 d'une réception (WIP)" />
</component>
<component name="XDebuggerManager">
<breakpoint-manager>
<breakpoints>
<line-breakpoint enabled="true" type="php">
<url>file://$PROJECT_DIR$/src/Entity/ReceptionPelletBuilding.php</url>
<line>6</line>
<option name="timeStamp" value="3" />
</line-breakpoint>
</breakpoints>
</breakpoint-manager>
</component>
<component name="XSLT-Support.FileAssociations.UIState">
<expand />
<select />
</component>
<component name="github-copilot-workspace">
<instructionFileLocations>
<option value=".github/instructions" />
</instructionFileLocations>
<promptFileLocations>
<option value=".github/prompts" />
</promptFileLocations>
</component>
</project>

View File

@@ -93,6 +93,7 @@
"phpunit/phpunit": "^12.5",
"symfony/browser-kit": "8.0.*",
"symfony/css-selector": "8.0.*",
"symfony/maker-bundle": "^1.65",
"symfony/stopwatch": "8.0.*",
"symfony/web-profiler-bundle": "8.0.*"
},

100
composer.lock generated
View File

@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "9c4e168c0540baf5d7d5d54040834d79",
"content-hash": "9c04091eea0e10c19713a1d882b04f91",
"packages": [
{
"name": "api-platform/doctrine-common",
@@ -11357,6 +11357,104 @@
],
"time": "2025-12-06T17:00:47+00:00"
},
{
"name": "symfony/maker-bundle",
"version": "v1.65.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/maker-bundle.git",
"reference": "eba30452d212769c9a5bcf0716959fd8ba1e54e3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/maker-bundle/zipball/eba30452d212769c9a5bcf0716959fd8ba1e54e3",
"reference": "eba30452d212769c9a5bcf0716959fd8ba1e54e3",
"shasum": ""
},
"require": {
"doctrine/inflector": "^2.0",
"nikic/php-parser": "^5.0",
"php": ">=8.1",
"symfony/config": "^6.4|^7.0|^8.0",
"symfony/console": "^6.4|^7.0|^8.0",
"symfony/dependency-injection": "^6.4|^7.0|^8.0",
"symfony/deprecation-contracts": "^2.2|^3",
"symfony/filesystem": "^6.4|^7.0|^8.0",
"symfony/finder": "^6.4|^7.0|^8.0",
"symfony/framework-bundle": "^6.4|^7.0|^8.0",
"symfony/http-kernel": "^6.4|^7.0|^8.0",
"symfony/process": "^6.4|^7.0|^8.0"
},
"conflict": {
"doctrine/doctrine-bundle": "<2.10",
"doctrine/orm": "<2.15"
},
"require-dev": {
"composer/semver": "^3.0",
"doctrine/doctrine-bundle": "^2.5.0|^3.0.0",
"doctrine/orm": "^2.15|^3",
"doctrine/persistence": "^3.1|^4.0",
"symfony/http-client": "^6.4|^7.0|^8.0",
"symfony/phpunit-bridge": "^6.4.1|^7.0|^8.0",
"symfony/security-core": "^6.4|^7.0|^8.0",
"symfony/security-http": "^6.4|^7.0|^8.0",
"symfony/yaml": "^6.4|^7.0|^8.0",
"twig/twig": "^3.0|^4.x-dev"
},
"type": "symfony-bundle",
"extra": {
"branch-alias": {
"dev-main": "1.x-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Bundle\\MakerBundle\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony Maker helps you create empty commands, controllers, form classes, tests and more so you can forget about writing boilerplate code.",
"homepage": "https://symfony.com/doc/current/bundles/SymfonyMakerBundle/index.html",
"keywords": [
"code generator",
"dev",
"generator",
"scaffold",
"scaffolding"
],
"support": {
"issues": "https://github.com/symfony/maker-bundle/issues",
"source": "https://github.com/symfony/maker-bundle/tree/v1.65.1"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://github.com/nicolas-grekas",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2025-12-02T07:14:37+00:00"
},
{
"name": "symfony/process",
"version": "v8.0.4",

View File

@@ -10,6 +10,7 @@ use Lexik\Bundle\JWTAuthenticationBundle\LexikJWTAuthenticationBundle;
use Malio\EdnotifBundle\EdnotifBundle;
use Nelmio\CorsBundle\NelmioCorsBundle;
use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
use Symfony\Bundle\MakerBundle\MakerBundle;
use Symfony\Bundle\MonologBundle\MonologBundle;
use Symfony\Bundle\SecurityBundle\SecurityBundle;
use Symfony\Bundle\TwigBundle\TwigBundle;
@@ -28,4 +29,5 @@ return [
EdnotifBundle::class => ['all' => true],
WebProfilerBundle::class => ['dev' => true],
DoctrineFixturesBundle::class => ['dev' => true, 'test' => true],
MakerBundle::class => ['dev' => true],
];

View File

@@ -1503,7 +1503,7 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
* validation_error_resource_class?: scalar|Param|null, // The class used to represent validation errors in the OpenAPI documentation. // Default: null
* },
* maker?: bool|array{
* enabled?: bool|Param, // Default: false
* enabled?: bool|Param, // Default: true
* },
* exception_to_status?: array<string, int|Param>,
* formats?: array<string, array{ // Default: {"jsonld":{"mime_types":["application/ld+json"]}}
@@ -1778,6 +1778,11 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
* intercept_redirects?: bool|Param, // Default: false
* excluded_ajax_paths?: scalar|Param|null, // Default: "^/((index|app(_[\\w]+)?)\\.php/)?_wdt"
* }
* @psalm-type MakerConfig = array{
* root_namespace?: scalar|Param|null, // Default: "App"
* generate_final_classes?: bool|Param, // Default: true
* generate_final_entities?: bool|Param, // Default: false
* }
* @psalm-type ConfigType = array{
* imports?: ImportsConfig,
* parameters?: ParametersConfig,
@@ -1807,6 +1812,7 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
* monolog?: MonologConfig,
* ednotif?: EdnotifConfig,
* web_profiler?: WebProfilerConfig,
* maker?: MakerConfig,
* },
* "when@prod"?: array{
* imports?: ImportsConfig,

View File

@@ -7,3 +7,5 @@ POSTGRES_USER=root
POSTGRES_PASSWORD=root
POSTGRES_PORT=5432
XDEBUG_CLIENT_HOST=host.docker.internal
CURRENT_UID=1004
CURRENT_GID=1004

View File

@@ -0,0 +1,203 @@
<template>
<div
v-if="receptionStore.current?.receptionType?.code === RECEPTION_TYPE_CODES.BOVINS"
class="flex flex-col items-center gap-16">
<h1 class="text-4xl uppercase font-bold">Sélection des marchandises réceptionnnées</h1>
<div
class="flex flex-row gap-8 items-center">
<div
v-for="type in bovineType"
:key="type.id"
class="mt-8 flex flex-row mb-2 gap-6">
<UiNumberInput
:label="type.label"
:code="type.code"
v-model="bovineQuantities[String(type.id)]"
:placeholder="0"
:min="0"
:max="10"
/>
</div>
<div
class="mt-8 flex flex-row mb-2 gap-6">
<UiNumberInput
label="Autres"
v-model="otherQuantity"
/>
</div>
</div>
<button
class="text-xl uppercase bg-primary-500 text-white h-[50px] w-[272px]"
@click="goNext"
>Peser
</button>
</div>
</template>
<script setup lang="ts">
import type {BovineTypeData} from "~/services/dto/bovine-type-data";
import {getBovineTypeList} from "~/services/bovine-type";
import {MERCHANDISE_TYPE_CODES, RECEPTION_TYPE_CODES} from "~/utils/constants";
import {useReceptionStore} from '~/stores/reception'
import {
createReceptionBovine,
deleteReceptionBovine,
getReceptionBovineList,
updateReceptionBovine
} from "~/services/reception-bovine";
import {computed, onMounted, reactive, ref, watch} from "vue";
const isLoadingBovineType = ref(false)
const bovineType = ref<BovineTypeData[]>([])
const receptionStore = useReceptionStore()
const bovineQuantities = reactive<Record<string, number | null>>({})
const otherQuantity = ref<number | null>(0)
const receptionId = computed(() => receptionStore.current?.id ?? null)
const receptionIri = computed(() =>
receptionId.value ? `/api/receptions/${receptionId.value}` : null
)
const toast = useToast()
const nuxtApp = useNuxtApp()
const i18n = nuxtApp.$i18n as { t: (key: string) => string } |
undefined
const t = (key: string) => (i18n?.t ? String(i18n.t(key)) : key)
const totalBovineQuantity = computed(() => {
const baseTotal = Object.values(bovineQuantities).reduce((sum, value) => {
const n = typeof value === 'number' ? value : 0
return sum + n
}, 0)
const other = typeof otherQuantity.value === 'number' ? otherQuantity.value : 0
return baseTotal + other
})
const loadBovineType = async () => {
isLoadingBovineType.value = true
try {
bovineType.value = await getBovineTypeList()
} finally {
isLoadingBovineType.value = false
}
}
onMounted(async () => {
await loadBovineType()
})
watch(
() => receptionId.value,
async (id) => {
if (!id || !receptionIri.value) {
return
}
const selectionMap: Record<string, number | null> = {}
for (const type of bovineType.value) {
selectionMap[String(type.id)] = 0
}
const existing = await getReceptionBovineList(receptionIri.value)
for (const selection of existing) {
const bovineTypeId = String(selection.bovineType.id)
selectionMap[bovineTypeId] = selection.quantity ?? 0
}
for (const key of Object.keys(bovineQuantities)) {
delete bovineQuantities[key]
}
Object.assign(bovineQuantities, selectionMap)
const existingOther = receptionStore.current?.bovineDetail
const parsedOther =
typeof existingOther === 'string' && existingOther.trim() !== ''
? Number(existingOther)
: 0
otherQuantity.value = Number.isFinite(parsedOther) ? parsedOther : 0
},
{immediate: true}
)
async function syncBovineSelections(receptionIri: string) {
const existing = await getReceptionBovineList(receptionIri)
const existingMap = new Map<string, { id: number; quantity: number | null }>()
for (const selection of existing) {
const bovineTypeId = String(selection.bovineType.id)
existingMap.set(bovineTypeId, {
id: selection.id,
quantity: selection.quantity ?? 0
})
}
// Supprime les entrées supprimées ou modifiées
for (const [bovineTypeId, entry] of existingMap.entries()) {
const selectedQuantity = bovineQuantities[bovineTypeId] ?? 0
if (!selectedQuantity) {
await deleteReceptionBovine(entry.id)
existingMap.delete(bovineTypeId)
continue
}
if (selectedQuantity !== entry.quantity) {
await updateReceptionBovine(entry.id, {quantity: selectedQuantity})
existingMap.set(bovineTypeId, {
id: entry.id,
quantity: selectedQuantity
})
}
}
// Crée les entrées manquantes
for (const [bovineTypeId, quantity] of Object.entries(bovineQuantities)) {
if (!quantity) {
continue
}
if (existingMap.has(bovineTypeId)) {
// Déjà à jour
continue
}
await createReceptionBovine({
reception: receptionIri,
bovineType: `/api/bovine_types/${bovineTypeId}`,
quantity
})
}
}
const hasNegativeQuantity = computed(() => {
const anyNegativeInTypes =
Object.values(bovineQuantities).some((value) => {
return typeof value === 'number' && value < 0
})
const otherNegative =
typeof otherQuantity.value === 'number' &&
otherQuantity.value < 0
return anyNegativeInTypes || otherNegative
})
async function goNext() {
if (!receptionStore.current || !receptionIri.value) {
return
}
if (hasNegativeQuantity.value) {
toast.error({
title: 'Erreur',
message: ("La quantité de bovins ne peut pas être négative.")
})
return
}
// Le 52 à vérifier
if (totalBovineQuantity.value > 52) {
toast.error({
title: 'Erreur',
message: ('Le total des bovins ne peut pas dépasser 52.')
})
return
}
const nextStep = receptionStore.current.currentStep + 1
await syncBovineSelections(receptionIri.value)
await receptionStore.updateReception(receptionStore.current.id, {
merchandiseType: null,
merchandiseDetail: null,
bovineDetail: otherQuantity.value ? String(otherQuantity.value) : null,
currentStep: nextStep
})
}
</script>

View File

@@ -142,7 +142,8 @@ import type {DriverData} from '~/services/dto/driver-data'
import {getDriverList} from '~/services/driver'
import type {VehicleData} from '~/services/dto/vehicle-data'
import {getVehicleList} from '~/services/vehicle'
import {SUPLLIER_CODE} from "~/utils/constants";
import {RECEPTION_TYPE_CODES, SUPLLIER_CODE} from "~/utils/constants";
import {deleteReceptionBovine, getReceptionBovineList} from "~/services/reception-bovine";
type ReceptionFormData = {
licensePlate: string
@@ -221,6 +222,16 @@ const filteredVehicles = computed<VehicleData[]>(() => {
(!form.truckId || String(vehicle.truck?.id) === form.truckId)
)
})
const selectedReceptionType = computed(() =>
receptionTypes.value.find((type) => String(type.id) === form.receptionTypeId) ?? null
)
const clearReceptionBovines = async (receptionIri: string) => {
const existing = await getReceptionBovineList(receptionIri)
for (const selection of existing) {
await deleteReceptionBovine(selection.id)
}
}
// Hydrate le formulaire depuis la réception en cours
watch(
@@ -460,6 +471,9 @@ async function validate() {
const normalizedTruckId = form.truckId.trim()
const normalizedCarrierId = form.carrierId.trim()
const normalizedDriverId = form.driverId.trim()
const previousTypeCode = receptionStore.current.receptionType?.code ?? null
const nextTypeCode = selectedReceptionType.value?.code ?? null
const receptionIri = `/api/receptions/${receptionStore.current.id}`
const receptionTypeIri = normalizedReceptionTypeId
? `/api/reception_types/${normalizedReceptionTypeId}`
: null
@@ -508,7 +522,12 @@ async function validate() {
}
return
}
if (
previousTypeCode === RECEPTION_TYPE_CODES.BOVINS &&
nextTypeCode === RECEPTION_TYPE_CODES.MERCHANDISES
) {
await clearReceptionBovines(receptionIri)
}
const nextStep = receptionStore.current.currentStep + 1
await receptionStore.updateReception(receptionStore.current.id, {
currentStep: nextStep,

View File

@@ -1,10 +1,10 @@
<template>
<div class="flex flex-col items-center gap-16">
<!-- @TODO voir pour séparer dans un composant au moment de l'implémentation des Bovins -->
<!-- @TODO voir pour séparer dans un composant au moment de l'implémentation des Bovins -->
<div
v-if="receptionStore.current?.receptionType?.code === RECEPTION_TYPE_CODES.MERCHANDISES"
class="flex flex-col gap-16 items-center w-full">
<h1 class="text-4xl uppercase font-bold">Sélectionner des marchandises réceptionnnées</h1>
<h1 class="text-4xl uppercase font-bold">Sélection des marchandises réceptionnnées</h1>
<UiSelect
id="merchandise-type"
v-model="selectedMerchandiseTypeId"
@@ -12,7 +12,6 @@
:options="merchandiseTypes.map((type) => ({ value: String(type.id), label: type.label }))"
wrapper-class="w-[550px]"
/>
<div
v-if="selectedMerchandiseTypeId && isAutres"
class="flex flex-col w-full max-w-[550px]"
@@ -69,25 +68,27 @@
<button
class="text-xl uppercase bg-primary-500 text-white h-[50px] w-[272px]"
@click="goNext"
>Peser</button>
>Peser
</button>
</div>
</template>
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue'
import { getBuildingList } from '~/services/building'
import { getMerchandiseTypeList } from '~/services/merchandise-type'
import type { MerchandiseTypeData } from '~/services/dto/merchandise-type-data'
import type { BuildingData } from '~/services/dto/building-data'
import type { PelletTypeData } from '~/services/dto/pellet-type-data'
import { getPelletTypeList } from '~/services/pellet-type'
import {computed, onMounted, ref} from 'vue'
import {getBuildingList} from '~/services/building'
import {getMerchandiseTypeList} from '~/services/merchandise-type'
import type {MerchandiseTypeData} from '~/services/dto/merchandise-type-data'
import type {BuildingData} from '~/services/dto/building-data'
import type {PelletTypeData} from '~/services/dto/pellet-type-data'
import {getPelletTypeList} from '~/services/pellet-type'
import {
createReceptionPelletBuilding,
deleteReceptionPelletBuilding,
getReceptionPelletBuildingList
} from '~/services/reception-pellet-building'
import { useReceptionStore } from '~/stores/reception'
import { MERCHANDISE_TYPE_CODES, RECEPTION_TYPE_CODES } from '~/utils/constants'
import {useReceptionStore} from '~/stores/reception'
import {MERCHANDISE_TYPE_CODES, RECEPTION_TYPE_CODES} from '~/utils/constants'
import ReceptionBovineReceived from "~/components/reception/reception-bovine-received.vue";
const receptionStore = useReceptionStore()
const merchandiseTypes = ref<MerchandiseTypeData[]>([])
@@ -98,6 +99,7 @@ const selectedBuildingIds = ref<string[]>([])
const selectedPelletBuildingIds = ref<Record<string, string[]>>({})
const merchandiseDetail = ref('')
// Extrait l'ID d'une relation depuis un IRI ou un objet complet.
const getRelationId = (value: unknown): string | null => {
if (!value) {
@@ -173,7 +175,6 @@ onMounted(async () => {
}
selectedPelletBuildingIds.value = selectionMap
})
// Enregistre les sélections et passe à l'étape suivante
async function goNext() {
if (!receptionStore.current) {
@@ -191,6 +192,8 @@ async function goNext() {
buildings: isGranule.value
? []
: selectedBuildingIds.value.map((id) => `/api/buildings/${id}`),
bovineDetail: null,
bovinesTypes: null,
currentStep: nextStep
})
@@ -208,7 +211,6 @@ async function clearPelletSelections(receptionIri: string) {
await deleteReceptionPelletBuilding(selection.id)
}
}
// Synchronise les associations granulés/bâtiments avec l'état du formulaire
async function syncPelletSelections(receptionIri: string) {
const existing = await getReceptionPelletBuildingList(receptionIri)
@@ -227,7 +229,7 @@ async function syncPelletSelections(receptionIri: string) {
const desiredEntries: Array<{ pelletTypeId: string; buildingId: string }> = []
for (const [pelletTypeId, buildingIds] of Object.entries(selectedPelletBuildingIds.value)) {
for (const buildingId of buildingIds) {
desiredEntries.push({ pelletTypeId, buildingId })
desiredEntries.push({pelletTypeId, buildingId})
}
}

View File

@@ -0,0 +1,84 @@
<template>
<div :class="['flex flex-row items-center gap-2', wrapperClass]">
<label
v-if="label"
:for="id"
class="text-xl text-bold flex items-center gap-2"
:class="labelClass"
>
<span
v-if="label">
{{ label }}
</span>
<span
v-if="code" class="text-neutral-600">
({{ code }})
</span>
</label>
<input
:id="id"
type="number"
:value="modelValue ?? ''"
:min="min"
:max="max"
:step="step"
:disabled="disabled"
v-bind="attrs"
class="border-b border-black text-xl bg-transparent w-48"
:class="[
isEmpty ? 'text-neutral-400' : 'text-black',
disabled ? 'cursor-not-allowed' : 'cursor-text',
inputClass
]"
@input="onInput"
>
</div>
</template>
<script setup lang="ts">
import {computed, useAttrs} from 'vue'
defineOptions({inheritAttrs: false})
const props = withDefaults(
defineProps<{
id?: string
label?: string
code?: string
modelValue: number | string | null | undefined
min?: number | string
max?: number | string
step?: number | string
disabled?: boolean
wrapperClass?: string
labelClass?: string
inputClass?: string
}>(),
{
min: undefined,
max: undefined,
step: undefined,
disabled: false,
wrapperClass: '',
labelClass: '',
inputClass: ''
}
)
const emit = defineEmits<{
(event: 'update:modelValue', value: number | null): void
}>()
const attrs = useAttrs()
const isEmpty = computed(() => props.modelValue === null || props.modelValue === undefined || props.modelValue === '')
const onInput = (event: Event) => {
const target = event.target as HTMLInputElement
if (target.value === '') {
emit('update:modelValue', null)
return
}
const numeric = Number(target.value)
emit('update:modelValue', Number.isNaN(numeric) ? null : numeric)
}
</script>

View File

@@ -1,64 +1,72 @@
{
"errors": {
"http": {
"get": "Impossible de récupérer les données.",
"post": "Impossible de créer la ressource.",
"put": "Impossible de mettre à jour la ressource.",
"patch": "Impossible de mettre à jour la ressource.",
"delete": "Impossible de supprimer la ressource."
"errors": {
"http": {
"get": "Impossible de récupérer les données.",
"post": "Impossible de créer la ressource.",
"put": "Impossible de mettre à jour la ressource.",
"patch": "Impossible de mettre à jour la ressource.",
"delete": "Impossible de supprimer la ressource."
},
"reception": {
"list": "Impossible de récupérer la liste des réceptions.",
"fetch": "Impossible de récupérer la réception.",
"create": "Impossible de créer la réception.",
"update": "Impossible de mettre à jour la réception.",
"weigh": "Impossible de récupérer la pesée."
},
"receptionType": {
"list": "Impossible de récupérer la liste des types de réception."
},
"merchandiseType": {
"list": "Impossible de récupérer la liste des types de marchandises."
},
"building": {
"list": "Impossible de récupérer la liste des bâtiments."
},
"pelletType": {
"list": "Impossible de récupérer la liste des types de granulés."
},
"receptionPelletBuilding": {
"list": "Impossible de récupérer la liste des dépôts de granulés.",
"create": "Impossible d'enregistrer le dépôt de granulés.",
"delete": "Impossible de supprimer le dépôt de granulés."
},
"receptionBovine": {
"list": "Impossible de récupérer la liste des bovins de la réception.",
"create": "Impossible d'enregistrer le bovin.",
"delete": "Impossible de supprimer le bovin."
},
"supplier": {
"list": "Impossible de récupérer la liste des fournisseurs."
},
"truck": {
"list": "Impossible de récupérer la liste des camions."
},
"bovin": {
"list": "Impossible de récupérer la liste des races de bovins."
},
"carrier": {
"list": "Impossible de récupérer la liste des transporteurs."
},
"driver": {
"list": "Impossible de récupérer la liste des chauffeurs."
},
"vehicle": {
"list": "Impossible de récupérer la liste des immatriculations."
},
"auth": {
"login": "Identifiants invalides.",
"users": "Impossible de récupérer les utilisateurs.",
"logout": "Impossible de se déconnecter."
}
},
"reception": {
"list": "Impossible de récupérer la liste des réceptions.",
"fetch": "Impossible de récupérer la réception.",
"create": "Impossible de créer la réception.",
"update": "Impossible de mettre à jour la réception.",
"weigh": "Impossible de récupérer la pesée."
},
"receptionType": {
"list": "Impossible de récupérer la liste des types de réception."
},
"merchandiseType": {
"list": "Impossible de récupérer la liste des types de marchandises."
},
"building": {
"list": "Impossible de récupérer la liste des bâtiments."
},
"pelletType": {
"list": "Impossible de récupérer la liste des types de granulés."
},
"receptionPelletBuilding": {
"list": "Impossible de récupérer la liste des dépôts de granulés.",
"create": "Impossible d'enregistrer le dépôt de granulés.",
"delete": "Impossible de supprimer le dépôt de granulés."
},
"supplier": {
"list": "Impossible de récupérer la liste des fournisseurs."
},
"truck": {
"list": "Impossible de récupérer la liste des camions."
},
"carrier": {
"list": "Impossible de récupérer la liste des transporteurs."
},
"driver": {
"list": "Impossible de récupérer la liste des chauffeurs."
},
"vehicle": {
"list": "Impossible de récupérer la liste des immatriculations."
},
"auth": {
"login": "Identifiants invalides.",
"users": "Impossible de récupérer les utilisateurs.",
"logout": "Impossible de se déconnecter."
"success": {
"reception": {
"update": "Réception mise à jour avec succès."
},
"auth": {
"login": "Connexion réussie.",
"logout": "Déconnexion réussie."
}
}
},
"success": {
"reception": {
"update": "Réception mise à jour avec succès."
},
"auth": {
"login": "Connexion réussie.",
"logout": "Déconnexion réussie."
}
}
}

View File

@@ -16,7 +16,12 @@
</div>
<ReceptionForm v-if="!storeReception || storeReception.currentStep === 0"/>
<ReceptionWeight v-if="storeReception?.currentStep === 1" mode="gross"/>
<ReceptionProductReceived v-if="storeReception?.currentStep === 2"/>
<ReceptionProductReceived
v-if="storeReception?.currentStep === 2 &&
receptionStore.current?.receptionType?.code === RECEPTION_TYPE_CODES.MERCHANDISES"/>
<ReceptionBovineReceived
v-if="storeReception?.currentStep === 2 &&
receptionStore.current?.receptionType?.code === RECEPTION_TYPE_CODES.BOVINS"/>
<ReceptionWeight v-if="storeReception?.currentStep !== null && storeReception?.currentStep >= 3" mode="tare"/>
</div>
</template>
@@ -25,6 +30,7 @@
import {useReceptionStore} from '~/stores/reception'
import {storeToRefs} from 'pinia'
import {RECEPTION_STEP_LABELS} from '~/constants/steps'
import {RECEPTION_TYPE_CODES} from "~/utils/constants";
const route = useRoute()
const router = useRouter()

View File

@@ -0,0 +1,23 @@
import { useApi } from '~/composables/useApi'
import type {BovineTypeData} from "~/services/dto/bovine-type-data";
export type BovineTypeListResponse =
| BovineTypeData[]
| { 'hydra:member'?: BovineTypeData[] }
export async function getBovineTypeList(): Promise<BovineTypeData[]> {
const api = useApi()
const response = await api.get<BovineTypeListResponse>('bovine_types', {}, {
toastErrorKey: 'errors.bovin.list'
})
if (Array.isArray(response)) {
return response
}
if (response && typeof response === 'object' && Array.isArray(response['hydra:member'])) {
return response['hydra:member']
}
return []
}

View File

@@ -0,0 +1,5 @@
export interface BovineTypeData{
id: number
label: string
code: string
}

View File

@@ -0,0 +1,8 @@
import type {BovineTypeData} from "~/services/dto/bovine-type-data";
export interface ReceptionBovineTypeData{
id: number
quantity : number
reception?: string
bovineType: BovineTypeData
}

View File

@@ -8,6 +8,7 @@ import type { AddressData } from '~/services/dto/address-data'
import type { TruckData } from '~/services/dto/truck-data'
import type { CarrierData } from '~/services/dto/carrier-data'
import type { DriverData } from '~/services/dto/driver-data'
import type {BovineTypeData} from "~/services/dto/bovine-type-data";
export interface ReceptionData {
id: number
@@ -20,7 +21,9 @@ export interface ReceptionData {
receptionType?: ReceptionTypeData | null
merchandiseType?: MerchandiseTypeData | null
merchandiseDetail?: string | null
bovineDetail?: string | null
buildings?: BuildingData[] | null
bovinesTypes?: BovineTypeData[] | null
pelletBuildings?: ReceptionPelletBuildingData[] | null
user?: UserData | null
supplier?: SupplierData | null
@@ -46,7 +49,9 @@ export type ReceptionPayload = {
receptionType?: string | null
merchandiseType?: string | null
merchandiseDetail?: string | null
bovineDetail?: string | null
buildings?: string[] | null
bovinesTypes?: string[] | null
user?: string | null
supplier?: string | null
address?: string | null

View File

@@ -0,0 +1,59 @@
import { useApi } from '~/composables/useApi'
import type { ReceptionBovineTypeData } from '~/services/dto/reception-bovine-data'
export type ReceptionBovineListResponse =
| ReceptionBovineTypeData[]
| { 'hydra:member'?: ReceptionBovineTypeData[] }
export type ReceptionBovinePayload = {
quantity: number
reception: string
bovineType: string
}
export async function getReceptionBovineList(
receptionIri: string
): Promise<ReceptionBovineTypeData[]> {
const api = useApi()
const response = await api.get<ReceptionBovineListResponse>(
'reception_bovines',
{ reception: receptionIri },
{
toastErrorKey: 'errors.receptionBovine.list'
}
)
if (Array.isArray(response)) {
return response
}
if (response && typeof response === 'object' && Array.isArray(response['hydra:member'])) {
return response['hydra:member']
}
return []
}
export async function createReceptionBovine(
payload: ReceptionBovinePayload
): Promise<ReceptionBovineTypeData> {
const api = useApi()
return api.post<ReceptionBovineTypeData>('reception_bovines', payload, {
toastErrorKey: 'errors.receptionBovine.create'
})
}
export async function deleteReceptionBovine(id: number): Promise<void> {
const api = useApi()
await api.delete<void>(`reception_bovines/${id}`, {}, {
toastErrorKey: 'errors.receptionBovine.delete'
})
}
export async function updateReceptionBovine(
id: number,
payload: Partial<ReceptionBovinePayload>
): Promise<ReceptionBovineTypeData> {
const api = useApi()
return api.patch<ReceptionBovineTypeData>(`reception_bovines/${id}`, payload, {
toastErrorKey: 'errors.receptionBovine.update'
})
}

View File

@@ -1,5 +1,6 @@
export const RECEPTION_TYPE_CODES = {
MERCHANDISES: 'MARCHANDISES'
MERCHANDISES: 'MARCHANDISES',
BOVINS: 'BOVINS'
} as const
export const MERCHANDISE_TYPE_CODES = {

View File

@@ -7,6 +7,9 @@ ENV_FILE := $(if $(wildcard $(ENV_LOCAL)),$(ENV_LOCAL),$(ENV_DEFAULT))
include $(ENV_DEFAULT)
-include $(ENV_LOCAL)
HOST_UID := $(if $(SUDO_UID),$(SUDO_UID),$(shell id -u))
HOST_GID := $(if $(SUDO_GID),$(SUDO_GID),$(shell id -g))
PHP_CONTAINER = php-$(DOCKER_APP_NAME)-fpm
SYMFONY_CONSOLE = $(EXEC_PHP) php bin/console
@@ -24,13 +27,13 @@ FILES =
env-init:
@mkdir -p docker
@cp --update=none $(ENV_DEFAULT) $(ENV_LOCAL)
@cp -n $(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
@cp -n docker/.env.docker docker/.env.docker.local
CURRENT_UID=$(HOST_UID) CURRENT_GID=$(HOST_GID) $(DOCKER_COMPOSE) up -d
# Éteint le container
stop:
@@ -38,7 +41,7 @@ stop:
restart: env-init
$(DOCKER_COMPOSE) down
CURRENT_UID=$(shell id -u) CURRENT_GID=$(shell id -g) $(DOCKER_COMPOSE) up -d
CURRENT_UID=$(HOST_UID) CURRENT_GID=$(HOST_GID) $(DOCKER_COMPOSE) up -d
install: copy-git-hook composer-install cache-clear node-use build-nuxtJS migration-migrate
@@ -57,9 +60,8 @@ 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
rm -rf vendor/
rm -rf frontend/node_modules
remove_orphans:
$(DOCKER_COMPOSE) kill
@@ -69,12 +71,12 @@ 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)" \
--build-arg="CURRENT_UID=$(HOST_UID)" \
--build-arg="CURRENT_GID=$(HOST_GID)" \
--no-cache
migration-migrate:
$(SYMFONY_CONSOLE) doctrine:migrations:migrate --no-interaction
$(SYMFONY_CONSOLE) --no-interaction doctrine:migrations:migrate --allow-no-migration
fixtures:
$(SYMFONY_CONSOLE) doctrine:fixtures:load

View File

@@ -0,0 +1,96 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20260203123000 extends AbstractMigration
{
public function getDescription(): string
{
return 'Normalize index and constraint names to match current Doctrine naming strategy.';
}
public function up(Schema $schema): void
{
$this->addSql('ALTER INDEX idx_1b80e4864c3c5e0a RENAME TO IDX_1B80E48621DFC797');
$this->addSql('ALTER INDEX idx_1b80e4868bebb4b RENAME TO IDX_1B80E486C6957CCE');
$this->addSql('ALTER TABLE supplier_address DROP CONSTRAINT fk_3dce3c746f9b8a0');
$this->addSql('ALTER TABLE supplier_address DROP CONSTRAINT fk_3dce3c74f2c1d6a8');
$this->addSql('ALTER TABLE supplier_address ADD CONSTRAINT FK_8C2B1B9E2ADD6D8C FOREIGN KEY (supplier_id) REFERENCES supplier (id) ON DELETE CASCADE');
$this->addSql('ALTER TABLE supplier_address ADD CONSTRAINT FK_8C2B1B9EF5B7AF75 FOREIGN KEY (address_id) REFERENCES address (id) ON DELETE CASCADE');
$this->addSql('ALTER INDEX idx_3dce3c74f2c1d6a8 RENAME TO IDX_8C2B1B9E2ADD6D8C');
$this->addSql('ALTER INDEX idx_3dce3c746f9b8a0 RENAME TO IDX_8C2B1B9EF5B7AF75');
$this->addSql('DROP INDEX uniq_8d93d649f85e0677');
$this->addSql('DROP INDEX uniq_user_username');
$this->addSql('CREATE UNIQUE INDEX UNIQ_327C5DE7F85E0677 ON "user" (username)');
$this->addSql('ALTER INDEX idx_14b3bc5f4c3c5e0a RENAME TO IDX_11667CD921DFC797');
$this->addSql('ALTER INDEX idx_5df3aa933e4a2e34 RENAME TO IDX_5B59B8827C14DF52');
$this->addSql('ALTER INDEX idx_5df3aa93955258d RENAME TO IDX_5B59B882113969D3');
$this->addSql('ALTER INDEX idx_5df3aa934d2a7e12 RENAME TO IDX_5B59B8824D2A7E12');
$this->addSql('ALTER INDEX uniq_reception_identification_number RENAME TO UNIQ_50D6852F347639A5');
$this->addSql('ALTER INDEX idx_83dc02e37bd5b5d RENAME TO IDX_50D6852FB708E8B8');
$this->addSql('ALTER INDEX idx_83dc02e3bcaaa7c0 RENAME TO IDX_50D6852F928E8BF2');
$this->addSql('ALTER INDEX idx_83dc02e3a76ed395 RENAME TO IDX_50D6852FA76ED395');
$this->addSql('ALTER INDEX idx_83dc02e32add6e01 RENAME TO IDX_50D6852F2ADD6D8C');
$this->addSql('ALTER INDEX idx_83dc02e3f5b7af75 RENAME TO IDX_50D6852FF5B7AF75');
$this->addSql('ALTER INDEX idx_83dc02e3f7d15b1a RENAME TO IDX_50D6852FC6957CCE');
$this->addSql('ALTER INDEX idx_83dc02e34c3c5e0a RENAME TO IDX_50D6852F21DFC797');
$this->addSql('ALTER INDEX idx_83dc02e3f24c741b RENAME TO IDX_50D6852FC3423909');
$this->addSql('ALTER TABLE reception_building DROP CONSTRAINT fk_46e7f9f24d2a7e12');
$this->addSql('ALTER TABLE reception_building DROP CONSTRAINT fk_46e7f9f23e4a2e34');
$this->addSql('ALTER TABLE reception_building ADD CONSTRAINT FK_4DA1DCF07C14DF52 FOREIGN KEY (reception_id) REFERENCES reception (id) ON DELETE CASCADE');
$this->addSql('ALTER TABLE reception_building ADD CONSTRAINT FK_4DA1DCF04D2A7E12 FOREIGN KEY (building_id) REFERENCES building (id) ON DELETE CASCADE');
$this->addSql('ALTER INDEX idx_46e7f9f23e4a2e34 RENAME TO IDX_4DA1DCF07C14DF52');
$this->addSql('ALTER INDEX idx_46e7f9f24d2a7e12 RENAME TO IDX_4DA1DCF04D2A7E12');
}
public function down(Schema $schema): void
{
$this->addSql('ALTER INDEX IDX_1B80E48621DFC797 RENAME TO idx_1b80e4864c3c5e0a');
$this->addSql('ALTER INDEX IDX_1B80E486C6957CCE RENAME TO idx_1b80e4868bebb4b');
$this->addSql('ALTER TABLE supplier_address DROP CONSTRAINT FK_8C2B1B9E2ADD6D8C');
$this->addSql('ALTER TABLE supplier_address DROP CONSTRAINT FK_8C2B1B9EF5B7AF75');
$this->addSql('ALTER TABLE supplier_address ADD CONSTRAINT fk_3dce3c746f9b8a0 FOREIGN KEY (address_id) REFERENCES address (id) ON DELETE CASCADE');
$this->addSql('ALTER TABLE supplier_address ADD CONSTRAINT fk_3dce3c74f2c1d6a8 FOREIGN KEY (supplier_id) REFERENCES supplier (id) ON DELETE CASCADE');
$this->addSql('ALTER INDEX IDX_8C2B1B9E2ADD6D8C RENAME TO idx_3dce3c74f2c1d6a8');
$this->addSql('ALTER INDEX IDX_8C2B1B9EF5B7AF75 RENAME TO idx_3dce3c746f9b8a0');
$this->addSql('DROP INDEX UNIQ_327C5DE7F85E0677');
$this->addSql('CREATE UNIQUE INDEX uniq_8d93d649f85e0677 ON "user" (username)');
$this->addSql('CREATE UNIQUE INDEX uniq_user_username ON "user" (username)');
$this->addSql('ALTER INDEX IDX_11667CD921DFC797 RENAME TO idx_14b3bc5f4c3c5e0a');
$this->addSql('ALTER INDEX IDX_5B59B8827C14DF52 RENAME TO idx_5df3aa933e4a2e34');
$this->addSql('ALTER INDEX IDX_5B59B882113969D3 RENAME TO idx_5df3aa93955258d');
$this->addSql('ALTER INDEX IDX_5B59B8824D2A7E12 RENAME TO idx_5df3aa934d2a7e12');
$this->addSql('ALTER INDEX UNIQ_50D6852F347639A5 RENAME TO uniq_reception_identification_number');
$this->addSql('ALTER INDEX IDX_50D6852FB708E8B8 RENAME TO idx_83dc02e37bd5b5d');
$this->addSql('ALTER INDEX IDX_50D6852F928E8BF2 RENAME TO idx_83dc02e3bcaaa7c0');
$this->addSql('ALTER INDEX IDX_50D6852FA76ED395 RENAME TO idx_83dc02e3a76ed395');
$this->addSql('ALTER INDEX IDX_50D6852F2ADD6D8C RENAME TO idx_83dc02e32add6e01');
$this->addSql('ALTER INDEX IDX_50D6852FF5B7AF75 RENAME TO idx_83dc02e3f5b7af75');
$this->addSql('ALTER INDEX IDX_50D6852FC6957CCE RENAME TO idx_83dc02e3f7d15b1a');
$this->addSql('ALTER INDEX IDX_50D6852F21DFC797 RENAME TO idx_83dc02e34c3c5e0a');
$this->addSql('ALTER INDEX IDX_50D6852FC3423909 RENAME TO idx_83dc02e3f24c741b');
$this->addSql('ALTER TABLE reception_building DROP CONSTRAINT FK_4DA1DCF07C14DF52');
$this->addSql('ALTER TABLE reception_building DROP CONSTRAINT FK_4DA1DCF04D2A7E12');
$this->addSql('ALTER TABLE reception_building ADD CONSTRAINT fk_46e7f9f23e4a2e34 FOREIGN KEY (reception_id) REFERENCES reception (id) ON DELETE CASCADE');
$this->addSql('ALTER TABLE reception_building ADD CONSTRAINT fk_46e7f9f24d2a7e12 FOREIGN KEY (building_id) REFERENCES building (id) ON DELETE CASCADE');
$this->addSql('ALTER INDEX IDX_4DA1DCF07C14DF52 RENAME TO idx_46e7f9f23e4a2e34');
$this->addSql('ALTER INDEX IDX_4DA1DCF04D2A7E12 RENAME TO idx_46e7f9f24d2a7e12');
}
}

View File

@@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20260203123833 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE TABLE bovine_type (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, label VARCHAR(120) NOT NULL, code VARCHAR(50) NOT NULL, PRIMARY KEY (id))');
$this->addSql('CREATE TABLE reception_bovine (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, quantity INT NOT NULL, reception_id INT DEFAULT NULL, bovine_type_id INT NOT NULL, PRIMARY KEY (id))');
$this->addSql('CREATE INDEX IDX_636B9DB97C14DF52 ON reception_bovine (reception_id)');
$this->addSql('CREATE INDEX IDX_636B9DB97899F32E ON reception_bovine (bovine_type_id)');
$this->addSql('ALTER TABLE reception_bovine ADD CONSTRAINT FK_636B9DB97C14DF52 FOREIGN KEY (reception_id) REFERENCES reception (id)');
$this->addSql('ALTER TABLE reception_bovine ADD CONSTRAINT FK_636B9DB97899F32E FOREIGN KEY (bovine_type_id) REFERENCES bovine_type (id) NOT DEFERRABLE');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE reception_bovine DROP CONSTRAINT FK_636B9DB97C14DF52');
$this->addSql('ALTER TABLE reception_bovine DROP CONSTRAINT FK_636B9DB97899F32E');
$this->addSql('DROP TABLE bovine_type');
$this->addSql('DROP TABLE reception_bovine');
}
}

View File

@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20260204141406 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE reception_bovine ALTER quantity SET DEFAULT 0');
$this->addSql('CREATE UNIQUE INDEX uniq_reception_bovine_type ON reception_bovine (reception_id, bovine_type_id)');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('DROP INDEX uniq_reception_bovine_type');
$this->addSql('ALTER TABLE reception_bovine ALTER quantity DROP DEFAULT');
}
}

View File

@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20260205070819 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE reception ADD bovine_detail VARCHAR(255) DEFAULT NULL');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE reception DROP bovine_detail');
}
}

70
src/Entity/BovineType.php Normal file
View File

@@ -0,0 +1,70 @@
<?php
declare(strict_types=1);
namespace App\Entity;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Attribute\Groups;
#[ORM\Entity]
#[ApiResource(
operations: [
new Get(
requirements: ['id' => '\d+'],
normalizationContext: ['groups' => ['bovine-type:read']],
),
new GetCollection(
normalizationContext: ['groups' => ['bovine-type:read']],
),
],
security: "is_granted('ROLE_USER')",
)]
class BovineType
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
#[Groups(['bovine-type:read', 'reception:read', 'reception-bovine:read'])]
private ?int $id = null;
#[ORM\Column(length: 120)]
#[Groups(['bovine-type:read', 'reception:read', 'reception-bovine:read'])]
private ?string $label = null;
#[ORM\Column(length: 50)]
#[Groups(['bovine-type:read', 'reception:read', 'reception-bovine:read'])]
private ?string $code = null;
public function getId(): ?int
{
return $this->id;
}
public function getLabel(): ?string
{
return $this->label;
}
public function setLabel(string $label): static
{
$this->label = $label;
return $this;
}
public function getCode(): ?string
{
return $this->code;
}
public function setCode(string $code): static
{
$this->code = $code;
return $this;
}
}

View File

@@ -72,27 +72,27 @@ class Reception
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
#[Groups(['reception:read'])]
#[Groups(['reception:read', 'reception-bovine:read'])]
private ?int $id = null;
#[ORM\Column(length: 20, nullable: true)]
#[Groups(['reception:read', 'reception:write'])]
#[Groups(['reception:read', 'reception:write', 'reception-bovine:read'])]
private ?string $licensePlate = null;
#[ORM\Column(length: 20, unique: true, nullable: true)]
#[Groups(['reception:read'])]
#[Groups(['reception:read', 'reception-bovine:read'])]
private ?string $identificationNumber = null;
#[ORM\Column(options: ['default' => 0])]
#[Groups(['reception:read', 'reception:write'])]
#[Groups(['reception:read', 'reception:write', 'reception-bovine:read'])]
private int $currentStep = 0;
#[ORM\Column(options: ['default' => false])]
#[Groups(['reception:read', 'reception:write'])]
#[Groups(['reception:read', 'reception:write', 'reception-bovine:read'])]
private bool $isValid = false;
#[ORM\Column(name: 'date_reception', type: 'datetime_immutable')]
#[Groups(['reception:read', 'reception:write'])]
#[Groups(['reception:read', 'reception:write', 'reception-bovine:read'])]
#[Context([DateTimeNormalizer::FORMAT_KEY => 'Y-m-d'])]
private ?DateTimeImmutable $receptionDate = null;
@@ -168,6 +168,20 @@ class Reception
#[ApiProperty(readableLink: true)]
private ?Driver $driver = null;
/**
* @var Collection<int, ReceptionBovine>
*/
#[ORM\OneToMany(targetEntity: ReceptionBovine::class, mappedBy: 'reception', cascade: ['persist', 'remove'], orphanRemoval: true)]
#[Assert\Range(
min: 0
)]
#[Groups(['reception:read', 'reception:write'])]
private Collection $bovines_types;
#[ORM\Column(length: 255, nullable: true)]
#[Groups(['reception:read', 'reception:write'])]
private ?string $bovineDetail = null;
public function __construct(
?DateTimeImmutable $receptionDate = null,
) {
@@ -175,6 +189,7 @@ class Reception
$this->weights = new ArrayCollection();
$this->buildings = new ArrayCollection();
$this->pelletBuildings = new ArrayCollection();
$this->bovines_types = new ArrayCollection();
}
public function getId(): ?int
@@ -469,4 +484,46 @@ class Reception
)
;
}
/**
* @return Collection<int, ReceptionBovine>
*/
public function getBovinesTypes(): Collection
{
return $this->bovines_types;
}
public function addBovinesType(ReceptionBovine $bovinesType): static
{
if (!$this->bovines_types->contains($bovinesType)) {
$this->bovines_types->add($bovinesType);
$bovinesType->setReception($this);
}
return $this;
}
public function removeBovinesType(ReceptionBovine $bovinesType): static
{
if ($this->bovines_types->removeElement($bovinesType)) {
// set the owning side to null (unless already changed)
if ($bovinesType->getReception() === $this) {
$bovinesType->setReception(null);
}
}
return $this;
}
public function getBovineDetail(): ?string
{
return $this->bovineDetail;
}
public function setBovineDetail(?string $bovineDetail): static
{
$this->bovineDetail = $bovineDetail;
return $this;
}
}

View File

@@ -0,0 +1,109 @@
<?php
declare(strict_types=1);
namespace App\Entity;
use ApiPlatform\Doctrine\Orm\Filter\SearchFilter;
use ApiPlatform\Metadata\ApiFilter;
use ApiPlatform\Metadata\ApiProperty;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Delete;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Patch;
use ApiPlatform\Metadata\Post;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Attribute\Groups;
#[ORM\Entity]
#[ApiFilter(SearchFilter::class, properties: ['reception' => 'exact'])]
#[ORM\UniqueConstraint(name: 'uniq_reception_bovine_type', columns: ['reception_id', 'bovine_type_id'])]
#[ApiResource(
operations: [
new Get(
requirements: ['id' => '\d+'],
normalizationContext: ['groups' => ['reception-bovine:read']],
),
new GetCollection(
normalizationContext: ['groups' => ['reception-bovine:read']],
),
new Post(
normalizationContext: ['groups' => ['reception-bovine:read']],
denormalizationContext: ['groups' => ['reception-bovine:write']],
),
new Patch(
normalizationContext: ['groups' => ['reception-bovine:read']],
denormalizationContext: ['groups' => ['reception-bovine:write']],
),
new Delete(),
],
security: "is_granted('ROLE_USER')",
)]
class ReceptionBovine
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
#[Groups(['reception-bovine:read', 'reception:read'])]
private ?int $id = null;
#[ORM\ManyToOne(inversedBy: 'bovines_types')]
#[Groups(['reception-bovine:read', 'reception-bovine:write'])]
private ?Reception $reception = null;
#[ORM\ManyToOne]
#[ORM\JoinColumn(nullable: false)]
#[Groups(['reception-bovine:read', 'reception-bovine:write', 'reception:read'])]
#[ApiProperty(readableLink: true)]
private ?BovineType $bovineType = null;
#[ORM\Column(options: ['default' => 0])]
#[Assert\Range(
min: 0
)]
#[Groups(['reception-bovine:read', 'reception-bovine:write', 'reception:read'])]
private ?int $quantity = null;
public function getId(): ?int
{
return $this->id;
}
public function getReception(): ?Reception
{
return $this->reception;
}
public function setReception(?Reception $reception): static
{
$this->reception = $reception;
return $this;
}
public function getBovineType(): ?BovineType
{
return $this->bovineType;
}
public function setBovineType(?BovineType $bovineType): static
{
$this->bovineType = $bovineType;
return $this;
}
public function getQuantity(): ?int
{
return $this->quantity;
}
public function setQuantity(int $quantity): static
{
$this->quantity = $quantity;
return $this;
}
}

View File

@@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
namespace App\Repository;
use App\Entity\BovineType;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @extends ServiceEntityRepository<BovineType>
*/
class BovineTypeRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, BovineType::class);
}
// /**
// * @return BovineType[] Returns an array of BovineType objects
// */
// public function findByExampleField($value): array
// {
// return $this->createQueryBuilder('b')
// ->andWhere('b.exampleField = :val')
// ->setParameter('val', $value)
// ->orderBy('b.id', 'ASC')
// ->setMaxResults(10)
// ->getQuery()
// ->getResult()
// ;
// }
// public function findOneBySomeField($value): ?BovineType
// {
// return $this->createQueryBuilder('b')
// ->andWhere('b.exampleField = :val')
// ->setParameter('val', $value)
// ->getQuery()
// ->getOneOrNullResult()
// ;
// }
}

View File

@@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
namespace App\Repository;
use App\Entity\ReceptionBovine;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @extends ServiceEntityRepository<ReceptionBovine>
*/
class ReceptionBovineRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, ReceptionBovine::class);
}
// /**
// * @return ReceptionBovine[] Returns an array of ReceptionBovine objects
// */
// public function findByExampleField($value): array
// {
// return $this->createQueryBuilder('r')
// ->andWhere('r.exampleField = :val')
// ->setParameter('val', $value)
// ->orderBy('r.id', 'ASC')
// ->setMaxResults(10)
// ->getQuery()
// ->getResult()
// ;
// }
// public function findOneBySomeField($value): ?ReceptionBovine
// {
// return $this->createQueryBuilder('r')
// ->andWhere('r.exampleField = :val')
// ->setParameter('val', $value)
// ->getQuery()
// ->getOneOrNullResult()
// ;
// }
}

View File

@@ -160,6 +160,15 @@
".editorconfig"
]
},
"symfony/maker-bundle": {
"version": "1.65",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "1.0",
"ref": "fadbfe33303a76e25cb63401050439aa9b1a9c7f"
}
},
"symfony/monolog-bundle": {
"version": "4.0",
"recipe": {

View File

@@ -0,0 +1,102 @@
<?php
declare(strict_types=1);
namespace App\Tests\State;
use ApiPlatform\Metadata\Operation;
use App\State\BovinIdentificationProvider;
use DateTimeImmutable;
use Malio\EdnotifBundle\Bovin\Api\BovinApiInterface;
use Malio\EdnotifBundle\Bovin\Dto\AnimalFileDto;
use Malio\EdnotifBundle\Bovin\Dto\BovinIdentificationDto;
use Malio\EdnotifBundle\Bovin\Dto\BovinRef;
use Malio\EdnotifBundle\Bovin\Dto\DateValueDto;
use Malio\EdnotifBundle\Bovin\Dto\ExploitationRef;
use Malio\EdnotifBundle\Bovin\Dto\MovementDto;
use Malio\EdnotifBundle\Bovin\Dto\ParentInfoDto;
use Malio\EdnotifBundle\Bovin\Dto\PresencePeriodDto;
use Malio\EdnotifBundle\Shared\Dto\StandardResponseDto;
use PHPUnit\Framework\TestCase;
/**
* @internal
*/
final class BovinIdentificationProviderTest extends TestCase
{
public function testReturnsNullWhenNumeroNationalMissing(): void
{
$api = $this->createMock(BovinApiInterface::class);
$api->expects(self::never())->method('getAnimalFile');
$provider = new BovinIdentificationProvider($api);
$result = $provider->provide($this->createStub(Operation::class), []);
self::assertNull($result);
}
public function testMapsIdentificationAndPresencePeriods(): void
{
$api = $this->createMock(BovinApiInterface::class);
$identification = new BovinIdentificationDto(
bovin: new BovinRef('FR', 'IGNORED'),
sex: 'F',
breedType: 'LIM',
birthDate: new DateValueDto(new DateTimeImmutable('2024-01-02'), 'Y'),
workNumber: 'W123',
isFilie: true,
motherCarrier: new ParentInfoDto(new BovinRef('FR', 'MOM1'), 'CHA'),
fatherIpg: new ParentInfoDto(new BovinRef('FR', 'DAD1'), 'BBB'),
birthExploitation: new ExploitationRef('FR', 'EXP1'),
);
$presencePeriods = [
new PresencePeriodDto(
new MovementDto(new DateTimeImmutable('2024-03-01'), 'ENTRY', null),
new MovementDto(new DateTimeImmutable('2024-03-10'), 'EXIT', null),
),
];
$animalFile = new AnimalFileDto(
new StandardResponseDto(true, null),
$identification,
$presencePeriods,
null
);
$api->expects(self::once())
->method('getAnimalFile')
->with('FR123', 'FR')
->willReturn($animalFile)
;
$provider = new BovinIdentificationProvider($api);
$result = $provider->provide(
$this->createStub(Operation::class),
['numeroNational' => 'FR123']
);
self::assertNotNull($result);
self::assertSame('FR123', $result->numeroNational);
self::assertSame('F', $result->sex);
self::assertSame('LIM', $result->breedType);
self::assertSame('W123', $result->workNumber);
self::assertSame('2024-01-02', $result->birthDate);
self::assertSame('Y', $result->birthDateCompletenessFlag);
self::assertTrue($result->isFilie);
self::assertSame('MOM1', $result->motherNationalNumber);
self::assertSame('CHA', $result->motherBreedType);
self::assertSame('DAD1', $result->fatherNationalNumber);
self::assertSame('BBB', $result->fatherBreedType);
self::assertSame('EXP1', $result->birthExploitationNumber);
self::assertCount(1, $result->presencePeriods);
self::assertSame('2024-03-01', $result->presencePeriods[0]->entryDate);
self::assertSame('ENTRY', $result->presencePeriods[0]->entryCause);
self::assertSame('2024-03-10', $result->presencePeriods[0]->exitDate);
self::assertSame('EXIT', $result->presencePeriods[0]->exitCause);
}
}

View File

@@ -0,0 +1,48 @@
<?php
declare(strict_types=1);
namespace App\Tests\State;
use ApiPlatform\Metadata\Operation;
use App\Entity\User;
use App\State\MeProvider;
use PHPUnit\Framework\TestCase;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException;
/**
* @internal
*/
final class MeProviderTest extends TestCase
{
public function testProvideReturnUser(): void
{
$user = new User();
$security = $this->createStub(Security::class);
$security->method('getUser')->willReturn($user);
$provider = new MeProvider($security);
$result = $provider->provide($this->createStub(Operation::class));
self::assertSame($user, $result);
self::assertInstanceOf(User::class, $result);
}
public function testProvideThrowAccessDeniedException(): void
{
$user = null;
$security = $this->createStub(Security::class);
$security->method('getUser')->willReturn($user);
$provider = new MeProvider($security);
$this->expectException(AccessDeniedException::class);
$this->expectExceptionMessage('User not authenticated.');
$provider->provide($this->createStub(Operation::class));
}
}