Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ee766311e3 | |||
| 2f8aa1dd32 | |||
| de76a77120 | |||
| 642ee43c53 |
@@ -4,9 +4,7 @@
|
|||||||
"Bash(npm run:*)",
|
"Bash(npm run:*)",
|
||||||
"WebFetch(domain:geo.api.gouv.fr)",
|
"WebFetch(domain:geo.api.gouv.fr)",
|
||||||
"Bash(pip3 install:*)",
|
"Bash(pip3 install:*)",
|
||||||
"Bash(python3 -c \":*)",
|
"Bash(python3 -c \":*)"
|
||||||
"Bash(make cache-clear *)",
|
|
||||||
"Bash(make test *)"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
-10
@@ -1,15 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="db-forest-configuration">
|
|
||||||
<data version="2">.
|
|
||||||
----------------------------------------
|
|
||||||
1:0:9cad43df-2147-4989-b7a4-443067034884
|
|
||||||
2:0:ae622167-c834-4e7b-87a5-c1721036f5dc
|
|
||||||
3:0:f407a514-c6b4-4b26-9555-445a85892502
|
|
||||||
4:0:09e221b8-067a-488b-9c1d-4e155a333079
|
|
||||||
5:0:9d8c1ad3-2491-4642-964a-666003c14128
|
|
||||||
.</data>
|
|
||||||
</component>
|
|
||||||
<component name="db-tree-configuration">
|
<component name="db-tree-configuration">
|
||||||
<option name="data" value="---------------------------------------- 1:0:f407a514-c6b4-4b26-9555-445a85892502 2:0:ae622167-c834-4e7b-87a5-c1721036f5dc 3:0:9cad43df-2147-4989-b7a4-443067034884 4:0:09e221b8-067a-488b-9c1d-4e155a333079 " />
|
<option name="data" value="---------------------------------------- 1:0:f407a514-c6b4-4b26-9555-445a85892502 2:0:ae622167-c834-4e7b-87a5-c1721036f5dc 3:0:9cad43df-2147-4989-b7a4-443067034884 4:0:09e221b8-067a-488b-9c1d-4e155a333079 " />
|
||||||
</component>
|
</component>
|
||||||
|
|||||||
Generated
-5
@@ -155,11 +155,6 @@
|
|||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/data-fixtures" />
|
<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/doctrine/doctrine-fixtures-bundle" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/maker-bundle" />
|
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/maker-bundle" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/maennchen/zipstream-php" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/markbaker/complex" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/markbaker/matrix" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/phpoffice/phpspreadsheet" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/simple-cache" />
|
|
||||||
<excludePattern pattern="reference.php" />
|
<excludePattern pattern="reference.php" />
|
||||||
</content>
|
</content>
|
||||||
<orderEntry type="inheritedJdk" />
|
<orderEntry type="inheritedJdk" />
|
||||||
|
|||||||
Generated
-5
@@ -174,11 +174,6 @@
|
|||||||
<path value="$PROJECT_DIR$/vendor/doctrine/doctrine-fixtures-bundle" />
|
<path value="$PROJECT_DIR$/vendor/doctrine/doctrine-fixtures-bundle" />
|
||||||
<path value="$PROJECT_DIR$/vendor/doctrine/data-fixtures" />
|
<path value="$PROJECT_DIR$/vendor/doctrine/data-fixtures" />
|
||||||
<path value="$PROJECT_DIR$/vendor/symfony/maker-bundle" />
|
<path value="$PROJECT_DIR$/vendor/symfony/maker-bundle" />
|
||||||
<path value="$PROJECT_DIR$/vendor/maennchen/zipstream-php" />
|
|
||||||
<path value="$PROJECT_DIR$/vendor/psr/simple-cache" />
|
|
||||||
<path value="$PROJECT_DIR$/vendor/markbaker/matrix" />
|
|
||||||
<path value="$PROJECT_DIR$/vendor/markbaker/complex" />
|
|
||||||
<path value="$PROJECT_DIR$/vendor/phpoffice/phpspreadsheet" />
|
|
||||||
</include_path>
|
</include_path>
|
||||||
</component>
|
</component>
|
||||||
<component name="PhpProjectSharedConfiguration" php_language_level="8.4" />
|
<component name="PhpProjectSharedConfiguration" php_language_level="8.4" />
|
||||||
|
|||||||
Generated
+62
-83
@@ -4,16 +4,12 @@
|
|||||||
<option name="autoReloadType" value="SELECTIVE" />
|
<option name="autoReloadType" value="SELECTIVE" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ChangeListManager">
|
<component name="ChangeListManager">
|
||||||
<list default="true" id="7c107abe-5995-4428-8429-b146aaca8386" name="Changes" comment="fix : label age bovin">
|
<list default="true" id="7c107abe-5995-4428-8429-b146aaca8386" name="Changes" comment="fix : les non-admin ne peuvent plus supprimer de réception/expédition en attente">
|
||||||
<change beforePath="$PROJECT_DIR$/.claude/settings.local.json" beforeDir="false" afterPath="$PROJECT_DIR$/.claude/settings.local.json" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/.idea/db-forest-config.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/db-forest-config.xml" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/.idea/ferme.iml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/ferme.iml" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/.idea/php.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/php.xml" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/CHANGELOG.md" beforeDir="false" afterPath="$PROJECT_DIR$/CHANGELOG.md" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/config/reference.php" beforeDir="false" afterPath="$PROJECT_DIR$/config/reference.php" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/config/reference.php" beforeDir="false" afterPath="$PROJECT_DIR$/config/reference.php" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/frontend/pages/bovine/[id].vue" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/pages/bovine/[id].vue" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/frontend/pages/reception/waiting-reception.vue" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/pages/reception/waiting-reception.vue" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/src/Entity/BovineMovement.php" beforeDir="false" afterPath="$PROJECT_DIR$/src/Entity/BovineMovement.php" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/frontend/pages/shipment/waiting-shipment.vue" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/pages/shipment/waiting-shipment.vue" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/src/State/Bovin/BovineMovementProcessor.php" beforeDir="false" afterPath="$PROJECT_DIR$/src/State/Bovin/BovineMovementProcessor.php" afterDir="false" />
|
|
||||||
</list>
|
</list>
|
||||||
<option name="SHOW_DIALOG" value="false" />
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||||
@@ -45,7 +41,7 @@
|
|||||||
<component name="Git.Settings">
|
<component name="Git.Settings">
|
||||||
<option name="RECENT_BRANCH_BY_REPOSITORY">
|
<option name="RECENT_BRANCH_BY_REPOSITORY">
|
||||||
<map>
|
<map>
|
||||||
<entry key="$PROJECT_DIR$" value="feat/entree-sortie" />
|
<entry key="$PROJECT_DIR$" value="feature/FER-13-faire-des-recherches-sur-le-scanner-des-betes" />
|
||||||
</map>
|
</map>
|
||||||
</option>
|
</option>
|
||||||
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
||||||
@@ -217,11 +213,6 @@
|
|||||||
<path value="$PROJECT_DIR$/vendor/doctrine/doctrine-fixtures-bundle" />
|
<path value="$PROJECT_DIR$/vendor/doctrine/doctrine-fixtures-bundle" />
|
||||||
<path value="$PROJECT_DIR$/vendor/doctrine/data-fixtures" />
|
<path value="$PROJECT_DIR$/vendor/doctrine/data-fixtures" />
|
||||||
<path value="$PROJECT_DIR$/vendor/symfony/maker-bundle" />
|
<path value="$PROJECT_DIR$/vendor/symfony/maker-bundle" />
|
||||||
<path value="$PROJECT_DIR$/vendor/maennchen/zipstream-php" />
|
|
||||||
<path value="$PROJECT_DIR$/vendor/psr/simple-cache" />
|
|
||||||
<path value="$PROJECT_DIR$/vendor/markbaker/matrix" />
|
|
||||||
<path value="$PROJECT_DIR$/vendor/markbaker/complex" />
|
|
||||||
<path value="$PROJECT_DIR$/vendor/phpoffice/phpspreadsheet" />
|
|
||||||
</include_path>
|
</include_path>
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectColorInfo">{
|
<component name="ProjectColorInfo">{
|
||||||
@@ -241,9 +232,7 @@
|
|||||||
"RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true",
|
"RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true",
|
||||||
"RunOnceActivity.git.unshallow": "true",
|
"RunOnceActivity.git.unshallow": "true",
|
||||||
"RunOnceActivity.typescript.service.memoryLimit.init": "true",
|
"RunOnceActivity.typescript.service.memoryLimit.init": "true",
|
||||||
"codeWithMe.voiceChat.enabledByDefault": "false",
|
"git-widget-placeholder": "fix/FER-15-fix-droit-de-suppression-reception-expedition-util",
|
||||||
"git-widget-placeholder": "feat/vie-du-bovin",
|
|
||||||
"git.auto.fetch.suggestion.counter": "3",
|
|
||||||
"last_opened_file_path": "//wsl.localhost/Ubuntu-24.04/home/m-tristan/workspace/Ferme",
|
"last_opened_file_path": "//wsl.localhost/Ubuntu-24.04/home/m-tristan/workspace/Ferme",
|
||||||
"node.js.detected.package.eslint": "true",
|
"node.js.detected.package.eslint": "true",
|
||||||
"node.js.detected.package.tslint": "true",
|
"node.js.detected.package.tslint": "true",
|
||||||
@@ -285,7 +274,7 @@
|
|||||||
<component name="SharedIndexes">
|
<component name="SharedIndexes">
|
||||||
<attachedChunks>
|
<attachedChunks>
|
||||||
<set>
|
<set>
|
||||||
<option value="bundled-php-predefined-a98d8de5180a-022fa7b8ab75-com.jetbrains.php.sharedIndexes-PS-261.23567.149" />
|
<option value="bundled-php-predefined-a98d8de5180a-0e0d91225499-com.jetbrains.php.sharedIndexes-PS-253.32098.40" />
|
||||||
</set>
|
</set>
|
||||||
</attachedChunks>
|
</attachedChunks>
|
||||||
</component>
|
</component>
|
||||||
@@ -338,16 +327,54 @@
|
|||||||
<workItem from="1773824491213" duration="24805000" />
|
<workItem from="1773824491213" duration="24805000" />
|
||||||
<workItem from="1774275549972" duration="51000" />
|
<workItem from="1774275549972" duration="51000" />
|
||||||
<workItem from="1774276665015" duration="33750000" />
|
<workItem from="1774276665015" duration="33750000" />
|
||||||
<workItem from="1776755742205" duration="88521000" />
|
</task>
|
||||||
<workItem from="1777453284124" duration="86000" />
|
<task id="LOCAL-00037" summary="feat : finalisation de l'étape 1 "Réception" (formulaire)">
|
||||||
<workItem from="1777453433907" duration="337000" />
|
<option name="closed" value="true" />
|
||||||
<workItem from="1777454070632" duration="17254000" />
|
<created>1769529522614</created>
|
||||||
<workItem from="1777540415843" duration="13205000" />
|
<option name="number" value="00037" />
|
||||||
<workItem from="1777877316149" duration="29389000" />
|
<option name="presentableId" value="LOCAL-00037" />
|
||||||
<workItem from="1777982616362" duration="23909000" />
|
<option name="project" value="LOCAL" />
|
||||||
<workItem from="1778482021120" duration="1280000" />
|
<updated>1769529522614</updated>
|
||||||
<workItem from="1778656317630" duration="279000" />
|
</task>
|
||||||
<workItem from="1778664396844" duration="2576000" />
|
<task id="LOCAL-00038" summary="feat : ajout du numéro identification des receptions et ajustement du bon de reception">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1769676223697</created>
|
||||||
|
<option name="number" value="00038" />
|
||||||
|
<option name="presentableId" value="LOCAL-00038" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1769676223697</updated>
|
||||||
|
</task>
|
||||||
|
<task id="LOCAL-00039" summary="feat : ajout de la partie reception des marchandises (étape 3) et modification du bon de réception">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1769700808988</created>
|
||||||
|
<option name="number" value="00039" />
|
||||||
|
<option name="presentableId" value="LOCAL-00039" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1769700808988</updated>
|
||||||
|
</task>
|
||||||
|
<task id="LOCAL-00040" summary="feat : mise en place de composant UI pour les select, checkbox, date, text">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1769705141157</created>
|
||||||
|
<option name="number" value="00040" />
|
||||||
|
<option name="presentableId" value="LOCAL-00040" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1769705141157</updated>
|
||||||
|
</task>
|
||||||
|
<task id="LOCAL-00041" summary="feat : update CHANGELOG.md">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1769705240487</created>
|
||||||
|
<option name="number" value="00041" />
|
||||||
|
<option name="presentableId" value="LOCAL-00041" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1769705240487</updated>
|
||||||
|
</task>
|
||||||
|
<task id="LOCAL-00042" summary="feat : ajout de commentaire">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1769760766200</created>
|
||||||
|
<option name="number" value="00042" />
|
||||||
|
<option name="presentableId" value="LOCAL-00042" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1769760766200</updated>
|
||||||
</task>
|
</task>
|
||||||
<task id="LOCAL-00043" summary="fix : correction de l'affichage de l'immatriculation sur une réception en cours + correction css étape 3 d'une réception">
|
<task id="LOCAL-00043" summary="fix : correction de l'affichage de l'immatriculation sur une réception en cours + correction css étape 3 d'une réception">
|
||||||
<option name="closed" value="true" />
|
<option name="closed" value="true" />
|
||||||
@@ -693,55 +720,7 @@
|
|||||||
<option name="project" value="LOCAL" />
|
<option name="project" value="LOCAL" />
|
||||||
<updated>1774543840891</updated>
|
<updated>1774543840891</updated>
|
||||||
</task>
|
</task>
|
||||||
<task id="LOCAL-00086" summary="fix : update icon entrée/sortie">
|
<option name="localTasksCounter" value="86" />
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1777896558092</created>
|
|
||||||
<option name="number" value="00086" />
|
|
||||||
<option name="presentableId" value="LOCAL-00086" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1777896558092</updated>
|
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00087" summary="fix : wording">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1777983048277</created>
|
|
||||||
<option name="number" value="00087" />
|
|
||||||
<option name="presentableId" value="LOCAL-00087" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1777983048278</updated>
|
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00088" summary="fix : wording">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1777983581324</created>
|
|
||||||
<option name="number" value="00088" />
|
|
||||||
<option name="presentableId" value="LOCAL-00088" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1777983581324</updated>
|
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00089" summary="feat : update CHANGELOG.md">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1778073247660</created>
|
|
||||||
<option name="number" value="00089" />
|
|
||||||
<option name="presentableId" value="LOCAL-00089" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1778073247660</updated>
|
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00090" summary="feat : amélioration du tableau bovin">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1778135981350</created>
|
|
||||||
<option name="number" value="00090" />
|
|
||||||
<option name="presentableId" value="LOCAL-00090" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1778135981350</updated>
|
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00091" summary="fix : label age bovin">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1778136373027</created>
|
|
||||||
<option name="number" value="00091" />
|
|
||||||
<option name="presentableId" value="LOCAL-00091" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1778136373027</updated>
|
|
||||||
</task>
|
|
||||||
<option name="localTasksCounter" value="92" />
|
|
||||||
<servers />
|
<servers />
|
||||||
</component>
|
</component>
|
||||||
<component name="TypeScriptGeneratedFilesManager">
|
<component name="TypeScriptGeneratedFilesManager">
|
||||||
@@ -791,6 +770,10 @@
|
|||||||
</option>
|
</option>
|
||||||
</component>
|
</component>
|
||||||
<component name="VcsManagerConfiguration">
|
<component name="VcsManagerConfiguration">
|
||||||
|
<MESSAGE value="feat : changelog update" />
|
||||||
|
<MESSAGE value="fix : color tab" />
|
||||||
|
<MESSAGE value="feat : modification front de la page admin transporteur" />
|
||||||
|
<MESSAGE value="fix : espacement et changelog" />
|
||||||
<MESSAGE value="fix : espacement" />
|
<MESSAGE value="fix : espacement" />
|
||||||
<MESSAGE value="fix : text" />
|
<MESSAGE value="fix : text" />
|
||||||
<MESSAGE value="feat : front page admin bovin et changelog" />
|
<MESSAGE value="feat : front page admin bovin et changelog" />
|
||||||
@@ -809,14 +792,10 @@
|
|||||||
<MESSAGE value="feat : système de blocage utilisateur" />
|
<MESSAGE value="feat : système de blocage utilisateur" />
|
||||||
<MESSAGE value="feat : ajout d'un système de scanner bovin" />
|
<MESSAGE value="feat : ajout d'un système de scanner bovin" />
|
||||||
<MESSAGE value="feat : mise à jour du CLAUDE.md" />
|
<MESSAGE value="feat : mise à jour du CLAUDE.md" />
|
||||||
|
<MESSAGE value="feat : update CHANGELOG.md" />
|
||||||
<MESSAGE value="feat : la page de scanner est accessible que pour les admins" />
|
<MESSAGE value="feat : la page de scanner est accessible que pour les admins" />
|
||||||
<MESSAGE value="fix : les non-admin ne peuvent plus supprimer de réception/expédition en attente" />
|
<MESSAGE value="fix : les non-admin ne peuvent plus supprimer de réception/expédition en attente" />
|
||||||
<MESSAGE value="fix : update icon entrée/sortie" />
|
<option name="LAST_COMMIT_MESSAGE" value="fix : les non-admin ne peuvent plus supprimer de réception/expédition en attente" />
|
||||||
<MESSAGE value="fix : wording" />
|
|
||||||
<MESSAGE value="feat : update CHANGELOG.md" />
|
|
||||||
<MESSAGE value="feat : amélioration du tableau bovin" />
|
|
||||||
<MESSAGE value="fix : label age bovin" />
|
|
||||||
<option name="LAST_COMMIT_MESSAGE" value="fix : label age bovin" />
|
|
||||||
</component>
|
</component>
|
||||||
<component name="XDebuggerManager">
|
<component name="XDebuggerManager">
|
||||||
<breakpoint-manager>
|
<breakpoint-manager>
|
||||||
|
|||||||
@@ -65,8 +65,6 @@ Ajouter dans le fichier .env du frontend
|
|||||||
* [#FER-15] Les non-admin ne peuvent plus supprimer de réception/expédition en attente
|
* [#FER-15] Les non-admin ne peuvent plus supprimer de réception/expédition en attente
|
||||||
* [#FER-17] Ecran d'ajout de bovin
|
* [#FER-17] Ecran d'ajout de bovin
|
||||||
* [#FER-18] Mise à jour du tableau d'arrivage
|
* [#FER-18] Mise à jour du tableau d'arrivage
|
||||||
* [#FER-26] Passeport du bovin
|
|
||||||
* [#FER-27] Fix export inventaire bovin
|
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,2 +1,2 @@
|
|||||||
parameters:
|
parameters:
|
||||||
app.version: '0.0.102'
|
app.version: '0.0.98'
|
||||||
|
|||||||
@@ -14,10 +14,14 @@
|
|||||||
|
|
||||||
<UiTabs
|
<UiTabs
|
||||||
v-model="activeTab"
|
v-model="activeTab"
|
||||||
:tabs="tabs"
|
:tabs="[
|
||||||
|
{ key: 'mouvement', label: 'Mouvement' },
|
||||||
|
{ key: 'passeport', label: 'Passeport bovin' },
|
||||||
|
{ key: 'sante', label: 'Santé' }
|
||||||
|
]"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div v-if="auth.isBureau" v-show="activeTab === 'mouvement'">
|
<div v-show="activeTab === 'mouvement'">
|
||||||
<form :class="{ submitted: movementSubmitted }" @submit.prevent="submitMovement">
|
<form :class="{ submitted: movementSubmitted }" @submit.prevent="submitMovement">
|
||||||
<div class="flex flex-cols-3 justify-between mb-10">
|
<div class="flex flex-cols-3 justify-between mb-10">
|
||||||
<UiSelect
|
<UiSelect
|
||||||
@@ -37,13 +41,7 @@
|
|||||||
wrapper-class="w-[280px]"
|
wrapper-class="w-[280px]"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
<UiDateInput
|
<div class="w-[280px]" />
|
||||||
id="movement-date"
|
|
||||||
v-model="newMovementDate"
|
|
||||||
label="Date mouvement"
|
|
||||||
wrapper-class="w-[280px]"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center justify-center mb-11">
|
<div class="flex items-center justify-center mb-11">
|
||||||
@@ -160,19 +158,11 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { getBuildingList } from '~/services/building'
|
import { getBuildingList } from '~/services/building'
|
||||||
import type { BuildingData } from '~/services/dto/building-data'
|
import type { BuildingData } from '~/services/dto/building-data'
|
||||||
import { useAuthStore } from '~/stores/auth'
|
|
||||||
|
|
||||||
useHead({ title: 'Vie du bovin' })
|
useHead({ title: 'Vie du bovin' })
|
||||||
|
|
||||||
const auth = useAuthStore()
|
|
||||||
|
|
||||||
type BovineTab = 'mouvement' | 'passeport' | 'sante'
|
type BovineTab = 'mouvement' | 'passeport' | 'sante'
|
||||||
const tabs = computed(() => [
|
const activeTab = ref<BovineTab>('mouvement')
|
||||||
...(auth.isBureau ? [{ key: 'mouvement' as const, label: 'Mouvement' }] : []),
|
|
||||||
{ key: 'passeport' as const, label: 'Passeport bovin' },
|
|
||||||
{ key: 'sante' as const, label: 'Santé' }
|
|
||||||
])
|
|
||||||
const activeTab = ref<BovineTab>(auth.isBureau ? 'mouvement' : 'passeport')
|
|
||||||
|
|
||||||
interface BovineTypeRef {
|
interface BovineTypeRef {
|
||||||
id: number
|
id: number
|
||||||
@@ -194,6 +184,7 @@ interface BovineMovementData {
|
|||||||
enteredAt: string
|
enteredAt: string
|
||||||
leftAt: string | null
|
leftAt: string | null
|
||||||
buildingCase: BuildingCaseRef | null
|
buildingCase: BuildingCaseRef | null
|
||||||
|
building: BuildingRef | null
|
||||||
}
|
}
|
||||||
|
|
||||||
interface BovinePassportData {
|
interface BovinePassportData {
|
||||||
@@ -224,13 +215,10 @@ const goBack = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const todayIso = () => new Date().toISOString().slice(0, 10)
|
|
||||||
|
|
||||||
const bovine = ref<BovinePassportData | null>(null)
|
const bovine = ref<BovinePassportData | null>(null)
|
||||||
const buildings = ref<BuildingData[]>([])
|
const buildings = ref<BuildingData[]>([])
|
||||||
const newMovementBuildingId = ref<string | number | null>(null)
|
const newMovementBuildingId = ref<string | number | null>(null)
|
||||||
const newMovementCaseId = ref<string | number | null>(null)
|
const newMovementCaseId = ref<string | number | null>(null)
|
||||||
const newMovementDate = ref<string>(todayIso())
|
|
||||||
const isSubmittingMovement = ref(false)
|
const isSubmittingMovement = ref(false)
|
||||||
const movementSubmitted = ref(false)
|
const movementSubmitted = ref(false)
|
||||||
const movementFilters = ref({ building: '', case: '' })
|
const movementFilters = ref({ building: '', case: '' })
|
||||||
@@ -300,7 +288,7 @@ const movementRows = computed(() => {
|
|||||||
const list = bovine.value?.movements ?? []
|
const list = bovine.value?.movements ?? []
|
||||||
return list.map(m => ({
|
return list.map(m => ({
|
||||||
id: m.id,
|
id: m.id,
|
||||||
building: m.buildingCase?.building?.label ?? '—',
|
building: m.buildingCase?.building?.label ?? m.building?.label ?? '—',
|
||||||
case: m.buildingCase?.caseNumber != null ? `Case ${m.buildingCase.caseNumber}` : '—',
|
case: m.buildingCase?.caseNumber != null ? `Case ${m.buildingCase.caseNumber}` : '—',
|
||||||
enteredAt: formatDate(m.enteredAt),
|
enteredAt: formatDate(m.enteredAt),
|
||||||
leftAt: m.leftAt ? formatDate(m.leftAt) : null,
|
leftAt: m.leftAt ? formatDate(m.leftAt) : null,
|
||||||
@@ -319,27 +307,16 @@ const filteredMovementRows = computed(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const submitMovement = async () => {
|
const submitMovement = async () => {
|
||||||
if (!newMovementCaseId.value || !newMovementDate.value || bovineId.value === null) return
|
if (!newMovementCaseId.value || bovineId.value === null) return
|
||||||
|
|
||||||
const buildingLabel = buildingOptions.value.find(o => o.value === Number(newMovementBuildingId.value))?.label ?? '—'
|
|
||||||
const caseLabel = caseOptions.value.find(o => o.value === Number(newMovementCaseId.value))?.label ?? '—'
|
|
||||||
const dateLabel = formatDate(newMovementDate.value)
|
|
||||||
const confirmed = window.confirm(
|
|
||||||
`Confirmer la création du mouvement ?\n\nBâtiment : ${buildingLabel}\nCase : ${caseLabel}\nDate : ${dateLabel}`
|
|
||||||
)
|
|
||||||
if (!confirmed) return
|
|
||||||
|
|
||||||
isSubmittingMovement.value = true
|
isSubmittingMovement.value = true
|
||||||
try {
|
try {
|
||||||
await api.post('bovine_movements', {
|
await api.post('bovine_movements', {
|
||||||
bovine: `/api/bovines/${bovineId.value}`,
|
bovine: `/api/bovines/${bovineId.value}`,
|
||||||
buildingCase: `/api/building_cases/${newMovementCaseId.value}`,
|
buildingCase: `/api/building_cases/${newMovementCaseId.value}`
|
||||||
enteredAt: newMovementDate.value
|
|
||||||
}, { toastSuccessMessage: 'Mouvement enregistré' })
|
}, { toastSuccessMessage: 'Mouvement enregistré' })
|
||||||
bovine.value = await api.get<BovinePassportData>(`bovines/${bovineId.value}`)
|
bovine.value = await api.get<BovinePassportData>(`bovines/${bovineId.value}`)
|
||||||
newMovementBuildingId.value = null
|
newMovementBuildingId.value = null
|
||||||
newMovementCaseId.value = null
|
newMovementCaseId.value = null
|
||||||
newMovementDate.value = todayIso()
|
|
||||||
movementSubmitted.value = false
|
movementSubmitted.value = false
|
||||||
} finally {
|
} finally {
|
||||||
isSubmittingMovement.value = false
|
isSubmittingMovement.value = false
|
||||||
|
|||||||
@@ -125,7 +125,7 @@
|
|||||||
{{ formatDate(item.arrivalDate) }}
|
{{ formatDate(item.arrivalDate) }}
|
||||||
</template>
|
</template>
|
||||||
<template #cell-buildingCase.building.label="{ item }">
|
<template #cell-buildingCase.building.label="{ item }">
|
||||||
{{ item.buildingCase?.building?.label ?? '—' }}
|
{{ item.effectiveBuilding?.label ?? '—' }}
|
||||||
</template>
|
</template>
|
||||||
<template #cell-buildingCase.caseNumber="{ item }">
|
<template #cell-buildingCase.caseNumber="{ item }">
|
||||||
{{ item.buildingCase?.caseNumber ?? '—' }}
|
{{ item.buildingCase?.caseNumber ?? '—' }}
|
||||||
|
|||||||
@@ -9,3 +9,34 @@ export async function createBovine(payload: BovinePayload) {
|
|||||||
toastSuccessKey: 'success.bovine.create'
|
toastSuccessKey: 'success.bovine.create'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function createBovines(nationalNumbers: string[]): Promise<{ created: BovineData[]; errors: string[] }> {
|
||||||
|
const created: BovineData[] = []
|
||||||
|
const errors: string[] = []
|
||||||
|
|
||||||
|
for (const nationalNumber of nationalNumbers) {
|
||||||
|
try {
|
||||||
|
const bovine = await createBovine({ nationalNumber })
|
||||||
|
if (bovine) {
|
||||||
|
created.push(bovine)
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
errors.push(nationalNumber)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { created, errors }
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getBovine(id: number) {
|
||||||
|
const api = useApi()
|
||||||
|
return api.get<BovineData>(`bovines/${id}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateBovine(id: number, payload: BovinePayload) {
|
||||||
|
const api = useApi()
|
||||||
|
return api.patch<BovineData>(`bovines/${id}`, payload, {
|
||||||
|
toastErrorKey: 'errors.bovine.update',
|
||||||
|
toastSuccessKey: 'success.bovine.update'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ export interface BovineData {
|
|||||||
arrivalDate: string | null
|
arrivalDate: string | null
|
||||||
exitDate: string | null
|
exitDate: string | null
|
||||||
buildingCase: BovineBuildingCaseRef | null
|
buildingCase: BovineBuildingCaseRef | null
|
||||||
|
building: BovineBuildingRef | null
|
||||||
|
effectiveBuilding: BovineBuildingRef | null
|
||||||
supplier: string | null
|
supplier: string | null
|
||||||
workNumber: string | null
|
workNumber: string | null
|
||||||
birthDate: string | null
|
birthDate: string | null
|
||||||
@@ -27,5 +29,9 @@ export interface BovineData {
|
|||||||
|
|
||||||
export type BovinePayload = {
|
export type BovinePayload = {
|
||||||
nationalNumber?: string
|
nationalNumber?: string
|
||||||
|
receivedWeight?: number | null
|
||||||
|
pricePerKg?: number | null
|
||||||
|
arrivalDate?: string | null
|
||||||
buildingCase?: string | null
|
buildingCase?: string | null
|
||||||
|
supplier?: string | null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ export const formatAgeLabel = (months: number | null | undefined): string => {
|
|||||||
const remaining = months % 12
|
const remaining = months % 12
|
||||||
let label = ''
|
let label = ''
|
||||||
if (years > 0) label = `${years} an${years > 1 ? 's' : ''}`
|
if (years > 0) label = `${years} an${years > 1 ? 's' : ''}`
|
||||||
if (remaining > 0) label += `${label ? ' ' : ''}${remaining} m`
|
if (remaining > 0) label += `${label ? ' ' : ''}${remaining} mois`
|
||||||
if (!label) label = '< 1 mois'
|
if (!label) label = '< 1 mois'
|
||||||
return label
|
return label
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,93 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Command;
|
||||||
|
|
||||||
|
use App\Entity\Bovine;
|
||||||
|
use App\Entity\BovineMovement;
|
||||||
|
use DateTimeImmutable;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Symfony\Component\Console\Attribute\AsCommand;
|
||||||
|
use Symfony\Component\Console\Command\Command;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
|
|
||||||
|
use function count;
|
||||||
|
|
||||||
|
#[AsCommand(
|
||||||
|
name: 'app:backfill-bovine-movements',
|
||||||
|
description: 'Crée un mouvement initial pour chaque bovin ayant une case ou un bâtiment mais aucun mouvement enregistré.'
|
||||||
|
)]
|
||||||
|
class BackfillBovineMovementsCommand extends Command
|
||||||
|
{
|
||||||
|
private const FLUSH_EVERY = 100;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
private readonly EntityManagerInterface $entityManager,
|
||||||
|
) {
|
||||||
|
parent::__construct();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||||
|
{
|
||||||
|
$io = new SymfonyStyle($input, $output);
|
||||||
|
|
||||||
|
$bovines = $this->entityManager->createQueryBuilder()
|
||||||
|
->select('b')
|
||||||
|
->from(Bovine::class, 'b')
|
||||||
|
->where('b.buildingCase IS NOT NULL OR b.building IS NOT NULL')
|
||||||
|
->andWhere('NOT EXISTS (SELECT 1 FROM '.BovineMovement::class.' m WHERE m.bovine = b)')
|
||||||
|
->getQuery()
|
||||||
|
->getResult()
|
||||||
|
;
|
||||||
|
|
||||||
|
$total = count($bovines);
|
||||||
|
if (0 === $total) {
|
||||||
|
$io->success('Aucun bovin à backfiller.');
|
||||||
|
|
||||||
|
return Command::SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
$io->info(sprintf('%d bovin(s) à backfiller.', $total));
|
||||||
|
|
||||||
|
$now = new DateTimeImmutable();
|
||||||
|
$created = 0;
|
||||||
|
$fallback = 0;
|
||||||
|
|
||||||
|
foreach ($bovines as $i => $bovine) {
|
||||||
|
$movement = new BovineMovement();
|
||||||
|
$movement->setBovine($bovine);
|
||||||
|
|
||||||
|
if (null !== $bovine->getBuildingCase()) {
|
||||||
|
$movement->setBuildingCase($bovine->getBuildingCase());
|
||||||
|
} else {
|
||||||
|
$movement->setBuilding($bovine->getBuilding());
|
||||||
|
}
|
||||||
|
|
||||||
|
$enteredAt = $bovine->getArrivalDate();
|
||||||
|
if (null === $enteredAt) {
|
||||||
|
$enteredAt = $now;
|
||||||
|
++$fallback;
|
||||||
|
}
|
||||||
|
$movement->setEnteredAt($enteredAt);
|
||||||
|
|
||||||
|
$this->entityManager->persist($movement);
|
||||||
|
++$created;
|
||||||
|
|
||||||
|
if (0 === ($i + 1) % self::FLUSH_EVERY) {
|
||||||
|
$this->entityManager->flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->entityManager->flush();
|
||||||
|
|
||||||
|
$io->success(sprintf('%d mouvement(s) créé(s).', $created));
|
||||||
|
if ($fallback > 0) {
|
||||||
|
$io->warning(sprintf("%d bovin(s) sans date d'arrivée → enteredAt = maintenant.", $fallback));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Command::SUCCESS;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,215 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Command;
|
||||||
|
|
||||||
|
use App\Entity\Bovine;
|
||||||
|
use App\Entity\Building;
|
||||||
|
use App\Entity\Supplier;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use PhpOffice\PhpSpreadsheet\IOFactory;
|
||||||
|
use Symfony\Component\Console\Attribute\AsCommand;
|
||||||
|
use Symfony\Component\Console\Command\Command;
|
||||||
|
use Symfony\Component\Console\Input\InputArgument;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
#[AsCommand(
|
||||||
|
name: 'app:feed-bovine-prices',
|
||||||
|
description: 'Met à jour le poids, le prix au kilo et le fournisseur des bovins existants depuis un fichier XLSX.'
|
||||||
|
)]
|
||||||
|
final class FeedBovinePricesCommand extends Command
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private EntityManagerInterface $em,
|
||||||
|
) {
|
||||||
|
parent::__construct();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function configure(): void
|
||||||
|
{
|
||||||
|
$this
|
||||||
|
->addArgument('file', InputArgument::REQUIRED, 'Chemin absolu vers le fichier XLSX')
|
||||||
|
->addOption('dry-run', null, InputOption::VALUE_NONE, 'Simule sans persister en BDD')
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||||
|
{
|
||||||
|
$io = new SymfonyStyle($input, $output);
|
||||||
|
$file = (string) $input->getArgument('file');
|
||||||
|
$dryRun = (bool) $input->getOption('dry-run');
|
||||||
|
|
||||||
|
if (!file_exists($file)) {
|
||||||
|
$io->error(sprintf('Fichier introuvable : %s', $file));
|
||||||
|
|
||||||
|
return Command::FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
$io->title('Feed bovins depuis '.basename($file));
|
||||||
|
if ($dryRun) {
|
||||||
|
$io->warning('Dry-run activé : aucune écriture en BDD.');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$spreadsheet = IOFactory::load($file);
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
$io->error('Impossible de lire le fichier : '.$e->getMessage());
|
||||||
|
|
||||||
|
return Command::FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sheet = $spreadsheet->getActiveSheet();
|
||||||
|
$highestRow = $sheet->getHighestRow();
|
||||||
|
|
||||||
|
// Pré-chargement des fournisseurs pour des lookups rapides (insensible casse).
|
||||||
|
$supplierByName = [];
|
||||||
|
foreach ($this->em->getRepository(Supplier::class)->findAll() as $supplier) {
|
||||||
|
$supplierByName[mb_strtoupper($supplier->getName())] = $supplier;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pré-chargement des bâtiments par code (insensible casse).
|
||||||
|
$buildingByCode = [];
|
||||||
|
foreach ($this->em->getRepository(Building::class)->findAll() as $building) {
|
||||||
|
$buildingByCode[mb_strtoupper($building->getCode())] = $building;
|
||||||
|
}
|
||||||
|
|
||||||
|
$bovineRepo = $this->em->getRepository(Bovine::class);
|
||||||
|
|
||||||
|
$stats = [
|
||||||
|
'total' => 0,
|
||||||
|
'updated' => 0,
|
||||||
|
'notFound' => 0,
|
||||||
|
'invalid' => 0,
|
||||||
|
'supplierMissing' => 0,
|
||||||
|
'buildingMissing' => 0,
|
||||||
|
];
|
||||||
|
$missingNationalNumbers = [];
|
||||||
|
$missingSuppliers = [];
|
||||||
|
$missingBuildings = [];
|
||||||
|
|
||||||
|
$io->progressStart($highestRow);
|
||||||
|
for ($row = 1; $row <= $highestRow; ++$row) {
|
||||||
|
++$stats['total'];
|
||||||
|
|
||||||
|
$rawNationalNumber = (string) ($sheet->getCell([1, $row])->getValue() ?? '');
|
||||||
|
$rawSupplier = (string) ($sheet->getCell([2, $row])->getValue() ?? '');
|
||||||
|
$rawWeight = $sheet->getCell([3, $row])->getValue();
|
||||||
|
$rawPrice = $sheet->getCell([4, $row])->getValue();
|
||||||
|
$rawBuilding = (string) ($sheet->getCell([5, $row])->getValue() ?? '');
|
||||||
|
|
||||||
|
$rawNationalNumber = trim($rawNationalNumber);
|
||||||
|
if ('' === $rawNationalNumber) {
|
||||||
|
++$stats['invalid'];
|
||||||
|
$io->progressAdvance();
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Garde : strip "FR" + espace optionnel uniquement s'il est présent.
|
||||||
|
$nationalNumber = preg_replace('/^FR\s*/i', '', $rawNationalNumber);
|
||||||
|
|
||||||
|
$bovine = $bovineRepo->findOneBy(['nationalNumber' => $nationalNumber]);
|
||||||
|
if (null === $bovine) {
|
||||||
|
++$stats['notFound'];
|
||||||
|
$missingNationalNumbers[] = $nationalNumber;
|
||||||
|
$io->progressAdvance();
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lookup supplier (peut être null si introuvable ou colonne vide).
|
||||||
|
$supplier = null;
|
||||||
|
$supplierName = mb_strtoupper(trim($rawSupplier));
|
||||||
|
if ('' !== $supplierName) {
|
||||||
|
$supplier = $supplierByName[$supplierName] ?? null;
|
||||||
|
if (null === $supplier) {
|
||||||
|
++$stats['supplierMissing'];
|
||||||
|
$missingSuppliers[$supplierName] = ($missingSuppliers[$supplierName] ?? 0) + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$weight = is_numeric($rawWeight) ? (int) $rawWeight : null;
|
||||||
|
$price = is_numeric($rawPrice) ? (float) $rawPrice : null;
|
||||||
|
|
||||||
|
if (null !== $weight) {
|
||||||
|
$bovine->setReceivedWeight($weight);
|
||||||
|
}
|
||||||
|
if (null !== $price) {
|
||||||
|
$bovine->setPricePerKg($price);
|
||||||
|
}
|
||||||
|
$bovine->setSupplier($supplier);
|
||||||
|
|
||||||
|
// Bâtiment direct : on n'écrase pas une affectation à une case existante.
|
||||||
|
$buildingCode = mb_strtoupper(trim($rawBuilding));
|
||||||
|
if ('' !== $buildingCode && null === $bovine->getBuildingCase()) {
|
||||||
|
$building = $buildingByCode[$buildingCode] ?? null;
|
||||||
|
if (null !== $building) {
|
||||||
|
$bovine->setBuilding($building);
|
||||||
|
} else {
|
||||||
|
++$stats['buildingMissing'];
|
||||||
|
$missingBuildings[$buildingCode] = ($missingBuildings[$buildingCode] ?? 0) + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
++$stats['updated'];
|
||||||
|
$io->progressAdvance();
|
||||||
|
}
|
||||||
|
$io->progressFinish();
|
||||||
|
|
||||||
|
if (!$dryRun) {
|
||||||
|
$this->em->flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
$io->section('Résultats');
|
||||||
|
$io->table(
|
||||||
|
['Métrique', 'Valeur'],
|
||||||
|
[
|
||||||
|
['Lignes totales', $stats['total']],
|
||||||
|
['Bovins mis à jour', $stats['updated']],
|
||||||
|
['Bovins introuvables', $stats['notFound']],
|
||||||
|
['Lignes invalides', $stats['invalid']],
|
||||||
|
['Fournisseurs introuvables (supplier=null)', $stats['supplierMissing']],
|
||||||
|
['Bâtiments introuvables (building non set)', $stats['buildingMissing']],
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
if ([] !== $missingNationalNumbers) {
|
||||||
|
$preview = array_slice($missingNationalNumbers, 0, 10);
|
||||||
|
$io->warning(sprintf(
|
||||||
|
'%d bovin(s) introuvable(s). Aperçu : %s%s',
|
||||||
|
count($missingNationalNumbers),
|
||||||
|
implode(', ', $preview),
|
||||||
|
count($missingNationalNumbers) > 10 ? '…' : '',
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ([] !== $missingSuppliers) {
|
||||||
|
$list = [];
|
||||||
|
foreach ($missingSuppliers as $name => $count) {
|
||||||
|
$list[] = sprintf('%s (%d)', $name, $count);
|
||||||
|
}
|
||||||
|
$io->warning('Fournisseurs introuvables (bovins rattachés en null) : '.implode(', ', $list));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ([] !== $missingBuildings) {
|
||||||
|
$list = [];
|
||||||
|
foreach ($missingBuildings as $code => $count) {
|
||||||
|
$list[] = sprintf('%s (%d)', $code, $count);
|
||||||
|
}
|
||||||
|
$io->warning('Bâtiments introuvables (champ non renseigné) : '.implode(', ', $list));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($dryRun) {
|
||||||
|
$io->success('Dry-run terminé. Relance sans --dry-run pour persister.');
|
||||||
|
} else {
|
||||||
|
$io->success('Feed terminé avec succès.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return Command::SUCCESS;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -96,6 +96,11 @@ class Bovine
|
|||||||
#[ApiProperty(readableLink: true)]
|
#[ApiProperty(readableLink: true)]
|
||||||
private ?BuildingCase $buildingCase = null;
|
private ?BuildingCase $buildingCase = null;
|
||||||
|
|
||||||
|
#[ORM\ManyToOne]
|
||||||
|
#[Groups(['bovine:read'])]
|
||||||
|
#[ApiProperty(readableLink: true)]
|
||||||
|
private ?Building $building = null;
|
||||||
|
|
||||||
#[ORM\ManyToOne]
|
#[ORM\ManyToOne]
|
||||||
#[Groups(['bovine:read', 'bovine:write', 'building_case:read'])]
|
#[Groups(['bovine:read', 'bovine:write', 'building_case:read'])]
|
||||||
private ?Supplier $supplier = null;
|
private ?Supplier $supplier = null;
|
||||||
@@ -239,6 +244,28 @@ class Bovine
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getBuilding(): ?Building
|
||||||
|
{
|
||||||
|
return $this->building;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setBuilding(?Building $building): static
|
||||||
|
{
|
||||||
|
$this->building = $building;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bâtiment effectif d'un bovin : la case affectée si elle existe (logique
|
||||||
|
* historique), sinon le bâtiment direct (fed depuis l'XLSX initial).
|
||||||
|
*/
|
||||||
|
#[Groups(['bovine:read', 'building_case:read'])]
|
||||||
|
public function getEffectiveBuilding(): ?Building
|
||||||
|
{
|
||||||
|
return $this->buildingCase?->getIdBuilding() ?? $this->building;
|
||||||
|
}
|
||||||
|
|
||||||
public function getSupplier(): ?Supplier
|
public function getSupplier(): ?Supplier
|
||||||
{
|
{
|
||||||
return $this->supplier;
|
return $this->supplier;
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ use Symfony\Component\Serializer\Attribute\Groups;
|
|||||||
processor: BovineMovementProcessor::class,
|
processor: BovineMovementProcessor::class,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
security: "is_granted('ROLE_BUREAU')",
|
security: "is_granted('ROLE_USER')",
|
||||||
)]
|
)]
|
||||||
class BovineMovement
|
class BovineMovement
|
||||||
{
|
{
|
||||||
@@ -44,8 +44,13 @@ class BovineMovement
|
|||||||
#[ApiProperty(readableLink: true)]
|
#[ApiProperty(readableLink: true)]
|
||||||
private ?BuildingCase $buildingCase = null;
|
private ?BuildingCase $buildingCase = null;
|
||||||
|
|
||||||
|
#[ORM\ManyToOne]
|
||||||
|
#[Groups(['bovine:read'])]
|
||||||
|
#[ApiProperty(readableLink: true)]
|
||||||
|
private ?Building $building = null;
|
||||||
|
|
||||||
#[ORM\Column(type: 'datetime_immutable')]
|
#[ORM\Column(type: 'datetime_immutable')]
|
||||||
#[Groups(['bovine:read', 'bovine_movement:write'])]
|
#[Groups(['bovine:read'])]
|
||||||
private DateTimeImmutable $enteredAt;
|
private DateTimeImmutable $enteredAt;
|
||||||
|
|
||||||
#[ORM\Column(type: 'datetime_immutable', nullable: true)]
|
#[ORM\Column(type: 'datetime_immutable', nullable: true)]
|
||||||
@@ -81,16 +86,23 @@ class BovineMovement
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getBuilding(): ?Building
|
||||||
|
{
|
||||||
|
return $this->building;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setBuilding(?Building $building): static
|
||||||
|
{
|
||||||
|
$this->building = $building;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
public function getEnteredAt(): DateTimeImmutable
|
public function getEnteredAt(): DateTimeImmutable
|
||||||
{
|
{
|
||||||
return $this->enteredAt;
|
return $this->enteredAt;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function hasEnteredAt(): bool
|
|
||||||
{
|
|
||||||
return isset($this->enteredAt);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setEnteredAt(DateTimeImmutable $enteredAt): static
|
public function setEnteredAt(DateTimeImmutable $enteredAt): static
|
||||||
{
|
{
|
||||||
$this->enteredAt = $enteredAt;
|
$this->enteredAt = $enteredAt;
|
||||||
|
|||||||
@@ -18,15 +18,13 @@ use PhpOffice\PhpSpreadsheet\Style\Border;
|
|||||||
use PhpOffice\PhpSpreadsheet\Style\Fill;
|
use PhpOffice\PhpSpreadsheet\Style\Fill;
|
||||||
use PhpOffice\PhpSpreadsheet\Worksheet\PageSetup;
|
use PhpOffice\PhpSpreadsheet\Worksheet\PageSetup;
|
||||||
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||||
use Psr\Log\LoggerInterface;
|
|
||||||
use Symfony\Component\HttpFoundation\RequestStack;
|
use Symfony\Component\HttpFoundation\RequestStack;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Throwable;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @implements ProviderInterface<Response>
|
* @implements ProviderInterface<Response>
|
||||||
*/
|
*/
|
||||||
final readonly class BovineInventoryExportProvider implements ProviderInterface
|
final class BovineInventoryExportProvider implements ProviderInterface
|
||||||
{
|
{
|
||||||
private const FARM_NAME = 'FERME SCEA LES NAUDS';
|
private const FARM_NAME = 'FERME SCEA LES NAUDS';
|
||||||
|
|
||||||
@@ -72,7 +70,6 @@ final readonly class BovineInventoryExportProvider implements ProviderInterface
|
|||||||
public function __construct(
|
public function __construct(
|
||||||
private BovineRepository $bovineRepository,
|
private BovineRepository $bovineRepository,
|
||||||
private RequestStack $requestStack,
|
private RequestStack $requestStack,
|
||||||
private LoggerInterface $logger,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public function provide(Operation $operation, array $uriVariables = [], array $context = []): Response
|
public function provide(Operation $operation, array $uriVariables = [], array $context = []): Response
|
||||||
@@ -256,16 +253,7 @@ final readonly class BovineInventoryExportProvider implements ProviderInterface
|
|||||||
// Lignes de données
|
// Lignes de données
|
||||||
$rowNumber = 5;
|
$rowNumber = 5;
|
||||||
foreach ($bovines as $bovine) {
|
foreach ($bovines as $bovine) {
|
||||||
try {
|
$this->writeBovineRow($sheet, $rowNumber, $bovine);
|
||||||
$this->writeBovineRow($sheet, $rowNumber, $bovine);
|
|
||||||
} catch (Throwable $e) {
|
|
||||||
$this->logger->warning('Export inventaire bovin : ligne ignorée suite à une erreur.', [
|
|
||||||
'bovineId' => $bovine->getId(),
|
|
||||||
'nationalNumber' => $bovine->getNationalNumber(),
|
|
||||||
'row' => $rowNumber,
|
|
||||||
'exception' => $e,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
++$rowNumber;
|
++$rowNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -288,7 +276,7 @@ final readonly class BovineInventoryExportProvider implements ProviderInterface
|
|||||||
$type = $bovine->getBovineType();
|
$type = $bovine->getBovineType();
|
||||||
$isLim = self::BREED_CODE_LIMOUSINE === $type?->getCode();
|
$isLim = self::BREED_CODE_LIMOUSINE === $type?->getCode();
|
||||||
$isCharo = self::BREED_CODE_CHAROLAISE === $type?->getCode();
|
$isCharo = self::BREED_CODE_CHAROLAISE === $type?->getCode();
|
||||||
$building = $bovine->getBuildingCase()?->getIdBuilding();
|
$building = $bovine->getBuildingCase()?->getIdBuilding() ?? $bovine->getBuilding();
|
||||||
$code = $building?->getCode();
|
$code = $building?->getCode();
|
||||||
|
|
||||||
$sheet->setCellValue('A'.$row, $isLim ? 'X' : '');
|
$sheet->setCellValue('A'.$row, $isLim ? 'X' : '');
|
||||||
@@ -296,25 +284,22 @@ final readonly class BovineInventoryExportProvider implements ProviderInterface
|
|||||||
? (int) $bovine->getWorkNumber()
|
? (int) $bovine->getWorkNumber()
|
||||||
: ($bovine->getWorkNumber() ?? ''));
|
: ($bovine->getWorkNumber() ?? ''));
|
||||||
$sheet->setCellValue('C'.$row, $isCharo ? 'X' : '');
|
$sheet->setCellValue('C'.$row, $isCharo ? 'X' : '');
|
||||||
$national = $bovine->getNationalNumber();
|
$sheet->setCellValue('D'.$row, 'FR '.$bovine->getNationalNumber());
|
||||||
$sheet->setCellValue('D'.$row, '' === $national ? '' : 'FR '.$national);
|
|
||||||
$sheet->setCellValue('E'.$row, 'B1' === $code ? 'X' : '');
|
$sheet->setCellValue('E'.$row, 'B1' === $code ? 'X' : '');
|
||||||
$sheet->setCellValue('F'.$row, 'B2' === $code ? 'X' : '');
|
$sheet->setCellValue('F'.$row, 'B2' === $code ? 'X' : '');
|
||||||
$sheet->setCellValue('G'.$row, 'B3' === $code ? 'X' : '');
|
$sheet->setCellValue('G'.$row, 'B3' === $code ? 'X' : '');
|
||||||
$sheet->setCellValue('H'.$row, $bovine->getBuildingCase()?->getCaseNumber() ?? '');
|
$sheet->setCellValue('H'.$row, $bovine->getBuildingCase()?->getCaseNumber() ?? '');
|
||||||
$sheet->setCellValue('I'.$row, $bovine->getSupplier()?->getName() ?? '');
|
$sheet->setCellValue('I'.$row, $bovine->getSupplier()?->getName() ?? '');
|
||||||
|
|
||||||
$birth = $bovine->getBirthDate();
|
$birth = $bovine->getBirthDate();
|
||||||
$arrival = $bovine->getArrivalDate();
|
$arrival = $bovine->getArrivalDate();
|
||||||
$birthExcel = $this->safePhpToExcel($birth);
|
if (null !== $birth) {
|
||||||
$arrivalExcel = $this->safePhpToExcel($arrival);
|
$sheet->setCellValue('J'.$row, ExcelDate::PHPToExcel($birth));
|
||||||
if (null !== $birthExcel) {
|
|
||||||
$sheet->setCellValue('J'.$row, $birthExcel);
|
|
||||||
}
|
}
|
||||||
if (null !== $arrivalExcel) {
|
if (null !== $arrival) {
|
||||||
$sheet->setCellValue('K'.$row, $arrivalExcel);
|
$sheet->setCellValue('K'.$row, ExcelDate::PHPToExcel($arrival));
|
||||||
}
|
}
|
||||||
if (null !== $birth && null !== $arrival && $birth <= $arrival) {
|
if (null !== $birth && null !== $arrival) {
|
||||||
$diff = $birth->diff($arrival);
|
$diff = $birth->diff($arrival);
|
||||||
$sheet->setCellValue('L'.$row, ($diff->y * 12) + $diff->m);
|
$sheet->setCellValue('L'.$row, ($diff->y * 12) + $diff->m);
|
||||||
}
|
}
|
||||||
@@ -358,24 +343,6 @@ final readonly class BovineInventoryExportProvider implements ProviderInterface
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Convertit une date PHP en numéro de série Excel, ou null si la date est absente / hors plage Excel (< 1900).
|
|
||||||
*/
|
|
||||||
private function safePhpToExcel(?DateTimeImmutable $date): ?float
|
|
||||||
{
|
|
||||||
if (null === $date) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$value = ExcelDate::PHPToExcel($date);
|
|
||||||
} catch (Throwable) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return is_float($value) ? $value : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sous-titre dynamique selon les tranches d'âge cochées.
|
* Sous-titre dynamique selon les tranches d'âge cochées.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -25,15 +25,16 @@ final class BovineMovementProcessor implements ProcessorInterface
|
|||||||
return $this->persistProcessor->process($data, $operation, $uriVariables, $context);
|
return $this->persistProcessor->process($data, $operation, $uriVariables, $context);
|
||||||
}
|
}
|
||||||
|
|
||||||
$enteredAt = $data->hasEnteredAt() ? $data->getEnteredAt() : new DateTimeImmutable();
|
$now = new DateTimeImmutable();
|
||||||
$data->setEnteredAt($enteredAt);
|
$data->setEnteredAt($now);
|
||||||
$data->setLeftAt(null);
|
$data->setLeftAt(null);
|
||||||
|
$data->setBuilding(null);
|
||||||
|
|
||||||
$bovine = $data->getBovine();
|
$bovine = $data->getBovine();
|
||||||
|
|
||||||
$openMovement = $this->movementRepository->findOpenMovement($bovine);
|
$openMovement = $this->movementRepository->findOpenMovement($bovine);
|
||||||
if (null !== $openMovement) {
|
if (null !== $openMovement) {
|
||||||
$openMovement->setLeftAt($enteredAt);
|
$openMovement->setLeftAt($now);
|
||||||
}
|
}
|
||||||
|
|
||||||
$bovine->setBuildingCase($data->getBuildingCase());
|
$bovine->setBuildingCase($data->getBuildingCase());
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
.sheet { width: auto; }
|
.sheet { width: auto; }
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
margin: 0 0 8px 0;
|
margin: 8px 0 16px 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
@@ -243,23 +243,11 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<table style="width:auto; border-collapse:collapse; margin-bottom: 8px; margin-top: 8px">
|
{% set buildingNumber = buildingCase.idBuilding.label ?? '' %}
|
||||||
<tr>
|
{% set buildingNumber = buildingNumber|replace({'Bâtiment': '', 'BÂTIMENT': '', 'Batiment': '', 'BATIMENT': ''})|trim %}
|
||||||
<td style="border:0; text-align:left; font-weight:700; font-size: 18px; padding-right: 8px;">BATIMENT N°</td>
|
<div style="font-weight:700; text-align:left; font-size: 18px; margin-bottom: 16px;">
|
||||||
<td style="border:1px solid #2b2b2b; width: 22px; height: 22px;"></td>
|
BÂTIMENT N°{{ buildingNumber }} - CASE N°{{ buildingCase.caseNumber ?? '' }}
|
||||||
<td style="border:0; width: 22px;"></td>
|
</div>
|
||||||
<td style="border:1px solid #2b2b2b; width: 22px; height: 22px;"></td>
|
|
||||||
<td style="border:0; width: 22px;"></td>
|
|
||||||
<td style="border:1px solid #2b2b2b; width: 22px; height: 22px;"></td>
|
|
||||||
<td style="border:0; width: 32px;"></td>
|
|
||||||
<td style="border:0; text-align:left; font-weight:700; font-size: 18px; padding-right: 8px;">CASE N°</td>
|
|
||||||
<td style="border:1px solid #2b2b2b; width: 22px; height: 22px;"></td>
|
|
||||||
<td style="border:0; width: 22px;"></td>
|
|
||||||
<td style="border:1px solid #2b2b2b; width: 22px; height: 22px;"></td>
|
|
||||||
<td style="border:0; width: 22px;"></td>
|
|
||||||
<td style="border:1px solid #2b2b2b; width: 22px; height: 22px;"></td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<!-- =========================
|
<!-- =========================
|
||||||
TABLEAU PRINCIPAL
|
TABLEAU PRINCIPAL
|
||||||
|
|||||||
Reference in New Issue
Block a user