Compare commits
1 Commits
v0.0.42
...
feat/admin
| Author | SHA1 | Date | |
|---|---|---|---|
| 311f523647 |
6
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
6
.idea/inspectionProfiles/Project_Default.xml
generated
Normal 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>
|
||||||
1
.idea/php.xml
generated
1
.idea/php.xml
generated
@@ -15,7 +15,6 @@
|
|||||||
<component name="PhpCSFixer">
|
<component name="PhpCSFixer">
|
||||||
<phpcsfixer_settings>
|
<phpcsfixer_settings>
|
||||||
<PhpCSFixerConfiguration tool_path="$PROJECT_DIR$/vendor/bin/php-cs-fixer" />
|
<PhpCSFixerConfiguration tool_path="$PROJECT_DIR$/vendor/bin/php-cs-fixer" />
|
||||||
<phpcs_fixer_by_interpreter asDefaultInterpreter="true" interpreter_id="990ff521-e6e9-4080-9cc9-228367d597f9" tool_path="\\wsl.localhost\Ubuntu-24.04\home\matte\Ferme\vendor\bin\php-cs-fixer" timeout="30000" />
|
|
||||||
</phpcsfixer_settings>
|
</phpcsfixer_settings>
|
||||||
</component>
|
</component>
|
||||||
<component name="PhpCodeSniffer">
|
<component name="PhpCodeSniffer">
|
||||||
|
|||||||
240
.idea/workspace.xml
generated
240
.idea/workspace.xml
generated
@@ -4,28 +4,16 @@
|
|||||||
<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="feat : Ajout de la sélection des bovins étape 3 d'une réception (WIP)">
|
<list default="true" id="7c107abe-5995-4428-8429-b146aaca8386" name="Changes" comment="feat : test auto-tag-develop.yml (auto incrément version)">
|
||||||
<change afterPath="$PROJECT_DIR$/frontend/components/shipment/shipment-form.vue" afterDir="false" />
|
<change afterPath="$PROJECT_DIR$/frontend/composables/useAppVersion.ts" afterDir="false" />
|
||||||
<change afterPath="$PROJECT_DIR$/frontend/pages/shipment/[[id]].vue" afterDir="false" />
|
|
||||||
<change afterPath="$PROJECT_DIR$/frontend/services/bovin-shipment.ts" afterDir="false" />
|
|
||||||
<change afterPath="$PROJECT_DIR$/frontend/services/customer.ts" afterDir="false" />
|
|
||||||
<change afterPath="$PROJECT_DIR$/frontend/services/dto/bovin-shipment-data.ts" afterDir="false" />
|
|
||||||
<change afterPath="$PROJECT_DIR$/frontend/services/dto/customer-data.ts" afterDir="false" />
|
|
||||||
<change afterPath="$PROJECT_DIR$/frontend/services/dto/shipment-data.ts" afterDir="false" />
|
|
||||||
<change afterPath="$PROJECT_DIR$/frontend/services/dto/shipment-type-data.ts" afterDir="false" />
|
|
||||||
<change afterPath="$PROJECT_DIR$/frontend/services/shipment-type.ts" afterDir="false" />
|
|
||||||
<change afterPath="$PROJECT_DIR$/frontend/services/shipment.ts" afterDir="false" />
|
|
||||||
<change afterPath="$PROJECT_DIR$/frontend/stores/shipment.ts" 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$/config/packages/security.yaml" beforeDir="false" afterPath="$PROJECT_DIR$/config/packages/security.yaml" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/config/reference.php" beforeDir="false" afterPath="$PROJECT_DIR$/config/reference.php" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/config/reference.php" beforeDir="false" afterPath="$PROJECT_DIR$/config/reference.php" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/frontend/components/ui/UiNumberInput.vue" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/components/ui/UiNumberInput.vue" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/frontend/app.vue" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/app.vue" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/frontend/constants/steps.ts" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/constants/steps.ts" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/frontend/layouts/default.vue" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/layouts/default.vue" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/frontend/i18n/locales/fr.json" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/i18n/locales/fr.json" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/frontend/pages/login.vue" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/pages/login.vue" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/frontend/pages/index.vue" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/pages/index.vue" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/src/Dto/AppVersion.php" beforeDir="false" afterPath="$PROJECT_DIR$/src/ApiResource/AppVersion.php" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/frontend/services/reception.ts" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/services/reception.ts" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/src/State/AppVersionProvider.php" beforeDir="false" afterPath="$PROJECT_DIR$/src/State/AppVersionProvider.php" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/src/Entity/Address.php" beforeDir="false" afterPath="$PROJECT_DIR$/src/Entity/Address.php" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/src/Entity/BovinShipment.php" beforeDir="false" afterPath="$PROJECT_DIR$/src/Entity/BovinShipment.php" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/src/Entity/Shipment.php" beforeDir="false" afterPath="$PROJECT_DIR$/src/Entity/Shipment.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" />
|
||||||
@@ -56,7 +44,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="fix/makefile" />
|
<entry key="$PROJECT_DIR$" value="feat/256-reception-etape-3-bovin" />
|
||||||
</map>
|
</map>
|
||||||
</option>
|
</option>
|
||||||
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
||||||
@@ -237,36 +225,36 @@
|
|||||||
<option name="hideEmptyMiddlePackages" value="true" />
|
<option name="hideEmptyMiddlePackages" value="true" />
|
||||||
<option name="showLibraryContents" value="true" />
|
<option name="showLibraryContents" value="true" />
|
||||||
</component>
|
</component>
|
||||||
<component name="PropertiesComponent"><![CDATA[{
|
<component name="PropertiesComponent">{
|
||||||
"keyToString": {
|
"keyToString": {
|
||||||
"RunOnceActivity.MCP Project settings loaded": "true",
|
"RunOnceActivity.MCP Project settings loaded": "true",
|
||||||
"RunOnceActivity.ShowReadmeOnStart": "true",
|
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||||
"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",
|
||||||
"git-widget-placeholder": "feat/271-expedition-etape-1",
|
"git-widget-placeholder": "develop",
|
||||||
"last_opened_file_path": "/home/sroy/Documents/test/Ferme",
|
"last_opened_file_path": "/home/sroy/Documents/test/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",
|
||||||
"node.js.selected.package.eslint": "(autodetect)",
|
"node.js.selected.package.eslint": "(autodetect)",
|
||||||
"node.js.selected.package.tslint": "(autodetect)",
|
"node.js.selected.package.tslint": "(autodetect)",
|
||||||
"nodejs_package_manager_path": "npm",
|
"nodejs_package_manager_path": "npm",
|
||||||
"settings.editor.selected.configurable": "configurable.tailwindcss",
|
"settings.editor.selected.configurable": "configurable.tailwindcss",
|
||||||
"ts.external.directory.path": "/opt/phpstorm/plugins/javascript-plugin/jsLanguageServicesImpl/external",
|
"ts.external.directory.path": "/opt/phpstorm/plugins/javascript-plugin/jsLanguageServicesImpl/external",
|
||||||
"vue.rearranger.settings.migration": "true"
|
"vue.rearranger.settings.migration": "true"
|
||||||
},
|
},
|
||||||
"keyToStringList": {
|
"keyToStringList": {
|
||||||
"DatabaseDriversLRU": [
|
"DatabaseDriversLRU": [
|
||||||
"postgresql"
|
"postgresql"
|
||||||
],
|
],
|
||||||
"com.intellij.ide.scratch.ScratchImplUtil$2/New Scratch File": [
|
"com.intellij.ide.scratch.ScratchImplUtil$2/New Scratch File": [
|
||||||
"TEXT"
|
"TEXT"
|
||||||
],
|
],
|
||||||
"vue.recent.templates": [
|
"vue.recent.templates": [
|
||||||
"Vue Composition API Component"
|
"Vue Composition API Component"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}]]></component>
|
}</component>
|
||||||
<component name="RecentsManager">
|
<component name="RecentsManager">
|
||||||
<key name="MoveFile.RECENT_KEYS">
|
<key name="MoveFile.RECENT_KEYS">
|
||||||
<recent name="\\wsl.localhost\Ubuntu-24.04\home\m-tristan\workspace\Ferme" />
|
<recent name="\\wsl.localhost\Ubuntu-24.04\home\m-tristan\workspace\Ferme" />
|
||||||
@@ -314,70 +302,6 @@
|
|||||||
<workItem from="1770195959162" duration="18915000" />
|
<workItem from="1770195959162" duration="18915000" />
|
||||||
<workItem from="1770274844804" duration="3940000" />
|
<workItem from="1770274844804" duration="3940000" />
|
||||||
</task>
|
</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" />
|
|
||||||
<created>1768237763998</created>
|
|
||||||
<option name="number" value="00001" />
|
|
||||||
<option name="presentableId" value="LOCAL-00001" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1768237763998</updated>
|
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00002" summary="feat : Ajout de zod, création d'un composant de chargement loading-dots.vue et finalisation du flow d'une reception">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1768316052474</created>
|
|
||||||
<option name="number" value="00002" />
|
|
||||||
<option name="presentableId" value="LOCAL-00002" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1768316052474</updated>
|
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00003" summary="feat : Ajout d'un composable pour la pesée qui sera réutilisable pour l'expédition, ajout de contrainte sur les entity de reception et weight pour plus de robustesse et correction de la class active des liens dans la nav">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1768316835575</created>
|
|
||||||
<option name="number" value="00003" />
|
|
||||||
<option name="presentableId" value="LOCAL-00003" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1768316835575</updated>
|
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00004" summary="feat : update du fichier AGENTS.md">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1768316965511</created>
|
|
||||||
<option name="number" value="00004" />
|
|
||||||
<option name="presentableId" value="LOCAL-00004" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1768316965511</updated>
|
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00005" summary="feat : update du fichier README.md et CHANGELOG.md">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1768317786187</created>
|
|
||||||
<option name="number" value="00005" />
|
|
||||||
<option name="presentableId" value="LOCAL-00005" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1768317786187</updated>
|
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00006" summary="fix : correction du useApi pour qu'il n'y ait plus de retry lors d'une erreur 500 par exemple">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1768318875533</created>
|
|
||||||
<option name="number" value="00006" />
|
|
||||||
<option name="presentableId" value="LOCAL-00006" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1768318875533</updated>
|
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00007" summary="test : ajout de TU sur les services et providers">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1768318921478</created>
|
|
||||||
<option name="number" value="00007" />
|
|
||||||
<option name="presentableId" value="LOCAL-00007" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1768318921478</updated>
|
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00008" summary="feat : ajout de la génération du bon de reception, correction de la base du formulaire multi-etape de reception et ajout d'une gestion d'erreur global">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1768498751836</created>
|
|
||||||
<option name="number" value="00008" />
|
|
||||||
<option name="presentableId" value="LOCAL-00008" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1768498751836</updated>
|
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00009" summary="feat : ajout d'une gestion d'erreur au global côté front avec la lib toaster et I18n pour centraliser les messages d'erreur">
|
<task id="LOCAL-00009" summary="feat : ajout d'une gestion d'erreur au global côté front avec la lib toaster et I18n pour centraliser les messages d'erreur">
|
||||||
<option name="closed" value="true" />
|
<option name="closed" value="true" />
|
||||||
<created>1768555180530</created>
|
<created>1768555180530</created>
|
||||||
@@ -706,7 +630,71 @@
|
|||||||
<option name="project" value="LOCAL" />
|
<option name="project" value="LOCAL" />
|
||||||
<updated>1770217875423</updated>
|
<updated>1770217875423</updated>
|
||||||
</task>
|
</task>
|
||||||
<option name="localTasksCounter" value="50" />
|
<task id="LOCAL-00050" summary="feat : Ajout de la sélection des bovins étape 3 d'une réception (WIP)">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1770283622425</created>
|
||||||
|
<option name="number" value="00050" />
|
||||||
|
<option name="presentableId" value="LOCAL-00050" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1770283622425</updated>
|
||||||
|
</task>
|
||||||
|
<task id="LOCAL-00051" summary="feat : ajout du responsive sur la navbar et la page d'accueil">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1770308927948</created>
|
||||||
|
<option name="number" value="00051" />
|
||||||
|
<option name="presentableId" value="LOCAL-00051" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1770308927948</updated>
|
||||||
|
</task>
|
||||||
|
<task id="LOCAL-00052" summary="fix : logo centré en mod mobile">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1770310504254</created>
|
||||||
|
<option name="number" value="00052" />
|
||||||
|
<option name="presentableId" value="LOCAL-00052" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1770310504254</updated>
|
||||||
|
</task>
|
||||||
|
<task id="LOCAL-00053" summary="feat : ajout d'un numéro de version automatique via la CI">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1770369945257</created>
|
||||||
|
<option name="number" value="00053" />
|
||||||
|
<option name="presentableId" value="LOCAL-00053" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1770369945257</updated>
|
||||||
|
</task>
|
||||||
|
<task id="LOCAL-00054" summary="feat : update numéro de version">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1770370216428</created>
|
||||||
|
<option name="number" value="00054" />
|
||||||
|
<option name="presentableId" value="LOCAL-00054" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1770370216428</updated>
|
||||||
|
</task>
|
||||||
|
<task id="LOCAL-00055" summary="fix : auto-tag-develop.yml">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1770370700697</created>
|
||||||
|
<option name="number" value="00055" />
|
||||||
|
<option name="presentableId" value="LOCAL-00055" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1770370700698</updated>
|
||||||
|
</task>
|
||||||
|
<task id="LOCAL-00056" summary="fix : auto-tag-develop.yml">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1770370919043</created>
|
||||||
|
<option name="number" value="00056" />
|
||||||
|
<option name="presentableId" value="LOCAL-00056" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1770370919043</updated>
|
||||||
|
</task>
|
||||||
|
<task id="LOCAL-00057" summary="feat : test auto-tag-develop.yml (auto incrément version)">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1770371073055</created>
|
||||||
|
<option name="number" value="00057" />
|
||||||
|
<option name="presentableId" value="LOCAL-00057" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1770371073055</updated>
|
||||||
|
</task>
|
||||||
|
<option name="localTasksCounter" value="58" />
|
||||||
<servers />
|
<servers />
|
||||||
</component>
|
</component>
|
||||||
<component name="TypeScriptGeneratedFilesManager">
|
<component name="TypeScriptGeneratedFilesManager">
|
||||||
@@ -756,12 +744,6 @@
|
|||||||
</option>
|
</option>
|
||||||
</component>
|
</component>
|
||||||
<component name="VcsManagerConfiguration">
|
<component name="VcsManagerConfiguration">
|
||||||
<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" />
|
|
||||||
<MESSAGE value="fix : modification du script de déploiement pour corriger le problème d'écriture des logs de prod" />
|
|
||||||
<MESSAGE value="fix : doc de déploiement" />
|
|
||||||
<MESSAGE value="fix : doc et script de déploiement" />
|
|
||||||
<MESSAGE value="fix : gitea workflow" />
|
<MESSAGE value="fix : gitea workflow" />
|
||||||
<MESSAGE value="fix : script de déploiement" />
|
<MESSAGE value="fix : script de déploiement" />
|
||||||
<MESSAGE value="feat : ajout plus d'information sur la liste des réceptions côté front sur la page d'accueil" />
|
<MESSAGE value="feat : ajout plus d'information sur la liste des réceptions côté front sur la page d'accueil" />
|
||||||
@@ -781,23 +763,13 @@
|
|||||||
<MESSAGE value="feat : ajout de colonne pour les Supplier, Address. Modification du numéro de réception et ajout de fixtures" />
|
<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" />
|
<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)" />
|
<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)" />
|
<MESSAGE value="feat : ajout du responsive sur la navbar et la page d'accueil" />
|
||||||
</component>
|
<MESSAGE value="fix : logo centré en mod mobile" />
|
||||||
<component name="XDebuggerManager">
|
<MESSAGE value="feat : ajout d'un numéro de version automatique via la CI" />
|
||||||
<breakpoint-manager>
|
<MESSAGE value="feat : update numéro de version" />
|
||||||
<breakpoints>
|
<MESSAGE value="fix : auto-tag-develop.yml" />
|
||||||
<line-breakpoint enabled="true" type="php">
|
<MESSAGE value="feat : test auto-tag-develop.yml (auto incrément version)" />
|
||||||
<url>file://$PROJECT_DIR$/src/Entity/ReceptionPelletBuilding.php</url>
|
<option name="LAST_COMMIT_MESSAGE" value="feat : test auto-tag-develop.yml (auto incrément version)" />
|
||||||
<line>6</line>
|
|
||||||
<option name="timeStamp" value="3" />
|
|
||||||
</line-breakpoint>
|
|
||||||
<line-breakpoint enabled="true" type="javascript">
|
|
||||||
<url>file://$PROJECT_DIR$/frontend/services/shipment.ts</url>
|
|
||||||
<properties lambdaOrdinal="-1" />
|
|
||||||
<option name="timeStamp" value="37" />
|
|
||||||
</line-breakpoint>
|
|
||||||
</breakpoints>
|
|
||||||
</breakpoint-manager>
|
|
||||||
</component>
|
</component>
|
||||||
<component name="XSLT-Support.FileAssociations.UIState">
|
<component name="XSLT-Support.FileAssociations.UIState">
|
||||||
<expand />
|
<expand />
|
||||||
|
|||||||
15
CHANGELOG.md
15
CHANGELOG.md
@@ -29,21 +29,6 @@ Ajouter dans le fichier .env du frontend
|
|||||||
* Finalisation de la partie réception de marchandise
|
* Finalisation de la partie réception de marchandise
|
||||||
* [#267] Lister les réceptions en attente
|
* [#267] Lister les réceptions en attente
|
||||||
* [#268] Lister les réceptions terminées
|
* [#268] Lister les réceptions terminées
|
||||||
* [#316] Admin liste des transporteurs
|
|
||||||
* [#312] Creation administration listing fournisseurs
|
|
||||||
* [#315] Creation page admin utilisateur
|
|
||||||
* [#317] Admin modification creation transporteur
|
|
||||||
* [#318] Affichage modification reception terminée
|
|
||||||
* [#320] Affichage modification reception terminée suite
|
|
||||||
* [#271] Créer une nouvelle expédition (étape 1)
|
|
||||||
* [#272] Créer une nouvelle expédition (étape 2)
|
|
||||||
* [#273] Créer une nouvelle expédition (étape 3)
|
|
||||||
* [#256] Créer une nouvelle réception (étape 3 - bovin)
|
|
||||||
* [#314] Création d'une page d'administration : listing des utilisateurs
|
|
||||||
* [#313] Admin modification creation fournisseur
|
|
||||||
* [#275] Lister les expéditions en attente
|
|
||||||
* [#276] Lister les expéditions terminées
|
|
||||||
* [#324] Creation page admin listing clients
|
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
parameters:
|
parameters:
|
||||||
app.version: '0.0.42'
|
app.version: '0.0.32'
|
||||||
|
|||||||
@@ -1,81 +0,0 @@
|
|||||||
<template>
|
|
||||||
<form @submit.prevent="validateForm">
|
|
||||||
<div class="flex items-center justify-between gap-10">
|
|
||||||
<div>
|
|
||||||
<h1 class="text-3xl font-bold uppercase">
|
|
||||||
{{ props.address ? "Modification d'une adresse" : "Ajout d'une adresse" }}
|
|
||||||
</h1>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button
|
|
||||||
class="text-xl uppercase bg-primary-500 text-white h-[50px] w-[272px]"
|
|
||||||
type="submit"
|
|
||||||
:disabled="isLoading"
|
|
||||||
>
|
|
||||||
{{ props.address? "Sauvegarder" : "Ajouter" }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="grid grid-cols-2 gap-y-16 gap-x-12 mb-16 mt-10">
|
|
||||||
<UiTextInput id="address-label" v-model="form.label" label="Libellé" />
|
|
||||||
<UiTextInput id="address-street" v-model="form.street" label="Rue" />
|
|
||||||
<UiTextInput id="address-street2" v-model="form.street2" label="Complément" />
|
|
||||||
<UiTextInput id="address-postalCode" v-model="form.postalCode" label="Code postal" />
|
|
||||||
<UiTextInput id="address-city" v-model="form.city" label="Ville" />
|
|
||||||
<UiTextInput id="address-country" v-model="form.countryCode" label="Pays" />
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { AddressPayload } from "~/services/address"
|
|
||||||
|
|
||||||
|
|
||||||
const route = useRoute()
|
|
||||||
const router = useRouter()
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
type?: "supplier" | "customer",
|
|
||||||
address?: AddressPayload | null
|
|
||||||
}>()
|
|
||||||
|
|
||||||
const isLoading = ref(false)
|
|
||||||
|
|
||||||
const emptyForm = (): AddressPayload => ({
|
|
||||||
label: "",
|
|
||||||
street: "",
|
|
||||||
street2: null,
|
|
||||||
postalCode: "",
|
|
||||||
city: "",
|
|
||||||
countryCode: "",
|
|
||||||
})
|
|
||||||
|
|
||||||
const form = reactive<AddressPayload>(emptyForm())
|
|
||||||
|
|
||||||
const hydrateForm = (address?: AddressPayload | null) => {
|
|
||||||
const data = address ?? emptyForm()
|
|
||||||
form.label = data.label ?? ""
|
|
||||||
form.street = data.street ?? ""
|
|
||||||
form.street2 = data.street2 ?? null
|
|
||||||
form.postalCode = data.postalCode ?? ""
|
|
||||||
form.city = data.city ?? ""
|
|
||||||
form.countryCode = data.countryCode ?? ""
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.address,
|
|
||||||
(addr) => {
|
|
||||||
hydrateForm(addr)
|
|
||||||
},
|
|
||||||
{ immediate: true }
|
|
||||||
)
|
|
||||||
|
|
||||||
const validateForm = () => {
|
|
||||||
if (isLoading.value) return
|
|
||||||
emit("validate", {...form})
|
|
||||||
}
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
|
||||||
(event: 'validate', form: AddressPayload): void
|
|
||||||
}>()
|
|
||||||
</script>
|
|
||||||
@@ -123,7 +123,6 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
@@ -143,9 +142,21 @@ import type {DriverData} from '~/services/dto/driver-data'
|
|||||||
import {getDriverList} from '~/services/driver'
|
import {getDriverList} from '~/services/driver'
|
||||||
import type {VehicleData} from '~/services/dto/vehicle-data'
|
import type {VehicleData} from '~/services/dto/vehicle-data'
|
||||||
import {getVehicleList} from '~/services/vehicle'
|
import {getVehicleList} from '~/services/vehicle'
|
||||||
import {RECEPTION_TYPE_CODES, SUPPLIER_CODE} from "~/utils/constants";
|
import {RECEPTION_TYPE_CODES, SUPLLIER_CODE} from "~/utils/constants";
|
||||||
import {deleteReceptionBovine, getReceptionBovineList} from "~/services/reception-bovine";
|
import {deleteReceptionBovine, getReceptionBovineList} from "~/services/reception-bovine";
|
||||||
import type {ReceptionFormData} from "~/services/dto/reception-data";
|
|
||||||
|
type ReceptionFormData = {
|
||||||
|
licensePlate: string
|
||||||
|
receptionDate: string
|
||||||
|
receptionTypeId: string
|
||||||
|
userId: string
|
||||||
|
supplierId: string
|
||||||
|
addressId: string
|
||||||
|
truckId: string
|
||||||
|
carrierId: string
|
||||||
|
driverId: string
|
||||||
|
vehicleId: string
|
||||||
|
}
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const receptionStore = useReceptionStore()
|
const receptionStore = useReceptionStore()
|
||||||
@@ -184,7 +195,7 @@ const selectedCarrier = computed(() =>
|
|||||||
carriers.value.find((carrier) => String(carrier.id) === form.carrierId) ?? null
|
carriers.value.find((carrier) => String(carrier.id) === form.carrierId) ?? null
|
||||||
)
|
)
|
||||||
// Indique si le transporteur est LIOT
|
// Indique si le transporteur est LIOT
|
||||||
const isLiotCarrier = computed(() => selectedCarrier.value?.code === SUPPLIER_CODE.LIOT)
|
const isLiotCarrier = computed(() => selectedCarrier.value?.code === SUPLLIER_CODE.LIOT)
|
||||||
// Adresses disponibles pour le fournisseur sélectionné
|
// Adresses disponibles pour le fournisseur sélectionné
|
||||||
const supplierAddresses = computed(() => {
|
const supplierAddresses = computed(() => {
|
||||||
const supplierId = Number(form.supplierId)
|
const supplierId = Number(form.supplierId)
|
||||||
|
|||||||
@@ -74,9 +74,7 @@ const printReceipt = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await saveWeight()
|
await saveWeight()
|
||||||
const reception = receptionStore.current
|
await printPdf(`/receptions/${receptionStore.current.id}/receipt`)
|
||||||
const filename = `${reception.identificationNumber ?? reception.id}_${reception.supplier?.name ?? 'fournisseur'}_${reception.licensePlate ?? 'immat'}.pdf`
|
|
||||||
await printPdf(`/receptions/${reception.id}/receipt`, filename)
|
|
||||||
|
|
||||||
// Laisse le temps a la boite de dialogue d'impression de s'ouvrir.
|
// Laisse le temps a la boite de dialogue d'impression de s'ouvrir.
|
||||||
await new Promise((resolve) => setTimeout(resolve, 600))
|
await new Promise((resolve) => setTimeout(resolve, 600))
|
||||||
|
|||||||
@@ -1,183 +0,0 @@
|
|||||||
<template>
|
|
||||||
<form @submit.prevent="validate">
|
|
||||||
<div
|
|
||||||
class="flex flex-col items-center gap-16">
|
|
||||||
<div
|
|
||||||
class="flex flex-row gap-6 items-center">
|
|
||||||
<div
|
|
||||||
v-for="type in bovineType"
|
|
||||||
:key="type.id"
|
|
||||||
class="flex flex-row mb-2 gap-6 ">
|
|
||||||
<UiNumberInput
|
|
||||||
:label="type.label"
|
|
||||||
:code="type.code"
|
|
||||||
v-model="bovineQuantities[String(type.id)]"
|
|
||||||
:disabled="!auth.isAdmin"
|
|
||||||
:placeholder="0"
|
|
||||||
:min="0"
|
|
||||||
:max="10"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class=" flex flex-row mb-2 gap-6">
|
|
||||||
<UiNumberInput
|
|
||||||
label="Autres"
|
|
||||||
v-model="otherQuantity"
|
|
||||||
:disabled="!auth.isAdmin"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
class="text-xl uppercase bg-primary-500 text-white h-[50px] w-[272px]"
|
|
||||||
:disabled="!auth.isAdmin"
|
|
||||||
>Valider
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</template>
|
|
||||||
<script setup lang="ts">
|
|
||||||
import type {BovineTypeData} from "~/services/dto/bovine-type-data";
|
|
||||||
import {getBovineTypeList} from "~/services/bovine-type";
|
|
||||||
import {
|
|
||||||
createReceptionBovine,
|
|
||||||
deleteReceptionBovine,
|
|
||||||
getReceptionBovineList,
|
|
||||||
updateReceptionBovine
|
|
||||||
} from "~/services/reception-bovine";
|
|
||||||
import {computed, onMounted, reactive, ref, watch} from "vue";
|
|
||||||
import {getReception, updateReception} from "~/services/reception";
|
|
||||||
const toast = useToast()
|
|
||||||
const isLoadingBovineType = ref(false)
|
|
||||||
const bovineType = ref<BovineTypeData[]>([])
|
|
||||||
const bovineQuantities = reactive<Record<string, number | null>>({})
|
|
||||||
const otherQuantity = ref<number | null>(0)
|
|
||||||
const auth = useAuthStore()
|
|
||||||
const props = defineProps<{
|
|
||||||
idReception: number
|
|
||||||
}>()
|
|
||||||
const receptionId = props.idReception
|
|
||||||
const reception = await getReception(receptionId)
|
|
||||||
|
|
||||||
const receptionIri = computed(() =>
|
|
||||||
receptionId ? `/api/receptions/${receptionId}` : null
|
|
||||||
)
|
|
||||||
const totalBovines = computed(() => {
|
|
||||||
const base = Object.values(bovineQuantities).reduce((sum, value) => {
|
|
||||||
return sum + (value ?? 0)
|
|
||||||
}, 0)
|
|
||||||
return base + (otherQuantity.value ?? 0)
|
|
||||||
})
|
|
||||||
|
|
||||||
const loadBovineType = async () => {
|
|
||||||
isLoadingBovineType.value = true
|
|
||||||
try {
|
|
||||||
bovineType.value = await getBovineTypeList()
|
|
||||||
} finally {
|
|
||||||
isLoadingBovineType.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
await loadBovineType()
|
|
||||||
})
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => receptionId,
|
|
||||||
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 = await reception.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
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function validate() {
|
|
||||||
// @TODO Ajouter un composable pour le toaster qui gère les key i18n
|
|
||||||
if (totalBovines.value > 52) {
|
|
||||||
toast.error({
|
|
||||||
title: 'Erreur',
|
|
||||||
message: ('Le total des bovins ne peut pas dépasser 52.')
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
await syncBovineSelections(receptionIri.value)
|
|
||||||
|
|
||||||
await updateReception(receptionId, {
|
|
||||||
merchandiseType: null,
|
|
||||||
merchandiseDetail: null,
|
|
||||||
bovineDetail: otherQuantity.value ? String(otherQuantity.value) : null,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -1,257 +0,0 @@
|
|||||||
<template>
|
|
||||||
<form @submit.prevent="validate">
|
|
||||||
<div class="flex flex-col items-center gap-16">
|
|
||||||
<div
|
|
||||||
class="flex flex-col gap-16 items-center w-full">
|
|
||||||
<UiTextInput
|
|
||||||
id="merchandise-type"
|
|
||||||
v-model="selectedMerchandiseTypeId"
|
|
||||||
label="Type de marchandises"
|
|
||||||
:value="reception.merchandiseType?.label"
|
|
||||||
wrapper-class="w-[550px]"
|
|
||||||
:disabled="true"
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
v-if="merchandiseTypeId && isAutres"
|
|
||||||
class="flex flex-col w-full max-w-[550px]"
|
|
||||||
>
|
|
||||||
<UiTextInput
|
|
||||||
id="merchandise-detail"
|
|
||||||
:disabled="!auth.isAdmin"
|
|
||||||
v-model="merchandiseDetail"
|
|
||||||
label="Préciser"
|
|
||||||
placeholder="Précisions complémentaires"
|
|
||||||
:maxlength="255"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
v-if="merchandiseTypeId && !isGranule"
|
|
||||||
class="flex gap-4 w-[550px] justify-evenly"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
v-for="building in buildings"
|
|
||||||
:key="building.id"
|
|
||||||
>
|
|
||||||
<UiCheckbox
|
|
||||||
v-model="selectedBuildingIds"
|
|
||||||
:value="String(building.id)"
|
|
||||||
:label="building.label"
|
|
||||||
:disabled="!auth.isAdmin"
|
|
||||||
label-class="text-xl"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
v-if="merchandiseTypeId && isGranule"
|
|
||||||
class="flex flex-col gap-10 w-full max-w-[1100px]"
|
|
||||||
>
|
|
||||||
<div class="grid grid-cols-1 gap-10 md:grid-cols-4">
|
|
||||||
<div v-for="type in pelletTypes" :key="type.id" class="flex flex-col gap-4">
|
|
||||||
<p class="font-bold uppercase">{{ type.label }}</p>
|
|
||||||
<div
|
|
||||||
v-for="building in buildings"
|
|
||||||
:key="building.id"
|
|
||||||
class="flex items-center gap-2 text-lg"
|
|
||||||
>
|
|
||||||
<UiCheckbox
|
|
||||||
v-model="selectedPelletBuildingIds[String(type.id)]"
|
|
||||||
:value="String(building.id)"
|
|
||||||
:label="building.label"
|
|
||||||
:disabled="!auth.isAdmin"
|
|
||||||
label-class="text-lg"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
class="text-xl uppercase bg-primary-500 text-white h-[50px] w-[272px]"
|
|
||||||
:disabled="!auth.isAdmin"
|
|
||||||
>Valider
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</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 {
|
|
||||||
createReceptionPelletBuilding,
|
|
||||||
deleteReceptionPelletBuilding,
|
|
||||||
getReceptionPelletBuildingList
|
|
||||||
} from '~/services/reception-pellet-building'
|
|
||||||
import {MERCHANDISE_TYPE_CODES} from '~/utils/constants'
|
|
||||||
import {getReception, updateReception} from "~/services/reception";
|
|
||||||
|
|
||||||
const merchandiseTypes = ref<MerchandiseTypeData[]>([])
|
|
||||||
const buildings = ref<BuildingData[]>([])
|
|
||||||
const pelletTypes = ref<PelletTypeData[]>([])
|
|
||||||
const selectedMerchandiseTypeId = ref('')
|
|
||||||
const selectedBuildingIds = ref<string[]>([])
|
|
||||||
const selectedPelletBuildingIds = ref<Record<string, string[]>>({})
|
|
||||||
const merchandiseDetail = ref('')
|
|
||||||
const auth = useAuthStore()
|
|
||||||
const props = defineProps<{
|
|
||||||
idReception: number
|
|
||||||
}>()
|
|
||||||
const receptionId = props.idReception
|
|
||||||
const reception = await getReception(receptionId)
|
|
||||||
const merchandiseTypeId = await reception.receptionType?.id
|
|
||||||
|
|
||||||
// Extrait l'ID d'une relation depuis un IRI ou un objet complet.
|
|
||||||
const getRelationId = (value: unknown): string | null => {
|
|
||||||
if (!value) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof value === 'string') {
|
|
||||||
const match = value.match(/\/(\d+)$/)
|
|
||||||
return match ? match[1] : null
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof value === 'object' && 'id' in value) {
|
|
||||||
const record = value as { id?: number | string }
|
|
||||||
if (typeof record.id === 'number') {
|
|
||||||
return String(record.id)
|
|
||||||
}
|
|
||||||
if (typeof record.id === 'string') {
|
|
||||||
return record.id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
// Type de marchandise sélectionné dans le select
|
|
||||||
const selectedMerchandiseType = computed(() =>
|
|
||||||
merchandiseTypes.value.find((type) => String(type.id) === selectedMerchandiseTypeId.value)
|
|
||||||
)
|
|
||||||
// Indique si le type est "Granulé"
|
|
||||||
const isGranule = computed(() => selectedMerchandiseType.value?.code === MERCHANDISE_TYPE_CODES.GRANULE)
|
|
||||||
// Indique si le type est "Autres"
|
|
||||||
const isAutres = computed(() => selectedMerchandiseType.value?.code === MERCHANDISE_TYPE_CODES.AUTRES)
|
|
||||||
|
|
||||||
// Charge les référentiels et hydrate le formulaire depuis la réception
|
|
||||||
onMounted(async () => {
|
|
||||||
const [merchandiseTypeList, buildingList, pelletTypeList] = await Promise.all([
|
|
||||||
getMerchandiseTypeList(),
|
|
||||||
getBuildingList(),
|
|
||||||
getPelletTypeList()
|
|
||||||
])
|
|
||||||
merchandiseTypes.value = merchandiseTypeList
|
|
||||||
buildings.value = buildingList
|
|
||||||
pelletTypes.value = pelletTypeList
|
|
||||||
|
|
||||||
const currentId = reception.merchandiseType?.id
|
|
||||||
if (currentId) {
|
|
||||||
selectedMerchandiseTypeId.value = String(currentId)
|
|
||||||
}
|
|
||||||
merchandiseDetail.value = reception.merchandiseDetail ?? ''
|
|
||||||
|
|
||||||
selectedBuildingIds.value =
|
|
||||||
reception.buildings?.map((building) => String(building.id)) ?? []
|
|
||||||
|
|
||||||
const existingPelletSelections = reception.pelletBuildings ?? []
|
|
||||||
const selectionMap: Record<string, string[]> = {}
|
|
||||||
for (const selection of existingPelletSelections) {
|
|
||||||
// L'API peut renvoyer les relations comme IRI ou comme objets selon le contexte.
|
|
||||||
const pelletTypeId = getRelationId(selection.pelletType)
|
|
||||||
const buildingId = getRelationId(selection.building)
|
|
||||||
if (!pelletTypeId || !buildingId) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if (!selectionMap[pelletTypeId]) {
|
|
||||||
selectionMap[pelletTypeId] = []
|
|
||||||
}
|
|
||||||
selectionMap[pelletTypeId].push(buildingId)
|
|
||||||
}
|
|
||||||
for (const pelletType of pelletTypes.value) {
|
|
||||||
const key = String(pelletType.id)
|
|
||||||
if (!selectionMap[key]) {
|
|
||||||
selectionMap[key] = []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
selectedPelletBuildingIds.value = selectionMap
|
|
||||||
})
|
|
||||||
// Enregistre les sélections et passe à l'étape suivante
|
|
||||||
async function validate() {
|
|
||||||
|
|
||||||
const receptionIri = `/api/receptions/${reception.id}`
|
|
||||||
|
|
||||||
await updateReception(reception.id, {
|
|
||||||
merchandiseDetail: isAutres.value ? merchandiseDetail.value.trim() : null,
|
|
||||||
buildings: isGranule.value
|
|
||||||
? []
|
|
||||||
: selectedBuildingIds.value.map((id) => `/api/buildings/${id}`),
|
|
||||||
bovineDetail: null,
|
|
||||||
bovinesTypes: null,
|
|
||||||
})
|
|
||||||
|
|
||||||
if (isGranule.value) {
|
|
||||||
await syncPelletSelections(receptionIri)
|
|
||||||
} else {
|
|
||||||
await clearPelletSelections(receptionIri)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Supprime toutes les associations granulés/bâtiments existantes
|
|
||||||
async function clearPelletSelections(receptionIri: string) {
|
|
||||||
const existing = await getReceptionPelletBuildingList(receptionIri)
|
|
||||||
for (const selection of existing) {
|
|
||||||
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)
|
|
||||||
const existingMap = new Map<string, number>()
|
|
||||||
for (const selection of existing) {
|
|
||||||
// Construit la table de correspondance avec des IDs normalisés pour éviter les doublons.
|
|
||||||
const pelletTypeId = getRelationId(selection.pelletType)
|
|
||||||
const buildingId = getRelationId(selection.building)
|
|
||||||
if (!pelletTypeId || !buildingId) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
const key = `${pelletTypeId}:${buildingId}`
|
|
||||||
existingMap.set(key, selection.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
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})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const desiredKeys = new Set(desiredEntries.map(
|
|
||||||
(entry) => `${entry.pelletTypeId}:${entry.buildingId}`
|
|
||||||
))
|
|
||||||
|
|
||||||
for (const [key, id] of existingMap.entries()) {
|
|
||||||
if (!desiredKeys.has(key)) {
|
|
||||||
await deleteReceptionPelletBuilding(id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const entry of desiredEntries) {
|
|
||||||
const key = `${entry.pelletTypeId}:${entry.buildingId}`
|
|
||||||
if (!existingMap.has(key)) {
|
|
||||||
await createReceptionPelletBuilding({
|
|
||||||
reception: receptionIri,
|
|
||||||
pelletType: `/api/pellet_types/${entry.pelletTypeId}`,
|
|
||||||
building: `/api/buildings/${entry.buildingId}`
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
<template>
|
|
||||||
<form @submit.prevent="validate">
|
|
||||||
|
|
||||||
<div class="grid grid-cols-2 gap-x-40 gap-y-8 mb-16">
|
|
||||||
<UiNumberInput
|
|
||||||
label="Pesée à vide"
|
|
||||||
v-model="form.weights[0].weight"
|
|
||||||
:disabled="!auth.isAdmin"
|
|
||||||
:min="0"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<UiNumberInput
|
|
||||||
label="Pesée à plein"
|
|
||||||
v-model="form.weights[1].weight"
|
|
||||||
:disabled="!auth.isAdmin"
|
|
||||||
:min="0"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex justify-center">
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
class="text-xl uppercase bg-primary-500 text-white h-[50px] w-[272px]"
|
|
||||||
:disabled="!auth.isAdmin"
|
|
||||||
>
|
|
||||||
Valider
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</form>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import type {ReceptionFormWeight} from '~/services/dto/reception-data'
|
|
||||||
import { getReception } from '~/services/reception'
|
|
||||||
import {updateWeight} from "~/services/weight";
|
|
||||||
import {useAuthStore} from "~/stores/auth";
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
idReception: number
|
|
||||||
}>()
|
|
||||||
|
|
||||||
const idReception = props.idReception
|
|
||||||
const auth = useAuthStore()
|
|
||||||
|
|
||||||
const form = reactive({
|
|
||||||
weights: [
|
|
||||||
{ id: 0, type: 'tare' as const, weight: 0 },
|
|
||||||
{ id: 0, type: 'gross' as const, weight: 0 }
|
|
||||||
]
|
|
||||||
})
|
|
||||||
|
|
||||||
const hydrateFromReception = (reception: ReceptionFormWeight) => {
|
|
||||||
const tare = reception.weights.find(weight => weight.type === 'tare')
|
|
||||||
const gross = reception.weights.find(weight => weight.type === 'gross')
|
|
||||||
|
|
||||||
if (tare) form.weights[0] = { ...tare }
|
|
||||||
if (gross) form.weights[1] = { ...gross }
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
const reception = await getReception(idReception)
|
|
||||||
hydrateFromReception(reception)
|
|
||||||
})
|
|
||||||
|
|
||||||
async function validate() {
|
|
||||||
|
|
||||||
for (const weight of form.weights) {
|
|
||||||
if (weight.id) {
|
|
||||||
await updateWeight(weight.id, {weight: weight.weight})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -1,605 +0,0 @@
|
|||||||
<template>
|
|
||||||
<form @submit.prevent="validate">
|
|
||||||
<div class="grid grid-cols-2 items-start gap-y-8 gap-x-40 mb-16">
|
|
||||||
<h1 class="font-bold text-5xl uppercase col-start-1 row-start-1">Expédition</h1>
|
|
||||||
<!-- Nom de l'utilisateur -->
|
|
||||||
<UiSelect
|
|
||||||
id="shipment-user"
|
|
||||||
v-model="form.userId"
|
|
||||||
label="Nom de l'utilisateur"
|
|
||||||
:options="users.map((user) => ({
|
|
||||||
value: String(user.id),
|
|
||||||
label: user.username
|
|
||||||
}))"
|
|
||||||
:loading="isLoadingUsers"
|
|
||||||
wrapper-class="col-start-1 row-start-2"
|
|
||||||
/>
|
|
||||||
<!-- Date de l'éxpedition -->
|
|
||||||
<UiDateInput
|
|
||||||
id="shipment-date"
|
|
||||||
v-model="form.shipmentDate"
|
|
||||||
label="Date du jour"
|
|
||||||
wrapper-class="col-start-1 row-start-3"
|
|
||||||
/>
|
|
||||||
<!-- Type d'expédition -->
|
|
||||||
<div class="col-start-1 row-start-4">
|
|
||||||
<label class="font-bold uppercase text-xl mb-2 block">
|
|
||||||
Type d'expédition
|
|
||||||
</label>
|
|
||||||
<div class="grid grid-cols-2 gap-x-8">
|
|
||||||
<div
|
|
||||||
v-for="type in bovineShipment"
|
|
||||||
:key="type.id"
|
|
||||||
class="mt-8 flex flex-row gap-6"
|
|
||||||
>
|
|
||||||
<UiNumberInput
|
|
||||||
:label="type.label"
|
|
||||||
v-model="bovineQuantities[String(type.id)]"
|
|
||||||
:placeholder="0"
|
|
||||||
:min="0"
|
|
||||||
:max="10"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- Client -->
|
|
||||||
<UiSelect
|
|
||||||
id="shipment-customer"
|
|
||||||
v-model="form.customerId"
|
|
||||||
label="Client"
|
|
||||||
:options="customers.map((customer) => ({
|
|
||||||
value: String(customer.id),
|
|
||||||
label: customer.label
|
|
||||||
}))"
|
|
||||||
:loading="isLoadingCustomers"
|
|
||||||
wrapper-class="col-start-1 row-start-5"
|
|
||||||
/>
|
|
||||||
<!-- Adresse du client -->
|
|
||||||
<UiSelect
|
|
||||||
id="shipment-address"
|
|
||||||
v-model="form.addressId"
|
|
||||||
:options="customerAddressOptions"
|
|
||||||
:disabled="isLoadingCustomers || customerAddresses.length === 0"
|
|
||||||
label="Adresse"
|
|
||||||
wrapper-class="col-start-2 row-start-1"
|
|
||||||
/>
|
|
||||||
<!-- Camion -->
|
|
||||||
<UiSelect
|
|
||||||
id="shipment-truck"
|
|
||||||
v-model="form.truckId"
|
|
||||||
label="Camion"
|
|
||||||
:options="trucks.map((truck) => ({
|
|
||||||
value: String(truck.id),
|
|
||||||
label: truck.name
|
|
||||||
}))"
|
|
||||||
:loading="isLoadingTrucks"
|
|
||||||
wrapper-class="col-start-2 row-start-2"
|
|
||||||
/>
|
|
||||||
<!-- Transporteur -->
|
|
||||||
<UiSelect
|
|
||||||
id="shipment-carrier"
|
|
||||||
v-model="form.carrierId"
|
|
||||||
label="Transporteur"
|
|
||||||
:options="carriers.map((carrier) => ({
|
|
||||||
value: String(carrier.id),
|
|
||||||
label: carrier.name
|
|
||||||
}))"
|
|
||||||
wrapper-class="col-start-2 row-start-3"
|
|
||||||
/>
|
|
||||||
<!-- Chauffeur (LIOT) -->
|
|
||||||
<UiSelect
|
|
||||||
id="shipment-driver"
|
|
||||||
v-model="form.driverId"
|
|
||||||
label="Nom du chauffeur si LIOT"
|
|
||||||
:options="filteredDrivers.map((driver) => ({
|
|
||||||
value: String(driver.id),
|
|
||||||
label: driver.name
|
|
||||||
}))"
|
|
||||||
:loading="isLoadingDrivers"
|
|
||||||
wrapper-class="col-start-2 row-start-4"
|
|
||||||
/>
|
|
||||||
<!-- Plaque d'immatriculation (hors LIOT) -->
|
|
||||||
<div v-if="!isLiotCarrier" class="col-start-2 row-start-5">
|
|
||||||
<UiLicensePlateInput
|
|
||||||
v-model="form.licencePlate"
|
|
||||||
v-model:allowAny="allowAnyLicensePlate"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<!-- Immatriculation (LIOT) -->
|
|
||||||
<UiSelect
|
|
||||||
v-if="isLiotCarrier"
|
|
||||||
id="shipment-vehicle"
|
|
||||||
v-model="form.vehicleId"
|
|
||||||
label="Immatriculation"
|
|
||||||
:options="filteredVehicles.map((vehicle) => ({
|
|
||||||
value: String(vehicle.id),
|
|
||||||
label: vehicle.plate
|
|
||||||
}))"
|
|
||||||
:loading="isLoadingVehicles"
|
|
||||||
:disabled="isLoadingVehicles || filteredVehicles.length === 0"
|
|
||||||
wrapper-class="col-start-2 row-start-5"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="flex justify-center">
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
class="text-xl uppercase bg-primary-500 text-white h-[50px] w-[272px] justify-self-end"
|
|
||||||
>Valider
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</template>
|
|
||||||
<script setup lang="ts">
|
|
||||||
|
|
||||||
import type {UserData} from '~/services/dto/user-data'
|
|
||||||
import type {CustomerData} from '~/services/dto/customer-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 {VehicleData} from '~/services/dto/vehicle-data'
|
|
||||||
import type {AddressData} from '~/services/dto/address-data'
|
|
||||||
import {getUsers} from '~/services/auth'
|
|
||||||
import {getCustomerList} from '~/services/customer'
|
|
||||||
import {getTruckList} from '~/services/truck'
|
|
||||||
import {getCarrierList} from '~/services/carrier'
|
|
||||||
import {getVehicleList} from '~/services/vehicle'
|
|
||||||
import {getDriverList} from '~/services/driver'
|
|
||||||
import type {ShipmentFormData} from '~/services/dto/shipment-data'
|
|
||||||
import {SUPPLIER_CODE} from "~/utils/constants"
|
|
||||||
import {useAuthStore} from '~/stores/auth'
|
|
||||||
import {useShipmentStore} from '~/stores/shipment'
|
|
||||||
import { computed, reactive, ref, watch, onMounted } from 'vue'
|
|
||||||
import type {ShipmentTypeData} from "~/services/dto/shipment-type-data";
|
|
||||||
import {getShipmentTypeList} from "~/services/shipment-type";
|
|
||||||
import {
|
|
||||||
createShipmentBovine,
|
|
||||||
deleteShipmentBovine,
|
|
||||||
getBovinShipmentList,
|
|
||||||
updateShipmentBovine
|
|
||||||
} from "~/services/bovin-shipment";
|
|
||||||
|
|
||||||
const users = ref<UserData[]>([])
|
|
||||||
const customers = ref<CustomerData[]>([])
|
|
||||||
const trucks = ref<TruckData[]>([])
|
|
||||||
const carriers = ref<CarrierData[]>([])
|
|
||||||
const drivers = ref<DriverData[]>([])
|
|
||||||
const vehicles = ref<VehicleData[]>([])
|
|
||||||
|
|
||||||
const isLoadingUsers = ref(false)
|
|
||||||
const isLoadingShipmentTypes = ref(false)
|
|
||||||
const isLoadingCustomers = ref(false)
|
|
||||||
const isLoadingTrucks = ref(false)
|
|
||||||
const isLoadingCarriers = ref(false)
|
|
||||||
const isHydrating = ref(false)
|
|
||||||
const isLoadingVehicles = ref(false)
|
|
||||||
const allowAnyLicensePlate = ref(false)
|
|
||||||
const isLoadingDrivers = ref(false)
|
|
||||||
const authStore = useAuthStore()
|
|
||||||
const shipmentStore = useShipmentStore()
|
|
||||||
const router = useRouter()
|
|
||||||
const bovineQuantities = ref<Record<string, number | null>>({})
|
|
||||||
const bovineShipment = ref<ShipmentTypeData[]>([])
|
|
||||||
// Transporteur sélectionné dans le formulaire
|
|
||||||
const selectedCarrier = computed(() =>
|
|
||||||
carriers.value.find((carrier) => String(carrier.id) === form.carrierId) ?? null
|
|
||||||
)
|
|
||||||
const isLiotCarrier = computed(() => selectedCarrier.value?.code === SUPPLIER_CODE.LIOT)
|
|
||||||
|
|
||||||
const form = reactive<ShipmentFormData>({
|
|
||||||
userId: '',
|
|
||||||
shipmentDate: new Date().toISOString().slice(0, 10),
|
|
||||||
customerId: '',
|
|
||||||
addressId: '',
|
|
||||||
truckId: '',
|
|
||||||
carrierId: '',
|
|
||||||
driverId: '',
|
|
||||||
vehicleId: '',
|
|
||||||
licencePlate: '',
|
|
||||||
})
|
|
||||||
// Adresses liées au client sélectionné
|
|
||||||
const customerAddresses = computed<AddressData[]>(() => {
|
|
||||||
const customerId = Number(form.customerId)
|
|
||||||
if (!Number.isFinite(customerId)) {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
return customers.value.find((customer) => customer.id === customerId)?.addresses ?? []
|
|
||||||
})
|
|
||||||
// Options pour le select des adresses du client
|
|
||||||
const customerAddressOptions = computed(() =>
|
|
||||||
customerAddresses.value
|
|
||||||
.map((address) => ({
|
|
||||||
value: String(address.id),
|
|
||||||
label: address.fullAddress
|
|
||||||
}))
|
|
||||||
)
|
|
||||||
// Chauffeurs liés au transporteur sélectionné (LIOT)
|
|
||||||
const filteredDrivers = computed<DriverData[]>(() => {
|
|
||||||
if (!form.carrierId) {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
return drivers.value.filter((driver) => String(driver.carrier?.id) === form.carrierId)
|
|
||||||
})
|
|
||||||
// Véhicules liés au transporteur + camion sélectionnés (LIOT)
|
|
||||||
const filteredVehicles = computed<VehicleData[]>(() => {
|
|
||||||
if (!form.carrierId) {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
return vehicles.value.filter(
|
|
||||||
(vehicle) =>
|
|
||||||
String(vehicle.carrier?.id) === form.carrierId &&
|
|
||||||
(!form.truckId || String(vehicle.truck?.id) === form.truckId)
|
|
||||||
)
|
|
||||||
})
|
|
||||||
// Chargement des données pour les selects
|
|
||||||
const loadUsers = async () => {
|
|
||||||
isLoadingUsers.value = true
|
|
||||||
try {
|
|
||||||
users.value = await getUsers()
|
|
||||||
} finally {
|
|
||||||
isLoadingUsers.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const loadShipmentType = async () => {
|
|
||||||
isLoadingShipmentTypes.value = true
|
|
||||||
try {
|
|
||||||
bovineShipment.value = await getShipmentTypeList()
|
|
||||||
} finally {
|
|
||||||
isLoadingShipmentTypes.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const loadCustomers = async () => {
|
|
||||||
isLoadingCustomers.value = true
|
|
||||||
try {
|
|
||||||
customers.value = await getCustomerList()
|
|
||||||
} finally {
|
|
||||||
isLoadingCustomers.value = false
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
const loadTrucks = async () => {
|
|
||||||
isLoadingTrucks.value = true
|
|
||||||
try {
|
|
||||||
trucks.value = await getTruckList()
|
|
||||||
} finally {
|
|
||||||
isLoadingTrucks.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const loadCarriers = async () => {
|
|
||||||
isLoadingCarriers.value = true
|
|
||||||
try {
|
|
||||||
carriers.value = await getCarrierList()
|
|
||||||
} finally {
|
|
||||||
isLoadingCarriers.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const loadVehicles = async () => {
|
|
||||||
isLoadingVehicles.value = true
|
|
||||||
try {
|
|
||||||
vehicles.value = await getVehicleList()
|
|
||||||
} finally {
|
|
||||||
isLoadingVehicles.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const loadDrivers = async () => {
|
|
||||||
isLoadingDrivers.value = true
|
|
||||||
try {
|
|
||||||
drivers.value = await getDriverList()
|
|
||||||
} finally {
|
|
||||||
isLoadingDrivers.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// On met le user connecté par défaut dans le select
|
|
||||||
const setDefaultUser = () => {
|
|
||||||
if (form.userId) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (authStore.user?.id) {
|
|
||||||
form.userId = String(authStore.user.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Chargement initial des données
|
|
||||||
onMounted(async () => {
|
|
||||||
await loadShipmentType()
|
|
||||||
await loadUsers()
|
|
||||||
await loadCustomers()
|
|
||||||
await loadTrucks()
|
|
||||||
await loadCarriers()
|
|
||||||
await loadVehicles()
|
|
||||||
await loadDrivers()
|
|
||||||
await authStore.ensureSession()
|
|
||||||
setDefaultUser()
|
|
||||||
})
|
|
||||||
// Hydrate le formulaire depuis l'expédition en cours
|
|
||||||
watch(
|
|
||||||
() => shipmentStore.current,
|
|
||||||
(shipment) => {
|
|
||||||
isHydrating.value = true
|
|
||||||
form.licencePlate = shipment?.licencePlate ?? ''
|
|
||||||
form.shipmentDate = shipment?.shipmentDate ?? new Date().toISOString().slice(0, 10)
|
|
||||||
form.userId = shipment?.user?.id ? String(shipment.user.id) :
|
|
||||||
form.userId
|
|
||||||
form.customerId = shipment?.customer?.id ?
|
|
||||||
String(shipment.customer.id) : ''
|
|
||||||
form.addressId = shipment?.address?.id ? String(shipment.address.id) : ''
|
|
||||||
form.truckId = shipment?.truck?.id ? String(shipment.truck.id) : ''
|
|
||||||
form.carrierId = shipment?.carrier?.id ? String(shipment.carrier.id) : ''
|
|
||||||
form.driverId = shipment?.driver?.id ? String(shipment.driver.id) : ''
|
|
||||||
form.vehicleId = shipment?.vehicle?.id ? String(shipment.vehicle.id) : ''
|
|
||||||
if (!shipment || !shipment.bovinShipments) {
|
|
||||||
bovineQuantities.value = {}
|
|
||||||
} else {
|
|
||||||
const next: Record<string, number | null> = {}
|
|
||||||
for (const entry of shipment.bovinShipments) {
|
|
||||||
const typeId = entry.shipmentType?.id
|
|
||||||
if (!typeId) continue
|
|
||||||
next[String(typeId)] = entry.nbBovinSend ?? null
|
|
||||||
}
|
|
||||||
bovineQuantities.value = next
|
|
||||||
}
|
|
||||||
isHydrating.value = false
|
|
||||||
},
|
|
||||||
{immediate: true}
|
|
||||||
)
|
|
||||||
// Ajuste driver/vehicle quand le transporteur change (logique LIOT)
|
|
||||||
watch(
|
|
||||||
() => [form.customerId, customers.value],
|
|
||||||
() => {
|
|
||||||
if (!form.customerId) {
|
|
||||||
form.addressId = ''
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!form.addressId && customerAddresses.value.length === 1) {
|
|
||||||
form.addressId = String(customerAddresses.value[0].id)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!form.addressId) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const matches = customerAddresses.value.some(
|
|
||||||
(address) => String(address.id) === form.addressId
|
|
||||||
)
|
|
||||||
if (!matches) {
|
|
||||||
form.addressId = ''
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{immediate: true}
|
|
||||||
)
|
|
||||||
// Valide/auto-sélectionne le véhicule selon camion + transporteur (LIOT)
|
|
||||||
const applyLiotDefaults = () => {
|
|
||||||
if (isHydrating.value) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!form.carrierId) {
|
|
||||||
form.driverId = ''
|
|
||||||
form.vehicleId = ''
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!isLiotCarrier.value) {
|
|
||||||
form.driverId = ''
|
|
||||||
form.vehicleId = ''
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (filteredDrivers.value.length === 1) {
|
|
||||||
form.driverId = String(filteredDrivers.value[0].id)
|
|
||||||
}
|
|
||||||
if (filteredVehicles.value.length === 1) {
|
|
||||||
form.vehicleId = String(filteredVehicles.value[0].id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
watch(
|
|
||||||
() => form.carrierId,
|
|
||||||
() => {
|
|
||||||
applyLiotDefaults()
|
|
||||||
},
|
|
||||||
{immediate: true}
|
|
||||||
)
|
|
||||||
watch(
|
|
||||||
() => isHydrating.value,
|
|
||||||
(value) => {
|
|
||||||
if (!value) {
|
|
||||||
applyLiotDefaults()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
// Récupère la plaque depuis le véhicule choisi (LIOT)
|
|
||||||
watch(
|
|
||||||
() => [form.truckId, form.carrierId, vehicles.value],
|
|
||||||
() => {
|
|
||||||
if (!isLiotCarrier.value) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (filteredVehicles.value.length === 1) {
|
|
||||||
form.vehicleId = String(filteredVehicles.value[0].id)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!form.vehicleId) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const matches = filteredVehicles.value.some(
|
|
||||||
(vehicle) => String(vehicle.id) === form.vehicleId
|
|
||||||
)
|
|
||||||
if (!matches) {
|
|
||||||
form.vehicleId = ''
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{immediate: true}
|
|
||||||
)
|
|
||||||
// Auto-renseigne le véhicule si la plaque correspond (LIOT)
|
|
||||||
watch(
|
|
||||||
() => [form.vehicleId, form.carrierId, vehicles.value],
|
|
||||||
() => {
|
|
||||||
if (!isLiotCarrier.value) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (isHydrating.value) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const selected = filteredVehicles.value.find(
|
|
||||||
(vehicle) => String(vehicle.id) === form.vehicleId
|
|
||||||
)
|
|
||||||
if (selected) {
|
|
||||||
form.licencePlate = selected.plate
|
|
||||||
allowAnyLicensePlate.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
watch(
|
|
||||||
() => [form.licencePlate, form.carrierId, vehicles.value],
|
|
||||||
() => {
|
|
||||||
if (!isLiotCarrier.value || form.vehicleId) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const match = filteredVehicles.value.find(
|
|
||||||
(vehicle) => vehicle.plate === form.licencePlate
|
|
||||||
)
|
|
||||||
if (match) {
|
|
||||||
form.vehicleId = String(match.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
const buildDesiredBovinShipments = () => {
|
|
||||||
return bovineShipment.value
|
|
||||||
.map((type) => {
|
|
||||||
const raw = bovineQuantities.value[String(type.id)]
|
|
||||||
const quantity = raw === null || raw === undefined ? 0 : Number(raw)
|
|
||||||
return {
|
|
||||||
type,
|
|
||||||
quantity: Number.isFinite(quantity) ? Math.max(0, Math.trunc(quantity)) : 0
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.filter((entry) => entry.quantity > 0)
|
|
||||||
}
|
|
||||||
const syncBovinShipments = async (
|
|
||||||
shipmentId: number,
|
|
||||||
existing: Array<{ id?: number; nbBovinSend: number | null; shipmentType?: unknown }> = []
|
|
||||||
) => {
|
|
||||||
const shipmentIri = `/api/shipments/${shipmentId}`
|
|
||||||
const desired = buildDesiredBovinShipments()
|
|
||||||
const desiredByTypeId = new Map<number, number>()
|
|
||||||
for (const entry of desired) {
|
|
||||||
desiredByTypeId.set(entry.type.id, entry.quantity)
|
|
||||||
}
|
|
||||||
for (const entry of existing) {
|
|
||||||
if (!entry.id) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
const rawType = entry.shipmentType
|
|
||||||
let typeId: number | null = null
|
|
||||||
if (rawType && typeof rawType === 'object' && 'id' in rawType) {
|
|
||||||
typeId = Number((rawType as { id: number }).id)
|
|
||||||
} else if (typeof rawType === 'string') {
|
|
||||||
const match = rawType.match(/\/shipment_types\/(\\d+)$/)
|
|
||||||
typeId = match ? Number(match[1]) : null
|
|
||||||
}
|
|
||||||
if (!typeId) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
const desiredQuantity = desiredByTypeId.get(typeId)
|
|
||||||
if (!desiredQuantity) {
|
|
||||||
await deleteShipmentBovine(entry.id)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if (entry.nbBovinSend !== desiredQuantity) {
|
|
||||||
await updateShipmentBovine(entry.id, {nbBovinSend: desiredQuantity})
|
|
||||||
}
|
|
||||||
desiredByTypeId.delete(typeId)
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const [typeId, quantity] of desiredByTypeId.entries()) {
|
|
||||||
await createShipmentBovine({
|
|
||||||
shipment: shipmentIri,
|
|
||||||
shipmentType: `/api/shipment_types/${typeId}`,
|
|
||||||
nbBovinSend: quantity
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const buildPayload = () => {
|
|
||||||
const normalizedLicensePlate = form.licencePlate.trim()
|
|
||||||
const normalizedShipmentDate = form.shipmentDate.trim()
|
|
||||||
const normalizedCustomerId = form.customerId.trim()
|
|
||||||
const normalizedTruckId = form.truckId.trim()
|
|
||||||
const normalizedCarrierId = form.carrierId.trim()
|
|
||||||
const normalizedDriverId = form.driverId.trim()
|
|
||||||
const normalizedUserId = form.userId.trim()
|
|
||||||
const normalizedAddressId = form.addressId.trim()
|
|
||||||
const customerIri = normalizedCustomerId
|
|
||||||
? `/api/customers/${normalizedCustomerId}`
|
|
||||||
: null
|
|
||||||
const truckIri = normalizedTruckId
|
|
||||||
? `/api/trucks/${normalizedTruckId}`
|
|
||||||
: null
|
|
||||||
const carrierIri = normalizedCarrierId
|
|
||||||
? `/api/carriers/${normalizedCarrierId}`
|
|
||||||
: null
|
|
||||||
const userIri = normalizedUserId
|
|
||||||
? `/api/users/${normalizedUserId}`
|
|
||||||
: null
|
|
||||||
const driverIri = normalizedDriverId
|
|
||||||
? `/api/drivers/${normalizedDriverId}`
|
|
||||||
: null
|
|
||||||
const addressIri = normalizedAddressId
|
|
||||||
? `/api/addresses/${normalizedAddressId}`
|
|
||||||
: null
|
|
||||||
|
|
||||||
return {
|
|
||||||
licencePlate: normalizedLicensePlate,
|
|
||||||
shipmentDate: normalizedShipmentDate,
|
|
||||||
customer: customerIri,
|
|
||||||
truck: truckIri,
|
|
||||||
carrier: carrierIri,
|
|
||||||
driver: driverIri,
|
|
||||||
user: userIri,
|
|
||||||
address: addressIri
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const saveDraft = async () => {
|
|
||||||
const payload = buildPayload()
|
|
||||||
if (!shipmentStore.current) {
|
|
||||||
const created = await shipmentStore.createShipment({
|
|
||||||
currentStep: 0,
|
|
||||||
...payload
|
|
||||||
})
|
|
||||||
if (created) {
|
|
||||||
await syncBovinShipments(created.id, [])
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
await shipmentStore.updateShipment(shipmentStore.current.id, {
|
|
||||||
currentStep: shipmentStore.current.currentStep,
|
|
||||||
...payload
|
|
||||||
})
|
|
||||||
await syncBovinShipments(
|
|
||||||
shipmentStore.current.id,
|
|
||||||
shipmentStore.current?.bovinShipments ?? []
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
defineExpose({saveDraft})
|
|
||||||
// Valide le formulaire et crée/met à jour l'expédition
|
|
||||||
const validate = async () => {
|
|
||||||
const payload = buildPayload()
|
|
||||||
if (!shipmentStore.current) {
|
|
||||||
const created = await shipmentStore.createShipment({
|
|
||||||
currentStep: 1,
|
|
||||||
...payload
|
|
||||||
})
|
|
||||||
if (created) {
|
|
||||||
await shipmentStore.loadShipment(created.id)
|
|
||||||
await syncBovinShipments(created.id, shipmentStore.current?.bovinShipments ?? [])
|
|
||||||
await router.push(`/shipment/${created.id}`)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const nextStep = shipmentStore.current.currentStep + 1
|
|
||||||
await shipmentStore.updateShipment(shipmentStore.current.id, {
|
|
||||||
currentStep: nextStep,
|
|
||||||
...payload
|
|
||||||
})
|
|
||||||
await shipmentStore.loadShipment(shipmentStore.current.id)
|
|
||||||
await syncBovinShipments(shipmentStore.current.id, shipmentStore.current?.bovinShipments ?? [])
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="flex justify-center">
|
|
||||||
<div class="flex flex-col items-center w-[660px]">
|
|
||||||
<h1 class="font-bold text-5xl uppercase">{{ title }}</h1>
|
|
||||||
<!--@TODO Voir comment faire pour savoir si le pont-bascule et bien connecté + ajouter un icon comme sur la maquette-->
|
|
||||||
<p class="text-primary-500 uppercase text-2xl mt-2">Pont-bascule connecté</p>
|
|
||||||
<div
|
|
||||||
v-if="showLoadingBox"
|
|
||||||
class="w-full flex flex-col items-center justify-center border border-black h-[90px] mt-12 mb-[86px]">
|
|
||||||
<UiLoadingDots />
|
|
||||||
</div>
|
|
||||||
<div v-else-if="displayWeight !== null" class="w-full">
|
|
||||||
<div
|
|
||||||
class="w-full flex flex-col items-center justify-center border border-black h-[90px] mt-12 mb-[25px] text-4xl">
|
|
||||||
{{ displayWeight }} kg
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex justify-center mt-[54px]">
|
|
||||||
<button
|
|
||||||
class="text-xl uppercase bg-primary-500 text-white h-[50px] w-[272px]"
|
|
||||||
@click="fetchWeight"
|
|
||||||
>{{ displayWeight !== null ? 'refaire une pesee' : 'peser' }}</button>
|
|
||||||
<button
|
|
||||||
v-if="displayWeight !== null && !showGenerateReceipt"
|
|
||||||
class="text-xl uppercase bg-primary-500 text-white h-[50px] w-[272px] ml-4"
|
|
||||||
@click="saveWeight"
|
|
||||||
>Valider la pesée</button>
|
|
||||||
<button
|
|
||||||
v-if="showGenerateReceipt"
|
|
||||||
class="text-xl uppercase bg-primary-500 text-white h-[50px] w-[272px] ml-4"
|
|
||||||
@click="printReceipt"
|
|
||||||
>Générer le bon</button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { computed, onMounted } from 'vue'
|
|
||||||
import { storeToRefs } from 'pinia'
|
|
||||||
import { useWeighingShipment } from '~/composables/useWeighing'
|
|
||||||
import { usePdfPrinter } from '~/composables/usePdfPrinter'
|
|
||||||
import { useShipmentStore } from '~/stores/shipment'
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
mode: 'gross' | 'tare'
|
|
||||||
}>()
|
|
||||||
|
|
||||||
const router = useRouter()
|
|
||||||
const shipmentStore = useShipmentStore()
|
|
||||||
const { current: storeShipment } = storeToRefs(shipmentStore)
|
|
||||||
const { printPdf } = usePdfPrinter()
|
|
||||||
const {
|
|
||||||
displayWeight,
|
|
||||||
title,
|
|
||||||
showLoadingBox,
|
|
||||||
fetchWeight,
|
|
||||||
saveWeight
|
|
||||||
} = useWeighingShipment({
|
|
||||||
modeShipment: props.mode,
|
|
||||||
shipment: storeShipment,
|
|
||||||
updateShipment: shipmentStore.updateShipment,
|
|
||||||
loadShipment: shipmentStore.loadShipment
|
|
||||||
})
|
|
||||||
// Affiche le bouton de génération du bon à l'étape tare
|
|
||||||
const showGenerateReceipt = computed(
|
|
||||||
() => props.mode === 'tare' && displayWeight.value !== null
|
|
||||||
)
|
|
||||||
|
|
||||||
// Génère le bon d'expédition, puis clôture l'expédition
|
|
||||||
const printReceipt = async () => {
|
|
||||||
if (!import.meta.client || !shipmentStore.current) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
await saveWeight()
|
|
||||||
const shipment = shipmentStore.current
|
|
||||||
const filename = `${shipment.identificationNumber ?? shipment.id}_${shipment.customer?.label ?? 'client'}_${shipment.licencePlate ?? 'immat'}.pdf`
|
|
||||||
await printPdf(`/shipments/${shipment.id}/receipt`, filename)
|
|
||||||
|
|
||||||
// Laisse le temps a la boite de dialogue d'impression de s'ouvrir.
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 600))
|
|
||||||
|
|
||||||
const result = await shipmentStore.updateShipment(shipmentStore.current.id, {
|
|
||||||
isValid: true
|
|
||||||
})
|
|
||||||
if (!result) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
shipmentStore.clearCurrent()
|
|
||||||
await router.push('/')
|
|
||||||
}
|
|
||||||
|
|
||||||
// Récupère le poids dès l'arrivée sur l'écran
|
|
||||||
onMounted(() => {
|
|
||||||
if (displayWeight.value === null) {
|
|
||||||
fetchWeight()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
<label
|
<label
|
||||||
v-if="label"
|
v-if="label"
|
||||||
:for="id"
|
:for="id"
|
||||||
class="text-xl text-bold flex items-center"
|
class="text-xl text-bold flex items-center gap-2"
|
||||||
:class="labelClass"
|
:class="labelClass"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
@@ -11,8 +11,7 @@
|
|||||||
{{ label }}
|
{{ label }}
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
v-if="code"
|
v-if="code" class="text-neutral-600">
|
||||||
class="text-neutral-600">
|
|
||||||
({{ code }})
|
({{ code }})
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
@@ -25,7 +24,7 @@
|
|||||||
:step="step"
|
:step="step"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
v-bind="attrs"
|
v-bind="attrs"
|
||||||
class="border-b border-black text-xl bg-transparent w-12"
|
class="border-b border-black text-xl bg-transparent w-48"
|
||||||
:class="[
|
:class="[
|
||||||
isEmpty ? 'text-neutral-400' : 'text-black',
|
isEmpty ? 'text-neutral-400' : 'text-black',
|
||||||
disabled ? 'cursor-not-allowed' : 'cursor-text',
|
disabled ? 'cursor-not-allowed' : 'cursor-text',
|
||||||
|
|||||||
@@ -1,26 +1,30 @@
|
|||||||
import { useApi } from '~/composables/useApi'
|
import {useApi} from '~/composables/useApi'
|
||||||
|
|
||||||
export const usePdfPrinter = () => {
|
export const usePdfPrinter = () => {
|
||||||
const api = useApi()
|
const api = useApi()
|
||||||
|
const receptionStore = useReceptionStore()
|
||||||
|
const currentReception = receptionStore.current
|
||||||
|
|
||||||
const printPdf = async (url: string, filename = 'document.pdf'): Promise<void> => {
|
const printPdf = async (url: string): Promise<void> => {
|
||||||
const blob = await api.getBlob(url)
|
const blob = await api.getBlob(url);
|
||||||
|
|
||||||
const pdfBlob = blob.type === 'application/pdf'
|
const pdfBlob = blob.type === 'application/pdf'
|
||||||
? blob
|
? blob
|
||||||
: new Blob([blob], { type: 'application/pdf' })
|
: new Blob([blob], { type: 'application/pdf' });
|
||||||
|
|
||||||
const blobUrl = URL.createObjectURL(pdfBlob)
|
const blobUrl = URL.createObjectURL(pdfBlob);
|
||||||
|
|
||||||
const a = document.createElement('a')
|
const filename = `${currentReception.identificationNumber}_${currentReception.supplier.name}_${currentReception.licensePlate}.pdf`;
|
||||||
a.href = blobUrl
|
|
||||||
a.download = filename
|
const a = document.createElement('a');
|
||||||
a.style.display = 'none'
|
a.href = blobUrl;
|
||||||
document.body.appendChild(a)
|
a.download = filename;
|
||||||
a.click()
|
a.style.display = 'none';
|
||||||
a.remove()
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
a.remove();
|
||||||
// L'ouverture dans un nouvel onglet déclenche un 2e PDF sans le nom personnalisé.
|
// L'ouverture dans un nouvel onglet déclenche un 2e PDF sans le nom personnalisé.
|
||||||
setTimeout(() => URL.revokeObjectURL(blobUrl), 60_000)
|
setTimeout(() => URL.revokeObjectURL(blobUrl), 60_000);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -3,20 +3,23 @@ import {computed, ref} from 'vue'
|
|||||||
import type {ReceptionData, ReceptionPayload, WeightEntryData} from '~/services/dto/reception-data'
|
import type {ReceptionData, ReceptionPayload, WeightEntryData} from '~/services/dto/reception-data'
|
||||||
import type {WeightData} from '~/services/dto/weight-data'
|
import type {WeightData} from '~/services/dto/weight-data'
|
||||||
import {getWeight} from '~/services/reception'
|
import {getWeight} from '~/services/reception'
|
||||||
import {getWeightShipment} from '~/services/shipment'
|
|
||||||
import {createWeight, updateWeight} from '~/services/weight'
|
import {createWeight, updateWeight} from '~/services/weight'
|
||||||
import type {UseWeighingShipmentOptions, UseWeighingOptions} from '~/services/weight'
|
|
||||||
import type {WeightShipmentEntryData} from "~/services/dto/shipment-data";
|
|
||||||
|
|
||||||
export type WeighingMode = 'gross' | 'tare'
|
export type WeighingMode = 'gross' | 'tare'
|
||||||
|
|
||||||
|
type UseWeighingOptions = {
|
||||||
|
mode: WeighingMode
|
||||||
|
reception: Ref<ReceptionData | null>
|
||||||
|
updateReception: (id: number, payload: ReceptionPayload) => Promise<ReceptionData | null>
|
||||||
|
loadReception?: (id: number) => Promise<ReceptionData | null>
|
||||||
|
}
|
||||||
|
|
||||||
export const useWeighing = ({
|
export const useWeighing = ({
|
||||||
mode,
|
mode,
|
||||||
reception,
|
reception,
|
||||||
updateReception,
|
updateReception,
|
||||||
loadReception
|
loadReception
|
||||||
}: UseWeighingOptions) => {
|
}: UseWeighingOptions) => {
|
||||||
const weightData = ref<WeightData | null>(null)
|
const weightData = ref<WeightData | null>(null)
|
||||||
const isFetching = ref(false)
|
const isFetching = ref(false)
|
||||||
|
|
||||||
@@ -94,87 +97,3 @@ export const useWeighing = ({
|
|||||||
saveWeight
|
saveWeight
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useWeighingShipment = ({
|
|
||||||
modeShipment,
|
|
||||||
shipment,
|
|
||||||
updateShipment,
|
|
||||||
loadShipment
|
|
||||||
}: UseWeighingShipmentOptions) => {
|
|
||||||
const weightData = ref<WeightData | null>(null)
|
|
||||||
const isFetching = ref(false)
|
|
||||||
|
|
||||||
const currentWeightEntry = computed<WeightShipmentEntryData | null>(() => {
|
|
||||||
const weights = shipment.value?.weights ?? []
|
|
||||||
return weights.find((entry) => entry.type === modeShipment) ?? null
|
|
||||||
})
|
|
||||||
|
|
||||||
const displayWeight = computed(() => weightData.value?.weight ?? currentWeightEntry.value?.weight ?? null)
|
|
||||||
const displayDsd = computed(() => weightData.value?.dsd ?? currentWeightEntry.value?.dsd ?? '-')
|
|
||||||
const title = computed(() => (modeShipment === 'gross' ? 'Pesée à plein' : 'Pesée à vide'))
|
|
||||||
const showLoadingBox = computed(
|
|
||||||
() => isFetching.value || (displayWeight.value === null && currentWeightEntry.value === null)
|
|
||||||
)
|
|
||||||
|
|
||||||
const fetchWeight = async () => {
|
|
||||||
isFetching.value = true
|
|
||||||
weightData.value = await getWeightShipment().finally(() => {
|
|
||||||
isFetching.value = false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const saveWeight = async () => {
|
|
||||||
if (!shipment.value) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const existingEntry = currentWeightEntry.value
|
|
||||||
const baseDsd = weightData.value?.dsd ?? existingEntry?.dsd ?? null
|
|
||||||
const baseWeight = weightData.value?.weight ?? existingEntry?.weight ?? null
|
|
||||||
const baseWeighedAt = weightData.value?.weighedAt ?? existingEntry?.weighedAt ?? null
|
|
||||||
|
|
||||||
if (baseWeight === null) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (existingEntry?.id) {
|
|
||||||
await updateWeight(existingEntry.id, {
|
|
||||||
type: modeShipment,
|
|
||||||
dsd: baseDsd,
|
|
||||||
weight: baseWeight,
|
|
||||||
weighedAt: baseWeighedAt
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
await createWeight({
|
|
||||||
shipment: `api/shipments/${shipment.value.id}`,
|
|
||||||
type: modeShipment,
|
|
||||||
dsd: baseDsd,
|
|
||||||
weight: baseWeight,
|
|
||||||
weighedAt: baseWeighedAt
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const nextStep = modeShipment === 'tare'
|
|
||||||
? shipment.value.currentStep
|
|
||||||
: shipment.value.currentStep + 1
|
|
||||||
await updateShipment(shipment.value.id, {
|
|
||||||
currentStep: nextStep,
|
|
||||||
isValid: shipment.value.isValid
|
|
||||||
})
|
|
||||||
|
|
||||||
if (loadShipment) {
|
|
||||||
await loadShipment(shipment.value.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
weightData,
|
|
||||||
currentWeightEntry,
|
|
||||||
displayWeight,
|
|
||||||
displayDsd,
|
|
||||||
title,
|
|
||||||
showLoadingBox,
|
|
||||||
fetchWeight,
|
|
||||||
saveWeight
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,8 +2,7 @@ export enum StepLabel {
|
|||||||
Reception = 'Réception',
|
Reception = 'Réception',
|
||||||
GrossWeighing = 'Pesée à plein',
|
GrossWeighing = 'Pesée à plein',
|
||||||
Selection = 'Sélection réceptionnées',
|
Selection = 'Sélection réceptionnées',
|
||||||
TareWeighing = 'Pesée à vide',
|
TareWeighing = 'Pesée à vide'
|
||||||
Shipment = 'Expédition',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const RECEPTION_STEP_LABELS = [
|
export const RECEPTION_STEP_LABELS = [
|
||||||
@@ -12,9 +11,3 @@ export const RECEPTION_STEP_LABELS = [
|
|||||||
StepLabel.Selection,
|
StepLabel.Selection,
|
||||||
StepLabel.TareWeighing
|
StepLabel.TareWeighing
|
||||||
]
|
]
|
||||||
|
|
||||||
export const SHIPMENT_STEP_LABELS = [
|
|
||||||
StepLabel.Shipment,
|
|
||||||
StepLabel.TareWeighing,
|
|
||||||
StepLabel.GrossWeighing,
|
|
||||||
]
|
|
||||||
|
|||||||
@@ -12,27 +12,8 @@
|
|||||||
"fetch": "Impossible de récupérer la réception.",
|
"fetch": "Impossible de récupérer la réception.",
|
||||||
"create": "Impossible de créer la réception.",
|
"create": "Impossible de créer la réception.",
|
||||||
"update": "Impossible de mettre à jour la réception.",
|
"update": "Impossible de mettre à jour la réception.",
|
||||||
"weight": "Impossible de récupérer la pesée."
|
|
||||||
},
|
|
||||||
"weight": {
|
|
||||||
"update": "Impossible de mettre à jour la pesée"
|
|
||||||
},
|
|
||||||
"shipment": {
|
|
||||||
"list": "Impossible de récupérer la liste des éxpeditions.",
|
|
||||||
"fetch": "Impossible de récupérer l'éxpeditions.",
|
|
||||||
"create": "Impossible de créer l'éxpeditions.",
|
|
||||||
"update": "Impossible de mettre à jour l'éxpeditions.",
|
|
||||||
"weigh": "Impossible de récupérer la pesée."
|
"weigh": "Impossible de récupérer la pesée."
|
||||||
},
|
},
|
||||||
"shipmentBovine": {
|
|
||||||
"list": "Impossible de récupérer la liste des bovins de l'éxpedition.",
|
|
||||||
"create": "Impossible d'enregistrer le bovin.",
|
|
||||||
"delete": "Impossible de supprimer le bovin.",
|
|
||||||
"update": "Impossible de mettre à jour le bovin."
|
|
||||||
},
|
|
||||||
"shipmentType": {
|
|
||||||
"list": "Impossible de récupérer la liste des types d'éxpedition."
|
|
||||||
},
|
|
||||||
"receptionType": {
|
"receptionType": {
|
||||||
"list": "Impossible de récupérer la liste des types de réception."
|
"list": "Impossible de récupérer la liste des types de réception."
|
||||||
},
|
},
|
||||||
@@ -56,27 +37,7 @@
|
|||||||
"delete": "Impossible de supprimer le bovin."
|
"delete": "Impossible de supprimer le bovin."
|
||||||
},
|
},
|
||||||
"supplier": {
|
"supplier": {
|
||||||
"list": "Impossible de récupérer la liste des fournisseurs.",
|
"list": "Impossible de récupérer la liste des fournisseurs."
|
||||||
"fetch": "Impossible de récupérer le fournisseur.",
|
|
||||||
"create": "Impossible de créer le fournisseur.",
|
|
||||||
"update": "Impossible de mettre à jour le fournisseur.",
|
|
||||||
"nameRequired": "Le nom du fournisseur est obligatoire."
|
|
||||||
},
|
|
||||||
"address": {
|
|
||||||
"fetch": "Impossible de récupérer l'adresse.",
|
|
||||||
"create": "Impossible de créer l'adresse.",
|
|
||||||
"update": "Impossible de mettre à jour l'adresse.",
|
|
||||||
"entityNotFound": "Entité introuvable.",
|
|
||||||
"streetRequired": "La rue est obligatoire.",
|
|
||||||
"postalCodeRequired": "Le code postal est obligatoire.",
|
|
||||||
"cityRequired": "La ville est obligatoire.",
|
|
||||||
"countryCodeInvalid": "Le pays doit être un code ISO2 (2 lettres)."
|
|
||||||
},
|
|
||||||
"customer": {
|
|
||||||
"list": "Impossible de récupérer la liste des clients.",
|
|
||||||
"fetch": "Impossible de récupérer le client.",
|
|
||||||
"create": "Impossible de créer le client.",
|
|
||||||
"update": "Impossible de mettre à jour le client."
|
|
||||||
},
|
},
|
||||||
"truck": {
|
"truck": {
|
||||||
"list": "Impossible de récupérer la liste des camions."
|
"list": "Impossible de récupérer la liste des camions."
|
||||||
@@ -85,10 +46,7 @@
|
|||||||
"list": "Impossible de récupérer la liste des races de bovins."
|
"list": "Impossible de récupérer la liste des races de bovins."
|
||||||
},
|
},
|
||||||
"carrier": {
|
"carrier": {
|
||||||
"list": "Impossible de récupérer la liste des transporteurs.",
|
"list": "Impossible de récupérer la liste des transporteurs."
|
||||||
"fetch": "Impossible de récupérer les données du transporteur",
|
|
||||||
"update": "Impossible de mettre à jour le transporteur",
|
|
||||||
"create": "Impossible de créer le transporteur"
|
|
||||||
},
|
},
|
||||||
"driver": {
|
"driver": {
|
||||||
"list": "Impossible de récupérer la liste des chauffeurs."
|
"list": "Impossible de récupérer la liste des chauffeurs."
|
||||||
@@ -99,38 +57,16 @@
|
|||||||
"auth": {
|
"auth": {
|
||||||
"login": "Identifiants invalides.",
|
"login": "Identifiants invalides.",
|
||||||
"users": "Impossible de récupérer les utilisateurs.",
|
"users": "Impossible de récupérer les utilisateurs.",
|
||||||
"logout": "Impossible de se déconnecter.",
|
"logout": "Impossible de se déconnecter."
|
||||||
"update": "Impossible de mettre à jour l'utilisateur.",
|
|
||||||
"create": "Impossible de créer l'utilisateur."
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"success": {
|
"success": {
|
||||||
"reception": {
|
"reception": {
|
||||||
"update": "Réception mise à jour avec succès."
|
"update": "Réception mise à jour avec succès."
|
||||||
},
|
},
|
||||||
"shipment": {
|
|
||||||
"update": "Éxpedition mise à jour avec succès."
|
|
||||||
},
|
|
||||||
"supplier": {
|
|
||||||
"create": "Fournisseur créé avec succès.",
|
|
||||||
"update": "Fournisseur mis à jour avec succès."
|
|
||||||
},
|
|
||||||
"address": {
|
|
||||||
"create": "Adresse créée avec succès.",
|
|
||||||
"update": "Adresse mise à jour avec succès."
|
|
||||||
},
|
|
||||||
"auth": {
|
"auth": {
|
||||||
"update": "Utilisateur mis à jour avec succès.",
|
|
||||||
"create": "Utilisateur créé avec succès.",
|
|
||||||
"login": "Connexion réussie.",
|
"login": "Connexion réussie.",
|
||||||
"logout": "Déconnexion réussie."
|
"logout": "Déconnexion réussie."
|
||||||
},
|
|
||||||
"carrier": {
|
|
||||||
"update": "Transporteur mis à jour",
|
|
||||||
"create": "Transporteur créé"
|
|
||||||
},
|
|
||||||
"weight": {
|
|
||||||
"update": "Pesée mis à jour"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,24 +21,18 @@
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="grid grid-cols-[16rem,1fr] h-[calc(100vh-85px)] min-h-0">
|
<div class="grid grid-cols-[16rem,1fr] h-[calc(100vh-85px)] min-h-0">
|
||||||
<aside class="bg-primary-500 text-white min-h-0 flex flex-col justify-between">
|
<aside class="bg-primary-500 text-white min-h-0 grid grid-rows-[auto,1fr,auto]">
|
||||||
<div class="flex flex-col gap-4 p-4 font-bold text-xl">
|
<div class="p-4 font-semibold">Tableau de bord</div>
|
||||||
|
|
||||||
|
<div class="overflow-y-auto min-h-0 p-4 space-y-3">
|
||||||
<!-- Liste des liens à ajouter ci-dessous -->
|
<!-- Liste des liens à ajouter ci-dessous -->
|
||||||
<NuxtLink to="/admin/dashboard">
|
<!--Button pour afficher le component admin-users -->
|
||||||
Tableau de bord
|
<NuxtLink
|
||||||
</NuxtLink>
|
to="/admin/user-list"
|
||||||
<NuxtLink to="/admin/supplier/supplier-list">
|
class="block px-4 py-2 rounded hover:bg-primary-600 transition"
|
||||||
Fournisseur
|
>
|
||||||
</NuxtLink>
|
|
||||||
<NuxtLink to="/admin/carrier/carrier-list">
|
|
||||||
Transporteur
|
|
||||||
</NuxtLink>
|
|
||||||
<NuxtLink to="/admin/user/list">
|
|
||||||
Utilisateurs
|
Utilisateurs
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
<NuxtLink to="/admin/customer/customer-list">
|
|
||||||
Client
|
|
||||||
</NuxtLink>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="p-4">
|
<div class="p-4">
|
||||||
@@ -50,6 +44,7 @@
|
|||||||
Déconnexion
|
Déconnexion
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
<main class="min-h-0 overflow-auto px-12 py-12 ">
|
<main class="min-h-0 overflow-auto px-12 py-12 ">
|
||||||
@@ -63,9 +58,8 @@
|
|||||||
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {useAuthStore} from '~/stores/auth'
|
import auth from "~/layouts/auth.vue";
|
||||||
|
|
||||||
const auth = useAuthStore()
|
|
||||||
const { version } = useAppVersion()
|
const { version } = useAppVersion()
|
||||||
const handleLogout = async () => {
|
const handleLogout = async () => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -20,10 +20,7 @@
|
|||||||
Accueil
|
Accueil
|
||||||
</a>
|
</a>
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
<NuxtLink
|
<NuxtLink to="/admin/dashboard" custom v-slot="{ href, navigate, isActive }">
|
||||||
to="/admin/dashboard" custom v-slot="{ href, navigate, isActive }"
|
|
||||||
v-if="auth.isAdmin"
|
|
||||||
>
|
|
||||||
<a
|
<a
|
||||||
:href="href"
|
:href="href"
|
||||||
@click="navigate"
|
@click="navigate"
|
||||||
@@ -110,27 +107,27 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {useAuthStore} from '~/stores/auth'
|
import { useAuthStore } from '~/stores/auth'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const auth = useAuthStore()
|
const auth = useAuthStore()
|
||||||
const isMenuOpen = ref(false)
|
const isMenuOpen = ref(false)
|
||||||
const {version} = useAppVersion()
|
const { version } = useAppVersion()
|
||||||
|
|
||||||
const closeMenu = () => {
|
const closeMenu = () => {
|
||||||
isMenuOpen.value = false
|
isMenuOpen.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
const toggleMenu = () => {
|
const toggleMenu = () => {
|
||||||
isMenuOpen.value = !isMenuOpen.value
|
isMenuOpen.value = !isMenuOpen.value
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleLogout = async () => {
|
const handleLogout = async () => {
|
||||||
try {
|
try {
|
||||||
await auth.logout()
|
await auth.logout()
|
||||||
} finally {
|
} finally {
|
||||||
closeMenu()
|
closeMenu()
|
||||||
await navigateTo('/login')
|
await navigateTo('/login')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,100 +0,0 @@
|
|||||||
|
|
||||||
<template>
|
|
||||||
<form @submit.prevent="validate">
|
|
||||||
<div class="flex items-center justify-between ">
|
|
||||||
<h1 class="text-3xl font-bold uppercase">
|
|
||||||
{{ route.params.id ? 'Modifier transporteur' : 'Ajout transporteur' }}
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
class="text-xl uppercase bg-primary-500 text-white h-[50px] w-[272px] justify-self-end"
|
|
||||||
>Enregistrer
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="grid grid-cols-2 items-start gap-y-8 gap-x-40 mb-16">
|
|
||||||
<UiTextInput
|
|
||||||
label = "nom du fournisseur"
|
|
||||||
id="carrier-name"
|
|
||||||
v-model="form.name"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<UiTextInput
|
|
||||||
label = "code fournisseur"
|
|
||||||
id="code-id"
|
|
||||||
v-model="form.code"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import {createCarrier, getCarrier, updateCarrier} from "~/services/carrier";
|
|
||||||
import type {CarrierData, CarrierFormData} from "~/services/dto/carrier-data";
|
|
||||||
const router = useRouter()
|
|
||||||
const route = useRoute()
|
|
||||||
const idCarrier = Number(route.params.id)
|
|
||||||
const isLoading = ref(false)
|
|
||||||
const isHydrating = ref(false)
|
|
||||||
|
|
||||||
const form = reactive<CarrierFormData>({
|
|
||||||
code:'',
|
|
||||||
name:''
|
|
||||||
})
|
|
||||||
|
|
||||||
definePageMeta({
|
|
||||||
layout: 'admin'
|
|
||||||
})
|
|
||||||
|
|
||||||
const hydrateFromUser = (carrier: CarrierData | null) => {
|
|
||||||
if (!carrier) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
isHydrating.value = true
|
|
||||||
form.name = carrier.name ?? ''
|
|
||||||
form.code = carrier.code ?? ''
|
|
||||||
isHydrating.value = false
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => idCarrier,
|
|
||||||
async (id) => {
|
|
||||||
if (id === null) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
isLoading.value = true
|
|
||||||
try {
|
|
||||||
const user = await getCarrier(id)
|
|
||||||
hydrateFromUser(user)
|
|
||||||
} finally {
|
|
||||||
isLoading.value = false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{immediate: true}
|
|
||||||
)
|
|
||||||
async function validate() {
|
|
||||||
|
|
||||||
const normalizedCarrierCode = form.code.trim()
|
|
||||||
const normalizedCarrierName = form.name.trim()
|
|
||||||
|
|
||||||
const basePayload = {
|
|
||||||
name: normalizedCarrierName,
|
|
||||||
code: normalizedCarrierCode
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if(idCarrier){
|
|
||||||
await updateCarrier(idCarrier, basePayload)
|
|
||||||
navigate()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
await createCarrier(basePayload)
|
|
||||||
navigate()
|
|
||||||
}
|
|
||||||
|
|
||||||
function navigate(){
|
|
||||||
router.push("/admin/carrier/carrier-list")
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
<template>
|
|
||||||
|
|
||||||
<div class="flex items-center justify-between ">
|
|
||||||
<h1 class="text-3xl font-bold uppercase">listes des transporteurs</h1>
|
|
||||||
<NuxtLink
|
|
||||||
to="/admin/carrier"
|
|
||||||
class="flex items-center justify-center text-xl uppercase bg-primary-500 text-white h-[50px] w-[272px]"
|
|
||||||
>Ajouter
|
|
||||||
</NuxtLink>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mt-6 border border-slate-200 mb-16 ">
|
|
||||||
<div class="grid grid-cols-2 gap-4 bg-slate-100 px-4 py-3 text-sm font-semibold uppercase tracking-wide">
|
|
||||||
<div>Label</div>
|
|
||||||
<div>Code</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-for="carrier in carrierList"
|
|
||||||
:key="carrier.id"
|
|
||||||
class="grid grid-cols-2 gap-4 px-4 py-3 text-sm hover:bg-slate-50 cursor-pointer border-t border-slate-200"
|
|
||||||
role="button"
|
|
||||||
tabindex="0"
|
|
||||||
@click="goToCarrier(carrier.id)"
|
|
||||||
@keydown.enter="goToCarrier(carrier.id)"
|
|
||||||
>
|
|
||||||
<div>{{ carrier.name}}</div>
|
|
||||||
<div>{{ carrier.code }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import type {CarrierData} from "~/services/dto/carrier-data";
|
|
||||||
import {getCarrierList} from "~/services/carrier";
|
|
||||||
|
|
||||||
const carrierList = ref<CarrierData[]>()
|
|
||||||
const router = useRouter()
|
|
||||||
|
|
||||||
const goToCarrier = (id: number) => {
|
|
||||||
router.push(`/admin/carrier/${id}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
definePageMeta({
|
|
||||||
layout: 'admin'
|
|
||||||
})
|
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
carrierList.value = await getCarrierList(false)
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
<template>
|
|
||||||
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
definePageMeta({layout: "admin"})
|
|
||||||
|
|
||||||
const route = useRoute()
|
|
||||||
const router = useRouter()
|
|
||||||
const auth = useAuthStore()
|
|
||||||
|
|
||||||
</script>
|
|
||||||
@@ -1,112 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<h1 class="text-3xl font-bold uppercase">Client</h1>
|
|
||||||
<NuxtLink
|
|
||||||
to="/admin/customer"
|
|
||||||
class="flex items-center justify-center text-xl uppercase bg-primary-500 text-white h-[50px] w-[272px]"
|
|
||||||
:class="auth.isAdmin ? '' : 'cursor-not-allowed opacity-60'"
|
|
||||||
@click="handleAddClick"
|
|
||||||
>
|
|
||||||
Ajouter
|
|
||||||
</NuxtLink>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="auth.isAdmin" class="mt-6 border border-slate-200 mb-16">
|
|
||||||
<div class="max-h-96 overflow-y-auto">
|
|
||||||
<div
|
|
||||||
class="sticky top-0 z-10 grid grid-cols-7 gap-4 bg-slate-100 px-4 py-3 text-sm font-semibold uppercase tracking-wide"
|
|
||||||
>
|
|
||||||
<div>Nom</div>
|
|
||||||
<div>Code</div>
|
|
||||||
<div>Rue</div>
|
|
||||||
<div>Complément</div>
|
|
||||||
<div>Code Postal</div>
|
|
||||||
<div>Ville</div>
|
|
||||||
<div>Pays</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="customerList.length === 0" class="px-4 py-6 text-slate-400">
|
|
||||||
Aucun fournisseur.
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-for="customer in customerList" :key="customer.id">
|
|
||||||
<div
|
|
||||||
v-if="!customer.addresses || customer.addresses.length === 0"
|
|
||||||
class="grid grid-cols-7 border-t gap-4 px-4 py-2 hover:bg-slate-50 cursor-pointer"
|
|
||||||
@click="goToCustomer(customer.id)"
|
|
||||||
>
|
|
||||||
<div class="truncate">{{ customer.label }}</div>
|
|
||||||
<div class="truncate">{{ customer.code }}</div>
|
|
||||||
<div class="col-span-1">Pas d'adresse</div>
|
|
||||||
<div class="uppercase truncate">{{"—"}}</div>
|
|
||||||
<div class="uppercase truncate">{{"—"}}</div>
|
|
||||||
<div class="uppercase truncate">{{"—"}}</div>
|
|
||||||
<div class="uppercase truncate">{{"—"}}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<template v-else-if="customer.addresses.length > 0">
|
|
||||||
<div
|
|
||||||
v-for="(address, idx) in customer.addresses"
|
|
||||||
:key="address.id ?? `${customer.id}-${idx}-${address.street}-${address.postalCode}`"
|
|
||||||
class="grid grid-cols-7 hover:bg-slate-50 border-t gap-4 px-4 py-2 cursor-pointer"
|
|
||||||
:class="idx > 0 ? 'pl-4 border-l-4 border-l-slate-200 bg-slate-50' : ''"
|
|
||||||
@click="goToCustomer(customer.id)"
|
|
||||||
>
|
|
||||||
<div class="truncate">
|
|
||||||
{{ idx === 0 ? customer.label : "↳" }}
|
|
||||||
</div>
|
|
||||||
<div class="truncate">{{ idx === 0 ? customer.code : "" }}</div>
|
|
||||||
<div class="truncate">{{ address.street || "—" }}</div>
|
|
||||||
<div class="truncate">{{ address.street2 || "—" }}</div>
|
|
||||||
<div>{{ address.postalCode || "—" }}</div>
|
|
||||||
<div class="uppercase truncate">{{ address.city || "—" }}</div>
|
|
||||||
<div class="uppercase truncate">{{ address.countryCode || "—" }}</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template v-else>
|
|
||||||
<div
|
|
||||||
class="grid grid-cols-7 hover:bg-slate-50 border-t gap-4 px-4 py-2 cursor-pointer"
|
|
||||||
@click="goToCustomer(customer.id)"
|
|
||||||
>
|
|
||||||
<div class="truncate">{{ customer.label }}</div>
|
|
||||||
<div class="truncate">{{ customer.code }}</div>
|
|
||||||
<div class="col-span-5 text-slate-400">
|
|
||||||
Adresses non chargées
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-else class="mt-6 border border-slate-200 mb-16 px-4 py-6 text-slate-400">
|
|
||||||
Accès réservé aux administrateurs.
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { getCustomerList } from "~/services/customer"
|
|
||||||
import type { CustomerData } from "~/services/dto/customer-data"
|
|
||||||
import { useAuthStore } from "~/stores/auth"
|
|
||||||
|
|
||||||
definePageMeta({ layout: "admin" })
|
|
||||||
|
|
||||||
const customerList = ref<CustomerData[]>([])
|
|
||||||
const router = useRouter()
|
|
||||||
const auth = useAuthStore()
|
|
||||||
|
|
||||||
const goToCustomer = (id: number) => {
|
|
||||||
if (!auth.isAdmin) return
|
|
||||||
router.push(`/admin/customer/${id}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleAddClick = (event: Event) => {
|
|
||||||
if (auth.isAdmin) return
|
|
||||||
event.preventDefault()
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
if (!auth.isAdmin) return
|
|
||||||
customerList.value = await getCustomerList()
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
@@ -1,7 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
|
<admin-users v-if="activeCode === 'users'" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
layout: 'admin'
|
layout: 'admin'
|
||||||
})
|
})
|
||||||
|
const route = useRoute()
|
||||||
|
|
||||||
|
const activeCode = computed(() => (route.query.code as string))
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,194 +0,0 @@
|
|||||||
<template>
|
|
||||||
<form @submit.prevent="validate">
|
|
||||||
<div class="flex items-center justify-between gap-10">
|
|
||||||
<h1 class="text-3xl font-bold uppercase">
|
|
||||||
{{ supplierId ? "Modifications du fournisseur" : "Ajout d'un fournisseur" }}
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
<button
|
|
||||||
class="text-xl uppercase bg-primary-500 text-white h-[50px] w-[272px]"
|
|
||||||
type="submit"
|
|
||||||
:disabled="isLoading || !auth.isAdmin"
|
|
||||||
>
|
|
||||||
{{ supplierId ? "Sauvegarder" : "Ajouter" }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="grid grid-cols-2 gap-y-16 gap-x-12 mb-10 py-12 border-b border-black ">
|
|
||||||
<UiTextInput id="supplier-name" v-model="form.name" label="Nom du fournisseur" :disabled="!auth.isAdmin"/>
|
|
||||||
<UiTextInput id="supplier-email" v-model="form.email" label="Email" :disabled="!auth.isAdmin"/>
|
|
||||||
<UiTextInput id="supplier-phone" v-model="form.phone" label="Téléphone" :disabled="!auth.isAdmin"/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex items-center justify-between mb-4 py-6 border-t border-black"></div>
|
|
||||||
<div class="flex items-center justify-between mb-4">
|
|
||||||
<h2 class="text-3xl font-bold uppercase">Adresses fournisseur</h2>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="text-xl uppercase bg-primary-500 text-white h-[50px] w-[272px]"
|
|
||||||
:disabled="supplierId === null || !auth.isAdmin"
|
|
||||||
@click="goToAddAddress"
|
|
||||||
>
|
|
||||||
Ajouter
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="overflow-x-auto mb-10">
|
|
||||||
<table class="w-full border-collapse">
|
|
||||||
<thead>
|
|
||||||
<tr class="text-left border-b border-gray-200">
|
|
||||||
<th class="py-3 pr-4 text-sm uppercase">Libellé</th>
|
|
||||||
<th class="py-3 pr-4 text-sm uppercase">Rue</th>
|
|
||||||
<th class="py-3 pr-4 text-sm uppercase">Complément</th>
|
|
||||||
<th class="py-3 pr-4 text-sm uppercase">Code postal</th>
|
|
||||||
<th class="py-3 pr-4 text-sm uppercase">Ville</th>
|
|
||||||
<th class="py-3 pr-4 text-sm uppercase">Pays</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<template v-if="form.addresses.length === 0">
|
|
||||||
<tr>
|
|
||||||
<td colspan="6" class="py-4 text-slate-400">
|
|
||||||
Aucune adresse.
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<tr
|
|
||||||
v-for="(address, index) in form.addresses"
|
|
||||||
:key="address.id ?? index"
|
|
||||||
class="border-b border-gray-100 hover:bg-slate-50"
|
|
||||||
:class="auth.isAdmin ? 'cursor-pointer' : 'cursor-not-allowed opacity-60'"
|
|
||||||
@click="goToEditAddress(address.id ?? null)"
|
|
||||||
>
|
|
||||||
<td class="py-3 pr-4">{{ address.label || "—" }}</td>
|
|
||||||
<td class="py-3 pr-4">{{ address.street || "—" }}</td>
|
|
||||||
<td class="py-3 pr-4">{{ address.street2 || "—" }}</td>
|
|
||||||
<td class="py-3 pr-4">{{ address.postalCode || "—" }}</td>
|
|
||||||
<td class="py-3 pr-4">{{ address.city || "—" }}</td>
|
|
||||||
<td class="py-3 pr-4">{{ address.countryCode || "—" }}</td>
|
|
||||||
</tr>
|
|
||||||
</template>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import {computed, reactive, ref, watch} from "vue"
|
|
||||||
import {createSupplier, getSupplier, updateSupplier} from "~/services/supplier"
|
|
||||||
import type {SupplierData, SupplierFormData, SupplierPayload} from "~/services/dto/supplier-data"
|
|
||||||
import {useAuthStore} from "~/stores/auth"
|
|
||||||
|
|
||||||
definePageMeta({layout: "admin"})
|
|
||||||
|
|
||||||
const route = useRoute()
|
|
||||||
const router = useRouter()
|
|
||||||
const auth = useAuthStore()
|
|
||||||
|
|
||||||
const resolveId = (param: unknown) => {
|
|
||||||
const idStr = Array.isArray(param) ? param[0] : param
|
|
||||||
if (!idStr) return null
|
|
||||||
const id = Number(idStr)
|
|
||||||
return Number.isFinite(id) ? id : null
|
|
||||||
}
|
|
||||||
const supplierId = computed(() => resolveId(route.params.id))
|
|
||||||
const isLoading = ref(false)
|
|
||||||
const form = reactive<SupplierFormData>({
|
|
||||||
name: "",
|
|
||||||
email: "",
|
|
||||||
phone: "",
|
|
||||||
addresses: [],
|
|
||||||
})
|
|
||||||
|
|
||||||
const goToAddAddress = () => {
|
|
||||||
if (supplierId.value === null || !auth.isAdmin) return
|
|
||||||
router.push({
|
|
||||||
path: "/admin/supplier/address",
|
|
||||||
query: {
|
|
||||||
supplierId: String(supplierId.value),
|
|
||||||
fromSupplier: "1",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const goToEditAddress = (addressId: number | null) => {
|
|
||||||
if (supplierId.value === null || addressId === null || !auth.isAdmin) return
|
|
||||||
router.push({
|
|
||||||
path: "/admin/supplier/address",
|
|
||||||
query: {
|
|
||||||
supplierId: String(supplierId.value),
|
|
||||||
addressId: String(addressId),
|
|
||||||
fromSupplier: "1",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const hydrateFromSupplier = (supplier: SupplierData | null) => {
|
|
||||||
if (!supplier) return
|
|
||||||
form.name = supplier.name ?? ""
|
|
||||||
form.email = supplier.email ?? ""
|
|
||||||
form.phone = supplier.phone ?? ""
|
|
||||||
if (!Array.isArray(supplier.addresses) || supplier.addresses.length === 0) {
|
|
||||||
form.addresses = []
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (typeof supplier.addresses[0] === "string") {
|
|
||||||
form.addresses = []
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
form.addresses = supplier.addresses.map((address) => ({
|
|
||||||
id: address.id ?? null,
|
|
||||||
label: address.label ?? "",
|
|
||||||
street: address.street ?? "",
|
|
||||||
street2: address.street2 ?? null,
|
|
||||||
postalCode: address.postalCode ?? "",
|
|
||||||
city: address.city ?? "",
|
|
||||||
countryCode: address.countryCode ?? "",
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => supplierId.value,
|
|
||||||
async (id) => {
|
|
||||||
if (id === null) return
|
|
||||||
isLoading.value = true
|
|
||||||
try {
|
|
||||||
const supplier = await getSupplier(id)
|
|
||||||
hydrateFromSupplier(supplier)
|
|
||||||
} finally {
|
|
||||||
isLoading.value = false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{immediate: true}
|
|
||||||
)
|
|
||||||
|
|
||||||
async function validate() {
|
|
||||||
if (isLoading.value) return
|
|
||||||
if (!auth.isAdmin) return
|
|
||||||
isLoading.value = true
|
|
||||||
|
|
||||||
try {
|
|
||||||
const name = form.name.trim()
|
|
||||||
const email = (form.email ?? "").trim() || null
|
|
||||||
const phone = (form.phone ?? "").trim() || null
|
|
||||||
|
|
||||||
const supplierPayload: SupplierPayload = {
|
|
||||||
name,
|
|
||||||
email,
|
|
||||||
phone,
|
|
||||||
}
|
|
||||||
|
|
||||||
if (supplierId.value !== null) {
|
|
||||||
await updateSupplier(supplierId.value, supplierPayload)
|
|
||||||
} else {
|
|
||||||
await createSupplier(supplierPayload)
|
|
||||||
}
|
|
||||||
|
|
||||||
await router.push("/admin/supplier/supplier-list")
|
|
||||||
} finally {
|
|
||||||
isLoading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
<template>
|
|
||||||
<Address type="supplier" :address="address" @validate="validate"/>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import type {AddressData, AddressPayload} from "~/services/address";
|
|
||||||
import {createAddress, getAddress, updateAddress} from "~/services/address";
|
|
||||||
import {getSupplier, updateSupplier} from "~/services/supplier";
|
|
||||||
import type {SupplierData} from "~/services/dto/supplier-data";
|
|
||||||
|
|
||||||
definePageMeta({ layout: "admin" })
|
|
||||||
|
|
||||||
const route = useRoute()
|
|
||||||
const router = useRouter()
|
|
||||||
const supplierId = computed(() => { return Number(route.query.supplierId) })
|
|
||||||
const supplier = ref<SupplierData|null>(null);
|
|
||||||
const addressId = computed(() => { return route.query.addressId !== undefined ? Number(route.query.addressId) : null })
|
|
||||||
const address = ref<AddressData|null>(null)
|
|
||||||
|
|
||||||
const validate = async (address: AddressPayload) => {
|
|
||||||
try {
|
|
||||||
if (addressId.value !== null) {
|
|
||||||
await updateAddress(addressId.value, address)
|
|
||||||
} else {
|
|
||||||
await addAddress(address)
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
await router.push('/admin/supplier/' + supplierId.value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const addAddress = async (address: AddressPayload) => {
|
|
||||||
const response: AddressData = await createAddress(address)
|
|
||||||
const addressIRI = `/api/addresses/${response.id}`
|
|
||||||
const existingIris = (supplier.value.addresses ?? []).map((item: any) => `/api/addresses/${item.id}`)
|
|
||||||
const next = [...new Set([...existingIris, addressIRI])]
|
|
||||||
|
|
||||||
return await updateSupplier(supplierId.value, { addresses: next })
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
supplier.value = await getSupplier(supplierId.value)
|
|
||||||
if (addressId.value !== null) {
|
|
||||||
address.value = await getAddress(addressId.value)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
@@ -1,112 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<h1 class="text-3xl font-bold uppercase">Fournisseurs</h1>
|
|
||||||
<NuxtLink
|
|
||||||
to="/admin/supplier"
|
|
||||||
class="flex items-center justify-center text-xl uppercase bg-primary-500 text-white h-[50px] w-[272px]"
|
|
||||||
:class="auth.isAdmin ? '' : 'cursor-not-allowed opacity-60'"
|
|
||||||
@click="handleAddClick"
|
|
||||||
>
|
|
||||||
Ajouter
|
|
||||||
</NuxtLink>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="auth.isAdmin" class="mt-6 border border-slate-200 mb-16">
|
|
||||||
<div class="max-h-96 overflow-y-auto">
|
|
||||||
<div
|
|
||||||
class="sticky top-0 z-10 grid grid-cols-7 gap-4 bg-slate-100 px-4 py-3 text-sm font-semibold uppercase tracking-wide"
|
|
||||||
>
|
|
||||||
<div>Nom</div>
|
|
||||||
<div>Mail</div>
|
|
||||||
<div>Rue</div>
|
|
||||||
<div>Complément</div>
|
|
||||||
<div>Code Postal</div>
|
|
||||||
<div>Ville</div>
|
|
||||||
<div>Pays</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="supplierList.length === 0" class="px-4 py-6 text-slate-400">
|
|
||||||
Aucun fournisseur.
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-for="supplier in supplierList" :key="supplier.id">
|
|
||||||
<div
|
|
||||||
v-if="!supplier.addresses || supplier.addresses.length === 0"
|
|
||||||
class="grid grid-cols-7 border-t gap-4 px-4 py-2 hover:bg-slate-50 cursor-pointer"
|
|
||||||
@click="goToSupplier(supplier.id)"
|
|
||||||
>
|
|
||||||
<div class="truncate">{{ supplier.name }}</div>
|
|
||||||
<div class="truncate">{{ supplier.email }}</div>
|
|
||||||
<div class="col-span-1">Pas d'adresse</div>
|
|
||||||
<div class="uppercase truncate">{{"—"}}</div>
|
|
||||||
<div class="uppercase truncate">{{"—"}}</div>
|
|
||||||
<div class="uppercase truncate">{{"—"}}</div>
|
|
||||||
<div class="uppercase truncate">{{"—"}}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<template v-else-if="supplier.addresses.length > 0">
|
|
||||||
<div
|
|
||||||
v-for="(address, idx) in supplier.addresses"
|
|
||||||
:key="address.id ?? `${supplier.id}-${idx}-${address.street}-${address.postalCode}`"
|
|
||||||
class="grid grid-cols-7 hover:bg-slate-50 border-t gap-4 px-4 py-2 cursor-pointer"
|
|
||||||
:class="idx > 0 ? 'pl-4 border-l-4 border-l-slate-200 bg-slate-50' : ''"
|
|
||||||
@click="goToSupplier(supplier.id)"
|
|
||||||
>
|
|
||||||
<div class="truncate">
|
|
||||||
{{ idx === 0 ? supplier.name : "↳" }}
|
|
||||||
</div>
|
|
||||||
<div class="truncate">{{ idx === 0 ? supplier.email : "" }}</div>
|
|
||||||
<div class="truncate">{{ address.street || "—" }}</div>
|
|
||||||
<div class="truncate">{{ address.street2 || "—" }}</div>
|
|
||||||
<div>{{ address.postalCode || "—" }}</div>
|
|
||||||
<div class="uppercase truncate">{{ address.city || "—" }}</div>
|
|
||||||
<div class="uppercase truncate">{{ address.countryCode || "—" }}</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template v-else>
|
|
||||||
<div
|
|
||||||
class="grid grid-cols-7 hover:bg-slate-50 border-t gap-4 px-4 py-2 cursor-pointer"
|
|
||||||
@click="goToSupplier(supplier.id)"
|
|
||||||
>
|
|
||||||
<div class="truncate">{{ supplier.name }}</div>
|
|
||||||
<div class="truncate">{{ supplier.email }}</div>
|
|
||||||
<div class="col-span-5 text-slate-400">
|
|
||||||
Adresses non chargées
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-else class="mt-6 border border-slate-200 mb-16 px-4 py-6 text-slate-400">
|
|
||||||
Accès réservé aux administrateurs.
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { getSupplierList } from "~/services/supplier"
|
|
||||||
import type { SupplierData } from "~/services/dto/supplier-data"
|
|
||||||
import { useAuthStore } from "~/stores/auth"
|
|
||||||
|
|
||||||
definePageMeta({ layout: "admin" })
|
|
||||||
|
|
||||||
const supplierList = ref<SupplierData[]>([])
|
|
||||||
const router = useRouter()
|
|
||||||
const auth = useAuthStore()
|
|
||||||
|
|
||||||
const goToSupplier = (id: number) => {
|
|
||||||
if (!auth.isAdmin) return
|
|
||||||
router.push(`/admin/supplier/${id}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleAddClick = (event: Event) => {
|
|
||||||
if (auth.isAdmin) return
|
|
||||||
event.preventDefault()
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
if (!auth.isAdmin) return
|
|
||||||
supplierList.value = await getSupplierList()
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
@@ -1,13 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between gap-10">
|
||||||
<h1 class="text-3xl font-bold uppercase">Liste des utilisateurs</h1>
|
<h1 class="text-3xl font-bold uppercase">Utilisateurs</h1>
|
||||||
<NuxtLink
|
<button
|
||||||
class="flex items-center justify-center text-xl uppercase bg-primary-500 text-white h-[50px] w-[272px]"
|
class="text-xl uppercase bg-primary-500 text-white h-[50px] w-[272px]"
|
||||||
@click="router.push('/admin/user/')"
|
@click="null"
|
||||||
>
|
>
|
||||||
Ajouter
|
Ajouter
|
||||||
</NuxtLink>
|
</button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
@@ -15,43 +14,51 @@
|
|||||||
<div class="grid grid-cols-3 gap-4 bg-slate-100 px-4 py-3 text-sm font-semibold uppercase tracking-wide">
|
<div class="grid grid-cols-3 gap-4 bg-slate-100 px-4 py-3 text-sm font-semibold uppercase tracking-wide">
|
||||||
<div>Username</div>
|
<div>Username</div>
|
||||||
<div>Role</div>
|
<div>Role</div>
|
||||||
|
<div>Action</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-for="user in userList"
|
v-for="user in userList"
|
||||||
:key="user.id"
|
:key="user.id"
|
||||||
class="grid grid-cols-3 gap-4 px-4 py-3 text-sm hover:bg-slate-50 cursor-pointer border-t items-center"
|
class="grid grid-cols-3 gap-4 px-4 py-3 text-sm hover:bg-slate-50 cursor-pointer border-t border-slate-200 items-center"
|
||||||
role="button"
|
role="button"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
@click="goToUser(user.id)"
|
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
{{ user.username }}
|
{{ user.username}}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{{ user.roles?.join(', ') || ' ---' }}
|
{{ user.roles?.join(', ') || ' ---' }}
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="p-4">
|
||||||
|
<button
|
||||||
|
class="text-xl uppercase bg-primary-500 text-white h-[50px] w-[272px]"
|
||||||
|
@click="null"
|
||||||
|
>
|
||||||
|
Modifier
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
layout: 'admin'
|
layout: 'admin'
|
||||||
})
|
})
|
||||||
|
|
||||||
import type {UserData} from "~/services/dto/user-data";
|
import type {UserData} from "~/services/dto/user-data";
|
||||||
import {getAdminUsers, getUsers} from "~/services/auth";
|
import {getUsers} from "~/services/auth";
|
||||||
|
|
||||||
const userList = ref<UserData[]>([])
|
const userList = ref<UserData[]>([])
|
||||||
const router = useRouter()
|
|
||||||
|
|
||||||
const goToUser = (id: number) => {
|
|
||||||
router.push(`/admin/user/${id}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
userList.value = await getAdminUsers()
|
userList.value = await getUsers()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
@@ -1,125 +0,0 @@
|
|||||||
<template>
|
|
||||||
<form @submit.prevent="validate">
|
|
||||||
<div
|
|
||||||
class="flex items-center justify-between gap-10">
|
|
||||||
<h1 class="text-3xl font-bold uppercase">
|
|
||||||
{{ userId ? "Modifications de l'utilisateur" : "Ajout d'un utilisateur" }}
|
|
||||||
</h1>
|
|
||||||
<button
|
|
||||||
class="text-xl uppercase bg-primary-500 text-white h-[50px] w-[272px]"
|
|
||||||
type="submit"
|
|
||||||
>
|
|
||||||
{{ userId ? 'Sauvegarder' : 'Ajouter' }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="grid gap-y-16 gap-x-40 mb-16">
|
|
||||||
<UiTextInput
|
|
||||||
id="user-name"
|
|
||||||
v-model="form.username"
|
|
||||||
label="Nom de l'utilisateur"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<UiSelect
|
|
||||||
id="user-role"
|
|
||||||
v-model="form.role"
|
|
||||||
label="Rôle de l'utilisateur"
|
|
||||||
:options="ROLE"
|
|
||||||
/>
|
|
||||||
<UiTextInput
|
|
||||||
id="user-password"
|
|
||||||
v-model="form.password"
|
|
||||||
label="Mot de passe"
|
|
||||||
type="password"
|
|
||||||
|
|
||||||
/>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</template>
|
|
||||||
<script setup lang="ts">
|
|
||||||
definePageMeta({
|
|
||||||
layout: 'admin'
|
|
||||||
})
|
|
||||||
|
|
||||||
import {computed, reactive, ref, watch} from 'vue'
|
|
||||||
import {ROLE} from '~/utils/constants'
|
|
||||||
import {createUser, updateUser, getUser} from '~/services/auth'
|
|
||||||
import type {UserData, UserFormData} from '~/services/dto/user-data'
|
|
||||||
|
|
||||||
const route = useRoute()
|
|
||||||
const router = useRouter()
|
|
||||||
const userId = computed(() => resolveUserId(route.params.id))
|
|
||||||
const isLoading = ref(false)
|
|
||||||
const isHydrating = ref(false)
|
|
||||||
|
|
||||||
const resolveUserId = (param: unknown) => {
|
|
||||||
const idStr = Array.isArray(param) ? param[0] : param
|
|
||||||
if (!idStr) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
const id = Number(idStr)
|
|
||||||
return Number.isFinite(id) ? id : null
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const form = reactive<UserFormData>({
|
|
||||||
username: '',
|
|
||||||
password: '',
|
|
||||||
role: ''
|
|
||||||
})
|
|
||||||
|
|
||||||
const hydrateFromUser = (user: UserData | null) => {
|
|
||||||
if (!user) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
isHydrating.value = true
|
|
||||||
form.username = user.username ?? ''
|
|
||||||
const roles = user.roles ?? []
|
|
||||||
const hasAdmin = roles.includes("ROLE_ADMIN")
|
|
||||||
form.role = hasAdmin ? "ROLE_ADMIN" : "ROLE_USER"
|
|
||||||
form.password = ''
|
|
||||||
isHydrating.value = false
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => userId.value,
|
|
||||||
async (id) => {
|
|
||||||
if (id === null) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
isLoading.value = true
|
|
||||||
try {
|
|
||||||
const user = await getUser(id)
|
|
||||||
hydrateFromUser(user)
|
|
||||||
} finally {
|
|
||||||
isLoading.value = false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{immediate: true}
|
|
||||||
)
|
|
||||||
|
|
||||||
async function validate() {
|
|
||||||
|
|
||||||
const normalizedUsername = form.username.trim()
|
|
||||||
const normalizedRole = form.role.trim()
|
|
||||||
const normalizedPassword = form.password.trim()
|
|
||||||
|
|
||||||
const basePayload = {
|
|
||||||
username: normalizedUsername,
|
|
||||||
roles: normalizedRole ? [normalizedRole] : undefined,
|
|
||||||
password: normalizedPassword || undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
if (userId.value) {
|
|
||||||
await updateUser(userId.value, basePayload)
|
|
||||||
await router.push(`/admin/user/list/`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const created = await createUser(basePayload)
|
|
||||||
if (created) {
|
|
||||||
await router.push(`/admin/user/list/`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -3,13 +3,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex flex-wrap justify-center mt-8 gap-8 mb-8 md:mb-0">
|
<div class="flex flex-wrap justify-center mt-8 gap-8 mb-8 md:mb-0">
|
||||||
<card-link label="NOUVELLE RÉCEPTION" link="/reception" iconName="mdi:truck-outline" />
|
<card-link label="NOUVELLE RÉCEPTION" link="/reception" iconName="mdi:truck-outline" />
|
||||||
<card-link label="NOUVELLE EXPÉDITION" link="/shipment" iconName="mdi:truck-fast-outline" />
|
<card-link label="NOUVELLE EXPÉDITION" link="/" iconName="mdi:truck-fast-outline" />
|
||||||
<card-link label="PLAN DE SITE" link="/" iconName="mdi:warehouse" />
|
<card-link label="PLAN DE SITE" link="/" iconName="mdi:warehouse" />
|
||||||
<card-link label="RÉCEPTIONS EN ATTENTE" link="/reception/waiting-reception" iconName="mdi:truck-remove-outline" />
|
<card-link label="RÉCEPTIONS EN ATTENTE" link="/reception/waiting-reception" iconName="mdi:truck-remove-outline" />
|
||||||
<card-link label="EXPÉDITIONS EN ATTENTE" link="/shipment/waiting-shipment" iconName="mdi:truck-cargo-container" />
|
<card-link label="EXPÉDITIONS EN ATTENTE" link="/" iconName="mdi:truck-cargo-container" />
|
||||||
<card-link label="CASES" link="/" iconName="mdi:cube-outline" />
|
<card-link label="CASES" link="/" iconName="mdi:cube-outline" />
|
||||||
<card-link label="RÉCEPTIONS FINIES" link="/reception/finish-reception" iconName="mdi:truck-check-outline" />
|
<card-link label="RÉCEPTIONS FINIES" link="/reception/finish-reception" iconName="mdi:truck-check-outline" />
|
||||||
<card-link label="EXPÉDITIONS FINIES" link="/shipment/finish-shipment" iconName="mdi:truck-delivery-outline" />
|
<card-link label="EXPÉDITIONS FINIES" link="/" iconName="mdi:truck-delivery-outline" />
|
||||||
<card-link label="PASSEPORT DU BOVIN" link="/" iconName="mdi:cow" />
|
<card-link label="PASSEPORT DU BOVIN" link="/" iconName="mdi:cow" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -20,7 +20,6 @@
|
|||||||
class="grid grid-cols-6 gap-4 px-4 py-3 text-sm hover:bg-slate-50 cursor-pointer border-t border-slate-200"
|
class="grid grid-cols-6 gap-4 px-4 py-3 text-sm hover:bg-slate-50 cursor-pointer border-t border-slate-200"
|
||||||
role="button"
|
role="button"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
@click="goToReception(reception.id)"
|
|
||||||
>
|
>
|
||||||
<div>{{ reception.identificationNumber}}</div>
|
<div>{{ reception.identificationNumber}}</div>
|
||||||
<div>{{ reception.receptionDate}}</div>
|
<div>{{ reception.receptionDate}}</div>
|
||||||
@@ -48,10 +47,6 @@ const formatWeighing = (reception: ReceptionData, type: 'gross' | 'tare') => {
|
|||||||
return `${entry.weight} kg`
|
return `${entry.weight} kg`
|
||||||
}
|
}
|
||||||
|
|
||||||
const goToReception = (id: number) => {
|
|
||||||
router.push(`/reception/update/${id}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
receptionList.value = await getReceptionList(true)
|
receptionList.value = await getReceptionList(true)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,546 +0,0 @@
|
|||||||
<template>
|
|
||||||
|
|
||||||
<form @submit.prevent="validate">
|
|
||||||
<div class="flex items-center justify-between mt-8 mb-8 ">
|
|
||||||
<h1 class="font-bold text-5xl uppercase">Réception {{receptionLoad?.identificationNumber}}</h1>
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
class="text-xl uppercase bg-primary-500 text-white h-[50px] w-[272px] justify-self-end"
|
|
||||||
:disabled="!auth.isAdmin"
|
|
||||||
>Enregistrer
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="grid grid-cols-2 items-start gap-y-8 gap-x-40 mb-16">
|
|
||||||
<!-- Nom de l'utilisateur -->
|
|
||||||
<UiSelect
|
|
||||||
id="reception-user"
|
|
||||||
:disabled="!auth.isAdmin"
|
|
||||||
v-model="form.userId"
|
|
||||||
label="Nom de l'utilisateur"
|
|
||||||
:options="users.map((user) => ({
|
|
||||||
value: String(user.id),
|
|
||||||
label: user.username
|
|
||||||
}))"
|
|
||||||
:loading="isLoadingUsers"
|
|
||||||
wrapper-class="col-start-1 row-start-1"
|
|
||||||
/>
|
|
||||||
<!-- Date de réception -->
|
|
||||||
<UiDateInput
|
|
||||||
id="reception-date"
|
|
||||||
:disabled="!auth.isAdmin"
|
|
||||||
v-model="form.receptionDate"
|
|
||||||
label="Date de réception"
|
|
||||||
wrapper-class="col-start-1 row-start-2"
|
|
||||||
/>
|
|
||||||
<!-- Fournisseur -->
|
|
||||||
<UiSelect
|
|
||||||
id="reception-supplier"
|
|
||||||
v-model="form.supplierId"
|
|
||||||
:disabled="!auth.isAdmin"
|
|
||||||
label="Fournisseur"
|
|
||||||
:options="suppliers.map((supplier) => ({
|
|
||||||
value: String(supplier.id),
|
|
||||||
label: supplier.name
|
|
||||||
}))"
|
|
||||||
:loading="isLoadingSuppliers"
|
|
||||||
wrapper-class="col-start-1 row-start-3"
|
|
||||||
/>
|
|
||||||
<!-- Adresse fournisseur -->
|
|
||||||
<UiSelect
|
|
||||||
id="reception-address"
|
|
||||||
v-model="form.addressId"
|
|
||||||
label="Adresse"
|
|
||||||
:options="supplierAddresses.map((address) => ({
|
|
||||||
value: String(address.id),
|
|
||||||
label: address.fullAddress
|
|
||||||
}))"
|
|
||||||
:disabled="(isLoadingSuppliers || supplierAddresses.length === 0) && !auth.isAdmin"
|
|
||||||
wrapper-class="col-start-1 row-start-4"
|
|
||||||
/>
|
|
||||||
<!-- Camion -->
|
|
||||||
<UiSelect
|
|
||||||
id="reception-truck"
|
|
||||||
v-model="form.truckId"
|
|
||||||
:disabled="!auth.isAdmin"
|
|
||||||
label="Camion"
|
|
||||||
:options="trucks.map((truck) => ({
|
|
||||||
value: String(truck.id),
|
|
||||||
label: truck.name
|
|
||||||
}))"
|
|
||||||
:loading="isLoadingTrucks"
|
|
||||||
wrapper-class="col-start-2 row-start-1"
|
|
||||||
/>
|
|
||||||
<!-- Transporteur -->
|
|
||||||
<UiSelect
|
|
||||||
id="reception-carrier"
|
|
||||||
v-model="form.carrierId"
|
|
||||||
label="Transporteur"
|
|
||||||
:disabled="!auth.isAdmin"
|
|
||||||
:options="carriers.map((carrier) => ({
|
|
||||||
value: String(carrier.id),
|
|
||||||
label: carrier.name
|
|
||||||
}))"
|
|
||||||
:loading="isLoadingCarriers"
|
|
||||||
select-class="h-[34px]"
|
|
||||||
wrapper-class="col-start-2 row-start-2"
|
|
||||||
/>
|
|
||||||
<!-- Chauffeur (LIOT) -->
|
|
||||||
<UiSelect
|
|
||||||
id="reception-driver"
|
|
||||||
v-model="form.driverId"
|
|
||||||
:disabled="!auth.isAdmin"
|
|
||||||
label="Nom du chauffeur si LIOT"
|
|
||||||
:options="filteredDrivers.map((driver) => ({
|
|
||||||
value: String(driver.id),
|
|
||||||
label: driver.name
|
|
||||||
}))"
|
|
||||||
:loading="isLoadingDrivers"
|
|
||||||
wrapper-class="col-start-2 row-start-3"
|
|
||||||
/>
|
|
||||||
<!-- Plaque d'immatriculation -->
|
|
||||||
<div v-if="!isLiotCarrier" class="col-start-2 row-start-4">
|
|
||||||
<UiLicensePlateInput
|
|
||||||
:disabled="!auth.isAdmin"
|
|
||||||
v-model="form.licensePlate"
|
|
||||||
v-model:allowAny="allowAnyLicensePlate"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<!-- Immatriculation (LIOT) -->
|
|
||||||
<UiSelect
|
|
||||||
v-if="isLiotCarrier"
|
|
||||||
id="reception-vehicle"
|
|
||||||
v-model="form.vehicleId"
|
|
||||||
label="Immatriculation"
|
|
||||||
:options="filteredVehicles.map((vehicle) => ({
|
|
||||||
value: String(vehicle.id),
|
|
||||||
label: vehicle.plate
|
|
||||||
}))"
|
|
||||||
:loading="isLoadingVehicles"
|
|
||||||
:disabled="(isLoadingVehicles || filteredVehicles.length === 0) && !auth.isAdmin"
|
|
||||||
wrapper-class="col-start-2 row-start-4"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="grid grid-cols-2 items-start gap-y-8 gap-x-40 mb-16">
|
|
||||||
<h1 class="font-bold text-5xl uppercase col-start-1 row-start-1" @click="isBtWeight = true" >pesées</h1>
|
|
||||||
<h1 class="font-bold text-5xl uppercase col-start-2 row-start-1" @click="isBtWeight = false">{{isMerchandise ? "Marchandises" : "Bovins"}}</h1>
|
|
||||||
</div>
|
|
||||||
<update-weight
|
|
||||||
v-if="isBtWeight"
|
|
||||||
:idReception="idReception"
|
|
||||||
:disabled="!auth.isAdmin"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<update-merchandise
|
|
||||||
v-else-if="isMerchandise"
|
|
||||||
:idReception="idReception"
|
|
||||||
:disabled="!auth.isAdmin"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<update-bovin
|
|
||||||
v-else
|
|
||||||
:idReception="idReception"
|
|
||||||
:disabled="!auth.isAdmin"
|
|
||||||
/>
|
|
||||||
|
|
||||||
</form>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import {useReceptionStore} from '~/stores/reception'
|
|
||||||
import type {UserData} from '~/services/dto/user-data'
|
|
||||||
import {getUsers} from '~/services/auth'
|
|
||||||
import {useAuthStore} from '~/stores/auth'
|
|
||||||
import type {SupplierData} from '~/services/dto/supplier-data'
|
|
||||||
import {getSupplierList} from '~/services/supplier'
|
|
||||||
import type {TruckData} from '~/services/dto/truck-data'
|
|
||||||
import {getTruckList} from '~/services/truck'
|
|
||||||
import type {CarrierData} from '~/services/dto/carrier-data'
|
|
||||||
import {getCarrierList} from '~/services/carrier'
|
|
||||||
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 {SUPPLIER_CODE} from "~/utils/constants";
|
|
||||||
import {deleteReceptionBovine, getReceptionBovineList} from "~/services/reception-bovine";
|
|
||||||
import type {ReceptionData, ReceptionFormData} from "~/services/dto/reception-data";
|
|
||||||
import {getReception} from "~/services/reception";
|
|
||||||
import UpdateWeight from "~/components/reception/update-weight.vue";
|
|
||||||
import UpdateMerchandise from "~/components/reception/update-merchandise.vue";
|
|
||||||
import UpdateBovin from "~/components/reception/update-bovin.vue";
|
|
||||||
|
|
||||||
const router = useRouter()
|
|
||||||
const receptionStore = useReceptionStore()
|
|
||||||
const form = reactive<ReceptionFormData>({
|
|
||||||
licensePlate: '',
|
|
||||||
receptionDate: new Date().toISOString().slice(0, 10),
|
|
||||||
receptionTypeId: '',
|
|
||||||
userId: '',
|
|
||||||
supplierId: '',
|
|
||||||
addressId: '',
|
|
||||||
truckId: '',
|
|
||||||
carrierId: '',
|
|
||||||
driverId: '',
|
|
||||||
vehicleId: ''
|
|
||||||
})
|
|
||||||
const allowAnyLicensePlate = ref(false)
|
|
||||||
const isLoading = ref(false)
|
|
||||||
const users = ref<UserData[]>([])
|
|
||||||
const isLoadingUsers = ref(false)
|
|
||||||
const suppliers = ref<SupplierData[]>([])
|
|
||||||
const isLoadingSuppliers = ref(false)
|
|
||||||
const trucks = ref<TruckData[]>([])
|
|
||||||
const isLoadingTrucks = ref(false)
|
|
||||||
const carriers = ref<CarrierData[]>([])
|
|
||||||
const isLoadingCarriers = ref(false)
|
|
||||||
const drivers = ref<DriverData[]>([])
|
|
||||||
const isLoadingDrivers = ref(false)
|
|
||||||
const vehicles = ref<VehicleData[]>([])
|
|
||||||
const isLoadingVehicles = ref(false)
|
|
||||||
const authStore = useAuthStore()
|
|
||||||
|
|
||||||
// Empêche les watchers de reset des champs pendant le remplissage initial
|
|
||||||
const isHydrating = ref(false)
|
|
||||||
const route = useRoute()
|
|
||||||
const idReception = Number(route.params.id)
|
|
||||||
const receptionLoad = await getReception(idReception)
|
|
||||||
const receptionType = receptionLoad.receptionType
|
|
||||||
const auth = useAuthStore()
|
|
||||||
const isBtWeight = ref(true)
|
|
||||||
const isMerchandise = ref(receptionType.code === 'MARCHANDISES')
|
|
||||||
|
|
||||||
// Transporteur sélectionné dans le formulaire
|
|
||||||
const selectedCarrier = computed(() =>
|
|
||||||
carriers.value.find((carrier) => String(carrier.id) === form.carrierId) ?? null
|
|
||||||
)
|
|
||||||
// Indique si le transporteur est LIOT
|
|
||||||
const isLiotCarrier = computed(() => selectedCarrier.value?.code === SUPPLIER_CODE.LIOT)
|
|
||||||
// Adresses disponibles pour le fournisseur sélectionné
|
|
||||||
const supplierAddresses = computed(() => {
|
|
||||||
const supplierId = Number(form.supplierId)
|
|
||||||
if (!Number.isFinite(supplierId)) {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
return suppliers.value.find((supplier) => supplier.id === supplierId)?.addresses ?? []
|
|
||||||
})
|
|
||||||
// Chauffeurs filtrés par transporteur (LIOT)
|
|
||||||
const filteredDrivers = computed<DriverData[]>(() => {
|
|
||||||
if (!form.carrierId) {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
return drivers.value.filter((driver) => String(driver.carrier?.id) === form.carrierId)
|
|
||||||
})
|
|
||||||
// Véhicules filtrés par transporteur + type de camion
|
|
||||||
const filteredVehicles = computed<VehicleData[]>(() => {
|
|
||||||
if (!form.carrierId) {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
return vehicles.value.filter(
|
|
||||||
(vehicle) =>
|
|
||||||
String(vehicle.carrier?.id) === form.carrierId &&
|
|
||||||
(!form.truckId || String(vehicle.truck?.id) === form.truckId)
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Supprime les données bovines si on change de type de réception
|
|
||||||
const clearReceptionBovines = async (receptionIri: string) => {
|
|
||||||
const existing = await getReceptionBovineList(receptionIri)
|
|
||||||
for (const selection of existing) {
|
|
||||||
await deleteReceptionBovine(selection.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const hydrateFromUser = (reception: ReceptionData | null)=> {
|
|
||||||
if (!reception) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
isHydrating.value = true
|
|
||||||
form.licensePlate = reception?.licensePlate ?? ''
|
|
||||||
form.receptionDate = reception?.receptionDate ?? new Date().toISOString().slice(0, 10)
|
|
||||||
form.userId = reception?.user?.id
|
|
||||||
? String(reception.user.id)
|
|
||||||
: form.userId
|
|
||||||
form.supplierId = reception?.supplier?.id
|
|
||||||
? String(reception.supplier.id)
|
|
||||||
: ''
|
|
||||||
form.addressId = reception?.address?.id
|
|
||||||
? String(reception.address.id)
|
|
||||||
: ''
|
|
||||||
form.truckId = reception?.truck?.id
|
|
||||||
? String(reception.truck.id)
|
|
||||||
: ''
|
|
||||||
form.carrierId = reception?.carrier?.id
|
|
||||||
? String(reception.carrier.id)
|
|
||||||
: ''
|
|
||||||
form.driverId = reception?.driver?.id
|
|
||||||
? String(reception.driver.id)
|
|
||||||
: ''
|
|
||||||
isHydrating.value = false
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => idReception,
|
|
||||||
async (id) => {
|
|
||||||
if (id === null) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
isLoading.value = true
|
|
||||||
try {
|
|
||||||
const user = await getReception(id)
|
|
||||||
hydrateFromUser(user)
|
|
||||||
} finally {
|
|
||||||
isLoading.value = false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{immediate: true}
|
|
||||||
)
|
|
||||||
|
|
||||||
// Charge la liste des users pour le select
|
|
||||||
const loadUsers = async () => {
|
|
||||||
isLoadingUsers.value = true
|
|
||||||
try {
|
|
||||||
users.value = await getUsers()
|
|
||||||
} finally {
|
|
||||||
isLoadingUsers.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Charge la liste des fournisseurs pour le select
|
|
||||||
const loadSuppliers = async () => {
|
|
||||||
isLoadingSuppliers.value = true
|
|
||||||
try {
|
|
||||||
suppliers.value = await getSupplierList()
|
|
||||||
} finally {
|
|
||||||
isLoadingSuppliers.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Charge la liste des camions pour le select
|
|
||||||
const loadTrucks = async () => {
|
|
||||||
isLoadingTrucks.value = true
|
|
||||||
try {
|
|
||||||
trucks.value = await getTruckList()
|
|
||||||
} finally {
|
|
||||||
isLoadingTrucks.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Charge la liste des transporteurs pour le select
|
|
||||||
const loadCarriers = async () => {
|
|
||||||
isLoadingCarriers.value = true
|
|
||||||
try {
|
|
||||||
carriers.value = await getCarrierList()
|
|
||||||
} finally {
|
|
||||||
isLoadingCarriers.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Charge la liste des chauffeurs pour le select
|
|
||||||
const loadDrivers = async () => {
|
|
||||||
isLoadingDrivers.value = true
|
|
||||||
try {
|
|
||||||
drivers.value = await getDriverList()
|
|
||||||
} finally {
|
|
||||||
isLoadingDrivers.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Charge la liste des véhicules pour le select
|
|
||||||
const loadVehicles = async () => {
|
|
||||||
isLoadingVehicles.value = true
|
|
||||||
try {
|
|
||||||
vehicles.value = await getVehicleList()
|
|
||||||
} finally {
|
|
||||||
isLoadingVehicles.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// On met le user connecté par défaut dans le select
|
|
||||||
const setDefaultUser = () => {
|
|
||||||
if (form.userId) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (authStore.user?.id) {
|
|
||||||
form.userId = String(authStore.user.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// On récupère toutes les données des selects au chargement du composant
|
|
||||||
onMounted(async () => {
|
|
||||||
await loadUsers()
|
|
||||||
await loadSuppliers()
|
|
||||||
await loadTrucks()
|
|
||||||
await loadCarriers()
|
|
||||||
await loadDrivers()
|
|
||||||
await loadVehicles()
|
|
||||||
await authStore.ensureSession()
|
|
||||||
setDefaultUser()
|
|
||||||
})
|
|
||||||
|
|
||||||
// Ajuste driver/vehicle quand le transporteur change (logique LIOT)
|
|
||||||
watch(
|
|
||||||
() => [form.supplierId, suppliers.value],
|
|
||||||
() => {
|
|
||||||
if (!form.supplierId) {
|
|
||||||
form.addressId = ''
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!form.addressId && supplierAddresses.value.length === 1) {
|
|
||||||
form.addressId = String(supplierAddresses.value[0].id)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!form.addressId) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const matches = supplierAddresses.value.some(
|
|
||||||
(address) => String(address.id) === form.addressId
|
|
||||||
)
|
|
||||||
if (!matches) {
|
|
||||||
form.addressId = ''
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{immediate: true}
|
|
||||||
)
|
|
||||||
|
|
||||||
// Valide/auto-sélectionne le véhicule selon camion + transporteur (LIOT)
|
|
||||||
watch(
|
|
||||||
() => form.carrierId,
|
|
||||||
() => {
|
|
||||||
if (isHydrating.value) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!form.carrierId && idReception == null) {
|
|
||||||
form.driverId = ''
|
|
||||||
form.vehicleId = ''
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!isLiotCarrier.value && idReception == null) {
|
|
||||||
form.driverId = ''
|
|
||||||
form.vehicleId = ''
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (filteredDrivers.value.length === 1) {
|
|
||||||
form.driverId = String(filteredDrivers.value[0].id)
|
|
||||||
}
|
|
||||||
if (filteredVehicles.value.length === 1) {
|
|
||||||
form.vehicleId = String(filteredVehicles.value[0].id)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{immediate: true}
|
|
||||||
)
|
|
||||||
|
|
||||||
// Récupère la plaque depuis le véhicule choisi (LIOT)
|
|
||||||
watch(
|
|
||||||
() => [form.truckId, form.carrierId, vehicles.value],
|
|
||||||
() => {
|
|
||||||
if (!isLiotCarrier.value) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (filteredVehicles.value.length === 1) {
|
|
||||||
form.vehicleId = String(filteredVehicles.value[0].id)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!form.vehicleId) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const matches = filteredVehicles.value.some(
|
|
||||||
(vehicle) => String(vehicle.id) === form.vehicleId
|
|
||||||
)
|
|
||||||
if (!matches) {
|
|
||||||
form.vehicleId = ''
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{immediate: true}
|
|
||||||
)
|
|
||||||
|
|
||||||
// Auto-renseigne le véhicule si la plaque correspond (LIOT)
|
|
||||||
watch(
|
|
||||||
() => [form.vehicleId, form.carrierId, vehicles.value],
|
|
||||||
() => {
|
|
||||||
if (!isLiotCarrier.value) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (isHydrating.value) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const selected = filteredVehicles.value.find(
|
|
||||||
(vehicle) => String(vehicle.id) === form.vehicleId
|
|
||||||
)
|
|
||||||
if (selected) {
|
|
||||||
form.licensePlate = selected.plate
|
|
||||||
allowAnyLicensePlate.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => [form.licensePlate, form.carrierId, vehicles.value],
|
|
||||||
() => {
|
|
||||||
if (!isLiotCarrier.value || form.vehicleId) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const match = filteredVehicles.value.find(
|
|
||||||
(vehicle) => vehicle.plate === form.licensePlate
|
|
||||||
)
|
|
||||||
if (match) {
|
|
||||||
form.vehicleId = String(match.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// Valide le formulaire et crée/met à jour la réception
|
|
||||||
async function validate() {
|
|
||||||
const normalizedLicensePlate = form.licensePlate.trim()
|
|
||||||
const normalizedReceptionDate = form.receptionDate.trim()
|
|
||||||
const normalizedUserId = form.userId.trim()
|
|
||||||
const normalizedSupplierId = form.supplierId.trim()
|
|
||||||
const normalizedAddressId = form.addressId.trim()
|
|
||||||
const normalizedTruckId = form.truckId.trim()
|
|
||||||
const normalizedCarrierId = form.carrierId.trim()
|
|
||||||
const normalizedDriverId = form.driverId.trim()
|
|
||||||
const userIri = normalizedUserId
|
|
||||||
? `/api/users/${normalizedUserId}`
|
|
||||||
: null
|
|
||||||
const supplierIri = normalizedSupplierId
|
|
||||||
? `/api/suppliers/${normalizedSupplierId}`
|
|
||||||
: null
|
|
||||||
const addressIri = normalizedAddressId
|
|
||||||
? `/api/addresses/${normalizedAddressId}`
|
|
||||||
: null
|
|
||||||
const truckIri = normalizedTruckId
|
|
||||||
? `/api/trucks/${normalizedTruckId}`
|
|
||||||
: null
|
|
||||||
const carrierIri = normalizedCarrierId
|
|
||||||
? `/api/carriers/${normalizedCarrierId}`
|
|
||||||
: null
|
|
||||||
const driverIri = normalizedDriverId
|
|
||||||
? `/api/drivers/${normalizedDriverId}`
|
|
||||||
: null
|
|
||||||
|
|
||||||
const basePayload = {
|
|
||||||
licensePlate: normalizedLicensePlate,
|
|
||||||
receptionDate: normalizedReceptionDate,
|
|
||||||
user: userIri,
|
|
||||||
supplier: supplierIri,
|
|
||||||
address: addressIri,
|
|
||||||
truck: truckIri,
|
|
||||||
carrier: carrierIri
|
|
||||||
}
|
|
||||||
|
|
||||||
const payload = {
|
|
||||||
...basePayload,
|
|
||||||
...(isLiotCarrier.value && driverIri ? {driver: driverIri} : {})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (idReception) {
|
|
||||||
const updated = await receptionStore.updateReception(idReception,{
|
|
||||||
...payload
|
|
||||||
})
|
|
||||||
if (updated) {
|
|
||||||
await router.push(`/reception/update/${updated.id}`)
|
|
||||||
}
|
|
||||||
router.push("/reception/finish-reception")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
<div class="flex justify-between h-[52px] mb-[80px]">
|
|
||||||
<div class="flex flex-1 mr-16">
|
|
||||||
<UiStepper
|
|
||||||
:labels="SHIPMENT_STEP_LABELS"
|
|
||||||
:current-step="storeShipment?.currentStep ?? 0"
|
|
||||||
@select="handleStepSelect"
|
|
||||||
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="flex flex-col justify-center uppercase text-xl bg-black text-white h-[50px] w-[272px] text-center"
|
|
||||||
@click="saveAndHold"
|
|
||||||
>Mettre en attente
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<ShipmentForm v-if="!storeShipment || storeShipment.currentStep === 0" ref="shipmentFormRef"/>
|
|
||||||
<ShipmentWeight v-if="storeShipment?.currentStep === 1" mode="gross"/>
|
|
||||||
<ShipmentWeight v-if="storeShipment?.currentStep >= 2" mode="tare"/>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<script setup lang="ts">
|
|
||||||
|
|
||||||
import {SHIPMENT_STEP_LABELS} from "~/constants/steps";
|
|
||||||
import {storeToRefs} from "pinia";
|
|
||||||
import {useShipmentStore} from "~/stores/shipment";
|
|
||||||
import { ref, watch } from 'vue'
|
|
||||||
const shipmentStore = useShipmentStore()
|
|
||||||
const {current: storeShipment} = storeToRefs(shipmentStore)
|
|
||||||
const shipmentFormRef = ref<{ saveDraft: () => Promise<void> } | null>(null)
|
|
||||||
|
|
||||||
const route = useRoute()
|
|
||||||
const router = useRouter()
|
|
||||||
|
|
||||||
const resolveShipmentId = (param: unknown) => {
|
|
||||||
const idStr = Array.isArray(param) ? param[0] : param
|
|
||||||
if (!idStr) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
const id = Number(idStr)
|
|
||||||
return Number.isFinite(id) ? id : null
|
|
||||||
}
|
|
||||||
|
|
||||||
watch (
|
|
||||||
() => route.params.id,
|
|
||||||
async (param) => {
|
|
||||||
const id = resolveShipmentId(param)
|
|
||||||
if (id === null) {
|
|
||||||
shipmentStore.clearCurrent()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
await shipmentStore.loadShipment(id)
|
|
||||||
},
|
|
||||||
{immediate: true}
|
|
||||||
)
|
|
||||||
|
|
||||||
const saveAndHold = async () => {
|
|
||||||
if (shipmentFormRef.value) {
|
|
||||||
await shipmentFormRef.value.saveDraft()
|
|
||||||
}
|
|
||||||
await router.push('/')
|
|
||||||
}
|
|
||||||
const handleStepSelect = async (step: number) => {
|
|
||||||
if (!shipmentStore.current) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (step === shipmentStore.current.currentStep) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
await shipmentStore.updateShipment(shipmentStore.current.id, {
|
|
||||||
currentStep: step
|
|
||||||
})
|
|
||||||
await shipmentStore.loadShipment(shipmentStore.current.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
</script>
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="flex items-center justify-start gap-10">
|
|
||||||
<Icon @click="router.push('/')" name="gg:arrow-left-o" style="color: black" size="44"/>
|
|
||||||
<h1 class="text-3xl font-bold uppercase">listes des expéditions finie</h1>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="ps-20 ">
|
|
||||||
<div class="mt-6 border border-slate-200 mb-16 ">
|
|
||||||
<div class="grid grid-cols-6 gap-4 bg-slate-100 px-4 py-3 text-sm font-semibold uppercase tracking-wide">
|
|
||||||
<div>Numéro</div>
|
|
||||||
<div>Date</div>
|
|
||||||
<div>Client</div>
|
|
||||||
<div>Adresse</div>
|
|
||||||
<div>Type d'expéditon</div>
|
|
||||||
<div>Poids</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-for="shipment in shipmentList"
|
|
||||||
:key="shipment
|
|
||||||
.id"
|
|
||||||
class="grid grid-cols-6 gap-4 px-4 py-3 text-sm hover:bg-slate-50 cursor-pointer border-t border-slate-200"
|
|
||||||
role="button"
|
|
||||||
tabindex="0"
|
|
||||||
@click="goToshipment(shipment.id)"
|
|
||||||
>
|
|
||||||
<div>{{ shipment.identificationNumber }}</div>
|
|
||||||
<div>{{ shipment.shipmentDate }}</div>
|
|
||||||
<div>{{ shipment.customer?.label }}</div>
|
|
||||||
<div>{{ shipment.address?.fullAddress }}</div>
|
|
||||||
<div>
|
|
||||||
<template v-if="formatBovinShipmentLines(shipment).length">
|
|
||||||
<div
|
|
||||||
v-for="(line, index) in formatBovinShipmentLines(shipment)"
|
|
||||||
:key="index"
|
|
||||||
class="leading-5"
|
|
||||||
>
|
|
||||||
{{ line }}
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
<div>{{ formatWeighing(shipment, 'gross') }} | {{ formatWeighing(shipment, 'tare') }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import type {ShipmentData} from "~/services/dto/shipment-data";
|
|
||||||
import {getShipmentList} from "~/services/shipment";
|
|
||||||
|
|
||||||
const shipmentList = ref<ShipmentData[]>()
|
|
||||||
const router = useRouter()
|
|
||||||
|
|
||||||
const formatWeighing = (shipment: ShipmentData, type: 'gross' | 'tare') => {
|
|
||||||
const entry = shipment.weights?.find((weight) => weight.type === type)
|
|
||||||
if (!entry || entry.weight == null || entry.dsd == null) {
|
|
||||||
return '—'
|
|
||||||
}
|
|
||||||
return `${entry.weight} kg`
|
|
||||||
}
|
|
||||||
|
|
||||||
const formatBovinShipmentLines = (shipment: ShipmentData) => {
|
|
||||||
if (!shipment.bovinShipments?.length) {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
return shipment.bovinShipments.map((entry) => {
|
|
||||||
const label = typeof entry.shipmentType === 'string'
|
|
||||||
? entry.shipmentType
|
|
||||||
: entry.shipmentType?.label
|
|
||||||
return `${label ?? '—'} : ${entry.nbBovinSend ?? '—'}`
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const goToshipment = (id: number) => {
|
|
||||||
//router.push(`/shipment/update/${id}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
shipmentList.value = await getShipmentList(true)
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="flex items-center justify-between ">
|
|
||||||
<div class="flex items-center gap-10">
|
|
||||||
<Icon @click="router.push('/')" name="gg:arrow-left-o" style="color: black" size="44"/>
|
|
||||||
<h1 class="text-3xl font-bold uppercase">listes des expéditions en attente</h1>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="ps-20 ">
|
|
||||||
<div class="mt-6 border border-slate-200 mb-16 ">
|
|
||||||
<div class="grid grid-cols-5 gap-4 bg-slate-100 px-4 py-3 text-sm font-semibold uppercase tracking-wide">
|
|
||||||
<div>Client</div>
|
|
||||||
<div>Adresse</div>
|
|
||||||
<div>Type d'expéditions</div>
|
|
||||||
<div>Transporteur</div>
|
|
||||||
<div>Immatriculation</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-for="shipment in shipmentList"
|
|
||||||
:key="shipment.id"
|
|
||||||
class="grid grid-cols-5 gap-4 px-4 py-3 text-sm hover:bg-slate-50 cursor-pointer border-t border-slate-200"
|
|
||||||
role="button"
|
|
||||||
tabindex="0"
|
|
||||||
@click="goToShipment(shipment.id)"
|
|
||||||
@keydown.enter="goToShipment(shipment.id)"
|
|
||||||
>
|
|
||||||
<div>{{ shipment.customer?.label }}</div>
|
|
||||||
<div>{{ shipment.address?.fullAddress }}</div>
|
|
||||||
<div>
|
|
||||||
<template v-if="formatBovinShipmentLines(shipment).length">
|
|
||||||
<div
|
|
||||||
v-for="(line, index) in formatBovinShipmentLines(shipment)"
|
|
||||||
:key="index"
|
|
||||||
class="leading-5"
|
|
||||||
>
|
|
||||||
{{ line }}
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
<div>{{ shipment.carrier?.name }}</div>
|
|
||||||
<div>{{ shipment.licencePlate }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
|
|
||||||
import type {ShipmentData} from "~/services/dto/shipment-data";
|
|
||||||
import {getShipmentList} from "~/services/shipment";
|
|
||||||
|
|
||||||
const shipmentList = ref<ShipmentData[]>()
|
|
||||||
const router = useRouter()
|
|
||||||
|
|
||||||
const goToShipment = (id: number) => {
|
|
||||||
router.push(`/shipment/${id}`)
|
|
||||||
}
|
|
||||||
const formatBovinShipmentLines = (shipment: ShipmentData) => {
|
|
||||||
if (!shipment.bovinShipments?.length) {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
return shipment.bovinShipments.map((entry) => {
|
|
||||||
const label = typeof entry.shipmentType === 'string'
|
|
||||||
? entry.shipmentType
|
|
||||||
: entry.shipmentType?.label
|
|
||||||
return `${label ?? '—'} : ${entry.nbBovinSend ?? '—'}`
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
shipmentList.value = await getShipmentList(false)
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
import { useApi } from '~/composables/useApi'
|
|
||||||
import type { AddressData } from '~/services/dto/address-data'
|
|
||||||
export interface AddressPayload {
|
|
||||||
label: string
|
|
||||||
street: string
|
|
||||||
street2?: string | null
|
|
||||||
postalCode: string
|
|
||||||
city: string
|
|
||||||
countryCode: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AddressData extends AddressPayload {
|
|
||||||
id: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function createAddress(
|
|
||||||
payload: AddressPayload
|
|
||||||
): Promise<AddressData> {
|
|
||||||
const api = useApi()
|
|
||||||
|
|
||||||
return await api.post<AddressData>('addresses', payload, {
|
|
||||||
toastErrorKey: 'errors.address.create',
|
|
||||||
toastSuccessKey: 'success.address.create',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function updateAddress(
|
|
||||||
id: number,
|
|
||||||
payload: AddressPayload
|
|
||||||
): Promise<AddressData> {
|
|
||||||
const api = useApi()
|
|
||||||
|
|
||||||
return await api.patch<AddressData>(`addresses/${id}`, payload, {
|
|
||||||
toastErrorKey: 'errors.address.update',
|
|
||||||
toastSuccessKey: 'success.address.update',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getAddress(id: number): Promise<AddressData> {
|
|
||||||
const api = useApi()
|
|
||||||
|
|
||||||
return await api.get<AddressData>(`addresses/${id}`, {}, {
|
|
||||||
toastErrorKey: 'errors.address.fetch',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
import { useApi } from '~/composables/useApi'
|
import { useApi } from '~/composables/useApi'
|
||||||
import type { UserData } from '~/services/dto/user-data'
|
import type { UserData } from '~/services/dto/user-data'
|
||||||
import type {UserPayload} from "~/services/dto/user-data";
|
|
||||||
|
|
||||||
export async function getUsers() {
|
export async function getUsers() {
|
||||||
const api = useApi()
|
const api = useApi()
|
||||||
@@ -13,40 +12,7 @@ export async function getUsers() {
|
|||||||
|
|
||||||
return data['hydra:member'] ?? []
|
return data['hydra:member'] ?? []
|
||||||
}
|
}
|
||||||
export async function getAdminUsers() {
|
|
||||||
const api = useApi()
|
|
||||||
const data = await api.get<UserData[] | { 'hydra:member': UserData[] }>('admin/users', {}, {
|
|
||||||
toastErrorKey: 'errors.auth.users'
|
|
||||||
})
|
|
||||||
if (Array.isArray(data)) {
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
|
|
||||||
return data['hydra:member'] ?? []
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getUser(id: number) {
|
|
||||||
const api = useApi()
|
|
||||||
return api.get<UserData>(`users/${id}`, {}, {
|
|
||||||
toastErrorKey: 'errors.auth.user'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function createUser(payload: UserPayload = {}) {
|
|
||||||
const api = useApi()
|
|
||||||
return api.post<UserData>('users', payload, {
|
|
||||||
toastErrorKey: 'errors.auth.create',
|
|
||||||
toastSuccessKey : 'success.auth.create'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function updateUser(id : number, playload: UserPayload = {}){
|
|
||||||
const api = useApi()
|
|
||||||
return api.patch<UserData>(`users/${id}`, playload, {
|
|
||||||
toastErrorKey: 'errors.auth.update',
|
|
||||||
toastSuccessKey: 'success.auth.update'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
export async function getCurrentUser() {
|
export async function getCurrentUser() {
|
||||||
const api = useApi()
|
const api = useApi()
|
||||||
return api.get<UserData>('me', {}, {
|
return api.get<UserData>('me', {}, {
|
||||||
|
|||||||
@@ -1,50 +0,0 @@
|
|||||||
import { useApi } from '~/composables/useApi'
|
|
||||||
import type { BovinShipmentData } from '~/services/dto/bovin-shipment-data'
|
|
||||||
import type { ShipmentBovinePayload, BovinShipmentListResponse } from '~/services/dto/bovin-shipment-data'
|
|
||||||
|
|
||||||
export async function getBovinShipmentList(
|
|
||||||
shipmentIri: string
|
|
||||||
): Promise<BovinShipmentData[]> {
|
|
||||||
const api = useApi()
|
|
||||||
const response = await api.get<BovinShipmentListResponse>(
|
|
||||||
'bovin_shipments',
|
|
||||||
{ shipment: shipmentIri },
|
|
||||||
{
|
|
||||||
toastErrorKey: 'errors.shipmentBovine.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 createShipmentBovine(
|
|
||||||
payload: ShipmentBovinePayload
|
|
||||||
): Promise<BovinShipmentData> {
|
|
||||||
const api = useApi()
|
|
||||||
return api.post<BovinShipmentData>('bovin_shipments', payload, {
|
|
||||||
toastErrorKey: 'errors.shipmentBovine.create'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function deleteShipmentBovine(id: number): Promise<void> {
|
|
||||||
const api = useApi()
|
|
||||||
await api.delete<void>(`bovin_shipments/${id}`, {}, {
|
|
||||||
toastErrorKey: 'errors.shipmentBovine.delete'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function updateShipmentBovine(
|
|
||||||
id: number,
|
|
||||||
payload: Partial<ShipmentBovinePayload>
|
|
||||||
): Promise<BovinShipmentData> {
|
|
||||||
const api = useApi()
|
|
||||||
return api.patch<BovinShipmentData>(`bovin_shipments/${id}`, payload, {
|
|
||||||
toastErrorKey: 'errors.shipmentBovine.update'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useApi } from '~/composables/useApi'
|
import { useApi } from '~/composables/useApi'
|
||||||
import type {CarrierData, CarrierPayload} from "~/services/dto/carrier-data";
|
import type { CarrierData } from '~/services/dto/carrier-data'
|
||||||
|
|
||||||
export type CarrierListResponse =
|
export type CarrierListResponse =
|
||||||
| CarrierData[]
|
| CarrierData[]
|
||||||
@@ -21,26 +21,3 @@ export async function getCarrierList(): Promise<CarrierData[]> {
|
|||||||
|
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getCarrier(id: number) {
|
|
||||||
const api = useApi()
|
|
||||||
return api.get<CarrierData>(`carriers/${id}`, {}, {
|
|
||||||
toastErrorKey: 'errors.carrier.fetch'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function updateCarrier(id: number, payload: CarrierPayload) {
|
|
||||||
const api = useApi()
|
|
||||||
return api.patch<CarrierData>(`carriers/${id}`, payload, {
|
|
||||||
toastErrorKey: 'errors.carrier.update',
|
|
||||||
toastSuccessKey: 'success.carrier.update'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function createCarrier(payload: CarrierPayload = {}) {
|
|
||||||
const api = useApi()
|
|
||||||
return api.post<CarrierData>('carriers', payload, {
|
|
||||||
toastErrorKey: 'errors.carrier.create',
|
|
||||||
toastSuccessKey: 'success.carrier.update'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
import { useApi } from '~/composables/useApi'
|
|
||||||
import type { CustomerData } from '~/services/dto/customer-data'
|
|
||||||
|
|
||||||
export type CustomerListResponse =
|
|
||||||
| CustomerData[]
|
|
||||||
| { 'hydra:member'?: CustomerData[] }
|
|
||||||
|
|
||||||
export async function getCustomerList(): Promise<CustomerData[]> {
|
|
||||||
const api = useApi()
|
|
||||||
const response = await api.get<CustomerListResponse>('customers', {}, {
|
|
||||||
toastErrorKey: 'errors.customer.list'
|
|
||||||
})
|
|
||||||
|
|
||||||
if (Array.isArray(response)) {
|
|
||||||
return response
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response && typeof response === 'object' && Array.isArray(response['hydra:member'])) {
|
|
||||||
return response['hydra:member']
|
|
||||||
}
|
|
||||||
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
@@ -6,14 +6,5 @@ export interface AddressData {
|
|||||||
postalCode: string
|
postalCode: string
|
||||||
city: string
|
city: string
|
||||||
countryCode: string
|
countryCode: string
|
||||||
}
|
fullAddress?: string
|
||||||
|
|
||||||
export interface AddressFormData {
|
|
||||||
id?: number | null
|
|
||||||
label: string
|
|
||||||
street: string
|
|
||||||
street2?: string | null
|
|
||||||
postalCode: string
|
|
||||||
city: string
|
|
||||||
countryCode: string
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
import type {ShipmentTypeData} from "~/services/dto/shipment-type-data";
|
|
||||||
|
|
||||||
export interface BovinShipmentData {
|
|
||||||
id: number
|
|
||||||
nbBovinSend: number | null
|
|
||||||
shipment?: string | null
|
|
||||||
shipmentType?: ShipmentTypeData | null
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ShipmentBovinePayload = {
|
|
||||||
nbBovinSend: number
|
|
||||||
shipment: string
|
|
||||||
shipmentType: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export type BovinShipmentListResponse =
|
|
||||||
| BovinShipmentData[]
|
|
||||||
| { 'hydra:member'?: BovinShipmentData[] }
|
|
||||||
@@ -3,13 +3,3 @@ export interface CarrierData {
|
|||||||
name: string
|
name: string
|
||||||
code: string
|
code: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CarrierFormData {
|
|
||||||
name: string
|
|
||||||
code: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export type CarrierPayload = {
|
|
||||||
name?: string | null
|
|
||||||
code?: string
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
import type { AddressData } from "~/services/dto/address-data"
|
|
||||||
|
|
||||||
export interface CustomerData {
|
|
||||||
id: number
|
|
||||||
label: string
|
|
||||||
code?: string | null
|
|
||||||
addresses?: AddressData[] | null
|
|
||||||
}
|
|
||||||
@@ -41,14 +41,6 @@ export interface WeightEntryData {
|
|||||||
weighedAt: string | null
|
weighedAt: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WeightFormData {
|
|
||||||
id: number
|
|
||||||
weight: number
|
|
||||||
type: 'gross' | 'tare'
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export type ReceptionPayload = {
|
export type ReceptionPayload = {
|
||||||
licensePlate?: string | null
|
licensePlate?: string | null
|
||||||
receptionDate?: string
|
receptionDate?: string
|
||||||
@@ -67,27 +59,3 @@ export type ReceptionPayload = {
|
|||||||
carrier?: string | null
|
carrier?: string | null
|
||||||
driver?: string | null
|
driver?: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ReceptionFormData = {
|
|
||||||
licensePlate: string
|
|
||||||
receptionDate: string
|
|
||||||
receptionTypeId: string
|
|
||||||
userId: string
|
|
||||||
supplierId: string
|
|
||||||
addressId: string
|
|
||||||
truckId: string
|
|
||||||
carrierId: string
|
|
||||||
driverId: string
|
|
||||||
vehicleId: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ReceptionFormWeight = {
|
|
||||||
weights: WeightFormData[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ReceptionUpdatePayload {
|
|
||||||
weights: {
|
|
||||||
id: number
|
|
||||||
weight: number
|
|
||||||
}[]
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,67 +0,0 @@
|
|||||||
import type {CarrierData} from '~/services/dto/carrier-data'
|
|
||||||
import type {TruckData} from '~/services/dto/truck-data'
|
|
||||||
import type {CustomerData} from '~/services/dto/customer-data'
|
|
||||||
import type {AddressData} from "~/services/dto/address-data";
|
|
||||||
|
|
||||||
export interface ShipmentTypeData {
|
|
||||||
id: number
|
|
||||||
label: string
|
|
||||||
code: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface BovinShipmentData {
|
|
||||||
id?: number
|
|
||||||
shipmentType?: ShipmentTypeData | string | null
|
|
||||||
nbBovinSend: number | null
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ShipmentData = {
|
|
||||||
id: number
|
|
||||||
identificationNumber?: string | null
|
|
||||||
licencePlate: string | null
|
|
||||||
shipmentDate: string
|
|
||||||
currentStep: number
|
|
||||||
isValid: boolean
|
|
||||||
address?: AddressData | null
|
|
||||||
carrier?: CarrierData | null
|
|
||||||
truck?: TruckData | null
|
|
||||||
customer?: CustomerData | null
|
|
||||||
bovinShipments?: BovinShipmentData[] | null
|
|
||||||
weights?: WeightShipmentEntryData[] | null
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface WeightShipmentEntryData {
|
|
||||||
id?: number
|
|
||||||
type: 'gross' | 'tare'
|
|
||||||
dsd: number | null
|
|
||||||
weight: number | null
|
|
||||||
weighedAt: string | null
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ShipmentFormData = {
|
|
||||||
userId: string,
|
|
||||||
shipmentDate: string,
|
|
||||||
customerId: string,
|
|
||||||
addressId: string,
|
|
||||||
truckId: string,
|
|
||||||
carrierId: string,
|
|
||||||
driverId: string,
|
|
||||||
vehicleId: string,
|
|
||||||
licencePlate: string,
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ShipmentPayload = {
|
|
||||||
licencePlate?: string | null
|
|
||||||
shipmentDate?: string
|
|
||||||
currentStep?: number
|
|
||||||
isValid?: boolean
|
|
||||||
carrier?: string | null
|
|
||||||
truck?: string | null
|
|
||||||
customer?: string | null
|
|
||||||
bovinShipments?: string[] | null
|
|
||||||
address?: string | null
|
|
||||||
user?: string | null
|
|
||||||
driver?: string | null
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
export interface ShipmentTypeData {
|
|
||||||
id: number
|
|
||||||
label: string
|
|
||||||
code: string
|
|
||||||
}
|
|
||||||
@@ -1,25 +1,9 @@
|
|||||||
import type { AddressFormData } from "~/services/dto/address-data"
|
import type { AddressData } from '~/services/dto/address-data'
|
||||||
|
|
||||||
export type SupplierAddresses = AddressFormData[] | string[]
|
|
||||||
|
|
||||||
export interface SupplierData {
|
export interface SupplierData {
|
||||||
id: number
|
id: number
|
||||||
name: string
|
name: string
|
||||||
email?: string | null
|
email?: string | null
|
||||||
phone?: string | null
|
phone?: string | null
|
||||||
addresses: SupplierAddresses
|
addresses?: AddressData[] | null
|
||||||
}
|
|
||||||
|
|
||||||
export interface SupplierFormData {
|
|
||||||
name: string
|
|
||||||
email?: string
|
|
||||||
phone?: string
|
|
||||||
addresses: AddressFormData[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export type SupplierPayload = {
|
|
||||||
name: string
|
|
||||||
email?: string | null
|
|
||||||
phone?: string | null
|
|
||||||
addresses?: string[]
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,5 @@
|
|||||||
export interface UserData {
|
export interface UserData {
|
||||||
id: number
|
id: number
|
||||||
username: string
|
username: string
|
||||||
roles: string[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export type UserPayload = {
|
|
||||||
username?: string
|
|
||||||
password?: string
|
|
||||||
roles?: string[]
|
roles?: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UserFormData = {
|
|
||||||
username: string
|
|
||||||
password: string
|
|
||||||
role: string
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
import { useApi } from '~/composables/useApi'
|
|
||||||
import type {ShipmentTypeData} from "~/services/dto/shipment-type-data";
|
|
||||||
|
|
||||||
export type ShipmentTypeListResponse =
|
|
||||||
| ShipmentTypeData[]
|
|
||||||
| { 'hydra:member'?: ShipmentTypeData[] }
|
|
||||||
|
|
||||||
|
|
||||||
export async function getShipmentTypeList(): Promise<ShipmentTypeData[]> {
|
|
||||||
const api = useApi()
|
|
||||||
const response = await api.get<ShipmentTypeListResponse>('shipment_types', {}, {
|
|
||||||
toastErrorKey: 'errors.shipmentType.list'
|
|
||||||
})
|
|
||||||
|
|
||||||
if (Array.isArray(response)) {
|
|
||||||
return response
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response && typeof response === 'object' && Array.isArray(response['hydra:member'])) {
|
|
||||||
return response['hydra:member']
|
|
||||||
}
|
|
||||||
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
import {useApi} from '~/composables/useApi'
|
|
||||||
import type {ShipmentData, ShipmentPayload} from '~/services/dto/shipment-data'
|
|
||||||
import type {WeightData} from '~/services/dto/weight-data'
|
|
||||||
|
|
||||||
export async function getShipmentList(isValid: boolean|null = null) {
|
|
||||||
const api = useApi()
|
|
||||||
const query = isValid !== null ? { isValid: isValid} : {}
|
|
||||||
return api.get<ShipmentData[]>('shipments', query, {
|
|
||||||
toastErrorKey: 'errors.shipment.list'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getShipment(id: number) {
|
|
||||||
const api = useApi()
|
|
||||||
return api.get<ShipmentData>(`shipments/${id}`, {}, {
|
|
||||||
toastErrorKey: 'errors.shipment.fetch'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function createShipment(payload: ShipmentPayload = {}) {
|
|
||||||
const api = useApi()
|
|
||||||
return api.post<ShipmentData>('shipments', payload, {
|
|
||||||
toastErrorKey: 'errors.shipment.create'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function updateShipment(id: number, payload: ShipmentPayload) {
|
|
||||||
const api = useApi()
|
|
||||||
return api.patch<ShipmentData>(`shipments/${id}`, payload, {
|
|
||||||
toastErrorKey: 'errors.shipment.update',
|
|
||||||
toastSuccessKey: 'success.shipment.update'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getWeightShipment(): Promise<WeightData> {
|
|
||||||
const api = useApi()
|
|
||||||
return api.get<WeightData>('shipments/weigh', {}, {
|
|
||||||
toastErrorKey: 'errors.shipment.weigh'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,42 +1,23 @@
|
|||||||
import { useApi } from "~/composables/useApi"
|
import { useApi } from '~/composables/useApi'
|
||||||
import type { SupplierData, SupplierPayload } from "~/services/dto/supplier-data"
|
import type { SupplierData } from '~/services/dto/supplier-data'
|
||||||
|
|
||||||
export type SupplierListResponse =
|
export type SupplierListResponse =
|
||||||
| SupplierData[]
|
| SupplierData[]
|
||||||
| { "hydra:member"?: SupplierData[] }
|
| { 'hydra:member'?: SupplierData[] }
|
||||||
|
|
||||||
export async function getSupplierList(): Promise<SupplierData[]> {
|
export async function getSupplierList(): Promise<SupplierData[]> {
|
||||||
const api = useApi()
|
const api = useApi()
|
||||||
const response = await api.get<SupplierListResponse>("suppliers", {}, {
|
const response = await api.get<SupplierListResponse>('suppliers', {}, {
|
||||||
toastErrorKey: "errors.supplier.list",
|
toastErrorKey: 'errors.supplier.list'
|
||||||
})
|
})
|
||||||
|
|
||||||
if (Array.isArray(response)) return response
|
if (Array.isArray(response)) {
|
||||||
if (response && typeof response === "object" && Array.isArray(response["hydra:member"])) {
|
return response
|
||||||
return response["hydra:member"]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (response && typeof response === 'object' && Array.isArray(response['hydra:member'])) {
|
||||||
|
return response['hydra:member']
|
||||||
|
}
|
||||||
|
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getSupplier(id: number): Promise<SupplierData> {
|
|
||||||
const api = useApi()
|
|
||||||
return api.get<SupplierData>(`suppliers/${id}`, {}, {
|
|
||||||
toastErrorKey: "errors.supplier.fetch",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function updateSupplier(id: number, payload: Partial<SupplierPayload>): Promise<SupplierData> {
|
|
||||||
const api = useApi()
|
|
||||||
return api.patch<SupplierData>(`suppliers/${id}`, payload, {
|
|
||||||
toastErrorKey: "errors.supplier.update",
|
|
||||||
toastSuccessKey: "success.supplier.update",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function createSupplier(payload: SupplierPayload): Promise<SupplierData> {
|
|
||||||
const api = useApi()
|
|
||||||
return api.post<SupplierData>("suppliers", payload, {
|
|
||||||
toastErrorKey: "errors.supplier.create",
|
|
||||||
toastSuccessKey: "success.supplier.create",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,12 +1,8 @@
|
|||||||
import { useApi } from '~/composables/useApi'
|
import { useApi } from '~/composables/useApi'
|
||||||
import type {ReceptionData, ReceptionPayload, WeightEntryData} from '~/services/dto/reception-data'
|
import type { WeightEntryData } from '~/services/dto/reception-data'
|
||||||
import type {Ref} from "vue";
|
|
||||||
import type {ShipmentData, ShipmentPayload} from "~/services/dto/shipment-data";
|
|
||||||
import type {WeighingMode} from "~/composables/useWeighing";
|
|
||||||
|
|
||||||
export type WeightPayload = {
|
export type WeightPayload = {
|
||||||
reception?: string
|
reception: string
|
||||||
shipment?: string
|
|
||||||
type: 'gross' | 'tare'
|
type: 'gross' | 'tare'
|
||||||
dsd: number | null
|
dsd: number | null
|
||||||
weight: number | null
|
weight: number | null
|
||||||
@@ -20,22 +16,5 @@ export async function createWeight(payload: WeightPayload) {
|
|||||||
|
|
||||||
export async function updateWeight(id: number, payload: Partial<WeightPayload>) {
|
export async function updateWeight(id: number, payload: Partial<WeightPayload>) {
|
||||||
const api = useApi()
|
const api = useApi()
|
||||||
return api.patch<WeightEntryData>(`weights/${id}`, payload,{
|
return api.patch<WeightEntryData>(`weights/${id}`, payload)
|
||||||
toastErrorKey: 'errors.weight.update',
|
|
||||||
toastSuccessKey: 'success.weight.update'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export type UseWeighingShipmentOptions = {
|
|
||||||
modeShipment: WeighingMode
|
|
||||||
shipment: Ref<ShipmentData | null>
|
|
||||||
updateShipment: (id: number, payload: ShipmentPayload) => Promise<ShipmentData | null>
|
|
||||||
loadShipment?: (id: number) => Promise<ShipmentData | null>
|
|
||||||
}
|
|
||||||
|
|
||||||
export type UseWeighingOptions = {
|
|
||||||
mode: WeighingMode
|
|
||||||
reception: Ref<ReceptionData | null>
|
|
||||||
updateReception: (id: number, payload: ReceptionPayload) => Promise<ReceptionData | null>
|
|
||||||
loadReception?: (id: number) => Promise<ReceptionData | null>
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,80 +1,63 @@
|
|||||||
import {defineStore} from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import type {UserData} from '~/services/dto/user-data'
|
import type { UserData } from '~/services/dto/user-data'
|
||||||
import {getCurrentUser, createUser, login, logout} from '~/services/auth'
|
import { getCurrentUser, login, logout } from '~/services/auth'
|
||||||
import type {UserPayload} from "~/services/dto/user-data";
|
|
||||||
import {ROLE} from '~/utils/constants'
|
|
||||||
|
|
||||||
export const useAuthStore = defineStore('auth', {
|
export const useAuthStore = defineStore('auth', {
|
||||||
state: () => ({
|
state: () => ({
|
||||||
user: null as UserData | null,
|
user: null as UserData | null,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
checked: false
|
checked: false
|
||||||
}),
|
}),
|
||||||
getters: {
|
getters: {
|
||||||
isAuthenticated: (state) => Boolean(state.user),
|
isAuthenticated: (state) => Boolean(state.user)
|
||||||
isAdmin: (state) => Boolean(state.user?.roles?.includes(ROLE[0].value))
|
},
|
||||||
|
actions: {
|
||||||
|
clearSession() {
|
||||||
|
this.user = null
|
||||||
|
this.checked = true
|
||||||
|
this.isLoading = false
|
||||||
},
|
},
|
||||||
actions: {
|
async ensureSession() {
|
||||||
clearSession() {
|
if (this.checked) {
|
||||||
this.user = null
|
return this.user
|
||||||
this.checked = true
|
}
|
||||||
this.isLoading = false
|
|
||||||
},
|
|
||||||
async ensureSession() {
|
|
||||||
if (this.checked) {
|
|
||||||
return this.user
|
|
||||||
}
|
|
||||||
|
|
||||||
this.checked = true
|
this.checked = true
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const me = await getCurrentUser()
|
const me = await getCurrentUser()
|
||||||
this.user = me
|
this.user = me
|
||||||
return me
|
return me
|
||||||
} catch {
|
} catch {
|
||||||
this.user = null
|
this.user = null
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async login(username: string, password: string) {
|
async login(username: string, password: string) {
|
||||||
this.isLoading = true
|
this.isLoading = true
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await login(username, password)
|
await login(username, password)
|
||||||
const me = await getCurrentUser()
|
const me = await getCurrentUser()
|
||||||
this.user = me
|
this.user = me
|
||||||
this.checked = true
|
this.checked = true
|
||||||
return me
|
return me
|
||||||
} finally {
|
} finally {
|
||||||
this.isLoading = false
|
this.isLoading = false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async createUser(payload: UserPayload = {}) {
|
async logout() {
|
||||||
this.isLoading = true
|
this.isLoading = true
|
||||||
const result = await createUser(payload).finally(() => {
|
|
||||||
this.isLoading = false
|
|
||||||
})
|
|
||||||
return result
|
|
||||||
},
|
|
||||||
async updateUser(id: number, payload: UserPayload) {
|
|
||||||
this.isLoading = true
|
|
||||||
const result = await createUser(payload).finally(() => {
|
|
||||||
this.isLoading = false
|
|
||||||
})
|
|
||||||
return result
|
|
||||||
},
|
|
||||||
async logout() {
|
|
||||||
this.isLoading = true
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await logout()
|
await logout()
|
||||||
} catch {
|
} catch {
|
||||||
// Ignore logout errors so we can still clear local auth state.
|
// Ignore logout errors so we can still clear local auth state.
|
||||||
} finally {
|
} finally {
|
||||||
this.user = null
|
this.user = null
|
||||||
this.checked = true
|
this.checked = true
|
||||||
this.isLoading = false
|
this.isLoading = false
|
||||||
}
|
}
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,58 +0,0 @@
|
|||||||
import { defineStore } from 'pinia'
|
|
||||||
import type {ShipmentData, ShipmentPayload} from "~/services/dto/shipment-data";
|
|
||||||
import {createShipment, getShipment, updateShipment} from "~/services/shipment";
|
|
||||||
|
|
||||||
const isShipmentData = (value: unknown): value is ShipmentData => {
|
|
||||||
return Boolean(value && typeof value === 'object' && 'id' in value)
|
|
||||||
}
|
|
||||||
export const useShipmentStore = defineStore('shipment', {
|
|
||||||
state: () => ({
|
|
||||||
current: null as ShipmentData | null,
|
|
||||||
isLoading: false
|
|
||||||
}),
|
|
||||||
actions: {
|
|
||||||
setCurrent(shipment: ShipmentData | null) {
|
|
||||||
this.current = shipment
|
|
||||||
},
|
|
||||||
clearCurrent() {
|
|
||||||
this.current = null
|
|
||||||
},
|
|
||||||
async loadShipment(id: number) {
|
|
||||||
this.isLoading = true
|
|
||||||
const result = await getShipment(id).finally(() => {
|
|
||||||
this.isLoading = false
|
|
||||||
})
|
|
||||||
if (!isShipmentData(result)) {
|
|
||||||
this.current = null
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
this.current = result
|
|
||||||
return result
|
|
||||||
},
|
|
||||||
async createShipment(payload: ShipmentPayload = {}) {
|
|
||||||
this.isLoading = true
|
|
||||||
const result = await createShipment(payload).finally(() => {
|
|
||||||
this.isLoading = false
|
|
||||||
})
|
|
||||||
if (!isShipmentData(result)) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
this.current = result
|
|
||||||
return result
|
|
||||||
},
|
|
||||||
async updateShipment(id: number, payload: ShipmentPayload) {
|
|
||||||
this.isLoading = true
|
|
||||||
const result = await updateShipment(id, payload).finally(() => {
|
|
||||||
this.isLoading = false
|
|
||||||
})
|
|
||||||
if (!isShipmentData(result)) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
this.current = result
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
@@ -8,10 +8,6 @@ export const MERCHANDISE_TYPE_CODES = {
|
|||||||
AUTRES: 'AUTRES'
|
AUTRES: 'AUTRES'
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
export const ROLE = [
|
export const SUPLLIER_CODE = {
|
||||||
{ label: 'Administrateur', value: 'ROLE_ADMIN' },
|
|
||||||
{ label: 'Utilisateur', value: 'ROLE_USER' }
|
|
||||||
]
|
|
||||||
export const SUPPLIER_CODE = {
|
|
||||||
LIOT: 'LIOT'
|
LIOT: 'LIOT'
|
||||||
}
|
}
|
||||||
|
|||||||
2
makefile
2
makefile
@@ -79,7 +79,7 @@ migration-migrate:
|
|||||||
$(SYMFONY_CONSOLE) --no-interaction doctrine:migrations:migrate --allow-no-migration
|
$(SYMFONY_CONSOLE) --no-interaction doctrine:migrations:migrate --allow-no-migration
|
||||||
|
|
||||||
fixtures:
|
fixtures:
|
||||||
$(SYMFONY_CONSOLE) --no-interaction doctrine:fixtures:load
|
$(SYMFONY_CONSOLE) doctrine:fixtures:load
|
||||||
|
|
||||||
# Attention, supprime votre bdd local
|
# Attention, supprime votre bdd local
|
||||||
db-reset:
|
db-reset:
|
||||||
|
|||||||
@@ -1,64 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace DoctrineMigrations;
|
|
||||||
|
|
||||||
use Doctrine\DBAL\Schema\Schema;
|
|
||||||
use Doctrine\Migrations\AbstractMigration;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Auto-generated Migration: Please modify to your needs!
|
|
||||||
*/
|
|
||||||
final class Version20260204101625 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 bovin_shipment (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, nb_bovin_send INT NOT NULL, shipment_id INT DEFAULT NULL, shipment_type_id INT DEFAULT NULL, PRIMARY KEY (id))');
|
|
||||||
$this->addSql('CREATE INDEX IDX_7049F4507BE036FC ON bovin_shipment (shipment_id)');
|
|
||||||
$this->addSql('CREATE INDEX IDX_7049F4502EE48A36 ON bovin_shipment (shipment_type_id)');
|
|
||||||
$this->addSql('CREATE TABLE customer (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, label VARCHAR(255) NOT NULL, code VARCHAR(255) NOT NULL, PRIMARY KEY (id))');
|
|
||||||
$this->addSql('CREATE TABLE customer_address (customer_id INT NOT NULL, address_id INT NOT NULL, PRIMARY KEY (customer_id, address_id))');
|
|
||||||
$this->addSql('CREATE INDEX IDX_1193CB3F9395C3F3 ON customer_address (customer_id)');
|
|
||||||
$this->addSql('CREATE INDEX IDX_1193CB3FF5B7AF75 ON customer_address (address_id)');
|
|
||||||
$this->addSql('CREATE TABLE shipment (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, licence_plate VARCHAR(255) NOT NULL, identification_number VARCHAR(20) DEFAULT NULL, current_step INT DEFAULT 0 NOT NULL, is_valid BOOLEAN NOT NULL, shipment_date TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, carrier_id INT DEFAULT NULL, vehicle_id INT DEFAULT NULL, truck_id INT DEFAULT NULL, customer_id INT DEFAULT NULL, PRIMARY KEY (id))');
|
|
||||||
$this->addSql('CREATE UNIQUE INDEX UNIQ_2CB20DC347639A5 ON shipment (identification_number)');
|
|
||||||
$this->addSql('CREATE INDEX IDX_2CB20DC21DFC797 ON shipment (carrier_id)');
|
|
||||||
$this->addSql('CREATE INDEX IDX_2CB20DC545317D1 ON shipment (vehicle_id)');
|
|
||||||
$this->addSql('CREATE INDEX IDX_2CB20DCC6957CCE ON shipment (truck_id)');
|
|
||||||
$this->addSql('CREATE INDEX IDX_2CB20DC9395C3F3 ON shipment (customer_id)');
|
|
||||||
$this->addSql('CREATE TABLE shipment_type (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, label VARCHAR(255) NOT NULL, code VARCHAR(255) NOT NULL, PRIMARY KEY (id))');
|
|
||||||
$this->addSql('ALTER TABLE bovin_shipment ADD CONSTRAINT FK_7049F4507BE036FC FOREIGN KEY (shipment_id) REFERENCES shipment (id)');
|
|
||||||
$this->addSql('ALTER TABLE bovin_shipment ADD CONSTRAINT FK_7049F4502EE48A36 FOREIGN KEY (shipment_type_id) REFERENCES shipment_type (id)');
|
|
||||||
$this->addSql('ALTER TABLE customer_address ADD CONSTRAINT FK_1193CB3F9395C3F3 FOREIGN KEY (customer_id) REFERENCES customer (id) ON DELETE CASCADE');
|
|
||||||
$this->addSql('ALTER TABLE customer_address ADD CONSTRAINT FK_1193CB3FF5B7AF75 FOREIGN KEY (address_id) REFERENCES address (id) ON DELETE CASCADE');
|
|
||||||
$this->addSql('ALTER TABLE shipment ADD CONSTRAINT FK_2CB20DC21DFC797 FOREIGN KEY (carrier_id) REFERENCES carrier (id) NOT DEFERRABLE');
|
|
||||||
$this->addSql('ALTER TABLE shipment ADD CONSTRAINT FK_2CB20DC545317D1 FOREIGN KEY (vehicle_id) REFERENCES vehicle (id)');
|
|
||||||
$this->addSql('ALTER TABLE shipment ADD CONSTRAINT FK_2CB20DCC6957CCE FOREIGN KEY (truck_id) REFERENCES truck (id) NOT DEFERRABLE');
|
|
||||||
$this->addSql('ALTER TABLE shipment ADD CONSTRAINT FK_2CB20DC9395C3F3 FOREIGN KEY (customer_id) REFERENCES customer (id)');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function down(Schema $schema): void
|
|
||||||
{
|
|
||||||
// this down() migration is auto-generated, please modify it to your needs
|
|
||||||
$this->addSql('ALTER TABLE bovin_shipment DROP CONSTRAINT FK_7049F4507BE036FC');
|
|
||||||
$this->addSql('ALTER TABLE bovin_shipment DROP CONSTRAINT FK_7049F4502EE48A36');
|
|
||||||
$this->addSql('ALTER TABLE customer_address DROP CONSTRAINT FK_1193CB3F9395C3F3');
|
|
||||||
$this->addSql('ALTER TABLE customer_address DROP CONSTRAINT FK_1193CB3FF5B7AF75');
|
|
||||||
$this->addSql('ALTER TABLE shipment DROP CONSTRAINT FK_2CB20DC21DFC797');
|
|
||||||
$this->addSql('ALTER TABLE shipment DROP CONSTRAINT FK_2CB20DC545317D1');
|
|
||||||
$this->addSql('ALTER TABLE shipment DROP CONSTRAINT FK_2CB20DCC6957CCE');
|
|
||||||
$this->addSql('ALTER TABLE shipment DROP CONSTRAINT FK_2CB20DC9395C3F3');
|
|
||||||
$this->addSql('DROP TABLE bovin_shipment');
|
|
||||||
$this->addSql('DROP TABLE customer');
|
|
||||||
$this->addSql('DROP TABLE customer_address');
|
|
||||||
$this->addSql('DROP TABLE shipment');
|
|
||||||
$this->addSql('DROP TABLE shipment_type');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace DoctrineMigrations;
|
|
||||||
|
|
||||||
use Doctrine\DBAL\Schema\Schema;
|
|
||||||
use Doctrine\Migrations\AbstractMigration;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Auto-generated Migration: Please modify to your needs!
|
|
||||||
*/
|
|
||||||
final class Version20260204102423 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 shipment DROP CONSTRAINT fk_2cb20dc545317d1');
|
|
||||||
$this->addSql('DROP INDEX idx_2cb20dc545317d1');
|
|
||||||
$this->addSql('ALTER TABLE shipment DROP vehicle_id');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function down(Schema $schema): void
|
|
||||||
{
|
|
||||||
// this down() migration is auto-generated, please modify it to your needs
|
|
||||||
$this->addSql('ALTER TABLE shipment ADD vehicle_id INT DEFAULT NULL');
|
|
||||||
$this->addSql('ALTER TABLE shipment ADD CONSTRAINT fk_2cb20dc545317d1 FOREIGN KEY (vehicle_id) REFERENCES vehicle (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
|
||||||
$this->addSql('CREATE INDEX idx_2cb20dc545317d1 ON shipment (vehicle_id)');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace DoctrineMigrations;
|
|
||||||
|
|
||||||
use Doctrine\DBAL\Schema\Schema;
|
|
||||||
use Doctrine\Migrations\AbstractMigration;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Auto-generated Migration: Please modify to your needs!
|
|
||||||
*/
|
|
||||||
final class Version20260211075656 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 UNIQUE INDEX uniq_bovin_shipment ON bovin_shipment (shipment_id, shipment_type_id)');
|
|
||||||
$this->addSql('ALTER TABLE shipment ADD user_id INT DEFAULT NULL');
|
|
||||||
$this->addSql('ALTER TABLE shipment ADD driver_id INT DEFAULT NULL');
|
|
||||||
$this->addSql('ALTER TABLE shipment ADD address_id INT DEFAULT NULL');
|
|
||||||
$this->addSql('ALTER TABLE shipment ADD CONSTRAINT FK_2CB20DCA76ED395 FOREIGN KEY (user_id) REFERENCES public."user" (id) NOT DEFERRABLE');
|
|
||||||
$this->addSql('ALTER TABLE shipment ADD CONSTRAINT FK_2CB20DCC3423909 FOREIGN KEY (driver_id) REFERENCES driver (id) NOT DEFERRABLE');
|
|
||||||
$this->addSql('ALTER TABLE shipment ADD CONSTRAINT FK_2CB20DCF5B7AF75 FOREIGN KEY (address_id) REFERENCES address (id) NOT DEFERRABLE');
|
|
||||||
$this->addSql('CREATE INDEX IDX_2CB20DCA76ED395 ON shipment (user_id)');
|
|
||||||
$this->addSql('CREATE INDEX IDX_2CB20DCC3423909 ON shipment (driver_id)');
|
|
||||||
$this->addSql('CREATE INDEX IDX_2CB20DCF5B7AF75 ON shipment (address_id)');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function down(Schema $schema): void
|
|
||||||
{
|
|
||||||
// this down() migration is auto-generated, please modify it to your needs
|
|
||||||
$this->addSql('DROP INDEX uniq_bovin_shipment');
|
|
||||||
$this->addSql('ALTER TABLE shipment DROP CONSTRAINT FK_2CB20DCA76ED395');
|
|
||||||
$this->addSql('ALTER TABLE shipment DROP CONSTRAINT FK_2CB20DCC3423909');
|
|
||||||
$this->addSql('ALTER TABLE shipment DROP CONSTRAINT FK_2CB20DCF5B7AF75');
|
|
||||||
$this->addSql('DROP INDEX IDX_2CB20DCA76ED395');
|
|
||||||
$this->addSql('DROP INDEX IDX_2CB20DCC3423909');
|
|
||||||
$this->addSql('DROP INDEX IDX_2CB20DCF5B7AF75');
|
|
||||||
$this->addSql('ALTER TABLE shipment DROP user_id');
|
|
||||||
$this->addSql('ALTER TABLE shipment DROP driver_id');
|
|
||||||
$this->addSql('ALTER TABLE shipment DROP address_id');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace DoctrineMigrations;
|
|
||||||
|
|
||||||
use Doctrine\DBAL\Schema\Schema;
|
|
||||||
use Doctrine\Migrations\AbstractMigration;
|
|
||||||
|
|
||||||
final class Version20260211123000 extends AbstractMigration
|
|
||||||
{
|
|
||||||
public function getDescription(): string
|
|
||||||
{
|
|
||||||
return 'Allow weight to belong to reception or shipment.';
|
|
||||||
}
|
|
||||||
|
|
||||||
public function up(Schema $schema): void
|
|
||||||
{
|
|
||||||
$this->addSql('ALTER TABLE weight ALTER COLUMN reception_id DROP NOT NULL');
|
|
||||||
$this->addSql('ALTER TABLE weight ADD shipment_id INT DEFAULT NULL');
|
|
||||||
$this->addSql('ALTER TABLE weight ADD CONSTRAINT FK_WEIGHT_SHIPMENT FOREIGN KEY (shipment_id) REFERENCES shipment (id) NOT DEFERRABLE');
|
|
||||||
$this->addSql('CREATE INDEX IDX_WEIGHT_SHIPMENT ON weight (shipment_id)');
|
|
||||||
$this->addSql('CREATE UNIQUE INDEX uniq_weight_reception_type ON weight (reception_id, type)');
|
|
||||||
$this->addSql('CREATE UNIQUE INDEX uniq_weight_shipment_type ON weight (shipment_id, type)');
|
|
||||||
$this->addSql('ALTER TABLE weight ADD CONSTRAINT chk_weight_reception_or_shipment CHECK ((reception_id IS NOT NULL AND shipment_id IS NULL) OR (reception_id IS NULL AND shipment_id IS NOT NULL))');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function down(Schema $schema): void
|
|
||||||
{
|
|
||||||
$this->addSql('ALTER TABLE weight DROP CONSTRAINT chk_weight_reception_or_shipment');
|
|
||||||
$this->addSql('DROP INDEX uniq_weight_shipment_type');
|
|
||||||
$this->addSql('DROP INDEX uniq_weight_reception_type');
|
|
||||||
$this->addSql('DROP INDEX IDX_WEIGHT_SHIPMENT');
|
|
||||||
$this->addSql('ALTER TABLE weight DROP CONSTRAINT FK_WEIGHT_SHIPMENT');
|
|
||||||
$this->addSql('ALTER TABLE weight DROP shipment_id');
|
|
||||||
$this->addSql('ALTER TABLE weight ALTER COLUMN reception_id SET NOT NULL');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -12,11 +12,11 @@ use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
|
|||||||
final readonly class PontBasculeReading
|
final readonly class PontBasculeReading
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
#[Groups(['reception:weigh:read', 'shipment:weigh:read'])]
|
#[Groups(['reception:weigh:read'])]
|
||||||
private ?int $dsd,
|
private ?int $dsd,
|
||||||
#[Groups(['reception:weigh:read', 'shipment:weigh:read'])]
|
#[Groups(['reception:weigh:read'])]
|
||||||
private ?float $weight,
|
private ?float $weight,
|
||||||
#[Groups(['reception:weigh:read', 'shipment:weigh:read'])]
|
#[Groups(['reception:weigh:read'])]
|
||||||
#[Context([DateTimeNormalizer::FORMAT_KEY => 'Y-m-d'])]
|
#[Context([DateTimeNormalizer::FORMAT_KEY => 'Y-m-d'])]
|
||||||
private ?DateTimeImmutable $weighedAt = null,
|
private ?DateTimeImmutable $weighedAt = null,
|
||||||
) {}
|
) {}
|
||||||
|
|||||||
@@ -7,8 +7,6 @@ namespace App\Entity;
|
|||||||
use ApiPlatform\Metadata\ApiResource;
|
use ApiPlatform\Metadata\ApiResource;
|
||||||
use ApiPlatform\Metadata\Get;
|
use ApiPlatform\Metadata\Get;
|
||||||
use ApiPlatform\Metadata\GetCollection;
|
use ApiPlatform\Metadata\GetCollection;
|
||||||
use ApiPlatform\Metadata\Patch;
|
|
||||||
use ApiPlatform\Metadata\Post;
|
|
||||||
use Doctrine\Common\Collections\ArrayCollection;
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
use Doctrine\Common\Collections\Collection;
|
use Doctrine\Common\Collections\Collection;
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
@@ -25,16 +23,6 @@ use Symfony\Component\Serializer\Attribute\Groups;
|
|||||||
new GetCollection(
|
new GetCollection(
|
||||||
normalizationContext: ['groups' => ['address:read']],
|
normalizationContext: ['groups' => ['address:read']],
|
||||||
),
|
),
|
||||||
new Post(
|
|
||||||
normalizationContext: ['groups' => ['address:read']],
|
|
||||||
denormalizationContext: ['groups' => ['address:write']],
|
|
||||||
security: "is_granted('ROLE_ADMIN')",
|
|
||||||
),
|
|
||||||
new Patch(
|
|
||||||
normalizationContext: ['groups' => ['address:read']],
|
|
||||||
denormalizationContext: ['groups' => ['address:write']],
|
|
||||||
security: "is_granted('ROLE_ADMIN')",
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
security: "is_granted('ROLE_USER')",
|
security: "is_granted('ROLE_USER')",
|
||||||
)]
|
)]
|
||||||
@@ -43,31 +31,31 @@ class Address
|
|||||||
#[ORM\Id]
|
#[ORM\Id]
|
||||||
#[ORM\GeneratedValue]
|
#[ORM\GeneratedValue]
|
||||||
#[ORM\Column]
|
#[ORM\Column]
|
||||||
#[Groups(['address:read', 'supplier:read', 'customer:read', 'shipment:read'])]
|
#[Groups(['address:read', 'supplier:read'])]
|
||||||
private ?int $id = null;
|
private ?int $id = null;
|
||||||
|
|
||||||
#[ORM\Column(length: 120)]
|
#[ORM\Column(length: 120)]
|
||||||
#[Groups(['address:read', 'supplier:read', 'reception:read', 'customer:read', 'shipment:read', 'address:write'])]
|
#[Groups(['address:read', 'supplier:read', 'reception:read'])]
|
||||||
private string $label = '';
|
private string $label = '';
|
||||||
|
|
||||||
#[ORM\Column(length: 180)]
|
#[ORM\Column(length: 180)]
|
||||||
#[Groups(['address:read', 'supplier:read', 'reception:read', 'customer:read', 'shipment:read', 'address:write'])]
|
#[Groups(['address:read', 'supplier:read', 'reception:read'])]
|
||||||
private string $street = '';
|
private string $street = '';
|
||||||
|
|
||||||
#[ORM\Column(name: 'street2', length: 180, nullable: true)]
|
#[ORM\Column(name: 'street2', length: 180, nullable: true)]
|
||||||
#[Groups(['address:read', 'supplier:read', 'reception:read', 'customer:read', 'shipment:read', 'address:write'])]
|
#[Groups(['address:read', 'supplier:read', 'reception:read'])]
|
||||||
private ?string $street2 = null;
|
private ?string $street2 = null;
|
||||||
|
|
||||||
#[ORM\Column(name: 'postal_code', length: 20)]
|
#[ORM\Column(name: 'postal_code', length: 20)]
|
||||||
#[Groups(['address:read', 'supplier:read', 'reception:read', 'customer:read', 'shipment:read', 'address:write'])]
|
#[Groups(['address:read', 'supplier:read', 'reception:read'])]
|
||||||
private string $postalCode = '';
|
private string $postalCode = '';
|
||||||
|
|
||||||
#[ORM\Column(length: 120)]
|
#[ORM\Column(length: 120)]
|
||||||
#[Groups(['address:read', 'supplier:read', 'reception:read', 'customer:read', 'shipment:read', 'address:write'])]
|
#[Groups(['address:read', 'supplier:read', 'reception:read'])]
|
||||||
private string $city = '';
|
private string $city = '';
|
||||||
|
|
||||||
#[ORM\Column(name: 'country_code', length: 2)]
|
#[ORM\Column(name: 'country_code', length: 2)]
|
||||||
#[Groups(['address:read', 'supplier:read', 'customer:read', 'address:write'])]
|
#[Groups(['address:read', 'supplier:read'])]
|
||||||
private string $countryCode = '';
|
private string $countryCode = '';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -76,16 +64,9 @@ class Address
|
|||||||
#[ORM\ManyToMany(targetEntity: Supplier::class, mappedBy: 'addresses')]
|
#[ORM\ManyToMany(targetEntity: Supplier::class, mappedBy: 'addresses')]
|
||||||
private Collection $suppliers;
|
private Collection $suppliers;
|
||||||
|
|
||||||
/**
|
|
||||||
* @var Collection<int, Shipment>
|
|
||||||
*/
|
|
||||||
#[ORM\OneToMany(targetEntity: Shipment::class, mappedBy: 'address')]
|
|
||||||
private Collection $shipments;
|
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->suppliers = new ArrayCollection();
|
$this->suppliers = new ArrayCollection();
|
||||||
$this->shipments = new ArrayCollection();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getId(): ?int
|
public function getId(): ?int
|
||||||
@@ -165,7 +146,7 @@ class Address
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Groups(['address:read', 'supplier:read', 'reception:read', 'shipment:read', 'customer:read'])]
|
#[Groups(['address:read', 'supplier:read', 'reception:read'])]
|
||||||
public function getFullAddress(): string
|
public function getFullAddress(): string
|
||||||
{
|
{
|
||||||
$parts = array_filter([
|
$parts = array_filter([
|
||||||
@@ -184,34 +165,4 @@ class Address
|
|||||||
{
|
{
|
||||||
return $this->suppliers;
|
return $this->suppliers;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return Collection<int, Shipment>
|
|
||||||
*/
|
|
||||||
public function getShipments(): Collection
|
|
||||||
{
|
|
||||||
return $this->shipments;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function addShipment(Shipment $shipment): static
|
|
||||||
{
|
|
||||||
if (!$this->shipments->contains($shipment)) {
|
|
||||||
$this->shipments->add($shipment);
|
|
||||||
$shipment->setAddress($this);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function removeShipment(Shipment $shipment): static
|
|
||||||
{
|
|
||||||
if ($this->shipments->removeElement($shipment)) {
|
|
||||||
// set the owning side to null (unless already changed)
|
|
||||||
if ($shipment->getAddress() === $this) {
|
|
||||||
$shipment->setAddress(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,101 +0,0 @@
|
|||||||
<?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: ['shipment' => 'exact'])]
|
|
||||||
#[ORM\UniqueConstraint(name: 'uniq_bovin_shipment', columns: ['shipment_id', 'shipment_type_id'])]
|
|
||||||
#[ORM\Table(name: 'bovin_shipment')]
|
|
||||||
#[ApiResource(
|
|
||||||
operations: [
|
|
||||||
new Get(
|
|
||||||
requirements: ['id' => '\d+'],
|
|
||||||
normalizationContext: ['groups' => ['shipment-bovine:read']],
|
|
||||||
),
|
|
||||||
new GetCollection(
|
|
||||||
normalizationContext: ['groups' => ['shipment-bovine:read']],
|
|
||||||
),
|
|
||||||
|
|
||||||
new Post(
|
|
||||||
normalizationContext: ['groups' => ['shipment-bovine:read']],
|
|
||||||
denormalizationContext: ['groups' => ['shipment-bovine:write']],
|
|
||||||
),
|
|
||||||
new Patch(
|
|
||||||
normalizationContext: ['groups' => ['shipment-bovine:read']],
|
|
||||||
denormalizationContext: ['groups' => ['shipment-bovine:write']],
|
|
||||||
),
|
|
||||||
new Delete(),
|
|
||||||
],
|
|
||||||
security: "is_granted('ROLE_USER')",
|
|
||||||
)]
|
|
||||||
class BovinShipment
|
|
||||||
{
|
|
||||||
#[ORM\Id]
|
|
||||||
#[ORM\GeneratedValue]
|
|
||||||
#[ORM\Column]
|
|
||||||
#[Groups(['shipment:read', 'shipment-bovine:read'])]
|
|
||||||
private ?int $id = null;
|
|
||||||
|
|
||||||
#[ORM\ManyToOne(inversedBy: 'bovinShipments')]
|
|
||||||
#[Groups(['shipment-bovine:read', 'shipment-bovine:write'])]
|
|
||||||
#[ApiProperty(readableLink: true)]
|
|
||||||
private ?Shipment $shipment = null;
|
|
||||||
|
|
||||||
#[ORM\ManyToOne]
|
|
||||||
#[Groups(['shipment:read', 'shipment-bovine:write', 'shipment-bovine:read'])]
|
|
||||||
#[ApiProperty(readableLink: true)]
|
|
||||||
private ?ShipmentType $shipmentType = null;
|
|
||||||
|
|
||||||
#[ORM\Column]
|
|
||||||
#[Groups(['shipment:read', 'shipment-bovine:write', 'shipment-bovine:read'])]
|
|
||||||
private ?int $nbBovinSend = null;
|
|
||||||
|
|
||||||
public function getId(): ?int
|
|
||||||
{
|
|
||||||
return $this->id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getShipment(): ?Shipment
|
|
||||||
{
|
|
||||||
return $this->shipment;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setShipment(?Shipment $shipment): void
|
|
||||||
{
|
|
||||||
$this->shipment = $shipment;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getShipmentType(): ?ShipmentType
|
|
||||||
{
|
|
||||||
return $this->shipmentType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setShipmentType(?ShipmentType $shipmentType): void
|
|
||||||
{
|
|
||||||
$this->shipmentType = $shipmentType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getNbBovinSend(): ?int
|
|
||||||
{
|
|
||||||
return $this->nbBovinSend;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setNbBovinSend(?int $nbBovinSend): void
|
|
||||||
{
|
|
||||||
$this->nbBovinSend = $nbBovinSend;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -7,8 +7,6 @@ namespace App\Entity;
|
|||||||
use ApiPlatform\Metadata\ApiResource;
|
use ApiPlatform\Metadata\ApiResource;
|
||||||
use ApiPlatform\Metadata\Get;
|
use ApiPlatform\Metadata\Get;
|
||||||
use ApiPlatform\Metadata\GetCollection;
|
use ApiPlatform\Metadata\GetCollection;
|
||||||
use ApiPlatform\Metadata\Patch;
|
|
||||||
use ApiPlatform\Metadata\Post;
|
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
use Symfony\Component\Serializer\Attribute\Groups;
|
use Symfony\Component\Serializer\Attribute\Groups;
|
||||||
|
|
||||||
@@ -23,17 +21,6 @@ use Symfony\Component\Serializer\Attribute\Groups;
|
|||||||
new GetCollection(
|
new GetCollection(
|
||||||
normalizationContext: ['groups' => ['carrier:read']],
|
normalizationContext: ['groups' => ['carrier:read']],
|
||||||
),
|
),
|
||||||
new Post(
|
|
||||||
normalizationContext: ['groups' => ['carrier:read']],
|
|
||||||
denormalizationContext: ['groups' => ['carrier:write']],
|
|
||||||
security: "is_granted('ROLE_ADMIN')"
|
|
||||||
),
|
|
||||||
new Patch(
|
|
||||||
requirements: ['id' => '\d+'],
|
|
||||||
normalizationContext: ['groups' => ['carrier:read']],
|
|
||||||
denormalizationContext: ['groups' => ['carrier:write']],
|
|
||||||
security: "is_granted('ROLE_ADMIN')"
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
security: "is_granted('ROLE_USER')",
|
security: "is_granted('ROLE_USER')",
|
||||||
)]
|
)]
|
||||||
@@ -42,15 +29,15 @@ class Carrier
|
|||||||
#[ORM\Id]
|
#[ORM\Id]
|
||||||
#[ORM\GeneratedValue]
|
#[ORM\GeneratedValue]
|
||||||
#[ORM\Column]
|
#[ORM\Column]
|
||||||
#[Groups(['carrier:read', 'driver:read', 'vehicle:read', 'reception:read', 'shipment:read'])]
|
#[Groups(['carrier:read', 'driver:read', 'vehicle:read', 'reception:read'])]
|
||||||
private ?int $id = null;
|
private ?int $id = null;
|
||||||
|
|
||||||
#[ORM\Column(length: 180)]
|
#[ORM\Column(length: 180)]
|
||||||
#[Groups(['carrier:read', 'carrier:write', 'driver:read', 'vehicle:read', 'reception:read', 'shipment:read'])]
|
#[Groups(['carrier:read', 'driver:read', 'vehicle:read', 'reception:read'])]
|
||||||
private string $name = '';
|
private string $name = '';
|
||||||
|
|
||||||
#[ORM\Column(length: 30, nullable: true)]
|
#[ORM\Column(length: 30, nullable: true)]
|
||||||
#[Groups(['carrier:read', 'carrier:write', 'driver:read', 'vehicle:read', 'reception:read', 'shipment:read'])]
|
#[Groups(['carrier:read', 'driver:read', 'vehicle:read', 'reception:read'])]
|
||||||
private ?string $code = null;
|
private ?string $code = null;
|
||||||
|
|
||||||
public function getId(): ?int
|
public function getId(): ?int
|
||||||
|
|||||||
@@ -1,94 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\Entity;
|
|
||||||
|
|
||||||
use ApiPlatform\Metadata\ApiProperty;
|
|
||||||
use ApiPlatform\Metadata\ApiResource;
|
|
||||||
use ApiPlatform\Metadata\Get;
|
|
||||||
use ApiPlatform\Metadata\GetCollection;
|
|
||||||
use Doctrine\Common\Collections\ArrayCollection;
|
|
||||||
use Doctrine\Common\Collections\Collection;
|
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
|
||||||
use Symfony\Component\Serializer\Attribute\Groups;
|
|
||||||
|
|
||||||
#[ORM\Entity]
|
|
||||||
#[ORM\Table(name: 'customer')]
|
|
||||||
#[ApiResource(
|
|
||||||
operations: [
|
|
||||||
new Get(
|
|
||||||
requirements: ['id' => '\d+'],
|
|
||||||
normalizationContext: ['groups' => ['customer:read']],
|
|
||||||
),
|
|
||||||
new GetCollection(
|
|
||||||
normalizationContext: ['groups' => ['customer:read']],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
security: "is_granted('ROLE_USER')",
|
|
||||||
)]
|
|
||||||
class Customer
|
|
||||||
{
|
|
||||||
#[ORM\Id]
|
|
||||||
#[ORM\GeneratedValue]
|
|
||||||
#[ORM\Column]
|
|
||||||
#[Groups(['shipment:read', 'customer:read'])]
|
|
||||||
private ?int $id = null;
|
|
||||||
|
|
||||||
#[ORM\Column(length: 255)]
|
|
||||||
#[Groups(['customer:read', 'shipment:read'])]
|
|
||||||
private ?string $label = null;
|
|
||||||
|
|
||||||
#[ORM\Column(length: 255)]
|
|
||||||
#[Groups(['customer:read', 'shipment:read'])]
|
|
||||||
private ?string $code = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var Collection<int, Address>
|
|
||||||
*/
|
|
||||||
#[ORM\ManyToMany(targetEntity: Address::class, inversedBy: 'customers')]
|
|
||||||
#[ORM\JoinTable(name: 'customer_address')]
|
|
||||||
#[Groups(['customer:read'])]
|
|
||||||
#[ApiProperty(readableLink: true)]
|
|
||||||
private Collection $addresses;
|
|
||||||
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
$this->addresses = new ArrayCollection();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getId(): ?int
|
|
||||||
{
|
|
||||||
return $this->id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getLabel(): ?string
|
|
||||||
{
|
|
||||||
return $this->label;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setLabel(?string $label): void
|
|
||||||
{
|
|
||||||
$this->label = $label;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getCode(): ?string
|
|
||||||
{
|
|
||||||
return $this->code;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setCode(?string $code): void
|
|
||||||
{
|
|
||||||
$this->code = $code;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getAddresses(): Collection
|
|
||||||
{
|
|
||||||
return $this->addresses;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setAddresses(Collection $addresses): void
|
|
||||||
{
|
|
||||||
$this->addresses = $addresses;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -30,11 +30,11 @@ class Driver
|
|||||||
#[ORM\Id]
|
#[ORM\Id]
|
||||||
#[ORM\GeneratedValue]
|
#[ORM\GeneratedValue]
|
||||||
#[ORM\Column]
|
#[ORM\Column]
|
||||||
#[Groups(['driver:read', 'reception:read', 'shipment:read'])]
|
#[Groups(['driver:read', 'reception:read'])]
|
||||||
private ?int $id = null;
|
private ?int $id = null;
|
||||||
|
|
||||||
#[ORM\Column(length: 180)]
|
#[ORM\Column(length: 180)]
|
||||||
#[Groups(['driver:read', 'reception:read', 'shipment:read'])]
|
#[Groups(['driver:read', 'reception:read'])]
|
||||||
private string $name = '';
|
private string $name = '';
|
||||||
|
|
||||||
#[ORM\ManyToOne]
|
#[ORM\ManyToOne]
|
||||||
|
|||||||
@@ -1,369 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\Entity;
|
|
||||||
|
|
||||||
use ApiPlatform\Doctrine\Orm\Filter\BooleanFilter;
|
|
||||||
use ApiPlatform\Metadata\ApiFilter;
|
|
||||||
use ApiPlatform\Metadata\ApiProperty;
|
|
||||||
use ApiPlatform\Metadata\ApiResource;
|
|
||||||
use ApiPlatform\Metadata\Get;
|
|
||||||
use ApiPlatform\Metadata\GetCollection;
|
|
||||||
use ApiPlatform\Metadata\Patch;
|
|
||||||
use ApiPlatform\Metadata\Post;
|
|
||||||
use ApiPlatform\OpenApi\Model\Operation as OpenApiOperation;
|
|
||||||
use App\Dto\PontBasculeReading;
|
|
||||||
use App\State\ShipmentReceiptProvider;
|
|
||||||
use App\State\ShipmentWeighingProvider;
|
|
||||||
use DateTimeImmutable;
|
|
||||||
use Doctrine\Common\Collections\ArrayCollection;
|
|
||||||
use Doctrine\Common\Collections\Collection;
|
|
||||||
use Doctrine\ORM\Event\PostPersistEventArgs;
|
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
|
||||||
use Symfony\Component\Serializer\Attribute\Context;
|
|
||||||
use Symfony\Component\Serializer\Attribute\Groups;
|
|
||||||
use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
|
|
||||||
|
|
||||||
#[ORM\Entity]
|
|
||||||
#[ORM\HasLifecycleCallbacks]
|
|
||||||
#[ORM\Table(name: 'shipment')]
|
|
||||||
#[ApiFilter(BooleanFilter::class, properties: ['isValid'])]
|
|
||||||
#[ApiResource(
|
|
||||||
operations: [
|
|
||||||
new Get(
|
|
||||||
requirements: ['id' => '\d+'],
|
|
||||||
normalizationContext: ['groups' => ['shipment:read']],
|
|
||||||
),
|
|
||||||
new GetCollection(
|
|
||||||
normalizationContext: ['groups' => ['shipment:read']],
|
|
||||||
),
|
|
||||||
new Post(
|
|
||||||
normalizationContext: ['groups' => ['shipment:read']],
|
|
||||||
denormalizationContext: ['groups' => ['shipment:write']],
|
|
||||||
),
|
|
||||||
new Patch(
|
|
||||||
requirements: ['id' => '\d+'],
|
|
||||||
normalizationContext: ['groups' => ['shipment:read']],
|
|
||||||
denormalizationContext: ['groups' => ['shipment:write']],
|
|
||||||
),
|
|
||||||
new Get(
|
|
||||||
uriTemplate: '/shipments/weigh',
|
|
||||||
openapi: new OpenApiOperation(
|
|
||||||
summary: 'Fetch the current weight reading',
|
|
||||||
description: 'Queries the pont-bascule and returns the weight data.',
|
|
||||||
),
|
|
||||||
normalizationContext: ['groups' => ['shipment:weigh:read']],
|
|
||||||
output: PontBasculeReading::class,
|
|
||||||
provider: ShipmentWeighingProvider::class,
|
|
||||||
),
|
|
||||||
new Get(
|
|
||||||
uriTemplate: '/shipments/{id}/receipt',
|
|
||||||
requirements: ['id' => '\d+'],
|
|
||||||
openapi: new OpenApiOperation(
|
|
||||||
summary: 'Render a shipment receipt',
|
|
||||||
description: 'Returns a PDF receipt for the shipment.',
|
|
||||||
),
|
|
||||||
output: false,
|
|
||||||
provider: ShipmentReceiptProvider::class,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
security: "is_granted('ROLE_USER')",
|
|
||||||
)]
|
|
||||||
class Shipment
|
|
||||||
{
|
|
||||||
#[ORM\Id]
|
|
||||||
#[ORM\GeneratedValue]
|
|
||||||
#[ORM\Column]
|
|
||||||
#[Groups(['shipment:read'])]
|
|
||||||
private ?int $id = null;
|
|
||||||
|
|
||||||
#[ORM\Column(length: 255)]
|
|
||||||
#[Groups(['shipment:read', 'shipment:write'])]
|
|
||||||
private ?string $licencePlate = null;
|
|
||||||
|
|
||||||
#[ORM\Column(length: 20, unique: true, nullable: true)]
|
|
||||||
#[Groups(['shipment:read'])]
|
|
||||||
private ?string $identificationNumber = null;
|
|
||||||
|
|
||||||
#[ORM\Column(options: ['default' => 0])]
|
|
||||||
#[Groups(['shipment:read', 'shipment:write'])]
|
|
||||||
private int $currentStep = 0;
|
|
||||||
|
|
||||||
#[ORM\Column]
|
|
||||||
#[Groups(['shipment:read', 'shipment:write'])]
|
|
||||||
private bool $isValid = false;
|
|
||||||
|
|
||||||
#[ORM\ManyToOne]
|
|
||||||
#[ORM\JoinColumn(nullable: true)]
|
|
||||||
#[Groups(['shipment:read', 'shipment:write'])]
|
|
||||||
#[ApiProperty(readableLink: true)]
|
|
||||||
private ?User $user = null;
|
|
||||||
|
|
||||||
#[ORM\Column(name: 'shipment_date', type: 'datetime_immutable')]
|
|
||||||
#[Groups(['shipment:read', 'shipment:write'])]
|
|
||||||
#[Context([DateTimeNormalizer::FORMAT_KEY => 'Y-m-d'])]
|
|
||||||
private ?DateTimeImmutable $shipmentDate = null;
|
|
||||||
|
|
||||||
#[ORM\ManyToOne]
|
|
||||||
#[ORM\JoinColumn(nullable: true)]
|
|
||||||
#[Groups(['shipment:read', 'shipment:write'])]
|
|
||||||
#[ApiProperty(readableLink: true)]
|
|
||||||
private ?Carrier $carrier = null;
|
|
||||||
|
|
||||||
#[ORM\ManyToOne]
|
|
||||||
#[ORM\JoinColumn(nullable: true)]
|
|
||||||
#[Groups(['shipment:read', 'shipment:write'])]
|
|
||||||
#[ApiProperty(readableLink: true)]
|
|
||||||
private ?Truck $truck = null;
|
|
||||||
|
|
||||||
#[ORM\ManyToOne]
|
|
||||||
#[ORM\JoinColumn(nullable: true)]
|
|
||||||
#[Groups(['shipment:read', 'shipment:write'])]
|
|
||||||
#[ApiProperty(readableLink: true)]
|
|
||||||
private ?Customer $customer = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var Collection<int, BovinShipment>
|
|
||||||
*/
|
|
||||||
#[ORM\OneToMany(
|
|
||||||
targetEntity: BovinShipment::class,
|
|
||||||
mappedBy: 'shipment',
|
|
||||||
cascade: ['persist', 'remove'],
|
|
||||||
orphanRemoval: true
|
|
||||||
)]
|
|
||||||
#[Groups(['shipment:read', 'shipment:write'])]
|
|
||||||
private Collection $bovinShipments;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var Collection<int, Weight>
|
|
||||||
*/
|
|
||||||
#[ORM\OneToMany(targetEntity: Weight::class, mappedBy: 'shipment', cascade: ['persist', 'remove'], orphanRemoval: true)]
|
|
||||||
#[Groups(['shipment:read'])]
|
|
||||||
private Collection $weights;
|
|
||||||
|
|
||||||
#[ORM\ManyToOne]
|
|
||||||
#[ORM\JoinColumn(nullable: true)]
|
|
||||||
#[Groups(['shipment:read', 'shipment:write'])]
|
|
||||||
#[ApiProperty(readableLink: true)]
|
|
||||||
private ?Driver $driver = null;
|
|
||||||
|
|
||||||
#[ORM\ManyToOne]
|
|
||||||
#[ORM\JoinColumn(nullable: true)]
|
|
||||||
#[Groups(['shipment:read', 'shipment:write'])]
|
|
||||||
#[ApiProperty(readableLink: true)]
|
|
||||||
private ?Address $address = null;
|
|
||||||
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
$this->bovinShipments = new ArrayCollection();
|
|
||||||
$this->weights = new ArrayCollection();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getId(): ?int
|
|
||||||
{
|
|
||||||
return $this->id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getLicencePlate(): ?string
|
|
||||||
{
|
|
||||||
return $this->licencePlate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setLicencePlate(?string $licencePlate): void
|
|
||||||
{
|
|
||||||
$this->licencePlate = $licencePlate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getIdentificationNumber(): ?string
|
|
||||||
{
|
|
||||||
return $this->identificationNumber;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setIdentificationNumber(?string $identificationNumber): void
|
|
||||||
{
|
|
||||||
$this->identificationNumber = $identificationNumber;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getCurrentStep(): int
|
|
||||||
{
|
|
||||||
return $this->currentStep;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setCurrentStep(int $currentStep): void
|
|
||||||
{
|
|
||||||
$this->currentStep = $currentStep;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getIsValid(): ?bool
|
|
||||||
{
|
|
||||||
return $this->isValid;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[Groups(['shipment:read'])]
|
|
||||||
public function isValid(): bool
|
|
||||||
{
|
|
||||||
return $this->isValid;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getUser(): ?User
|
|
||||||
{
|
|
||||||
return $this->user;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setUser(?User $user): void
|
|
||||||
{
|
|
||||||
$this->user = $user;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setIsValid(?bool $isValid): void
|
|
||||||
{
|
|
||||||
$this->isValid = $isValid;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getShipmentDate(): ?DateTimeImmutable
|
|
||||||
{
|
|
||||||
return $this->shipmentDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setShipmentDate(?DateTimeImmutable $shipmentDate): void
|
|
||||||
{
|
|
||||||
$this->shipmentDate = $shipmentDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getCarrier(): ?Carrier
|
|
||||||
{
|
|
||||||
return $this->carrier;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setCarrier(?Carrier $carrier): void
|
|
||||||
{
|
|
||||||
$this->carrier = $carrier;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getTruck(): ?Truck
|
|
||||||
{
|
|
||||||
return $this->truck;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setTruck(?Truck $truck): void
|
|
||||||
{
|
|
||||||
$this->truck = $truck;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getCustomer(): ?Customer
|
|
||||||
{
|
|
||||||
return $this->customer;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setCustomer(?Customer $customer): void
|
|
||||||
{
|
|
||||||
$this->customer = $customer;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getBovinShipments(): Collection
|
|
||||||
{
|
|
||||||
return $this->bovinShipments;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setBovinShipments(Collection $bovinShipments): void
|
|
||||||
{
|
|
||||||
$this->bovinShipments = $bovinShipments;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function addBovinShipment(BovinShipment $bovinShipment): self
|
|
||||||
{
|
|
||||||
if (!$this->bovinShipments->contains($bovinShipment)) {
|
|
||||||
$this->bovinShipments->add($bovinShipment);
|
|
||||||
$bovinShipment->setShipment($this);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function removeBovinShipment(BovinShipment $bovinShipment): self
|
|
||||||
{
|
|
||||||
if ($this->bovinShipments->removeElement($bovinShipment)) {
|
|
||||||
if ($bovinShipment->getShipment() === $this) {
|
|
||||||
$bovinShipment->setShipment(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return Collection<int, Weight>
|
|
||||||
*/
|
|
||||||
public function getWeights(): Collection
|
|
||||||
{
|
|
||||||
return $this->weights;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function addWeight(Weight $weight): void
|
|
||||||
{
|
|
||||||
if (!$this->weights->contains($weight)) {
|
|
||||||
$this->weights->add($weight);
|
|
||||||
$weight->setShipment($this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function removeWeight(Weight $weight): void
|
|
||||||
{
|
|
||||||
if ($this->weights->removeElement($weight)) {
|
|
||||||
if ($weight->getShipment() === $this) {
|
|
||||||
$weight->setShipment(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[ORM\PostPersist]
|
|
||||||
public function initializeIdentificationNumber(PostPersistEventArgs $args): void
|
|
||||||
{
|
|
||||||
if (null !== $this->identificationNumber) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (null === $this->id) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$number = sprintf('P-BR-%04d', $this->id);
|
|
||||||
$this->identificationNumber = $number;
|
|
||||||
|
|
||||||
$args->getObjectManager()
|
|
||||||
->getConnection()
|
|
||||||
->executeStatement(
|
|
||||||
'UPDATE shipment SET identification_number = :number WHERE id = :id',
|
|
||||||
[
|
|
||||||
'number' => $number,
|
|
||||||
'id' => $this->id,
|
|
||||||
]
|
|
||||||
)
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getDriver(): ?Driver
|
|
||||||
{
|
|
||||||
return $this->driver;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setDriver(?Driver $driver): static
|
|
||||||
{
|
|
||||||
$this->driver = $driver;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getAddress(): ?Address
|
|
||||||
{
|
|
||||||
return $this->address;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setAddress(?Address $address): static
|
|
||||||
{
|
|
||||||
$this->address = $address;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
<?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]
|
|
||||||
#[ORM\Table(name: 'shipment_type')]
|
|
||||||
#[ApiResource(
|
|
||||||
operations: [
|
|
||||||
new Get(
|
|
||||||
requirements: ['id' => '\d+'],
|
|
||||||
normalizationContext: ['groups' => ['shipment-type:read']],
|
|
||||||
),
|
|
||||||
new GetCollection(
|
|
||||||
normalizationContext: ['groups' => ['shipment-type:read']],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
security: "is_granted('ROLE_USER')",
|
|
||||||
)]
|
|
||||||
class ShipmentType
|
|
||||||
{
|
|
||||||
#[ORM\Id]
|
|
||||||
#[ORM\GeneratedValue]
|
|
||||||
#[ORM\Column]
|
|
||||||
#[Groups(['shipment-type:read', 'shipment:read'])]
|
|
||||||
private ?int $id = null;
|
|
||||||
|
|
||||||
#[ORM\Column(length: 255)]
|
|
||||||
#[Groups(['shipment-type:read', 'shipment:read'])]
|
|
||||||
private ?string $label = null;
|
|
||||||
|
|
||||||
#[ORM\Column(length: 255)]
|
|
||||||
#[Groups(['shipment-type:read', 'shipment: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): self
|
|
||||||
{
|
|
||||||
$this->code = $code;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -8,8 +8,6 @@ use ApiPlatform\Metadata\ApiProperty;
|
|||||||
use ApiPlatform\Metadata\ApiResource;
|
use ApiPlatform\Metadata\ApiResource;
|
||||||
use ApiPlatform\Metadata\Get;
|
use ApiPlatform\Metadata\Get;
|
||||||
use ApiPlatform\Metadata\GetCollection;
|
use ApiPlatform\Metadata\GetCollection;
|
||||||
use ApiPlatform\Metadata\Patch;
|
|
||||||
use ApiPlatform\Metadata\Post;
|
|
||||||
use Doctrine\Common\Collections\ArrayCollection;
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
use Doctrine\Common\Collections\Collection;
|
use Doctrine\Common\Collections\Collection;
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
@@ -26,21 +24,6 @@ use Symfony\Component\Serializer\Attribute\Groups;
|
|||||||
new GetCollection(
|
new GetCollection(
|
||||||
normalizationContext: ['groups' => ['supplier:read']],
|
normalizationContext: ['groups' => ['supplier:read']],
|
||||||
),
|
),
|
||||||
new GetCollection(
|
|
||||||
uriTemplate: '/admin/suppliers',
|
|
||||||
normalizationContext: ['groups' => ['supplier:read']],
|
|
||||||
security: "is_granted('ROLE_ADMIN')"
|
|
||||||
),
|
|
||||||
new Post(
|
|
||||||
normalizationContext: ['groups' => ['supplier:read']],
|
|
||||||
denormalizationContext: ['groups' => ['supplier:write']],
|
|
||||||
security: "is_granted('ROLE_ADMIN')",
|
|
||||||
),
|
|
||||||
new Patch(
|
|
||||||
normalizationContext: ['groups' => ['supplier:read']],
|
|
||||||
denormalizationContext: ['groups' => ['supplier:write']],
|
|
||||||
security: "is_granted('ROLE_ADMIN')",
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
security: "is_granted('ROLE_USER')",
|
security: "is_granted('ROLE_USER')",
|
||||||
)]
|
)]
|
||||||
@@ -53,15 +36,15 @@ class Supplier
|
|||||||
private ?int $id = null;
|
private ?int $id = null;
|
||||||
|
|
||||||
#[ORM\Column(length: 180)]
|
#[ORM\Column(length: 180)]
|
||||||
#[Groups(['supplier:read', 'reception:read', 'supplier:write'])]
|
#[Groups(['supplier:read', 'reception:read'])]
|
||||||
private string $name = '';
|
private string $name = '';
|
||||||
|
|
||||||
#[ORM\Column(length: 180, nullable: true)]
|
#[ORM\Column(length: 180, nullable: true)]
|
||||||
#[Groups(['supplier:read', 'reception:read', 'supplier:write'])]
|
#[Groups(['supplier:read', 'reception:read'])]
|
||||||
private ?string $email = null;
|
private ?string $email = null;
|
||||||
|
|
||||||
#[ORM\Column(length: 40, nullable: true)]
|
#[ORM\Column(length: 40, nullable: true)]
|
||||||
#[Groups(['supplier:read', 'reception:read', 'supplier:write'])]
|
#[Groups(['supplier:read', 'reception:read'])]
|
||||||
private ?string $phone = null;
|
private ?string $phone = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -69,7 +52,7 @@ class Supplier
|
|||||||
*/
|
*/
|
||||||
#[ORM\ManyToMany(targetEntity: Address::class, inversedBy: 'suppliers')]
|
#[ORM\ManyToMany(targetEntity: Address::class, inversedBy: 'suppliers')]
|
||||||
#[ORM\JoinTable(name: 'supplier_address')]
|
#[ORM\JoinTable(name: 'supplier_address')]
|
||||||
#[Groups(['supplier:read', 'supplier:write'])]
|
#[Groups(['supplier:read'])]
|
||||||
#[ApiProperty(readableLink: true)]
|
#[ApiProperty(readableLink: true)]
|
||||||
private Collection $addresses;
|
private Collection $addresses;
|
||||||
|
|
||||||
@@ -126,30 +109,4 @@ class Supplier
|
|||||||
{
|
{
|
||||||
return $this->addresses;
|
return $this->addresses;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setAddresses(iterable $addresses): self
|
|
||||||
{
|
|
||||||
$this->addresses->clear();
|
|
||||||
foreach ($addresses as $address) {
|
|
||||||
$this->addAddress($address);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function addAddress(Address $address): self
|
|
||||||
{
|
|
||||||
if (!$this->addresses->contains($address)) {
|
|
||||||
$this->addresses->add($address);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function removeAddress(Address $address): self
|
|
||||||
{
|
|
||||||
$this->addresses->removeElement($address);
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,11 +29,11 @@ class Truck
|
|||||||
#[ORM\Id]
|
#[ORM\Id]
|
||||||
#[ORM\GeneratedValue]
|
#[ORM\GeneratedValue]
|
||||||
#[ORM\Column]
|
#[ORM\Column]
|
||||||
#[Groups(['truck:read', 'vehicle:read', 'reception:read', 'shipment:read'])]
|
#[Groups(['truck:read', 'vehicle:read', 'reception:read'])]
|
||||||
private ?int $id = null;
|
private ?int $id = null;
|
||||||
|
|
||||||
#[ORM\Column(length: 180)]
|
#[ORM\Column(length: 180)]
|
||||||
#[Groups(['truck:read', 'vehicle:read', 'reception:read', 'shipment:read'])]
|
#[Groups(['truck:read', 'vehicle:read', 'reception:read'])]
|
||||||
private string $name = '';
|
private string $name = '';
|
||||||
|
|
||||||
public function getId(): ?int
|
public function getId(): ?int
|
||||||
|
|||||||
@@ -7,10 +7,7 @@ namespace App\Entity;
|
|||||||
use ApiPlatform\Metadata\ApiResource;
|
use ApiPlatform\Metadata\ApiResource;
|
||||||
use ApiPlatform\Metadata\Get;
|
use ApiPlatform\Metadata\Get;
|
||||||
use ApiPlatform\Metadata\GetCollection;
|
use ApiPlatform\Metadata\GetCollection;
|
||||||
use ApiPlatform\Metadata\Patch;
|
|
||||||
use ApiPlatform\Metadata\Post;
|
|
||||||
use App\State\MeProvider;
|
use App\State\MeProvider;
|
||||||
use App\State\UserPasswordProcessor;
|
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
|
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
|
||||||
use Symfony\Component\Security\Core\User\UserInterface;
|
use Symfony\Component\Security\Core\User\UserInterface;
|
||||||
@@ -31,27 +28,10 @@ use Symfony\Component\Serializer\Attribute\Groups;
|
|||||||
normalizationContext: ['groups' => ['user:read']],
|
normalizationContext: ['groups' => ['user:read']],
|
||||||
security: "is_granted('ROLE_USER')"
|
security: "is_granted('ROLE_USER')"
|
||||||
),
|
),
|
||||||
new Post(
|
|
||||||
normalizationContext: ['groups' => ['user:read']],
|
|
||||||
denormalizationContext: ['groups' => ['user:write']],
|
|
||||||
security: "is_granted('ROLE_ADMIN')",
|
|
||||||
processor: UserPasswordProcessor::class
|
|
||||||
),
|
|
||||||
new Patch(
|
|
||||||
normalizationContext: ['groups' => ['user:read']],
|
|
||||||
denormalizationContext: ['groups' => ['user:write']],
|
|
||||||
security: "is_granted('ROLE_ADMIN')",
|
|
||||||
processor: UserPasswordProcessor::class
|
|
||||||
),
|
|
||||||
new GetCollection(
|
new GetCollection(
|
||||||
normalizationContext: ['groups' => ['user-login:read']],
|
normalizationContext: ['groups' => ['user:read']],
|
||||||
security: "is_granted('PUBLIC_ACCESS')"
|
security: "is_granted('PUBLIC_ACCESS')"
|
||||||
),
|
),
|
||||||
new GetCollection(
|
|
||||||
uriTemplate: '/admin/users',
|
|
||||||
normalizationContext: ['groups' => ['user:read']],
|
|
||||||
security: "is_granted('ROLE_ADMIN')"
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
normalizationContext: ['groups' => ['user:read']],
|
normalizationContext: ['groups' => ['user:read']],
|
||||||
paginationEnabled: false
|
paginationEnabled: false
|
||||||
@@ -61,19 +41,18 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
|
|||||||
#[ORM\Id]
|
#[ORM\Id]
|
||||||
#[ORM\GeneratedValue]
|
#[ORM\GeneratedValue]
|
||||||
#[ORM\Column(type: 'integer')]
|
#[ORM\Column(type: 'integer')]
|
||||||
#[Groups(['user:read', 'user-login:read', 'reception:read', 'shipment:read'])]
|
#[Groups(['user:read', 'reception:read'])]
|
||||||
private ?int $id = null;
|
private ?int $id = null;
|
||||||
|
|
||||||
#[ORM\Column(length: 180, unique: true)]
|
#[ORM\Column(length: 180, unique: true)]
|
||||||
#[Groups(['user:read', 'user:write', 'user-login:read', 'reception:read', 'shipment:read'])]
|
#[Groups(['user:read', 'reception:read'])]
|
||||||
private string $username = '';
|
private string $username = '';
|
||||||
|
|
||||||
#[ORM\Column(type: 'json')]
|
#[ORM\Column(type: 'json')]
|
||||||
#[Groups(['user:write', 'user:read'])]
|
#[Groups(['user:read'])]
|
||||||
private array $roles = [];
|
private array $roles = [];
|
||||||
|
|
||||||
#[ORM\Column]
|
#[ORM\Column]
|
||||||
#[Groups(['user:write'])]
|
|
||||||
private string $password = '';
|
private string $password = '';
|
||||||
|
|
||||||
public function getId(): ?int
|
public function getId(): ?int
|
||||||
|
|||||||
@@ -35,46 +35,36 @@ use Symfony\Component\Validator\Constraints as Assert;
|
|||||||
security: "is_granted('ROLE_USER')",
|
security: "is_granted('ROLE_USER')",
|
||||||
)]
|
)]
|
||||||
#[UniqueEntity(fields: ['reception', 'type'], message: 'A weighing already exists for this type.')]
|
#[UniqueEntity(fields: ['reception', 'type'], message: 'A weighing already exists for this type.')]
|
||||||
#[UniqueEntity(fields: ['shipment', 'type'], message: 'A weighing already exists for this type.')]
|
|
||||||
#[Assert\Expression(
|
|
||||||
'(this.getReception() !== null and this.getShipment() === null) or (this.getReception() === null and this.getShipment() !== null)',
|
|
||||||
message: 'Either reception or shipment must be set, but not both.'
|
|
||||||
)]
|
|
||||||
class Weight
|
class Weight
|
||||||
{
|
{
|
||||||
#[ORM\Id]
|
#[ORM\Id]
|
||||||
#[ORM\GeneratedValue]
|
#[ORM\GeneratedValue]
|
||||||
#[ORM\Column]
|
#[ORM\Column]
|
||||||
#[Groups(['reception:read', 'shipment:read', 'weight:read'])]
|
#[Groups(['reception:read', 'weight:read'])]
|
||||||
private ?int $id = null;
|
private ?int $id = null;
|
||||||
|
|
||||||
#[ORM\ManyToOne(inversedBy: 'weights')]
|
#[ORM\ManyToOne(inversedBy: 'weights')]
|
||||||
#[ORM\JoinColumn(nullable: true)]
|
#[ORM\JoinColumn(nullable: false)]
|
||||||
#[Groups(['weight:read', 'weight:write'])]
|
#[Groups(['weight:read', 'weight:write'])]
|
||||||
private ?Reception $reception = null;
|
private ?Reception $reception = null;
|
||||||
|
|
||||||
#[ORM\ManyToOne(inversedBy: 'weights')]
|
|
||||||
#[ORM\JoinColumn(nullable: true)]
|
|
||||||
#[Groups(['weight:read', 'weight:write'])]
|
|
||||||
private ?Shipment $shipment = null;
|
|
||||||
|
|
||||||
#[ORM\Column(nullable: true)]
|
#[ORM\Column(nullable: true)]
|
||||||
#[Groups(['reception:read', 'shipment:read', 'weight:read', 'weight:write'])]
|
#[Groups(['reception:read', 'weight:read', 'weight:write'])]
|
||||||
#[Assert\PositiveOrZero]
|
#[Assert\PositiveOrZero]
|
||||||
private ?int $dsd = null;
|
private ?int $dsd = null;
|
||||||
|
|
||||||
#[ORM\Column(nullable: true)]
|
#[ORM\Column(nullable: true)]
|
||||||
#[Groups(['reception:read', 'shipment:read', 'weight:read', 'weight:write'])]
|
#[Groups(['reception:read', 'weight:read', 'weight:write'])]
|
||||||
#[Assert\PositiveOrZero]
|
#[Assert\PositiveOrZero]
|
||||||
private ?int $weight = null;
|
private ?int $weight = null;
|
||||||
|
|
||||||
#[ORM\Column(type: 'datetime_immutable', nullable: true)]
|
#[ORM\Column(type: 'datetime_immutable', nullable: true)]
|
||||||
#[Groups(['reception:read', 'shipment:read', 'weight:read', 'weight:write'])]
|
#[Groups(['reception:read', 'weight:read', 'weight:write'])]
|
||||||
#[Context([DateTimeNormalizer::FORMAT_KEY => 'Y-m-d'])]
|
#[Context([DateTimeNormalizer::FORMAT_KEY => 'Y-m-d'])]
|
||||||
private ?DateTimeImmutable $weighedAt = null;
|
private ?DateTimeImmutable $weighedAt = null;
|
||||||
|
|
||||||
#[ORM\Column(length: 10)]
|
#[ORM\Column(length: 10)]
|
||||||
#[Groups(['reception:read', 'shipment:read', 'weight:read', 'weight:write'])]
|
#[Groups(['reception:read', 'weight:read', 'weight:write'])]
|
||||||
#[Assert\NotBlank]
|
#[Assert\NotBlank]
|
||||||
#[Assert\Choice(choices: ['gross', 'tare'])]
|
#[Assert\Choice(choices: ['gross', 'tare'])]
|
||||||
private string $type = 'gross';
|
private string $type = 'gross';
|
||||||
@@ -100,22 +90,6 @@ class Weight
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getShipment(): ?Shipment
|
|
||||||
{
|
|
||||||
return $this->shipment;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setShipment(?Shipment $shipment): self
|
|
||||||
{
|
|
||||||
$this->shipment = $shipment;
|
|
||||||
|
|
||||||
if (null !== $shipment && !$shipment->getWeights()->contains($this)) {
|
|
||||||
$shipment->addWeight($this);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getDsd(): ?int
|
public function getDsd(): ?int
|
||||||
{
|
{
|
||||||
return $this->dsd;
|
return $this->dsd;
|
||||||
|
|||||||
@@ -1,63 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\State;
|
|
||||||
|
|
||||||
use ApiPlatform\Metadata\Operation;
|
|
||||||
use ApiPlatform\State\ProviderInterface;
|
|
||||||
use App\Entity\Shipment;
|
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
|
||||||
use Dompdf\Dompdf;
|
|
||||||
use Dompdf\Options;
|
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
|
||||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
|
||||||
use Twig\Environment;
|
|
||||||
use Twig\Error\LoaderError;
|
|
||||||
use Twig\Error\RuntimeError;
|
|
||||||
use Twig\Error\SyntaxError;
|
|
||||||
|
|
||||||
final readonly class ShipmentReceiptProvider implements ProviderInterface
|
|
||||||
{
|
|
||||||
public function __construct(
|
|
||||||
private Environment $twig,
|
|
||||||
private EntityManagerInterface $entityManager,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws RuntimeError
|
|
||||||
* @throws SyntaxError
|
|
||||||
* @throws LoaderError
|
|
||||||
*/
|
|
||||||
public function provide(Operation $operation, array $uriVariables = [], array $context = []): Response
|
|
||||||
{
|
|
||||||
$id = $uriVariables['id'] ?? null;
|
|
||||||
if (null === $id) {
|
|
||||||
throw new NotFoundHttpException('Shipment not found.');
|
|
||||||
}
|
|
||||||
|
|
||||||
$shipment = $this->entityManager->getRepository(Shipment::class)->find($id);
|
|
||||||
if (!$shipment instanceof Shipment) {
|
|
||||||
throw new NotFoundHttpException('Shipment not found.');
|
|
||||||
}
|
|
||||||
|
|
||||||
$options = new Options();
|
|
||||||
$options->set('isRemoteEnabled', true);
|
|
||||||
|
|
||||||
$dompdf = new Dompdf($options);
|
|
||||||
$html = $this->twig->render('shipment_voucher.html.twig', [
|
|
||||||
'shipment' => $shipment,
|
|
||||||
]);
|
|
||||||
|
|
||||||
$dompdf->loadHtml($html);
|
|
||||||
$dompdf->setPaper('A4');
|
|
||||||
$dompdf->render();
|
|
||||||
|
|
||||||
$filename = sprintf('bon-expedition-%d.pdf', $shipment->getId());
|
|
||||||
|
|
||||||
return new Response($dompdf->output(), Response::HTTP_OK, [
|
|
||||||
'Content-Type' => 'application/pdf',
|
|
||||||
'Content-Disposition' => 'inline; filename="'.$filename.'"',
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\State;
|
|
||||||
|
|
||||||
use ApiPlatform\Metadata\Operation;
|
|
||||||
use ApiPlatform\State\ProviderInterface;
|
|
||||||
use App\Dto\PontBasculeReading;
|
|
||||||
use App\Exception\PontBasculeException;
|
|
||||||
use App\Service\PontBasculeService;
|
|
||||||
use Symfony\Component\HttpKernel\Exception\HttpException;
|
|
||||||
|
|
||||||
final readonly class ShipmentWeighingProvider implements ProviderInterface
|
|
||||||
{
|
|
||||||
public function __construct(
|
|
||||||
private PontBasculeService $pontBasculeService,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
public function provide(Operation $operation, array $uriVariables = [], array $context = []): ?PontBasculeReading
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$result = $this->pontBasculeService->fetch();
|
|
||||||
} catch (PontBasculeException $exception) {
|
|
||||||
throw new HttpException(500, $exception->getMessage(), $exception);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\State;
|
|
||||||
|
|
||||||
use ApiPlatform\Metadata\Operation;
|
|
||||||
use ApiPlatform\State\ProcessorInterface;
|
|
||||||
use App\Entity\User;
|
|
||||||
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
|
||||||
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
|
|
||||||
|
|
||||||
final class UserPasswordProcessor implements ProcessorInterface
|
|
||||||
{
|
|
||||||
public function __construct(
|
|
||||||
private readonly UserPasswordHasherInterface $hasher,
|
|
||||||
#[Autowire(service: 'api_platform.doctrine.orm.state.persist_processor')]
|
|
||||||
private readonly ProcessorInterface $persistProcessor
|
|
||||||
) {}
|
|
||||||
|
|
||||||
public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): mixed
|
|
||||||
{
|
|
||||||
if ($data instanceof User) {
|
|
||||||
$plain = $data->getPassword();
|
|
||||||
if ('' !== $plain) {
|
|
||||||
$data->setPassword($this->hasher->hashPassword(
|
|
||||||
$data,
|
|
||||||
$plain
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->persistProcessor->process(
|
|
||||||
$data,
|
|
||||||
$operation,
|
|
||||||
$uriVariables,
|
|
||||||
$context
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -141,18 +141,16 @@
|
|||||||
|
|
||||||
<td style="width:30%; text-align:right; vertical-align:top; font-size: 14px;">
|
<td style="width:30%; text-align:right; vertical-align:top; font-size: 14px;">
|
||||||
<div style="display:inline-block; width:75mm; line-height:1.3;">
|
<div style="display:inline-block; width:75mm; line-height:1.3;">
|
||||||
<strong>{{ reception.supplier ? reception.supplier.name : '-' }}</strong><br>
|
<strong>{{ reception.supplier.name }}</strong><br>
|
||||||
<span>{{ reception.address ? reception.address.street : '' }}</span><br>
|
<span>{{ reception.address.street }}</span><br>
|
||||||
{% if reception.address and reception.address.street2 %}
|
{% if reception.address.street2 %}
|
||||||
<span>{{ reception.address.street2 }}</span><br>
|
<span>{{ reception.address.street2 }}</span><br>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if reception.address %}
|
<span>{{ reception.address.postalCode }} {{ reception.address.city }}</span><br>
|
||||||
<span>{{ reception.address.postalCode }} {{ reception.address.city }}</span><br>
|
{% if reception.supplier.phone %}
|
||||||
{% endif %}
|
|
||||||
{% if reception.supplier and reception.supplier.phone %}
|
|
||||||
<span>{{ reception.supplier.phone }}</span><br>
|
<span>{{ reception.supplier.phone }}</span><br>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if reception.supplier and reception.supplier.email %}
|
{% if reception.supplier.email %}
|
||||||
<span>{{ reception.supplier.email}}</span><br>
|
<span>{{ reception.supplier.email}}</span><br>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
@@ -170,9 +168,7 @@
|
|||||||
<th style="width:25%; text-align:center; white-space:nowrap;">N° réception</th>
|
<th style="width:25%; text-align:center; white-space:nowrap;">N° réception</th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td style="width:55%; text-align:center;">
|
<td style="width:55%; text-align:center;">{{ reception.supplier.name }}</td>
|
||||||
{{ reception.supplier ? reception.supplier.name : '-' }}
|
|
||||||
</td>
|
|
||||||
<td style="width:20%; text-align:center; white-space:nowrap;">
|
<td style="width:20%; text-align:center; white-space:nowrap;">
|
||||||
{{ reception.receptionDate|date('d/m/Y') }}
|
{{ reception.receptionDate|date('d/m/Y') }}
|
||||||
</td>
|
</td>
|
||||||
@@ -193,11 +189,13 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td style="width:75%;">
|
<td style="width:75%;">
|
||||||
<strong>{{ reception.receptionType ? reception.receptionType.label : '-' }}</strong><br><br>
|
<strong>{{ reception.receptionType.label }}</strong><br><br>
|
||||||
|
|
||||||
<div class="bigtable-notes">
|
<div class="bigtable-notes">
|
||||||
{% set grossWeight = null %}
|
{% set grossWeight = null %}
|
||||||
{% set tareWeight = null %}
|
{% set tareWeight = null %}
|
||||||
{% for weight in reception.weights|default([]) %}
|
|
||||||
|
{% for weight in reception.weights %}
|
||||||
{% if weight.type == 'gross' %}
|
{% if weight.type == 'gross' %}
|
||||||
{% set grossWeight = weight %}
|
{% set grossWeight = weight %}
|
||||||
<p>Poids à plein : {{ grossWeight.weight }}kg (pesée n°{{ grossWeight.dsd }} {{ grossWeight.weighedAt|date('d/m/Y H:i:s') }})</p>
|
<p>Poids à plein : {{ grossWeight.weight }}kg (pesée n°{{ grossWeight.dsd }} {{ grossWeight.weighedAt|date('d/m/Y H:i:s') }})</p>
|
||||||
@@ -221,57 +219,45 @@
|
|||||||
<tr class="border-bottom">
|
<tr class="border-bottom">
|
||||||
<td>
|
<td>
|
||||||
<strong>
|
<strong>
|
||||||
Type de bovins
|
{% if reception.merchandiseType %}
|
||||||
|
{{ reception.merchandiseType.label }}
|
||||||
|
{% else %}
|
||||||
|
-
|
||||||
|
{% endif %}
|
||||||
</strong>
|
</strong>
|
||||||
<br><br>
|
<br><br>
|
||||||
|
|
||||||
<div class="bigtable-notes">
|
<div class="bigtable-notes">
|
||||||
{% if reception.receptionType and reception.receptionType.code == 'BOVINS' %}
|
{% if reception.merchandiseType and reception.merchandiseType.code == 'AUTRES' and reception.merchandiseDetail %}
|
||||||
{% if reception.bovinesTypes is not empty %}
|
<p><strong>Précision</strong> : {{ reception.merchandiseDetail }}</p>
|
||||||
{% for entry in reception.bovinesTypes %}
|
{% endif %}
|
||||||
<p>
|
|
||||||
{{ entry.bovineType ? entry.bovineType.label : '-' }} : {{ entry.quantity ?? 0 }}
|
|
||||||
</p>
|
|
||||||
{% endfor %}
|
|
||||||
{% else %}
|
|
||||||
<p>-</p>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if reception.bovineDetail %}
|
{% if reception.merchandiseType and reception.merchandiseType.code == 'GRANULE' %}
|
||||||
<p>Autres : {{ reception.bovineDetail }}</p>
|
{% set pelletGroups = {} %}
|
||||||
{% endif %}
|
{% for selection in reception.pelletBuildings %}
|
||||||
{% else %}
|
{% set pelletLabel = selection.pelletType.label %}
|
||||||
{% if reception.merchandiseType and reception.merchandiseType.code == 'AUTRES' and reception.merchandiseDetail %}
|
{% if pelletGroups[pelletLabel] is not defined %}
|
||||||
<p><strong>Précision</strong> : {{ reception.merchandiseDetail }}</p>
|
{% set pelletGroups = pelletGroups|merge({ (pelletLabel): [] }) %}
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if reception.merchandiseType and reception.merchandiseType.code == 'GRANULE' %}
|
|
||||||
{% set pelletGroups = {} %}
|
|
||||||
{% for selection in reception.pelletBuildings|default([]) %}
|
|
||||||
{% set pelletLabel = selection.pelletType.label %}
|
|
||||||
{% if pelletGroups[pelletLabel] is not defined %}
|
|
||||||
{% set pelletGroups = pelletGroups|merge({ (pelletLabel): [] }) %}
|
|
||||||
{% endif %}
|
|
||||||
{% set pelletGroups = pelletGroups|merge({
|
|
||||||
(pelletLabel): pelletGroups[pelletLabel]|merge([selection.building.label])
|
|
||||||
}) %}
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
{% for pelletLabel, buildingLabels in pelletGroups %}
|
|
||||||
<p><strong>{{ pelletLabel }}</strong> : {{ buildingLabels|join(', ') }}</p>
|
|
||||||
{% else %}
|
|
||||||
<p>Aucun dépôt de granulés renseigné.</p>
|
|
||||||
{% endfor %}
|
|
||||||
{% else %}
|
|
||||||
{% set buildingLabels = [] %}
|
|
||||||
{% for building in reception.buildings|default([]) %}
|
|
||||||
{% set buildingLabels = buildingLabels|merge([building.label]) %}
|
|
||||||
{% endfor %}
|
|
||||||
{% if buildingLabels %}
|
|
||||||
<p><strong>Ferme</strong> : {{ buildingLabels|join(', ') }}</p>
|
|
||||||
{% else %}
|
|
||||||
<p>Aucun bâtiment renseigné.</p>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% set pelletGroups = pelletGroups|merge({
|
||||||
|
(pelletLabel): pelletGroups[pelletLabel]|merge([selection.building.label])
|
||||||
|
}) %}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{% for pelletLabel, buildingLabels in pelletGroups %}
|
||||||
|
<p><strong>{{ pelletLabel }}</strong> : {{ buildingLabels|join(', ') }}</p>
|
||||||
|
{% else %}
|
||||||
|
<p>Aucun dépôt de granulés renseigné.</p>
|
||||||
|
{% endfor %}
|
||||||
|
{% else %}
|
||||||
|
{% set buildingLabels = [] %}
|
||||||
|
{% for building in reception.buildings %}
|
||||||
|
{% set buildingLabels = buildingLabels|merge([building.label]) %}
|
||||||
|
{% endfor %}
|
||||||
|
{% if buildingLabels %}
|
||||||
|
<p><strong>Ferme</strong> : {{ buildingLabels|join(', ') }}</p>
|
||||||
|
{% else %}
|
||||||
|
<p>Aucun bâtiment renseigné.</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
@@ -287,9 +273,9 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td style="width:60%; padding-right:8mm; vertical-align:top;">
|
<td style="width:60%; padding-right:8mm; vertical-align:top;">
|
||||||
<div class="meta">
|
<div class="meta">
|
||||||
Transporteur : <strong>{{ reception.carrier ? reception.carrier.name : '-' }}</strong><br>
|
Transporteur : <strong>{{ reception.carrier.name }}</strong><br>
|
||||||
Mode de livraison : <strong>{{ reception.truck ? reception.truck.name : '-' }}</strong><br>
|
Mode de livraison : <strong>{{ reception.truck.name }}</strong><br>
|
||||||
Immatriculation : <strong>{{ reception.licensePlate ?? '-' }}</strong><br><br>
|
Immatriculation : <strong>{{ reception.licensePlate }}</strong><br><br>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
|
|||||||
@@ -1,292 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html lang="fr">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
|
|
||||||
<style>
|
|
||||||
@page {
|
|
||||||
margin: 56px 56px;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
|
||||||
font-size: 13px;
|
|
||||||
margin: 0;
|
|
||||||
color: #000;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
em {
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
.company-block {
|
|
||||||
font-size: 14px;
|
|
||||||
text-align: left;
|
|
||||||
line-height: 1.3;
|
|
||||||
}
|
|
||||||
|
|
||||||
.box {
|
|
||||||
border: 1px solid #000;
|
|
||||||
border-radius: 10px;
|
|
||||||
padding: 10px;
|
|
||||||
font-size: 16px;
|
|
||||||
height: 80px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
text-align: center;
|
|
||||||
font-size: 18pt;
|
|
||||||
font-weight: 700;
|
|
||||||
margin: 64px 0 20px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-table {
|
|
||||||
margin-bottom: 24px;
|
|
||||||
width: 100%;
|
|
||||||
border-collapse: collapse;
|
|
||||||
table-layout: fixed;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-table th {
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
table {
|
|
||||||
width: 100%;
|
|
||||||
border-collapse: collapse;
|
|
||||||
}
|
|
||||||
|
|
||||||
th, td {
|
|
||||||
border: 1px solid #333;
|
|
||||||
padding: 4px 6px;
|
|
||||||
vertical-align: top;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
th {
|
|
||||||
text-align: center;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.layout, .layout td {
|
|
||||||
border: none !important;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bigtable-wrap {
|
|
||||||
border: 1px solid #000;
|
|
||||||
height: 360px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bigtable {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
border: none;
|
|
||||||
border-collapse: collapse;
|
|
||||||
table-layout: fixed;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bigtable th,
|
|
||||||
.bigtable td {
|
|
||||||
font-size: 16px;
|
|
||||||
border: 1px solid #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bigtable thead th {
|
|
||||||
border-top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bigtable tbody tr:last-child td {
|
|
||||||
border-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bigtable tr th:first-child,
|
|
||||||
.bigtable tr td:first-child {
|
|
||||||
border-left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bigtable tr th:last-child,
|
|
||||||
.bigtable tr td:last-child {
|
|
||||||
border-right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bigtable thead th {
|
|
||||||
border-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bigtable tbody tr:first-child td {
|
|
||||||
border-top: 1px solid #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bigtable-notes {
|
|
||||||
font-size: 14px;
|
|
||||||
line-height: 1.25;
|
|
||||||
}
|
|
||||||
|
|
||||||
.border-bottom {
|
|
||||||
border-bottom: 1px solid #000;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer-block {
|
|
||||||
page-break-inside: avoid;
|
|
||||||
}
|
|
||||||
|
|
||||||
.signature-box {
|
|
||||||
height: 130px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
border: 0.5px solid #000;
|
|
||||||
|
|
||||||
padding: 6px 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.meta {
|
|
||||||
font-size: 16px;
|
|
||||||
line-height: 1.35;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<!-- HEADER -->
|
|
||||||
<table class="layout" style="width:100%;">
|
|
||||||
<tr>
|
|
||||||
<td style="width:70%; vertical-align:top;">
|
|
||||||
<table class="layout" style="width:100%;">
|
|
||||||
<tr>
|
|
||||||
<td class="company-block" style="padding:0; border:none;">
|
|
||||||
<strong>SCEA LES NAUDS</strong><br>
|
|
||||||
14 Allée d’Argenson<br>
|
|
||||||
Z.I Nord – Secteur Est<br>
|
|
||||||
86100 CHATELLERAULT<br>
|
|
||||||
Tel. : 05 49 20 09 10<br>
|
|
||||||
Email : lpc.contacts@lpc-liot.fr<br>
|
|
||||||
RCS Châtellerault B 444 262 455
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<td style="width:30%; text-align:right; vertical-align:top; font-size: 14px;">
|
|
||||||
<div style="display:inline-block; width:75mm; line-height:1.3;">
|
|
||||||
<strong>{{ shipment.customer ? shipment.customer.label : '-' }}</strong><br>
|
|
||||||
<span>{{ shipment.address ? shipment.address.street : '' }}</span><br>
|
|
||||||
{% if shipment.address and shipment.address.street2 %}
|
|
||||||
<span>{{ shipment.address.street2 }}</span><br>
|
|
||||||
{% endif %}
|
|
||||||
{% if shipment.address %}
|
|
||||||
<span>{{ shipment.address.postalCode }} {{ shipment.address.city }}</span><br>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<div class="title">BON D'EXPEDITION</div>
|
|
||||||
|
|
||||||
<!-- INFOS (code/date/num) -->
|
|
||||||
<table class="info-table">
|
|
||||||
<tr>
|
|
||||||
<th style="width:55%; text-align:center;">Code client</th>
|
|
||||||
<th style="width:20%; text-align:center; white-space:nowrap;">Date</th>
|
|
||||||
<th style="width:25%; text-align:center; white-space:nowrap;">N° expédition</th>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td style="width:55%; text-align:center;">
|
|
||||||
{{ shipment.customer ? shipment.customer.code : '-' }}
|
|
||||||
</td>
|
|
||||||
<td style="width:20%; text-align:center; white-space:nowrap;">
|
|
||||||
{{ shipment.shipmentDate|date('d/m/Y') }}
|
|
||||||
</td>
|
|
||||||
<td style="width:25%; text-align:center; white-space:nowrap;">
|
|
||||||
{{ shipment.identificationNumber ?? '-' }}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
<!-- GRAND TABLEAU -->
|
|
||||||
<div class="bigtable-wrap">
|
|
||||||
<table class="bigtable">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th style="width:75%; text-align:center;">Désignation</th>
|
|
||||||
<th style="width:25%; text-align:center; white-space:nowrap;">Qté expédiée (kg)</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
|
|
||||||
<tbody>
|
|
||||||
{% set grossWeight = null %}
|
|
||||||
{% set tareWeight = null %}
|
|
||||||
<tr>
|
|
||||||
<td style="width:75%;">
|
|
||||||
<strong>Expédition</strong><br><br>
|
|
||||||
<div class="bigtable-notes">
|
|
||||||
{% for weight in shipment.weights %}
|
|
||||||
{% if weight.type == 'gross' %}
|
|
||||||
{% set grossWeight = weight %}
|
|
||||||
<p>Poids à plein : {{ grossWeight.weight }}kg (pesée
|
|
||||||
n°{{ grossWeight.dsd }} {{ grossWeight.weighedAt|date('d/m/Y H:i:s') }})</p>
|
|
||||||
{% elseif weight.type == 'tare' %}
|
|
||||||
{% set tareWeight = weight %}
|
|
||||||
<p>Poids à vide : {{ tareWeight.weight }}kg (pesée
|
|
||||||
n°{{ tareWeight.dsd }} {{ tareWeight.weighedAt|date('d/m/Y H:i:s') }})</p>
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td style="width:25%; text-align:center; white-space:nowrap;">
|
|
||||||
{% if grossWeight and tareWeight %}
|
|
||||||
{{ grossWeight.weight - tareWeight.weight }}
|
|
||||||
{% else %}
|
|
||||||
0
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr class="border-bottom">
|
|
||||||
<td>
|
|
||||||
<strong>Bovin</strong><br><br>
|
|
||||||
<div class="bigtable-notes">
|
|
||||||
{% if shipment.bovinShipments is not empty %}
|
|
||||||
{% for entry in shipment.bovinShipments %}
|
|
||||||
<p>
|
|
||||||
{{ entry.shipmentType ? entry.shipmentType.label : '-' }} :
|
|
||||||
{{ entry.nbBovinSend ?? 0 }}
|
|
||||||
</p>
|
|
||||||
{% endfor %}
|
|
||||||
{% else %}
|
|
||||||
<p>-</p>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</td>
|
|
||||||
<td style="width:25%; text-align:center; white-space:nowrap;">
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- BAS : meta à gauche / signatures à droite -->
|
|
||||||
<table class="layout footer-block">
|
|
||||||
<tr>
|
|
||||||
<td style="width:60%; padding-right:8mm; vertical-align:top;">
|
|
||||||
<div class="meta">
|
|
||||||
<p>Transporteur : {{ shipment.carrier ? shipment.carrier.name : '-' }}</p>
|
|
||||||
<p>Mode de livraison : {{ shipment.truck ? shipment.truck.name : '-' }}</p>
|
|
||||||
<p>Immatriculation : {{ shipment.licencePlate ?? '-' }}</p>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<td style="width:40%; vertical-align:top;">
|
|
||||||
<div class="box signature-box">Signature les Nauds :</div>
|
|
||||||
<div class="box signature-box">Signature transporteur :</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
Reference in New Issue
Block a user