Compare commits
11 Commits
v0.0.46
...
5f4139fde3
| Author | SHA1 | Date | |
|---|---|---|---|
| 5f4139fde3 | |||
| 0181d72144 | |||
| bffdc88701 | |||
| 42c5a3b2d2 | |||
| 28d5bc7599 | |||
| 69d047fca0 | |||
| ba4375f609 | |||
| e249d44e78 | |||
| e8189a4d04 | |||
| 081c2ef403 | |||
| 5fd2ab8470 |
10
.idea/data_source_mapping.xml
generated
Normal file
10
.idea/data_source_mapping.xml
generated
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DataSourcePerFileMappings">
|
||||
<file url="file://$APPLICATION_CONFIG_DIR$/consoles/db/f407a514-c6b4-4b26-9555-445a85892502/console.sql" value="f407a514-c6b4-4b26-9555-445a85892502" />
|
||||
<file url="file://$APPLICATION_CONFIG_DIR$/consoles/db/f407a514-c6b4-4b26-9555-445a85892502/console_1.sql" value="f407a514-c6b4-4b26-9555-445a85892502" />
|
||||
<file url="file://$APPLICATION_CONFIG_DIR$/consoles/db/f407a514-c6b4-4b26-9555-445a85892502/console_2.sql" value="f407a514-c6b4-4b26-9555-445a85892502" />
|
||||
<file url="file://$APPLICATION_CONFIG_DIR$/consoles/db/f407a514-c6b4-4b26-9555-445a85892502/console_3.sql" value="f407a514-c6b4-4b26-9555-445a85892502" />
|
||||
<file url="file://$APPLICATION_CONFIG_DIR$/consoles/db/f407a514-c6b4-4b26-9555-445a85892502/console_4.sql" value="f407a514-c6b4-4b26-9555-445a85892502" />
|
||||
</component>
|
||||
</project>
|
||||
216
.idea/workspace.xml
generated
216
.idea/workspace.xml
generated
@@ -4,10 +4,28 @@
|
||||
<option name="autoReloadType" value="SELECTIVE" />
|
||||
</component>
|
||||
<component name="ChangeListManager">
|
||||
<list default="true" id="7c107abe-5995-4428-8429-b146aaca8386" name="Changes" comment="fix : corrections diverses">
|
||||
<change beforePath="$PROJECT_DIR$/.idea/data_source_mapping.xml" beforeDir="false" />
|
||||
<list default="true" id="7c107abe-5995-4428-8429-b146aaca8386" name="Changes" comment="feat : Ajout de la sélection des bovins étape 3 d'une réception (WIP)">
|
||||
<change afterPath="$PROJECT_DIR$/frontend/components/shipment/shipment-form.vue" 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$/CHANGELOG.md" beforeDir="false" afterPath="$PROJECT_DIR$/CHANGELOG.md" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/config/reference.php" beforeDir="false" afterPath="$PROJECT_DIR$/config/reference.php" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/frontend/components/ui/UiNumberInput.vue" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/components/ui/UiNumberInput.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/i18n/locales/fr.json" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/i18n/locales/fr.json" 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$/frontend/services/reception.ts" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/services/reception.ts" 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>
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||
@@ -32,21 +50,17 @@
|
||||
<list>
|
||||
<option value="Vue Composition API Component" />
|
||||
<option value="TypeScript File" />
|
||||
<option value="PHP File" />
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
<component name="Git.Settings">
|
||||
<option name="RECENT_BRANCH_BY_REPOSITORY">
|
||||
<map>
|
||||
<entry key="$PROJECT_DIR$" value="feat/276-lister-expeditions-terminees" />
|
||||
<entry key="$PROJECT_DIR$" value="fix/makefile" />
|
||||
</map>
|
||||
</option>
|
||||
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
||||
</component>
|
||||
<component name="HighlightingSettingsPerFile">
|
||||
<setting file="file://$PROJECT_DIR$/frontend/pages/admin/supplier/supplier-list.vue" root0="FORCE_HIGHLIGHTING" />
|
||||
</component>
|
||||
<component name="McpProjectServerCommands">
|
||||
<commands />
|
||||
<urls />
|
||||
@@ -223,42 +237,37 @@
|
||||
<option name="hideEmptyMiddlePackages" value="true" />
|
||||
<option name="showLibraryContents" value="true" />
|
||||
</component>
|
||||
<component name="PropertiesComponent">{
|
||||
"keyToString": {
|
||||
"RunOnceActivity.MCP Project settings loaded": "true",
|
||||
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||
"RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true",
|
||||
"RunOnceActivity.git.unshallow": "true",
|
||||
"RunOnceActivity.typescript.service.memoryLimit.init": "true",
|
||||
"git-widget-placeholder": "fix/325-corrections-diverses",
|
||||
"last_opened_file_path": "//wsl.localhost/Ubuntu-24.04/home/kevin/Stage/Ferme/frontend/pages/shipment",
|
||||
"node.js.detected.package.eslint": "true",
|
||||
"node.js.detected.package.tslint": "true",
|
||||
"node.js.selected.package.eslint": "(autodetect)",
|
||||
"node.js.selected.package.tslint": "(autodetect)",
|
||||
"nodejs_package_manager_path": "npm",
|
||||
"settings.editor.selected.configurable": "preferences.pluginManager",
|
||||
"ts.external.directory.path": "/opt/phpstorm/plugins/javascript-plugin/jsLanguageServicesImpl/external",
|
||||
"vue.rearranger.settings.migration": "true"
|
||||
<component name="PropertiesComponent"><![CDATA[{
|
||||
"keyToString": {
|
||||
"RunOnceActivity.MCP Project settings loaded": "true",
|
||||
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||
"RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true",
|
||||
"RunOnceActivity.git.unshallow": "true",
|
||||
"RunOnceActivity.typescript.service.memoryLimit.init": "true",
|
||||
"git-widget-placeholder": "feat/271-expedition-etape-1",
|
||||
"last_opened_file_path": "/home/sroy/Documents/test/Ferme",
|
||||
"node.js.detected.package.eslint": "true",
|
||||
"node.js.detected.package.tslint": "true",
|
||||
"node.js.selected.package.eslint": "(autodetect)",
|
||||
"node.js.selected.package.tslint": "(autodetect)",
|
||||
"nodejs_package_manager_path": "npm",
|
||||
"settings.editor.selected.configurable": "configurable.tailwindcss",
|
||||
"ts.external.directory.path": "/opt/phpstorm/plugins/javascript-plugin/jsLanguageServicesImpl/external",
|
||||
"vue.rearranger.settings.migration": "true"
|
||||
},
|
||||
"keyToStringList": {
|
||||
"DatabaseDriversLRU": [
|
||||
"postgresql"
|
||||
"keyToStringList": {
|
||||
"DatabaseDriversLRU": [
|
||||
"postgresql"
|
||||
],
|
||||
"com.intellij.ide.scratch.ScratchImplUtil$2/New Scratch File": [
|
||||
"TEXT"
|
||||
"com.intellij.ide.scratch.ScratchImplUtil$2/New Scratch File": [
|
||||
"TEXT"
|
||||
],
|
||||
"vue.recent.templates": [
|
||||
"Vue Composition API Component"
|
||||
"vue.recent.templates": [
|
||||
"Vue Composition API Component"
|
||||
]
|
||||
}
|
||||
}</component>
|
||||
}]]></component>
|
||||
<component name="RecentsManager">
|
||||
<key name="CopyFile.RECENT_KEYS">
|
||||
<recent name="\\wsl.localhost\Ubuntu-24.04\home\kevin\Stage\Ferme\frontend\pages\shipment" />
|
||||
<recent name="\\wsl.localhost\Ubuntu-24.04\home\kevin\Stage\Ferme\frontend\composables" />
|
||||
<recent name="\\wsl.localhost\Ubuntu-24.04\home\kevin\Stage\Ferme\frontend\components\shipment" />
|
||||
</key>
|
||||
<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\tristan\workspace\ferme\templates" />
|
||||
@@ -304,10 +313,54 @@
|
||||
<workItem from="1770195718952" duration="215000" />
|
||||
<workItem from="1770195959162" duration="18915000" />
|
||||
<workItem from="1770274844804" duration="3940000" />
|
||||
<workItem from="1770798536017" duration="20774000" />
|
||||
<workItem from="1770879701502" duration="25805000" />
|
||||
<workItem from="1770966186589" duration="914000" />
|
||||
<workItem from="1770967274060" duration="2388000" />
|
||||
</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" />
|
||||
@@ -653,55 +706,7 @@
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1770217875423</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00050" summary="feat : creer une nouvelle expedtion (WIP)">
|
||||
<option name="closed" value="true" />
|
||||
<created>1770736570645</created>
|
||||
<option name="number" value="00050" />
|
||||
<option name="presentableId" value="LOCAL-00050" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1770736570645</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00051" summary="feat : ajout d'une page de creation d'une expedition">
|
||||
<option name="closed" value="true" />
|
||||
<created>1770880791564</created>
|
||||
<option name="number" value="00051" />
|
||||
<option name="presentableId" value="LOCAL-00051" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1770880791565</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00052" summary="feat : changelog">
|
||||
<option name="closed" value="true" />
|
||||
<created>1770881437439</created>
|
||||
<option name="number" value="00052" />
|
||||
<option name="presentableId" value="LOCAL-00052" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1770881437439</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00053" summary="feat : lister les expeditions terminees">
|
||||
<option name="closed" value="true" />
|
||||
<created>1770883114609</created>
|
||||
<option name="number" value="00053" />
|
||||
<option name="presentableId" value="LOCAL-00053" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1770883114609</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00054" summary="feat : lister les expeditions terminees">
|
||||
<option name="closed" value="true" />
|
||||
<created>1770884154297</created>
|
||||
<option name="number" value="00054" />
|
||||
<option name="presentableId" value="LOCAL-00054" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1770884154297</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00055" summary="fix : corrections diverses">
|
||||
<option name="closed" value="true" />
|
||||
<created>1770969471135</created>
|
||||
<option name="number" value="00055" />
|
||||
<option name="presentableId" value="LOCAL-00055" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1770969471135</updated>
|
||||
</task>
|
||||
<option name="localTasksCounter" value="56" />
|
||||
<option name="localTasksCounter" value="50" />
|
||||
<servers />
|
||||
</component>
|
||||
<component name="TypeScriptGeneratedFilesManager">
|
||||
@@ -751,6 +756,12 @@
|
||||
</option>
|
||||
</component>
|
||||
<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 : 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" />
|
||||
@@ -770,13 +781,7 @@
|
||||
<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 : Ajout de la sélection des bovins étape 3 d'une réception (WIP)" />
|
||||
<MESSAGE value="feat : creer une nouvelle expedtion (WIP)" />
|
||||
<MESSAGE value="feat : ajout d'une page de creation d'une expedition" />
|
||||
<MESSAGE value="feat : changelog" />
|
||||
<MESSAGE value="feat : lister les expeditions terminees" />
|
||||
<MESSAGE value="fix: corrections diverses" />
|
||||
<MESSAGE value="fix : corrections diverses" />
|
||||
<option name="LAST_COMMIT_MESSAGE" value="fix : corrections diverses" />
|
||||
<option name="LAST_COMMIT_MESSAGE" value="feat : Ajout de la sélection des bovins étape 3 d'une réception (WIP)" />
|
||||
</component>
|
||||
<component name="XDebuggerManager">
|
||||
<breakpoint-manager>
|
||||
@@ -786,19 +791,10 @@
|
||||
<line>6</line>
|
||||
<option name="timeStamp" value="3" />
|
||||
</line-breakpoint>
|
||||
<line-breakpoint enabled="true" type="php">
|
||||
<url>file://$PROJECT_DIR$/src/Entity/Shipment.php</url>
|
||||
<line>6</line>
|
||||
<option name="timeStamp" value="45" />
|
||||
</line-breakpoint>
|
||||
<line-breakpoint enabled="true" type="javascript">
|
||||
<url>file://$PROJECT_DIR$/frontend/services/dto/shipment-data.ts</url>
|
||||
<option name="timeStamp" value="43" />
|
||||
</line-breakpoint>
|
||||
<line-breakpoint enabled="true" type="javascript">
|
||||
<url>file://$PROJECT_DIR$/frontend/layouts/default.vue</url>
|
||||
<line>72</line>
|
||||
<option name="timeStamp" value="48" />
|
||||
<url>file://$PROJECT_DIR$/frontend/services/shipment.ts</url>
|
||||
<properties lambdaOrdinal="-1" />
|
||||
<option name="timeStamp" value="37" />
|
||||
</line-breakpoint>
|
||||
</breakpoints>
|
||||
</breakpoint-manager>
|
||||
|
||||
@@ -34,18 +34,9 @@ Ajouter dans le fichier .env du frontend
|
||||
* [#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
|
||||
* [#326] Admin modification creation client
|
||||
* [#325] Correction diverses
|
||||
|
||||
### Changed
|
||||
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
parameters:
|
||||
app.version: '0.0.46'
|
||||
app.version: '0.0.37'
|
||||
|
||||
@@ -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>
|
||||
@@ -29,7 +29,7 @@
|
||||
<button
|
||||
class="text-xl uppercase bg-primary-500 text-white h-[50px] w-[272px]"
|
||||
@click="goNext"
|
||||
>Valider
|
||||
>Peser
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -119,11 +119,10 @@
|
||||
<button
|
||||
type="submit"
|
||||
class="text-xl uppercase bg-primary-500 text-white h-[50px] w-[272px] justify-self-end"
|
||||
>Valider
|
||||
>Peser
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@@ -342,7 +341,7 @@ onMounted(async () => {
|
||||
|
||||
// Ajuste driver/vehicle quand le transporteur change (logique LIOT)
|
||||
watch(
|
||||
() => [form.supplierId, form.addressId, suppliers.value],
|
||||
() => [form.supplierId, suppliers.value],
|
||||
() => {
|
||||
if (!form.supplierId) {
|
||||
form.addressId = ''
|
||||
@@ -359,11 +358,7 @@ watch(
|
||||
(address) => String(address.id) === form.addressId
|
||||
)
|
||||
if (!matches) {
|
||||
if (supplierAddresses.value.length === 1) {
|
||||
form.addressId = String(supplierAddresses.value[0].id)
|
||||
} else {
|
||||
form.addressId = ''
|
||||
}
|
||||
form.addressId = ''
|
||||
}
|
||||
},
|
||||
{immediate: true}
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
<button
|
||||
class="text-xl uppercase bg-primary-500 text-white h-[50px] w-[272px]"
|
||||
@click="goNext"
|
||||
>Valider
|
||||
>Peser
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
v-if="displayWeight !== null && !showGenerateReceipt"
|
||||
class="text-xl uppercase bg-primary-500 text-white h-[50px] w-[272px] ml-4"
|
||||
@click="saveWeight"
|
||||
>Valider</button>
|
||||
>Valider la pesée</button>
|
||||
<button
|
||||
v-if="showGenerateReceipt"
|
||||
class="text-xl uppercase bg-primary-500 text-white h-[50px] w-[272px] ml-4"
|
||||
@@ -36,7 +36,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {computed, onMounted} from 'vue'
|
||||
import { computed } from 'vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useWeighing } from '~/composables/useWeighing'
|
||||
import { usePdfPrinter } from '~/composables/usePdfPrinter'
|
||||
@@ -94,7 +94,7 @@ const printReceipt = async () => {
|
||||
|
||||
// Récupère le poids dès l'arrivée sur l'écran
|
||||
onMounted(() => {
|
||||
if (displayWeight.value === null) {
|
||||
if (false === displayWeight.value) {
|
||||
fetchWeight()
|
||||
}
|
||||
})
|
||||
|
||||
@@ -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,258 +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
|
||||
v-if="auth.isAdmin"
|
||||
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,120 +0,0 @@
|
||||
<template>
|
||||
<form @submit.prevent="validate">
|
||||
<div class="grid grid-cols-2 gap-x-40 gap-y-8 mb-8">
|
||||
<UiTextInput
|
||||
label="Dsd"
|
||||
class="col-start-2"
|
||||
v-model="sharedWeightMeta.dsd"
|
||||
:disabled="!auth.isAdmin"
|
||||
/>
|
||||
<UiDateInput
|
||||
label="Date pesée"
|
||||
v-model="sharedWeightMeta.weighedAt"
|
||||
:disabled="!auth.isAdmin"
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-x-40 mb-16">
|
||||
<UiNumberInput
|
||||
v-for="weight in form.weights"
|
||||
:key="weight.type"
|
||||
:label="getWeightLabel(weight.type)"
|
||||
labelClass="font-bold uppercase text-xl"
|
||||
v-model="weight.weight"
|
||||
:wrapper-class="weight.type === 'tare' ? 'col-start-1 row-start-1' : 'col-start-2 row-start-1'"
|
||||
:disabled="!auth.isAdmin"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-center">
|
||||
<button
|
||||
v-if="auth.isAdmin"
|
||||
type="submit"
|
||||
class="text-xl uppercase bg-primary-500 text-white h-[50px] w-[272px]"
|
||||
|
||||
>
|
||||
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, dsd: null, weighedAt: null},
|
||||
{id: 0, type: 'gross' as const, weight: 0, dsd: null, weighedAt: null}
|
||||
]
|
||||
})
|
||||
// DSD et date de pesée sont partagés entre tare et gross dans l'UI.
|
||||
const sharedWeightMeta = reactive<{
|
||||
dsd: number | string | null
|
||||
weighedAt: string | null
|
||||
}>({
|
||||
dsd: null,
|
||||
weighedAt: null
|
||||
})
|
||||
|
||||
const getWeightLabel = (type: 'tare' | 'gross'): string => {
|
||||
return type === 'tare' ? 'Pesée à vide' : 'Pesée à plein'
|
||||
}
|
||||
|
||||
const hydrateFromReception = (reception: ReceptionFormWeight) => {
|
||||
// On hydrate chaque ligne par son type (tare/gross), sans dépendre d'un index.
|
||||
for (const receptionWeight of reception.weights) {
|
||||
const formWeight = form.weights.find(weight => weight.type === receptionWeight.type)
|
||||
if (formWeight) {
|
||||
Object.assign(formWeight, receptionWeight)
|
||||
}
|
||||
}
|
||||
|
||||
// On récupère une valeur existante pour préremplir les champs partagés.
|
||||
const weightWithMeta = reception.weights.find(weight =>
|
||||
(weight.dsd !== null && weight.dsd !== undefined)
|
||||
|| (weight.weighedAt !== null && weight.weighedAt !== undefined && weight.weighedAt !== '')
|
||||
)
|
||||
|
||||
if (weightWithMeta) {
|
||||
sharedWeightMeta.dsd = weightWithMeta.dsd ?? null
|
||||
sharedWeightMeta.weighedAt = weightWithMeta.weighedAt ?? null
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
const reception = await getReception(idReception)
|
||||
hydrateFromReception(reception)
|
||||
})
|
||||
|
||||
async function validate() {
|
||||
const sharedDsd =
|
||||
sharedWeightMeta.dsd === null || sharedWeightMeta.dsd === undefined || sharedWeightMeta.dsd === ''
|
||||
? null
|
||||
: Number(sharedWeightMeta.dsd)
|
||||
const sharedWeighedAt =
|
||||
sharedWeightMeta.weighedAt === null || sharedWeightMeta.weighedAt === undefined || sharedWeightMeta.weighedAt === ''
|
||||
? null
|
||||
: sharedWeightMeta.weighedAt
|
||||
for (const weight of form.weights) {
|
||||
if (weight.id) {
|
||||
await updateWeight(weight.id, {
|
||||
weight: weight.weight,
|
||||
dsd: Number.isFinite(sharedDsd) ? sharedDsd : null,
|
||||
weighedAt: sharedWeighedAt
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
@@ -23,14 +23,14 @@
|
||||
/>
|
||||
<!-- Type d'expédition -->
|
||||
<div class="col-start-1 row-start-4">
|
||||
<label class="font-bold uppercase text-xl mb-2">
|
||||
<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-2 flex flex-row gap-6"
|
||||
class="mt-8 flex flex-row gap-6"
|
||||
>
|
||||
<UiNumberInput
|
||||
:label="type.label"
|
||||
@@ -49,7 +49,7 @@
|
||||
label="Client"
|
||||
:options="customers.map((customer) => ({
|
||||
value: String(customer.id),
|
||||
label: customer.name || `Client #${customer.id}`
|
||||
label: customer.label
|
||||
}))"
|
||||
:loading="isLoadingCustomers"
|
||||
wrapper-class="col-start-1 row-start-5"
|
||||
@@ -344,7 +344,7 @@ watch(
|
||||
)
|
||||
// Ajuste driver/vehicle quand le transporteur change (logique LIOT)
|
||||
watch(
|
||||
() => [form.customerId, form.addressId, customers.value],
|
||||
() => [form.customerId, customers.value],
|
||||
() => {
|
||||
if (!form.customerId) {
|
||||
form.addressId = ''
|
||||
@@ -361,11 +361,7 @@ watch(
|
||||
(address) => String(address.id) === form.addressId
|
||||
)
|
||||
if (!matches) {
|
||||
if (customerAddresses.value.length === 1) {
|
||||
form.addressId = String(customerAddresses.value[0].id)
|
||||
} else {
|
||||
form.addressId = ''
|
||||
}
|
||||
form.addressId = ''
|
||||
}
|
||||
},
|
||||
{immediate: true}
|
||||
|
||||
@@ -88,8 +88,8 @@ const printReceipt = async () => {
|
||||
return
|
||||
}
|
||||
|
||||
shipmentStore.clearCurrent()
|
||||
await router.push('/')
|
||||
/* shipmentStore.clearCurrent()
|
||||
await router.push('/')*/
|
||||
}
|
||||
|
||||
// Récupère le poids dès l'arrivée sur l'écran
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<label
|
||||
v-if="label"
|
||||
:for="id"
|
||||
class="text-xl flex items-center"
|
||||
class="text-xl text-bold flex items-center"
|
||||
:class="labelClass"
|
||||
>
|
||||
<span
|
||||
@@ -25,7 +25,7 @@
|
||||
:step="step"
|
||||
:disabled="disabled"
|
||||
v-bind="attrs"
|
||||
class="border-b border-black text-xl bg-transparent w-16"
|
||||
class="border-b border-black text-xl bg-transparent w-12"
|
||||
:class="[
|
||||
isEmpty ? 'text-neutral-400' : 'text-black',
|
||||
disabled ? 'cursor-not-allowed' : 'cursor-text',
|
||||
|
||||
@@ -1,22 +1,34 @@
|
||||
import type {Ref} from 'vue'
|
||||
import {computed, ref} from 'vue'
|
||||
import type {ReceptionData, ReceptionPayload, WeightEntryData} from '~/services/dto/reception-data'
|
||||
import type {ShipmentData, ShipmentPayload, WeightShipmentEntryData } from '~/services/dto/shipment-data'
|
||||
import type {WeightData} from '~/services/dto/weight-data'
|
||||
import {getWeight} from '~/services/reception'
|
||||
import {getWeightShipment} from '~/services/shipment'
|
||||
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'
|
||||
|
||||
type UseWeighingOptions = {
|
||||
mode: WeighingMode
|
||||
reception: Ref<ReceptionData | null>
|
||||
updateReception: (id: number, payload: ReceptionPayload) => Promise<ReceptionData | null>
|
||||
loadReception?: (id: number) => Promise<ReceptionData | null>
|
||||
}
|
||||
|
||||
type UseWeighingShipmentOptions = {
|
||||
modeShipment: WeighingMode
|
||||
shipment: Ref<ShipmentData | null>
|
||||
updateShipment: (id: number, payload: ShipmentPayload) => Promise<ShipmentData | null>
|
||||
loadShipment?: (id: number) => Promise<ShipmentData | null>
|
||||
}
|
||||
|
||||
export const useWeighing = ({
|
||||
mode,
|
||||
reception,
|
||||
updateReception,
|
||||
loadReception
|
||||
}: UseWeighingOptions) => {
|
||||
mode,
|
||||
reception,
|
||||
updateReception,
|
||||
loadReception
|
||||
}: UseWeighingOptions) => {
|
||||
const weightData = ref<WeightData | null>(null)
|
||||
const isFetching = ref(false)
|
||||
|
||||
@@ -96,11 +108,11 @@ export const useWeighing = ({
|
||||
}
|
||||
|
||||
export const useWeighingShipment = ({
|
||||
modeShipment,
|
||||
shipment,
|
||||
updateShipment,
|
||||
loadShipment
|
||||
}: UseWeighingShipmentOptions) => {
|
||||
modeShipment,
|
||||
shipment,
|
||||
updateShipment,
|
||||
loadShipment
|
||||
}: UseWeighingShipmentOptions) => {
|
||||
const weightData = ref<WeightData | null>(null)
|
||||
const isFetching = ref(false)
|
||||
|
||||
|
||||
@@ -12,10 +12,7 @@
|
||||
"fetch": "Impossible de récupérer la réception.",
|
||||
"create": "Impossible de créer la réception.",
|
||||
"update": "Impossible de mettre à jour la réception.",
|
||||
"weight": "Impossible de récupérer la pesée."
|
||||
},
|
||||
"weight": {
|
||||
"update": "Impossible de mettre à jour la pesée"
|
||||
"weigh": "Impossible de récupérer la pesée."
|
||||
},
|
||||
"shipment": {
|
||||
"list": "Impossible de récupérer la liste des éxpeditions.",
|
||||
@@ -56,27 +53,7 @@
|
||||
"delete": "Impossible de supprimer le bovin."
|
||||
},
|
||||
"supplier": {
|
||||
"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."
|
||||
"list": "Impossible de récupérer la liste des fournisseurs."
|
||||
},
|
||||
"truck": {
|
||||
"list": "Impossible de récupérer la liste des camions."
|
||||
@@ -111,18 +88,6 @@
|
||||
"shipment": {
|
||||
"update": "Éxpedition mise à jour avec succès."
|
||||
},
|
||||
"supplier": {
|
||||
"create": "Fournisseur créé avec succès.",
|
||||
"update": "Fournisseur mis à jour avec succès."
|
||||
},
|
||||
"customer": {
|
||||
"create": "Client créé avec succès.",
|
||||
"update": "Client mis à jour avec succès."
|
||||
},
|
||||
"address": {
|
||||
"create": "Adresse créée avec succès.",
|
||||
"update": "Adresse mise à jour avec succès."
|
||||
},
|
||||
"auth": {
|
||||
"update": "Utilisateur mis à jour avec succès.",
|
||||
"create": "Utilisateur créé avec succès.",
|
||||
@@ -132,9 +97,6 @@
|
||||
"carrier": {
|
||||
"update": "Transporteur mis à jour",
|
||||
"create": "Transporteur créé"
|
||||
},
|
||||
"weight": {
|
||||
"update": "Pesée mis à jour"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,64 +24,34 @@
|
||||
<aside class="bg-primary-500 text-white min-h-0 flex flex-col justify-between">
|
||||
<div class="flex flex-col gap-4 p-4 font-bold text-xl">
|
||||
<!-- Liste des liens à ajouter ci-dessous -->
|
||||
<NuxtLink
|
||||
to="/admin/dashboard"
|
||||
custom v-slot="{ href, navigate, isExactActive }">
|
||||
<a :href="href"
|
||||
@click="navigate"
|
||||
:class="isExactActive ? 'opacity-100' : 'opacity-50'">
|
||||
Tableau de bord
|
||||
</a>
|
||||
<NuxtLink to="/admin/dashboard">
|
||||
Tableau de bord
|
||||
</NuxtLink>
|
||||
<NuxtLink
|
||||
to="/admin/supplier/supplier-list"
|
||||
custom v-slot="{ href, navigate }">
|
||||
<a :href="href"
|
||||
@click="navigate"
|
||||
:class="route.path.startsWith('/admin/supplier') ? 'opacity-100' : 'opacity-50'">
|
||||
Fournisseur
|
||||
</a>
|
||||
<NuxtLink to="/admin/supplier/supplier-list">
|
||||
Fournisseur
|
||||
</NuxtLink>
|
||||
<NuxtLink
|
||||
to="/admin/carrier/carrier-list"
|
||||
custom v-slot="{ href, navigate }">
|
||||
<a :href="href"
|
||||
@click="navigate"
|
||||
:class="route.path.startsWith('/admin/carrier') ? 'opacity-100' : 'opacity-50'">
|
||||
Transporteur
|
||||
</a>
|
||||
<NuxtLink to="/admin/carrier/carrier-list">
|
||||
Transporteur
|
||||
</NuxtLink>
|
||||
<NuxtLink to="/admin/user/list" custom v-slot="{ href, navigate }">
|
||||
<a
|
||||
:href="href"
|
||||
@click="navigate"
|
||||
:class="route.path.startsWith('/admin/user') ? 'opacity-100' : 'opacity-50'"
|
||||
>
|
||||
Utilisateurs
|
||||
</a>
|
||||
</NuxtLink>
|
||||
<NuxtLink to="/admin/customer/customer-list">
|
||||
Client
|
||||
<NuxtLink to="/admin/user/list">
|
||||
Utilisateurs
|
||||
</NuxtLink>
|
||||
</div>
|
||||
|
||||
<div class="p-4">
|
||||
|
||||
<p class="font-bold text-white text-left">v{{ version }}</p>
|
||||
<button
|
||||
@click="handleLogout"
|
||||
class="w-full bg-red-600 hover:bg-red-700 py-2 rounded font-bold"
|
||||
>
|
||||
Déconnexion
|
||||
</button>
|
||||
<p class="font-bold text-white text-center pt-2">
|
||||
v{{ version }}
|
||||
</p>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<main class="min-h-0 overflow-auto px-12 py-12 ">
|
||||
<div class="w-full ">
|
||||
<slot/>
|
||||
<slot />
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
@@ -93,9 +63,7 @@
|
||||
import {useAuthStore} from '~/stores/auth'
|
||||
|
||||
const auth = useAuthStore()
|
||||
const {version} = useAppVersion()
|
||||
|
||||
const route = useRoute()
|
||||
const { version } = useAppVersion()
|
||||
const handleLogout = async () => {
|
||||
try {
|
||||
await auth.logout()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="min-h-screen text-neutral-900 grid grid-rows-[85px,1fr]">
|
||||
<header class="w-full border-b border-neutral-200 bg-primary-500">
|
||||
<div class="min-h-screen bg-white text-neutral-900">
|
||||
<header class="w-full border-b border-neutral-200 bg-primary-500">
|
||||
<div class="flex w-full items-center justify-center px-6 py-4">
|
||||
<button
|
||||
type="button"
|
||||
@@ -21,13 +21,12 @@
|
||||
</a>
|
||||
</NuxtLink>
|
||||
<NuxtLink
|
||||
to="/admin/dashboard" custom v-slot="{ href, navigate, isExactActive }"
|
||||
to="/admin/dashboard" custom v-slot="{ href, navigate, isActive }"
|
||||
v-if="auth.isAdmin"
|
||||
>
|
||||
<a
|
||||
:href="href"
|
||||
@click="navigate"
|
||||
:class="isExactActive ? 'opacity-100' : 'opacity-50'"
|
||||
>
|
||||
Admin
|
||||
</a>
|
||||
@@ -101,7 +100,7 @@
|
||||
</aside>
|
||||
</transition>
|
||||
</header>
|
||||
<main class="mx-auto w-full max-w-[1280px]">
|
||||
<main class="mx-auto w-full max-w-[1280px] pb-0">
|
||||
<slot/>
|
||||
</main>
|
||||
<footer class="w-full mt-8 bg-primary-500 p-6">
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
</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"
|
||||
|
||||
@@ -1,197 +0,0 @@
|
||||
<template>
|
||||
<form @submit.prevent="validate">
|
||||
<div class="flex items-center justify-between">
|
||||
<h1 class="text-3xl font-bold uppercase">
|
||||
{{ customerId ? "Modifications du client" : "Ajout d'un client" }}
|
||||
</h1>
|
||||
|
||||
<button
|
||||
class="text-xl uppercase bg-primary-500 text-white h-[50px] w-[272px]"
|
||||
type="submit"
|
||||
:disabled="isLoading || !auth.isAdmin"
|
||||
>
|
||||
{{ customerId ? "Sauvegarder" : "Ajouter" }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-y-8 gap-x-80 mb-10 py-12">
|
||||
<UiTextInput id="customer-name" v-model="form.name" label="Nom du client" :disabled="!auth.isAdmin"/>
|
||||
<UiTextInput id="customer-phone" v-model="form.phone" label="Téléphone" :disabled="!auth.isAdmin"/>
|
||||
<UiTextInput id="customer-email" v-model="form.email" label="Email" :disabled="!auth.isAdmin"/>
|
||||
</div>
|
||||
|
||||
<div class="mx-24 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 client</h2>
|
||||
<button
|
||||
type="button"
|
||||
class="text-xl uppercase bg-primary-500 text-white h-[50px] w-[272px]"
|
||||
:disabled="customerId === 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 {createCustomer, getCustomer, updateCustomer} from "~/services/customer"
|
||||
import type {CustomerData, CustomerFormData, CustomerPayload} from "~/services/dto/customer-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 customerId = computed(() => resolveId(route.params.id))
|
||||
const isLoading = ref(false)
|
||||
const form = reactive<CustomerFormData>({
|
||||
name: "",
|
||||
phone: "",
|
||||
email: "",
|
||||
addresses: [],
|
||||
})
|
||||
|
||||
const goToAddAddress = () => {
|
||||
if (customerId.value === null || !auth.isAdmin) return
|
||||
router.push({
|
||||
path: "/admin/customer/address",
|
||||
query: {
|
||||
customerId: String(customerId.value),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const goToEditAddress = (addressId: number | null) => {
|
||||
if (customerId.value === null || addressId === null || !auth.isAdmin) return
|
||||
router.push({
|
||||
path: "/admin/customer/address",
|
||||
query: {
|
||||
customerId: String(customerId.value),
|
||||
addressId: String(addressId),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const hydrateFromCustomer = (customer: CustomerData | null) => {
|
||||
if (!customer) return
|
||||
form.name = customer.name ?? ""
|
||||
form.phone = customer.phone ?? ""
|
||||
form.email = customer.email ?? ""
|
||||
if (!Array.isArray(customer.addresses) || customer.addresses.length === 0) {
|
||||
form.addresses = []
|
||||
return
|
||||
}
|
||||
if (typeof customer.addresses[0] === "string") {
|
||||
form.addresses = []
|
||||
return
|
||||
}
|
||||
|
||||
form.addresses = customer.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(
|
||||
() => customerId.value,
|
||||
async (id) => {
|
||||
if (id === null) return
|
||||
isLoading.value = true
|
||||
try {
|
||||
const customer = await getCustomer(id)
|
||||
hydrateFromCustomer(customer)
|
||||
} 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 phone = form.phone?.trim() || null
|
||||
const email = form.email?.trim() || null
|
||||
|
||||
const customerPayload: CustomerPayload = {
|
||||
name,
|
||||
phone,
|
||||
email,
|
||||
}
|
||||
let targetId: number | null = null
|
||||
|
||||
if (customerId.value !== null) {
|
||||
await updateCustomer(customerId.value, customerPayload)
|
||||
targetId = customerId.value
|
||||
} else {
|
||||
const created = await createCustomer(customerPayload)
|
||||
targetId = created.id
|
||||
}
|
||||
|
||||
if (targetId !== null) {
|
||||
await router.push(`/admin/customer/${targetId}`)
|
||||
}
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,49 +0,0 @@
|
||||
<template>
|
||||
<Address type="customer" :address="address" @validate="validate"/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { AddressData, AddressPayload } from "~/services/address"
|
||||
import { createAddress, getAddress, updateAddress } from "~/services/address"
|
||||
import { getCustomer, updateCustomer } from "~/services/customer"
|
||||
import type { CustomerData } from "~/services/dto/customer-data"
|
||||
|
||||
definePageMeta({ layout: "admin" })
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const customerId = computed(() => Number(route.query.customerId))
|
||||
const customer = ref<CustomerData | null>(null)
|
||||
const addressId = computed(() => (route.query.addressId !== undefined ? Number(route.query.addressId) : null))
|
||||
const address = ref<AddressData | null>(null)
|
||||
|
||||
const validate = async (payload: AddressPayload) => {
|
||||
try {
|
||||
if (addressId.value !== null) {
|
||||
await updateAddress(addressId.value, payload)
|
||||
} else {
|
||||
await addAddress(payload)
|
||||
}
|
||||
} finally {
|
||||
await router.push("/admin/customer/" + customerId.value)
|
||||
}
|
||||
}
|
||||
|
||||
const addAddress = async (payload: AddressPayload) => {
|
||||
const response: AddressData = await createAddress(payload)
|
||||
const addressIRI = `/api/addresses/${response.id}`
|
||||
const existingIris = (customer.value?.addresses ?? [])
|
||||
.map((item: any) => (typeof item === "string" ? item : `/api/addresses/${item.id}`))
|
||||
.filter((iri: string | null) => Boolean(iri)) as string[]
|
||||
const next = [...new Set([...existingIris, addressIRI])]
|
||||
|
||||
return await updateCustomer(customerId.value, { addresses: next })
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
customer.value = await getCustomer(customerId.value)
|
||||
if (addressId.value !== null) {
|
||||
address.value = await getAddress(addressId.value)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@@ -1,116 +0,0 @@
|
||||
<template>
|
||||
<div class="flex items-center justify-between">
|
||||
<h1 class="text-3xl font-bold uppercase">Liste des Clients</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-8 gap-4 bg-slate-100 px-4 py-3 text-sm font-semibold uppercase tracking-wide"
|
||||
>
|
||||
<div>Nom</div>
|
||||
<div>Téléphone</div>
|
||||
<div>Email</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 client.
|
||||
</div>
|
||||
|
||||
<div v-for="customer in customerList" :key="customer.id">
|
||||
<div
|
||||
v-if="!customer.addresses || customer.addresses.length === 0"
|
||||
class="grid grid-cols-8 border-t gap-4 px-4 py-2 hover:bg-slate-50 cursor-pointer"
|
||||
@click="goToCustomer(customer.id)"
|
||||
>
|
||||
<div class="truncate">{{ customer.name || "—" }}</div>
|
||||
<div class="truncate">{{ customer.phone || "—" }}</div>
|
||||
<div class="truncate">{{ customer.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="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-8 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.name || "—") : "↳" }}
|
||||
</div>
|
||||
<div class="truncate">{{ idx === 0 ? (customer.phone || "—") : "" }}</div>
|
||||
<div class="truncate">{{ idx === 0 ? (customer.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-8 hover:bg-slate-50 border-t gap-4 px-4 py-2 cursor-pointer"
|
||||
@click="goToCustomer(customer.id)"
|
||||
>
|
||||
<div class="truncate">{{ customer.name || "—" }}</div>
|
||||
<div class="truncate">{{ customer.phone || "—" }}</div>
|
||||
<div class="truncate">{{ customer.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 { 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,197 +0,0 @@
|
||||
<template>
|
||||
<form @submit.prevent="validate">
|
||||
<div class="flex items-center justify-between">
|
||||
<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-8 gap-x-80 mb-10 py-12">
|
||||
<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="mx-24 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,
|
||||
}
|
||||
let targetId: number | null = null
|
||||
|
||||
if (supplierId.value !== null) {
|
||||
await updateSupplier(supplierId.value, supplierPayload)
|
||||
targetId = supplierId.value
|
||||
} else {
|
||||
const created = await createSupplier(supplierPayload)
|
||||
targetId = created.id
|
||||
}
|
||||
|
||||
await router.push(`/admin/supplier/${targetId}`)
|
||||
} 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,20 +1,16 @@
|
||||
<template>
|
||||
<div class="flex items-center justify-between">
|
||||
<h1 class="text-3xl font-bold uppercase">Liste des 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"
|
||||
<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]"
|
||||
>
|
||||
Ajouter
|
||||
</NuxtLink>
|
||||
</div>
|
||||
|
||||
<div v-if="auth.isAdmin" class="mt-6 border border-slate-200 mb-16">
|
||||
<div 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"
|
||||
class="sticky top-0 z-10 grid grid-cols-6 gap-4 bg-slate-100 px-4 py-3 text-sm font-semibold uppercase tracking-wide"
|
||||
>
|
||||
<div>Nom</div>
|
||||
<div>Mail</div>
|
||||
@@ -22,91 +18,57 @@
|
||||
<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">
|
||||
<template v-if="supplier.addresses?.length">
|
||||
<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' : ''"
|
||||
v-for="addr in supplier.addresses"
|
||||
:key="addr.id"
|
||||
class="grid grid-cols-6 hover:bg-slate-50 border-t gap-4 px-4 py-2"
|
||||
@click="goToSupplier(supplier.id)"
|
||||
>
|
||||
<div class="truncate">
|
||||
{{ idx === 0 ? supplier.name : "↳" }}
|
||||
{{ 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 class="truncate">
|
||||
{{ supplier.email }}
|
||||
</div>
|
||||
<div class="truncate">
|
||||
{{ addr.street }}
|
||||
</div>
|
||||
<div class="truncate">
|
||||
{{ addr.street2 }}
|
||||
</div>
|
||||
<div>{{ addr.postalCode }}</div>
|
||||
<div class="uppercase truncate">
|
||||
{{ addr.city }}
|
||||
</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"
|
||||
import type {SupplierData} from "~/services/dto/supplier-data"
|
||||
import {getSupplierList} from "~/services/supplier"
|
||||
|
||||
definePageMeta({ layout: "admin" })
|
||||
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()
|
||||
supplierList.value = (await getSupplierList(false)) ?? []
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ definePageMeta({
|
||||
import {computed, reactive, ref, watch} from 'vue'
|
||||
import {ROLE} from '~/utils/constants'
|
||||
import {createUser, updateUser, getUser} from '~/services/auth'
|
||||
import type {UserData, UserFormData, UserPayload} from '~/services/dto/user-data'
|
||||
import type {UserData, UserFormData} from '~/services/dto/user-data'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
@@ -105,12 +105,10 @@ async function validate() {
|
||||
const normalizedRole = form.role.trim()
|
||||
const normalizedPassword = form.password.trim()
|
||||
|
||||
const basePayload: UserPayload = {
|
||||
const basePayload = {
|
||||
username: normalizedUsername,
|
||||
roles: normalizedRole ? [normalizedRole] : undefined,
|
||||
}
|
||||
if (normalizedPassword) {
|
||||
basePayload.password = normalizedPassword
|
||||
password: normalizedPassword || undefined
|
||||
}
|
||||
|
||||
if (userId.value) {
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
<card-link label="NOUVELLE EXPÉDITION" link="/shipment" iconName="mdi:truck-fast-outline" />
|
||||
<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="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="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" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="flex justify-between h-[52px] mt-6 mb-[80px]">
|
||||
<div class="flex justify-between h-[52px] mb-[80px]">
|
||||
<div class="flex flex-1 mr-16">
|
||||
<UiStepper
|
||||
:labels="RECEPTION_STEP_LABELS"
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
<template>
|
||||
|
||||
<form @submit.prevent="validate">
|
||||
<div class="flex items-center justify-between mt-12 mb-8 ">
|
||||
<h1 class="font-bold text-5xl uppercase">Réception {{ receptionLoad?.identificationNumber }}</h1>
|
||||
<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"
|
||||
>Enregistrer
|
||||
</button>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-start gap-y-8 gap-x-40 mb-12">
|
||||
<div class="grid grid-cols-2 items-start gap-y-8 gap-x-40 mb-16">
|
||||
<!-- Nom de l'utilisateur -->
|
||||
<UiSelect
|
||||
id="reception-user"
|
||||
@@ -114,50 +119,6 @@
|
||||
wrapper-class="col-start-2 row-start-4"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex justify-center mb-2">
|
||||
<button
|
||||
v-if="auth.isAdmin"
|
||||
type="submit"
|
||||
class="text-xl uppercase bg-primary-500 text-white h-[50px] w-[272px] mb-16"
|
||||
|
||||
>
|
||||
Enregistrer
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex justify-evenly gap-y-8 gap-x-40 mb-8 border-b border-slate-400">
|
||||
<h1
|
||||
class="font-bold text-3xl uppercase col-start-1 row-start-1 cursor-pointer"
|
||||
:class="activeTab === 'weights' ? 'underline' : ''"
|
||||
@click="activeTab = 'weights'"
|
||||
>
|
||||
pesées
|
||||
</h1>
|
||||
<h1
|
||||
class="font-bold text-3xl uppercase col-start-2 row-start-1 cursor-pointer"
|
||||
:class="activeTab === 'merchandise' ? 'underline' : ''"
|
||||
@click="activeTab = 'merchandise'"
|
||||
>
|
||||
{{ isMerchandise ? "Marchandise" : "Bovins" }}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<update-weight
|
||||
v-if="activeTab === 'weights'"
|
||||
:idReception="idReception"
|
||||
:disabled="!auth.isAdmin"
|
||||
/>
|
||||
|
||||
<update-merchandise
|
||||
v-else-if="activeTab === 'merchandise' && isMerchandise"
|
||||
:idReception="idReception"
|
||||
:disabled="!auth.isAdmin"
|
||||
/>
|
||||
|
||||
<update-bovin
|
||||
v-else
|
||||
:idReception="idReception"
|
||||
:disabled="!auth.isAdmin"
|
||||
/>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
@@ -180,11 +141,7 @@ 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 activeTab = ref<'weights' | 'merchandise'>('weights')
|
||||
const router = useRouter()
|
||||
const receptionStore = useReceptionStore()
|
||||
const form = reactive<ReceptionFormData>({
|
||||
@@ -222,8 +179,6 @@ 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(() =>
|
||||
@@ -266,7 +221,7 @@ const clearReceptionBovines = async (receptionIri: string) => {
|
||||
}
|
||||
}
|
||||
|
||||
const hydrateFromUser = (reception: ReceptionData | null) => {
|
||||
const hydrateFromUser = (reception: ReceptionData | null)=> {
|
||||
if (!reception) {
|
||||
return
|
||||
}
|
||||
@@ -395,7 +350,7 @@ onMounted(async () => {
|
||||
|
||||
// Ajuste driver/vehicle quand le transporteur change (logique LIOT)
|
||||
watch(
|
||||
() => [form.supplierId, form.addressId, suppliers.value],
|
||||
() => [form.supplierId, suppliers.value],
|
||||
() => {
|
||||
if (!form.supplierId) {
|
||||
form.addressId = ''
|
||||
@@ -412,11 +367,7 @@ watch(
|
||||
(address) => String(address.id) === form.addressId
|
||||
)
|
||||
if (!matches) {
|
||||
if (supplierAddresses.value.length === 1) {
|
||||
form.addressId = String(supplierAddresses.value[0].id)
|
||||
} else {
|
||||
form.addressId = ''
|
||||
}
|
||||
form.addressId = ''
|
||||
}
|
||||
},
|
||||
{immediate: true}
|
||||
@@ -553,7 +504,7 @@ async function validate() {
|
||||
}
|
||||
|
||||
if (idReception) {
|
||||
const updated = await receptionStore.updateReception(idReception, {
|
||||
const updated = await receptionStore.updateReception(idReception,{
|
||||
...payload
|
||||
})
|
||||
if (updated) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="flex justify-between h-[52px] mt-6 mb-[80px]">
|
||||
<div class="flex justify-between h-[52px] mb-[80px]">
|
||||
<div class="flex flex-1 mr-16">
|
||||
<UiStepper
|
||||
:labels="SHIPMENT_STEP_LABELS"
|
||||
|
||||
@@ -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,14 @@
|
||||
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 type BovinShipmentListResponse =
|
||||
| BovinShipmentData[]
|
||||
| { 'hydra:member'?: BovinShipmentData[] }
|
||||
|
||||
export type ShipmentBovinePayload = {
|
||||
nbBovinSend: number
|
||||
shipment: string
|
||||
shipmentType: string
|
||||
}
|
||||
|
||||
export async function getBovinShipmentList(
|
||||
shipmentIri: string
|
||||
|
||||
@@ -1,43 +1,23 @@
|
||||
import { useApi } from "~/composables/useApi"
|
||||
import type { CustomerData, CustomerPayload } from "~/services/dto/customer-data"
|
||||
import { useApi } from '~/composables/useApi'
|
||||
import type { CustomerData } from '~/services/dto/customer-data'
|
||||
|
||||
export type CustomerListResponse =
|
||||
| CustomerData[]
|
||||
| { "hydra:member"?: CustomerData[] }
|
||||
| { 'hydra:member'?: CustomerData[] }
|
||||
|
||||
export async function getCustomerList(): Promise<CustomerData[]> {
|
||||
const api = useApi()
|
||||
const response = await api.get<CustomerListResponse>("customers", {}, {
|
||||
toastErrorKey: "errors.customer.list",
|
||||
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"]
|
||||
if (Array.isArray(response)) {
|
||||
return response
|
||||
}
|
||||
|
||||
if (response && typeof response === 'object' && Array.isArray(response['hydra:member'])) {
|
||||
return response['hydra:member']
|
||||
}
|
||||
|
||||
return []
|
||||
}
|
||||
|
||||
export async function getCustomer(id: number): Promise<CustomerData> {
|
||||
const api = useApi()
|
||||
return api.get<CustomerData>(`customers/${id}`, {}, {
|
||||
toastErrorKey: "errors.customer.fetch",
|
||||
})
|
||||
}
|
||||
|
||||
export async function updateCustomer(id: number, payload: Partial<CustomerPayload>): Promise<CustomerData> {
|
||||
const api = useApi()
|
||||
return api.patch<CustomerData>(`customers/${id}`, payload, {
|
||||
toastErrorKey: "errors.customer.update",
|
||||
toastSuccessKey: "success.customer.update",
|
||||
})
|
||||
}
|
||||
|
||||
export async function createCustomer(payload: CustomerPayload): Promise<CustomerData> {
|
||||
const api = useApi()
|
||||
return api.post<CustomerData>("customers", payload, {
|
||||
toastErrorKey: "errors.customer.create",
|
||||
toastSuccessKey: "success.customer.create",
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -6,14 +6,5 @@ export interface AddressData {
|
||||
postalCode: string
|
||||
city: string
|
||||
countryCode: string
|
||||
}
|
||||
|
||||
export interface AddressFormData {
|
||||
id?: number | null
|
||||
label: string
|
||||
street: string
|
||||
street2?: string | null
|
||||
postalCode: string
|
||||
city: string
|
||||
countryCode: string
|
||||
fullAddress?: string
|
||||
}
|
||||
|
||||
@@ -6,13 +6,3 @@ export interface BovinShipmentData {
|
||||
shipment?: string | null
|
||||
shipmentType?: ShipmentTypeData | null
|
||||
}
|
||||
|
||||
export type ShipmentBovinePayload = {
|
||||
nbBovinSend: number
|
||||
shipment: string
|
||||
shipmentType: string
|
||||
}
|
||||
|
||||
export type BovinShipmentListResponse =
|
||||
| BovinShipmentData[]
|
||||
| { 'hydra:member'?: BovinShipmentData[] }
|
||||
|
||||
@@ -1,25 +1,8 @@
|
||||
import type { AddressFormData } from "~/services/dto/address-data"
|
||||
|
||||
export type CustomerAddresses = AddressFormData[] | string[]
|
||||
import type { AddressData } from "~/services/dto/address-data"
|
||||
|
||||
export interface CustomerData {
|
||||
id: number
|
||||
name: string
|
||||
phone?: string | null
|
||||
email?: string | null
|
||||
addresses: CustomerAddresses
|
||||
}
|
||||
|
||||
export interface CustomerFormData {
|
||||
name: string
|
||||
phone?: string
|
||||
email?: string
|
||||
addresses: AddressFormData[]
|
||||
}
|
||||
|
||||
export type CustomerPayload = {
|
||||
name: string
|
||||
phone?: string | null
|
||||
email?: string | null
|
||||
addresses?: string[]
|
||||
label: string
|
||||
code?: string | null
|
||||
addresses?: AddressData[] | null
|
||||
}
|
||||
|
||||
@@ -41,14 +41,6 @@ export interface WeightEntryData {
|
||||
weighedAt: string | null
|
||||
}
|
||||
|
||||
export interface WeightFormData {
|
||||
id: number
|
||||
weight: number
|
||||
type: 'gross' | 'tare'
|
||||
}
|
||||
|
||||
|
||||
|
||||
export type ReceptionPayload = {
|
||||
licensePlate?: string | null
|
||||
receptionDate?: string
|
||||
@@ -80,14 +72,3 @@ export type ReceptionFormData = {
|
||||
driverId: string
|
||||
vehicleId: string
|
||||
}
|
||||
|
||||
export type ReceptionFormWeight = {
|
||||
weights: WeightFormData[]
|
||||
}
|
||||
|
||||
export interface ReceptionUpdatePayload {
|
||||
weights: {
|
||||
id: number
|
||||
weight: number
|
||||
}[]
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
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
|
||||
@@ -22,7 +21,6 @@ export type ShipmentData = {
|
||||
shipmentDate: string
|
||||
currentStep: number
|
||||
isValid: boolean
|
||||
address?: AddressData | null
|
||||
carrier?: CarrierData | null
|
||||
truck?: TruckData | null
|
||||
customer?: CustomerData | null
|
||||
|
||||
@@ -1,25 +1,9 @@
|
||||
import type { AddressFormData } from "~/services/dto/address-data"
|
||||
|
||||
export type SupplierAddresses = AddressFormData[] | string[]
|
||||
import type { AddressData } from '~/services/dto/address-data'
|
||||
|
||||
export interface SupplierData {
|
||||
id: number
|
||||
name: string
|
||||
email?: string | null
|
||||
phone?: string | null
|
||||
addresses: SupplierAddresses
|
||||
}
|
||||
|
||||
export interface SupplierFormData {
|
||||
name: string
|
||||
email?: string
|
||||
phone?: string
|
||||
addresses: AddressFormData[]
|
||||
}
|
||||
|
||||
export type SupplierPayload = {
|
||||
name: string
|
||||
email?: string | null
|
||||
phone?: string | null
|
||||
addresses?: string[]
|
||||
addresses?: AddressData[] | null
|
||||
}
|
||||
|
||||
@@ -2,5 +2,4 @@ export interface WeightData {
|
||||
weight: number | null
|
||||
dsd: number | null
|
||||
weighedAt: string | null
|
||||
type : string | null
|
||||
}
|
||||
|
||||
@@ -1,42 +1,23 @@
|
||||
import { useApi } from "~/composables/useApi"
|
||||
import type { SupplierData, SupplierPayload } from "~/services/dto/supplier-data"
|
||||
import { useApi } from '~/composables/useApi'
|
||||
import type { SupplierData } from '~/services/dto/supplier-data'
|
||||
|
||||
export type SupplierListResponse =
|
||||
| SupplierData[]
|
||||
| { "hydra:member"?: SupplierData[] }
|
||||
| { 'hydra:member'?: SupplierData[] }
|
||||
|
||||
export async function getSupplierList(): Promise<SupplierData[]> {
|
||||
const api = useApi()
|
||||
const response = await api.get<SupplierListResponse>("suppliers", {}, {
|
||||
toastErrorKey: "errors.supplier.list",
|
||||
const response = await api.get<SupplierListResponse>('suppliers', {}, {
|
||||
toastErrorKey: 'errors.supplier.list'
|
||||
})
|
||||
|
||||
if (Array.isArray(response)) return response
|
||||
if (response && typeof response === "object" && Array.isArray(response["hydra:member"])) {
|
||||
return response["hydra:member"]
|
||||
if (Array.isArray(response)) {
|
||||
return response
|
||||
}
|
||||
|
||||
if (response && typeof response === 'object' && Array.isArray(response['hydra:member'])) {
|
||||
return response['hydra:member']
|
||||
}
|
||||
|
||||
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,8 +1,5 @@
|
||||
import { useApi } from '~/composables/useApi'
|
||||
import type {ReceptionData, ReceptionPayload, 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";
|
||||
import type { WeightEntryData } from '~/services/dto/reception-data'
|
||||
|
||||
export type WeightPayload = {
|
||||
reception?: string
|
||||
@@ -20,22 +17,5 @@ export async function createWeight(payload: WeightPayload) {
|
||||
|
||||
export async function updateWeight(id: number, payload: Partial<WeightPayload>) {
|
||||
const api = useApi()
|
||||
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>
|
||||
return api.patch<WeightEntryData>(`weights/${id}`, payload)
|
||||
}
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20260213093000 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Add name, phone and email fields to customer.';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE customer ADD name VARCHAR(255) DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE customer ADD phone VARCHAR(255) DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE customer ADD email VARCHAR(255) DEFAULT NULL');
|
||||
$this->addSql('UPDATE customer SET name = label WHERE name IS NULL');
|
||||
$this->addSql('ALTER TABLE customer ALTER COLUMN name SET NOT NULL');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE customer DROP name');
|
||||
$this->addSql('ALTER TABLE customer DROP phone');
|
||||
$this->addSql('ALTER TABLE customer DROP email');
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20260213101500 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Align customer with supplier: keep name/email/phone and drop label/code.';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE customer ALTER COLUMN name TYPE VARCHAR(180)');
|
||||
$this->addSql('ALTER TABLE customer ALTER COLUMN email TYPE VARCHAR(180)');
|
||||
$this->addSql('ALTER TABLE customer ALTER COLUMN phone TYPE VARCHAR(40)');
|
||||
$this->addSql('ALTER TABLE customer DROP COLUMN label');
|
||||
$this->addSql('ALTER TABLE customer DROP COLUMN code');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE customer ADD label VARCHAR(255) DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE customer ADD code VARCHAR(255) DEFAULT NULL');
|
||||
$this->addSql('UPDATE customer SET label = name WHERE label IS NULL');
|
||||
$this->addSql("UPDATE customer SET code = regexp_replace(upper(name), '[^A-Z0-9]+', '_', 'g') WHERE code IS NULL");
|
||||
$this->addSql('ALTER TABLE customer ALTER COLUMN label SET NOT NULL');
|
||||
$this->addSql('ALTER TABLE customer ALTER COLUMN code SET NOT NULL');
|
||||
$this->addSql('ALTER TABLE customer ALTER COLUMN email TYPE VARCHAR(255)');
|
||||
$this->addSql('ALTER TABLE customer ALTER COLUMN phone TYPE VARCHAR(255)');
|
||||
}
|
||||
}
|
||||
@@ -5,15 +5,12 @@ declare(strict_types=1);
|
||||
namespace App\Command;
|
||||
|
||||
use App\Entity\Address;
|
||||
use App\Entity\BovineType;
|
||||
use App\Entity\Building;
|
||||
use App\Entity\Carrier;
|
||||
use App\Entity\Customer;
|
||||
use App\Entity\Driver;
|
||||
use App\Entity\MerchandiseType;
|
||||
use App\Entity\PelletType;
|
||||
use App\Entity\ReceptionType;
|
||||
use App\Entity\ShipmentType;
|
||||
use App\Entity\Supplier;
|
||||
use App\Entity\Truck;
|
||||
use App\Entity\Vehicle;
|
||||
@@ -53,11 +50,7 @@ class SeedCommand extends Command
|
||||
$this->seedPelletTypes();
|
||||
$this->seedBuildings();
|
||||
$this->seedReceptionTypes();
|
||||
$this->seedBovineTypes();
|
||||
$this->seedShipmentTypes();
|
||||
$this->seedSuppliers();
|
||||
$this->entityManager->flush();
|
||||
$this->seedCustomers($io);
|
||||
|
||||
$this->entityManager->flush();
|
||||
|
||||
@@ -68,7 +61,7 @@ class SeedCommand extends Command
|
||||
|
||||
private function seedTrucks(): array
|
||||
{
|
||||
$trucks = ['Citerne', 'Porteur', 'Plateau', 'Remorque', 'Benne'];
|
||||
$trucks = ['Citerne', 'Porteur'];
|
||||
$citerne = null;
|
||||
$porteur = null;
|
||||
foreach ($trucks as $name) {
|
||||
@@ -230,39 +223,6 @@ class SeedCommand extends Command
|
||||
}
|
||||
}
|
||||
|
||||
private function seedBovineTypes(): void
|
||||
{
|
||||
$bovineTypes = [
|
||||
['label' => 'Limousine', 'code' => '34'],
|
||||
['label' => 'Charolaise', 'code' => '38'],
|
||||
['label' => 'Parthenaise', 'code' => '71'],
|
||||
];
|
||||
foreach ($bovineTypes as $type) {
|
||||
$this->upsertByCode(BovineType::class, $type['code'], static function (BovineType $entity) use ($type) {
|
||||
$entity
|
||||
->setLabel($type['label'])
|
||||
->setCode($type['code'])
|
||||
;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private function seedShipmentTypes(): void
|
||||
{
|
||||
$shipmentTypes = [
|
||||
['label' => 'Bovin de boucherie', 'code' => 'BDB'],
|
||||
['label' => "Bovin d'équarrissage", 'code' => 'BE'],
|
||||
];
|
||||
foreach ($shipmentTypes as $type) {
|
||||
$this->upsertByCode(ShipmentType::class, $type['code'], static function (ShipmentType $entity) use ($type) {
|
||||
$entity
|
||||
->setLabel($type['label'])
|
||||
->setCode($type['code'])
|
||||
;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private function seedSuppliers(): void
|
||||
{
|
||||
$suppliers = [
|
||||
@@ -498,130 +458,6 @@ class SeedCommand extends Command
|
||||
}
|
||||
}
|
||||
|
||||
private function seedCustomers(SymfonyStyle $io): void
|
||||
{
|
||||
$addressRepo = $this->entityManager->getRepository(Address::class);
|
||||
$customers = [
|
||||
[
|
||||
'name' => 'ARNAULT EURL',
|
||||
'phone' => '05.49.02.65.27',
|
||||
'email' => 'eurl.arnault86@orange.fr',
|
||||
'addresses' => [
|
||||
[
|
||||
'label' => 'ARNAULT EURL',
|
||||
'street' => 'Moulin du Guéret',
|
||||
'street2' => 'B.P 30425',
|
||||
'postalCode' => '86100',
|
||||
'city' => 'Antran',
|
||||
'countryCode' => 'FR',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'COVILIM',
|
||||
'phone' => '05.55.30.03.10',
|
||||
'email' => 'sandra.robineaux@covilim.com',
|
||||
'addresses' => [
|
||||
[
|
||||
'label' => 'COVILIM',
|
||||
'street' => 'Rue de Nexon',
|
||||
'street2' => null,
|
||||
'postalCode' => '87000',
|
||||
'city' => 'LIMOGES',
|
||||
'countryCode' => 'FR',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'Les producteurs de la marche (LPM)',
|
||||
'phone' => '05.55.63.04.53',
|
||||
'email' => 'f.legalliard@lpmcoop.fr',
|
||||
'addresses' => [
|
||||
[
|
||||
'label' => 'Les producteurs de la marche (LPM)',
|
||||
'street' => 'Rue de Nexon',
|
||||
'street2' => null,
|
||||
'postalCode' => '87000',
|
||||
'city' => 'LIMOGES',
|
||||
'countryCode' => 'FR',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'LORTHOLARY BETAIL',
|
||||
'phone' => '05.49.52.77.10',
|
||||
'email' => 'contact86@lortholarybetail.com',
|
||||
'addresses' => [
|
||||
[
|
||||
'label' => 'LORTHOLARY BETAIL',
|
||||
'street' => 'FERME DE GENIEC',
|
||||
'street2' => null,
|
||||
'postalCode' => '86550',
|
||||
'city' => 'MIGNALOUX BEAUVOIR',
|
||||
'countryCode' => 'FR',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'TERRENA',
|
||||
'phone' => '02.51.67.17.98',
|
||||
'email' => null,
|
||||
'addresses' => [
|
||||
[
|
||||
'label' => 'TERRENA',
|
||||
'street' => 'La Blanchardière',
|
||||
'street2' => null,
|
||||
'postalCode' => '44522',
|
||||
'city' => 'MESANGER',
|
||||
'countryCode' => 'FR',
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
foreach ($customers as $customerData) {
|
||||
$customerName = $customerData['name'] ?? $customerData['label'] ?? null;
|
||||
if (!$customerName) {
|
||||
$io->warning('Customer skipped: missing "name".');
|
||||
|
||||
continue;
|
||||
}
|
||||
$customer = $this->upsertByName(Customer::class, $customerName, static function (Customer $customer) use ($customerData, $customerName) {
|
||||
$customer
|
||||
->setName($customerName)
|
||||
->setPhone($customerData['phone'] ?? null)
|
||||
->setEmail($customerData['email'] ?? null)
|
||||
;
|
||||
});
|
||||
|
||||
$addresses = [];
|
||||
if (isset($customerData['addresses']) && is_array($customerData['addresses'])) {
|
||||
foreach ($customerData['addresses'] as $addressData) {
|
||||
$addresses[] = $this->upsertAddress($addressData);
|
||||
}
|
||||
} else {
|
||||
// Backward compatibility for older seed format with address ids.
|
||||
$addressIds = $customerData['addressIds'] ?? (isset($customerData['addressId']) ? [$customerData['addressId']] : []);
|
||||
foreach ($addressIds as $addressId) {
|
||||
$address = $addressRepo->find($addressId);
|
||||
if (!$address instanceof Address) {
|
||||
$io->warning(sprintf(
|
||||
'Customer "%s" skipped address id %d: not found.',
|
||||
$customerName,
|
||||
$addressId
|
||||
));
|
||||
|
||||
continue;
|
||||
}
|
||||
$addresses[] = $address;
|
||||
}
|
||||
}
|
||||
|
||||
$customer->setAddresses($addresses);
|
||||
$this->entityManager->persist($customer);
|
||||
}
|
||||
}
|
||||
|
||||
private function upsertByCode(string $entityClass, string $code, callable $apply): object
|
||||
{
|
||||
$repo = $this->entityManager->getRepository($entityClass);
|
||||
|
||||
@@ -20,6 +20,7 @@ class AppFixtures extends Fixture implements DependentFixtureInterface
|
||||
return [
|
||||
TransportFixtures::class,
|
||||
ReferenceFixtures::class,
|
||||
SupplierFixtures::class,
|
||||
UserFixtures::class,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -5,13 +5,10 @@ declare(strict_types=1);
|
||||
namespace App\DataFixtures;
|
||||
|
||||
use App\Entity\Address;
|
||||
use App\Entity\BovineType;
|
||||
use App\Entity\Building;
|
||||
use App\Entity\Customer;
|
||||
use App\Entity\MerchandiseType;
|
||||
use App\Entity\PelletType;
|
||||
use App\Entity\ReceptionType;
|
||||
use App\Entity\ShipmentType;
|
||||
use App\Entity\Supplier;
|
||||
use Doctrine\Bundle\FixturesBundle\Fixture;
|
||||
use Doctrine\Persistence\ObjectManager;
|
||||
@@ -20,8 +17,6 @@ class ReferenceFixtures extends Fixture
|
||||
{
|
||||
public function load(ObjectManager $manager): void
|
||||
{
|
||||
$addressIndex = [];
|
||||
|
||||
$merchandiseTypes = [
|
||||
['label' => 'Foin', 'code' => 'FOIN'],
|
||||
['label' => 'Paille', 'code' => 'PAILLE'],
|
||||
@@ -74,31 +69,6 @@ class ReferenceFixtures extends Fixture
|
||||
$manager->persist($receptionType);
|
||||
}
|
||||
|
||||
$bovineTypes = [
|
||||
['label' => 'Limousine', 'code' => '34'],
|
||||
['label' => 'Charolaise', 'code' => '38'],
|
||||
['label' => 'Parthenaise', 'code' => '71'],
|
||||
];
|
||||
foreach ($bovineTypes as $type) {
|
||||
$bovineType = new BovineType()
|
||||
->setLabel($type['label'])
|
||||
->setCode($type['code'])
|
||||
;
|
||||
$manager->persist($bovineType);
|
||||
}
|
||||
|
||||
$shipmentTypes = [
|
||||
['label' => 'Bovin de boucherie', 'code' => 'BDB'],
|
||||
['label' => "Bovin d'équarrissage", 'code' => 'BE'],
|
||||
];
|
||||
foreach ($shipmentTypes as $type) {
|
||||
$shipmentType = new ShipmentType()
|
||||
->setLabel($type['label'])
|
||||
->setCode($type['code'])
|
||||
;
|
||||
$manager->persist($shipmentType);
|
||||
}
|
||||
|
||||
$suppliers = [
|
||||
[
|
||||
'name' => 'LIOT',
|
||||
@@ -320,129 +290,21 @@ class ReferenceFixtures extends Fixture
|
||||
;
|
||||
|
||||
foreach ($supplierData['addresses'] as $addressData) {
|
||||
$addressKey = sprintf('%s|%s', $addressData['label'], $addressData['postalCode']);
|
||||
if (!isset($addressIndex[$addressKey])) {
|
||||
$addressIndex[$addressKey] = new Address()
|
||||
->setLabel($addressData['label'])
|
||||
->setStreet($addressData['street'])
|
||||
->setStreet2($addressData['street2'])
|
||||
->setPostalCode($addressData['postalCode'])
|
||||
->setCity($addressData['city'])
|
||||
->setCountryCode($addressData['countryCode'])
|
||||
;
|
||||
$manager->persist($addressIndex[$addressKey]);
|
||||
}
|
||||
$address = $addressIndex[$addressKey];
|
||||
$address = new Address()
|
||||
->setLabel($addressData['label'])
|
||||
->setStreet($addressData['street'])
|
||||
->setStreet2($addressData['street2'])
|
||||
->setPostalCode($addressData['postalCode'])
|
||||
->setCity($addressData['city'])
|
||||
->setCountryCode($addressData['countryCode'])
|
||||
;
|
||||
$manager->persist($address);
|
||||
$supplier->getAddresses()->add($address);
|
||||
}
|
||||
|
||||
$manager->persist($supplier);
|
||||
}
|
||||
|
||||
$customers = [
|
||||
[
|
||||
'name' => 'ARNAULT EURL',
|
||||
'phone' => '05.49.02.65.27',
|
||||
'email' => 'eurl.arnault86@orange.fr',
|
||||
'addresses' => [
|
||||
[
|
||||
'label' => 'ARNAULT EURL',
|
||||
'street' => 'Moulin du Guéret',
|
||||
'street2' => 'B.P 30425',
|
||||
'postalCode' => '86100',
|
||||
'city' => 'Antran',
|
||||
'countryCode' => 'FR',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'COVILIM',
|
||||
'phone' => '05.55.30.03.10',
|
||||
'email' => 'sandra.robineaux@covilim.com',
|
||||
'addresses' => [
|
||||
[
|
||||
'label' => 'COVILIM',
|
||||
'street' => 'Rue de Nexon',
|
||||
'street2' => null,
|
||||
'postalCode' => '87000',
|
||||
'city' => 'LIMOGES',
|
||||
'countryCode' => 'FR',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'Les producteurs de la marche (LPM)',
|
||||
'phone' => '05.55.63.04.53',
|
||||
'email' => 'f.legalliard@lpmcoop.fr',
|
||||
'addresses' => [
|
||||
[
|
||||
'label' => 'Les producteurs de la marche (LPM)',
|
||||
'street' => 'Rue de Nexon',
|
||||
'street2' => null,
|
||||
'postalCode' => '87000',
|
||||
'city' => 'LIMOGES',
|
||||
'countryCode' => 'FR',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'LORTHOLARY BETAIL',
|
||||
'phone' => '05.49.52.77.10',
|
||||
'email' => 'contact86@lortholarybetail.com',
|
||||
'addresses' => [
|
||||
[
|
||||
'label' => 'LORTHOLARY BETAIL',
|
||||
'street' => 'FERME DE GENIEC',
|
||||
'street2' => null,
|
||||
'postalCode' => '86550',
|
||||
'city' => 'MIGNALOUX BEAUVOIR',
|
||||
'countryCode' => 'FR',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'TERRENA',
|
||||
'phone' => '02.51.67.17.98',
|
||||
'email' => null,
|
||||
'addresses' => [
|
||||
[
|
||||
'label' => 'TERRENA',
|
||||
'street' => 'La Blanchardière',
|
||||
'street2' => null,
|
||||
'postalCode' => '44522',
|
||||
'city' => 'MESANGER',
|
||||
'countryCode' => 'FR',
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
foreach ($customers as $customerData) {
|
||||
$customer = new Customer()
|
||||
->setName($customerData['name'])
|
||||
->setPhone($customerData['phone'])
|
||||
->setEmail($customerData['email'])
|
||||
;
|
||||
|
||||
foreach ($customerData['addresses'] as $addressData) {
|
||||
$addressKey = sprintf('%s|%s', $addressData['label'], $addressData['postalCode']);
|
||||
if (!isset($addressIndex[$addressKey])) {
|
||||
$addressIndex[$addressKey] = new Address()
|
||||
->setLabel($addressData['label'])
|
||||
->setStreet($addressData['street'])
|
||||
->setStreet2($addressData['street2'])
|
||||
->setPostalCode($addressData['postalCode'])
|
||||
->setCity($addressData['city'])
|
||||
->setCountryCode($addressData['countryCode'])
|
||||
;
|
||||
$manager->persist($addressIndex[$addressKey]);
|
||||
}
|
||||
$customer->getAddresses()->add($addressIndex[$addressKey]);
|
||||
}
|
||||
|
||||
$manager->persist($customer);
|
||||
}
|
||||
|
||||
$manager->flush();
|
||||
}
|
||||
}
|
||||
|
||||
38
src/DataFixtures/SupplierFixtures.php
Normal file
38
src/DataFixtures/SupplierFixtures.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\DataFixtures;
|
||||
|
||||
use App\Entity\Address;
|
||||
use App\Entity\Supplier;
|
||||
use Doctrine\Bundle\FixturesBundle\Fixture;
|
||||
use Doctrine\Persistence\ObjectManager;
|
||||
|
||||
class SupplierFixtures extends Fixture
|
||||
{
|
||||
public function load(ObjectManager $manager): void
|
||||
{
|
||||
$address = new Address()
|
||||
->setLabel('LIOT CHATELLERAULT')
|
||||
->setStreet("14 Allée d'Argenson")
|
||||
->setStreet2('ZI Nord')
|
||||
->setPostalCode('86100')
|
||||
->setCity('CHATELLERAULT')
|
||||
->setCountryCode('FR')
|
||||
;
|
||||
|
||||
$supplier = new Supplier()
|
||||
->setName('LIOT')
|
||||
->setEmail('lpc.contacts@lpc-liot.fr')
|
||||
->setPhone('05.49.20.09.10')
|
||||
;
|
||||
|
||||
$supplier->getAddresses()->add($address);
|
||||
|
||||
$manager->persist($address);
|
||||
$manager->persist($supplier);
|
||||
|
||||
$manager->flush();
|
||||
}
|
||||
}
|
||||
@@ -15,17 +15,11 @@ class TransportFixtures extends Fixture
|
||||
{
|
||||
public function load(ObjectManager $manager): void
|
||||
{
|
||||
$citerne = new Truck()->setName('Citerne');
|
||||
$porteur = new Truck()->setName('Porteur');
|
||||
$plateau = new Truck()->setName('Plateau');
|
||||
$remorque = new Truck()->setName('Remorque');
|
||||
$benne = new Truck()->setName('Benne');
|
||||
$citerne = new Truck()->setName('Citerne');
|
||||
$porteur = new Truck()->setName('Porteur');
|
||||
|
||||
$manager->persist($citerne);
|
||||
$manager->persist($porteur);
|
||||
$manager->persist($plateau);
|
||||
$manager->persist($remorque);
|
||||
$manager->persist($benne);
|
||||
|
||||
$liot = new Carrier()
|
||||
->setName('LIOT')
|
||||
|
||||
@@ -7,8 +7,6 @@ namespace App\Entity;
|
||||
use ApiPlatform\Metadata\ApiResource;
|
||||
use ApiPlatform\Metadata\Get;
|
||||
use ApiPlatform\Metadata\GetCollection;
|
||||
use ApiPlatform\Metadata\Patch;
|
||||
use ApiPlatform\Metadata\Post;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
@@ -25,16 +23,6 @@ use Symfony\Component\Serializer\Attribute\Groups;
|
||||
new GetCollection(
|
||||
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')",
|
||||
)]
|
||||
@@ -47,27 +35,27 @@ class Address
|
||||
private ?int $id = null;
|
||||
|
||||
#[ORM\Column(length: 120)]
|
||||
#[Groups(['address:read', 'supplier:read', 'reception:read', 'customer:read', 'shipment:read', 'address:write'])]
|
||||
#[Groups(['address:read', 'supplier:read', 'reception:read', 'customer:read', 'shipment:read'])]
|
||||
private string $label = '';
|
||||
|
||||
#[ORM\Column(length: 180)]
|
||||
#[Groups(['address:read', 'supplier:read', 'reception:read', 'customer:read', 'shipment:read', 'address:write'])]
|
||||
#[Groups(['address:read', 'supplier:read', 'reception:read', 'customer:read', 'shipment:read'])]
|
||||
private string $street = '';
|
||||
|
||||
#[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', 'customer:read', 'shipment:read'])]
|
||||
private ?string $street2 = null;
|
||||
|
||||
#[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', 'customer:read', 'shipment:read'])]
|
||||
private string $postalCode = '';
|
||||
|
||||
#[ORM\Column(length: 120)]
|
||||
#[Groups(['address:read', 'supplier:read', 'reception:read', 'customer:read', 'shipment:read', 'address:write'])]
|
||||
#[Groups(['address:read', 'supplier:read', 'reception:read', 'customer:read', 'shipment:read'])]
|
||||
private string $city = '';
|
||||
|
||||
#[ORM\Column(name: 'country_code', length: 2)]
|
||||
#[Groups(['address:read', 'supplier:read', 'customer:read', 'address:write'])]
|
||||
#[Groups(['address:read', 'supplier:read', 'customer:read'])]
|
||||
private string $countryCode = '';
|
||||
|
||||
/**
|
||||
|
||||
@@ -19,11 +19,9 @@ use Symfony\Component\Serializer\Attribute\Groups;
|
||||
new Get(
|
||||
requirements: ['id' => '\d+'],
|
||||
normalizationContext: ['groups' => ['carrier:read']],
|
||||
security: "is_granted('ROLE_USER')"
|
||||
),
|
||||
new GetCollection(
|
||||
normalizationContext: ['groups' => ['carrier:read']],
|
||||
security: "is_granted('ROLE_USER')"
|
||||
),
|
||||
new Post(
|
||||
normalizationContext: ['groups' => ['carrier:read']],
|
||||
|
||||
@@ -8,8 +8,6 @@ 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 Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
@@ -26,16 +24,6 @@ use Symfony\Component\Serializer\Attribute\Groups;
|
||||
new GetCollection(
|
||||
normalizationContext: ['groups' => ['customer:read']],
|
||||
),
|
||||
new Post(
|
||||
normalizationContext: ['groups' => ['customer:read']],
|
||||
denormalizationContext: ['groups' => ['customer:write']],
|
||||
security: "is_granted('ROLE_ADMIN')",
|
||||
),
|
||||
new Patch(
|
||||
normalizationContext: ['groups' => ['customer:read']],
|
||||
denormalizationContext: ['groups' => ['customer:write']],
|
||||
security: "is_granted('ROLE_ADMIN')",
|
||||
),
|
||||
],
|
||||
security: "is_granted('ROLE_USER')",
|
||||
)]
|
||||
@@ -47,24 +35,20 @@ class Customer
|
||||
#[Groups(['shipment:read', 'customer:read'])]
|
||||
private ?int $id = null;
|
||||
|
||||
#[ORM\Column(length: 180)]
|
||||
#[Groups(['customer:read', 'customer:write', 'shipment:read'])]
|
||||
private string $name = '';
|
||||
#[ORM\Column(length: 255)]
|
||||
#[Groups(['customer:read', 'shipment:read'])]
|
||||
private ?string $label = null;
|
||||
|
||||
#[ORM\Column(length: 180, nullable: true)]
|
||||
#[Groups(['customer:read', 'customer:write', 'shipment:read'])]
|
||||
private ?string $email = null;
|
||||
|
||||
#[ORM\Column(length: 40, nullable: true)]
|
||||
#[Groups(['customer:read', 'customer:write', 'shipment:read'])]
|
||||
private ?string $phone = 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', 'customer:write'])]
|
||||
#[Groups(['customer:read'])]
|
||||
#[ApiProperty(readableLink: true)]
|
||||
private Collection $addresses;
|
||||
|
||||
@@ -78,40 +62,24 @@ class Customer
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
public function getLabel(): ?string
|
||||
{
|
||||
return $this->name;
|
||||
return $this->label;
|
||||
}
|
||||
|
||||
public function setName(string $name): self
|
||||
public function setLabel(?string $label): void
|
||||
{
|
||||
$this->name = $name;
|
||||
|
||||
return $this;
|
||||
$this->label = $label;
|
||||
}
|
||||
|
||||
public function getEmail(): ?string
|
||||
public function getCode(): ?string
|
||||
{
|
||||
return $this->email;
|
||||
return $this->code;
|
||||
}
|
||||
|
||||
public function setEmail(?string $email): self
|
||||
public function setCode(?string $code): void
|
||||
{
|
||||
$this->email = $email;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getPhone(): ?string
|
||||
{
|
||||
return $this->phone;
|
||||
}
|
||||
|
||||
public function setPhone(?string $phone): self
|
||||
{
|
||||
$this->phone = $phone;
|
||||
|
||||
return $this;
|
||||
$this->code = $code;
|
||||
}
|
||||
|
||||
public function getAddresses(): Collection
|
||||
@@ -119,29 +87,8 @@ class Customer
|
||||
return $this->addresses;
|
||||
}
|
||||
|
||||
public function setAddresses(iterable $addresses): self
|
||||
public function setAddresses(Collection $addresses): void
|
||||
{
|
||||
$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;
|
||||
$this->addresses = $addresses;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,6 @@ 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;
|
||||
@@ -28,7 +26,6 @@ use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
|
||||
#[ORM\Entity]
|
||||
#[ORM\HasLifecycleCallbacks]
|
||||
#[ORM\Table(name: 'shipment')]
|
||||
#[ApiFilter(BooleanFilter::class, properties: ['isValid'])]
|
||||
#[ApiResource(
|
||||
operations: [
|
||||
new Get(
|
||||
|
||||
@@ -8,8 +8,6 @@ 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 Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
@@ -22,21 +20,9 @@ use Symfony\Component\Serializer\Attribute\Groups;
|
||||
new Get(
|
||||
requirements: ['id' => '\d+'],
|
||||
normalizationContext: ['groups' => ['supplier:read']],
|
||||
security: "is_granted('ROLE_USER')"
|
||||
),
|
||||
new GetCollection(
|
||||
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')",
|
||||
@@ -50,15 +36,15 @@ class Supplier
|
||||
private ?int $id = null;
|
||||
|
||||
#[ORM\Column(length: 180)]
|
||||
#[Groups(['supplier:read', 'reception:read', 'supplier:write'])]
|
||||
#[Groups(['supplier:read', 'reception:read'])]
|
||||
private string $name = '';
|
||||
|
||||
#[ORM\Column(length: 180, nullable: true)]
|
||||
#[Groups(['supplier:read', 'reception:read', 'supplier:write'])]
|
||||
#[Groups(['supplier:read', 'reception:read'])]
|
||||
private ?string $email = null;
|
||||
|
||||
#[ORM\Column(length: 40, nullable: true)]
|
||||
#[Groups(['supplier:read', 'reception:read', 'supplier:write'])]
|
||||
#[Groups(['supplier:read', 'reception:read'])]
|
||||
private ?string $phone = null;
|
||||
|
||||
/**
|
||||
@@ -66,7 +52,7 @@ class Supplier
|
||||
*/
|
||||
#[ORM\ManyToMany(targetEntity: Address::class, inversedBy: 'suppliers')]
|
||||
#[ORM\JoinTable(name: 'supplier_address')]
|
||||
#[Groups(['supplier:read', 'supplier:write'])]
|
||||
#[Groups(['supplier:read'])]
|
||||
#[ApiProperty(readableLink: true)]
|
||||
private Collection $addresses;
|
||||
|
||||
@@ -123,30 +109,4 @@ class Supplier
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,12 +21,8 @@ final class UserPasswordProcessor implements ProcessorInterface
|
||||
public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): mixed
|
||||
{
|
||||
if ($data instanceof User) {
|
||||
$plain = $data->getPassword();
|
||||
$previous = $context['previous_data'] ?? null;
|
||||
if ($previous instanceof User && $plain === $previous->getPassword()) {
|
||||
// Password not changed in payload: keep existing hash.
|
||||
$data->setPassword($previous->getPassword());
|
||||
} elseif ('' !== $plain) {
|
||||
$plain = $data->getPassword();
|
||||
if ('' !== $plain) {
|
||||
$data->setPassword($this->hasher->hashPassword(
|
||||
$data,
|
||||
$plain
|
||||
|
||||
Reference in New Issue
Block a user