[#203] Réceptions — Parcours de pesée multi-étapes #3
58
.idea/workspace.xml
generated
58
.idea/workspace.xml
generated
@@ -4,28 +4,23 @@
|
|||||||
<option name="autoReloadType" value="SELECTIVE" />
|
<option name="autoReloadType" value="SELECTIVE" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ChangeListManager">
|
<component name="ChangeListManager">
|
||||||
<list default="true" id="7c107abe-5995-4428-8429-b146aaca8386" name="Changes" comment="Feat">
|
<list default="true" id="7c107abe-5995-4428-8429-b146aaca8386" name="Changes" comment="feat : Ajout de pinia, création de la table weight et reception mise en place du système de step pour les receptions (WIP)">
|
||||||
<change afterPath="$PROJECT_DIR$/frontend/components/reception/reception-form.vue" afterDir="false" />
|
<change afterPath="$PROJECT_DIR$/frontend/components/reception/reception-unloading.vue" afterDir="false" />
|
||||||
<change afterPath="$PROJECT_DIR$/frontend/components/reception/reception-weight.vue" afterDir="false" />
|
<change afterPath="$PROJECT_DIR$/frontend/components/ui/loading-dots.vue" afterDir="false" />
|
||||||
<change afterPath="$PROJECT_DIR$/frontend/services/dto/weight-data.ts" afterDir="false" />
|
|
||||||
<change afterPath="$PROJECT_DIR$/frontend/services/reception.ts" afterDir="false" />
|
|
||||||
<change afterPath="$PROJECT_DIR$/src/Dto/PontBasculeReading.php" afterDir="false" />
|
|
||||||
<change afterPath="$PROJECT_DIR$/src/Exception/PontBasculeException.php" afterDir="false" />
|
|
||||||
<change afterPath="$PROJECT_DIR$/src/Service/PontBasculePayloadDecoder.php" afterDir="false" />
|
|
||||||
<change afterPath="$PROJECT_DIR$/src/Service/PontBasculeService.php" afterDir="false" />
|
|
||||||
<change afterPath="$PROJECT_DIR$/src/State/ReceptionWeighingProvider.php" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/composer.json" beforeDir="false" afterPath="$PROJECT_DIR$/composer.json" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/composer.lock" beforeDir="false" afterPath="$PROJECT_DIR$/composer.lock" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/config/packages/api_platform.yaml" beforeDir="false" afterPath="$PROJECT_DIR$/config/packages/api_platform.yaml" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/config/reference.php" beforeDir="false" afterPath="$PROJECT_DIR$/config/reference.php" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/config/reference.php" beforeDir="false" afterPath="$PROJECT_DIR$/config/reference.php" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/config/services.yaml" beforeDir="false" afterPath="$PROJECT_DIR$/config/services.yaml" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/frontend/components/reception/reception-form.vue" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/components/reception/reception-form.vue" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/docker/php/config/vhost.conf" beforeDir="false" afterPath="$PROJECT_DIR$/docker/php/config/vhost.conf" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/frontend/components/reception/reception-weight.vue" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/components/reception/reception-weight.vue" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/frontend/composables/useApi.ts" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/composables/useApi.ts" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/frontend/nuxt.config.ts" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/nuxt.config.ts" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/frontend/package-lock.json" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/package-lock.json" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/frontend/package-lock.json" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/package-lock.json" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/frontend/package.json" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/package.json" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/frontend/package.json" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/package.json" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/frontend/pages/index.vue" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/pages/index.vue" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/frontend/pages/reception/[[id]].vue" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/pages/reception/[[id]].vue" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/frontend/services/dto/reception-data.ts" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/services/dto/reception-data.ts" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/frontend/services/dto/weight-data.ts" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/services/dto/weight-data.ts" 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/Dto/PontBasculeReading.php" beforeDir="false" afterPath="$PROJECT_DIR$/src/Dto/PontBasculeReading.php" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/src/Entity/Reception.php" beforeDir="false" afterPath="$PROJECT_DIR$/src/Entity/Reception.php" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/src/Entity/Weight.php" beforeDir="false" afterPath="$PROJECT_DIR$/src/Entity/Weight.php" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/src/State/ReceptionWeighingProvider.php" beforeDir="false" afterPath="$PROJECT_DIR$/src/State/ReceptionWeighingProvider.php" afterDir="false" />
|
||||||
</list>
|
</list>
|
||||||
<option name="SHOW_DIALOG" value="false" />
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||||
@@ -37,7 +32,7 @@
|
|||||||
<execution />
|
<execution />
|
||||||
</component>
|
</component>
|
||||||
<component name="EmbeddingIndexingInfo">
|
<component name="EmbeddingIndexingInfo">
|
||||||
<option name="cachedIndexableFilesCount" value="115" />
|
<option name="cachedIndexableFilesCount" value="137" />
|
||||||
<option name="fileBasedEmbeddingIndicesEnabled" value="true" />
|
<option name="fileBasedEmbeddingIndicesEnabled" value="true" />
|
||||||
</component>
|
</component>
|
||||||
<component name="FileTemplateManagerImpl">
|
<component name="FileTemplateManagerImpl">
|
||||||
@@ -229,7 +224,7 @@
|
|||||||
"node.js.selected.package.eslint": "(autodetect)",
|
"node.js.selected.package.eslint": "(autodetect)",
|
||||||
"node.js.selected.package.tslint": "(autodetect)",
|
"node.js.selected.package.tslint": "(autodetect)",
|
||||||
"nodejs_package_manager_path": "npm",
|
"nodejs_package_manager_path": "npm",
|
||||||
"settings.editor.selected.configurable": "settings.php.debug.servers",
|
"settings.editor.selected.configurable": "reference.webide.settings.project.settings.php.debug",
|
||||||
"vue.rearranger.settings.migration": "true"
|
"vue.rearranger.settings.migration": "true"
|
||||||
},
|
},
|
||||||
"keyToStringList": {
|
"keyToStringList": {
|
||||||
@@ -260,7 +255,17 @@
|
|||||||
<updated>1767956826164</updated>
|
<updated>1767956826164</updated>
|
||||||
<workItem from="1767956827666" duration="7866000" />
|
<workItem from="1767956827666" duration="7866000" />
|
||||||
<workItem from="1768201706520" duration="13383000" />
|
<workItem from="1768201706520" duration="13383000" />
|
||||||
|
<workItem from="1768287908317" duration="20308000" />
|
||||||
</task>
|
</task>
|
||||||
|
<task id="LOCAL-00001" summary="feat : Ajout de pinia, création de la table weight et reception mise en place du système de step pour les receptions (WIP)">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1768237763998</created>
|
||||||
|
<option name="number" value="00001" />
|
||||||
|
<option name="presentableId" value="LOCAL-00001" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1768237763998</updated>
|
||||||
|
</task>
|
||||||
|
<option name="localTasksCounter" value="2" />
|
||||||
<servers />
|
<servers />
|
||||||
</component>
|
</component>
|
||||||
<component name="TypeScriptGeneratedFilesManager">
|
<component name="TypeScriptGeneratedFilesManager">
|
||||||
@@ -277,14 +282,25 @@
|
|||||||
</map>
|
</map>
|
||||||
</option>
|
</option>
|
||||||
</component>
|
</component>
|
||||||
|
<component name="VcsManagerConfiguration">
|
||||||
|
<MESSAGE value="Feat : (WIP) Ajout de pinia, création de la table weight et reception mise en place du système de step pour les receptions" />
|
||||||
|
<MESSAGE value="Feat : Ajout de pinia, création de la table weight et reception mise en place du système de step pour les receptions (WIP)" />
|
||||||
|
<MESSAGE value="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="LAST_COMMIT_MESSAGE" value="feat : Ajout de pinia, création de la table weight et reception mise en place du système de step pour les receptions (WIP)" />
|
||||||
|
</component>
|
||||||
<component name="XDebuggerManager">
|
<component name="XDebuggerManager">
|
||||||
<breakpoint-manager>
|
<breakpoint-manager>
|
||||||
<breakpoints>
|
<breakpoints>
|
||||||
<line-breakpoint enabled="true" type="php">
|
<line-breakpoint enabled="true" type="php">
|
||||||
<url>file://$PROJECT_DIR$/src/State/ReceptionWeighingProvider.php</url>
|
<url>file://$PROJECT_DIR$/src/State/ReceptionWeighingProvider.php</url>
|
||||||
<line>28</line>
|
<line>27</line>
|
||||||
<option name="timeStamp" value="6" />
|
<option name="timeStamp" value="6" />
|
||||||
</line-breakpoint>
|
</line-breakpoint>
|
||||||
|
<line-breakpoint enabled="true" type="php">
|
||||||
|
<url>file://$PROJECT_DIR$/src/State/ReceptionWeighingProvider.php</url>
|
||||||
|
<line>22</line>
|
||||||
|
<option name="timeStamp" value="7" />
|
||||||
|
</line-breakpoint>
|
||||||
</breakpoints>
|
</breakpoints>
|
||||||
</breakpoint-manager>
|
</breakpoint-manager>
|
||||||
</component>
|
</component>
|
||||||
|
|||||||
@@ -1,36 +1,107 @@
|
|||||||
<template>
|
<template>
|
||||||
<h1>Formulaire</h1>
|
<form @submit.prevent="validate">
|
||||||
<button
|
<div class="grid grid-cols-1 items-start gap-8 mb-16">
|
||||||
@click="validate"
|
<h1 class="font-bold text-5xl uppercase">Réception</h1>
|
||||||
class="text-xl uppercase bg-primary-500 text-white h-[50px] w-[272px]"
|
<div class="flex flex-col">
|
||||||
>Valider
|
<label for="license-plate" class="font-bold uppercase text-xl mb-4">Immatriculation</label>
|
||||||
</button>
|
<input
|
||||||
|
id="license-plate"
|
||||||
|
v-model="form.licensePlate"
|
||||||
|
type="text"
|
||||||
|
class="border-b border-black justify-self-start text-xl pb-[6px] uppercase"
|
||||||
|
/>
|
||||||
|
<p v-if="fieldErrors.licensePlate" class="text-red-600 text-sm">{{ fieldErrors.licensePlate }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<label for="reception-date" class="font-bold uppercase text-xl mb-4">Date de reception</label>
|
||||||
|
<input
|
||||||
|
id="reception-date"
|
||||||
|
v-model="form.receptionDate"
|
||||||
|
type="date"
|
||||||
|
class="border-b border-black justify-self-start text-xl pb-[6px] uppercase"
|
||||||
|
/>
|
||||||
|
<p v-if="fieldErrors.receptionDate" class="text-red-600 text-sm">{{ fieldErrors.receptionDate }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-center">
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="text-xl uppercase bg-primary-500 text-white h-[50px] w-[272px] justify-self-end"
|
||||||
|
>Valider
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p v-if="errorMessage" class="text-red-600 mt-4">{{ errorMessage }}</p>
|
||||||
|
</form>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { storeToRefs } from 'pinia'
|
||||||
|
import { z } from 'zod'
|
||||||
|
import { mapZodErrors } from '~/utils/zod-errors'
|
||||||
import { useReceptionStore } from '~/stores/reception'
|
import { useReceptionStore } from '~/stores/reception'
|
||||||
|
|
||||||
|
type ReceptionFormData = {
|
||||||
|
licensePlate: string
|
||||||
|
receptionDate: string
|
||||||
|
}
|
||||||
|
|
||||||
const receptionStore = useReceptionStore()
|
const receptionStore = useReceptionStore()
|
||||||
const isLoading = ref<boolean>(false)
|
const { errorMessage: storeErrorMessage, current: storeReception } = storeToRefs(receptionStore)
|
||||||
const errorMessage = ref<string | null>(null)
|
const form = reactive<ReceptionFormData>({
|
||||||
|
licensePlate: '',
|
||||||
|
receptionDate: ''
|
||||||
|
})
|
||||||
|
const fieldErrors = reactive<Partial<Record<keyof ReceptionFormData, string>>>({
|
||||||
|
licensePlate: undefined,
|
||||||
|
receptionDate: undefined
|
||||||
|
})
|
||||||
|
const errorMessage = computed(() => storeErrorMessage.value)
|
||||||
|
const formSchema = z.object({
|
||||||
|
licensePlate: z
|
||||||
|
.string()
|
||||||
|
.min(1, 'Immatriculation requise.')
|
||||||
|
.max(20, 'Immatriculation trop longue (20 caracteres max).'),
|
||||||
|
receptionDate: z
|
||||||
|
.string()
|
||||||
|
.min(1, 'Date de reception requise.')
|
||||||
|
.regex(/^\d{4}-\d{2}-\d{2}$/, 'Date de reception invalide.')
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(
|
||||||
|
storeReception,
|
||||||
|
(reception) => {
|
||||||
|
form.licensePlate = reception?.licensePlate ?? ''
|
||||||
|
form.receptionDate = reception?.receptionDate ?? ''
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
)
|
||||||
|
|
||||||
async function validate() {
|
async function validate() {
|
||||||
if (!receptionStore.current) {
|
if (!receptionStore.current) {
|
||||||
errorMessage.value = 'Réception introuvable.'
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
isLoading.value = true
|
fieldErrors.licensePlate = undefined
|
||||||
try {
|
fieldErrors.receptionDate = undefined
|
||||||
const nextStep = receptionStore.current.currentStep + 1
|
const normalizedLicensePlate = form.licensePlate.trim()
|
||||||
await receptionStore.updateReception(receptionStore.current.id, {
|
const normalizedReceptionDate = form.receptionDate.trim()
|
||||||
currentStep: nextStep
|
const result = formSchema.safeParse({
|
||||||
})
|
licensePlate: normalizedLicensePlate,
|
||||||
} catch (error) {
|
receptionDate: normalizedReceptionDate
|
||||||
errorMessage.value = error.error ?? 'Erreur inconnue.'
|
})
|
||||||
} finally {
|
if (!result.success) {
|
||||||
isLoading.value = false
|
const errors = mapZodErrors<ReceptionFormData>(result.error)
|
||||||
|
fieldErrors.licensePlate = errors.licensePlate ?? 'Formulaire invalide.'
|
||||||
|
fieldErrors.receptionDate = errors.receptionDate ?? 'Formulaire invalide.'
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const nextStep = receptionStore.current.currentStep + 1
|
||||||
|
await receptionStore.updateReception(receptionStore.current.id, {
|
||||||
|
currentStep: nextStep,
|
||||||
|
licensePlate: normalizedLicensePlate || null,
|
||||||
|
receptionDate: normalizedReceptionDate || null
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
30
frontend/components/reception/reception-unloading.vue
Normal file
30
frontend/components/reception/reception-unloading.vue
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<template>
|
||||||
|
<div class="flex flex-col items-center mt-[164px] gap-32">
|
||||||
|
<div class="flex gap-8 items-center justify-center">
|
||||||
|
<!--@TODO Prendre en compte que l'on peut aussi décharger de la marchandise-->
|
||||||
|
<h1 class="text-3xl uppercase font-bold">Décharger les bêtes</h1>
|
||||||
|
<UiLoadingDots />
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
class="text-xl uppercase bg-primary-500 text-white h-[50px] w-[272px]"
|
||||||
|
@click="goNext"
|
||||||
|
>Suivant</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useReceptionStore } from '~/stores/reception'
|
||||||
|
|
||||||
|
const receptionStore = useReceptionStore()
|
||||||
|
|
||||||
|
async function goNext() {
|
||||||
|
if (!receptionStore.current) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextStep = receptionStore.current.currentStep + 1
|
||||||
|
await receptionStore.updateReception(receptionStore.current.id, {
|
||||||
|
currentStep: nextStep
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -1,41 +1,110 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-if="weightData">
|
<div class="flex justify-center">
|
||||||
<p>{{ weightData.weight }} kg</p>
|
<div class="flex flex-col items-center w-[660px]">
|
||||||
<p>DSD : {{ weightData.dsd }}</p>
|
<h1 class="font-bold text-5xl uppercase">{{ title }}</h1>
|
||||||
|
<!--@TODO Voir comment faire pour savoir si le pont-bascule et bien connecté + ajouter un icon comme sur la maquette-->
|
||||||
|
<p class="text-primary-500 uppercase text-2xl mt-2">Pont-bascule connecté</p>
|
||||||
|
<div
|
||||||
|
v-if="errorMessage || showLoadingBox"
|
||||||
|
class="w-full flex flex-col items-center justify-center border border-black h-[90px] mt-12 mb-[86px]">
|
||||||
|
<p v-if="errorMessage" class="text-red-500">{{ errorMessage }}</p>
|
||||||
|
<UiLoadingDots v-else />
|
||||||
|
</div>
|
||||||
|
<div v-else-if="displayWeight !== null" class="w-full">
|
||||||
|
<div
|
||||||
|
class="w-full flex flex-col items-center justify-center border border-black h-[90px] mt-12 mb-[25px] text-4xl">
|
||||||
|
{{ displayWeight }} kg
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 border border-black text-center">
|
||||||
|
<p class="border-r border-black py-3 text-4xl font-bold">DSD</p>
|
||||||
|
<p class="py-3 text-4xl">{{ displayDsd }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<div class="flex justify-center mt-[54px]">
|
||||||
class="text-xl uppercase bg-primary-500 text-white h-[50px] w-[272px]"
|
<button
|
||||||
@click="getReceptionWeight"
|
class="text-xl uppercase bg-primary-500 text-white h-[50px] w-[272px]"
|
||||||
>Peser</button>
|
@click="getReceptionWeight"
|
||||||
|
>{{ displayWeight !== null ? 'refaire une pesee' : 'peser' }}</button>
|
||||||
|
<button
|
||||||
|
v-if="displayWeight !== null"
|
||||||
|
class="text-xl uppercase bg-primary-500 text-white h-[50px] w-[272px] ml-4"
|
||||||
|
@click="validateWeight"
|
||||||
|
>Valider la pesée</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { storeToRefs } from 'pinia'
|
||||||
import { getWeight } from '~/services/reception'
|
import { getWeight } from '~/services/reception'
|
||||||
import type { WeightData } from '~/services/dto/weight-data'
|
import type { WeightData } from '~/services/dto/weight-data'
|
||||||
|
import { createWeight, updateWeight } from '~/services/weight'
|
||||||
import { useReceptionStore } from '~/stores/reception'
|
import { useReceptionStore } from '~/stores/reception'
|
||||||
|
|
||||||
const isLoading = ref(false)
|
const props = defineProps<{
|
||||||
|
mode: 'gross' | 'tare'
|
||||||
|
}>()
|
||||||
|
|
||||||
const weightData = ref<WeightData | null>(null)
|
const weightData = ref<WeightData | null>(null)
|
||||||
const errorMessage = ref<string | null>(null)
|
const localErrorMessage = ref<string | null>(null)
|
||||||
const receptionStore = useReceptionStore()
|
const receptionStore = useReceptionStore()
|
||||||
|
const { current: storeReception, errorMessage: storeErrorMessage } = storeToRefs(receptionStore)
|
||||||
|
const errorMessage = computed(() => localErrorMessage.value ?? storeErrorMessage.value)
|
||||||
|
const currentWeightEntry = computed(
|
||||||
|
() => storeReception.value?.weights?.find((entry) => entry.type === props.mode) ?? null
|
||||||
|
)
|
||||||
|
const displayWeight = computed(() => weightData.value?.weight ?? currentWeightEntry.value?.weight ?? null)
|
||||||
|
const displayDsd = computed(() => weightData.value?.dsd ?? currentWeightEntry.value?.dsd ?? '-')
|
||||||
|
const title = computed(() => (props.mode === 'gross' ? 'Pesée à plein' : 'Pesée à vide'))
|
||||||
|
const showLoadingBox = computed(() => displayWeight.value === null && !errorMessage.value)
|
||||||
|
|
||||||
async function getReceptionWeight() {
|
async function getReceptionWeight() {
|
||||||
isLoading.value = true
|
localErrorMessage.value = null
|
||||||
try {
|
try {
|
||||||
weightData.value = await getWeight()
|
weightData.value = await getWeight()
|
||||||
|
} catch (error) {
|
||||||
|
localErrorMessage.value = error?.data?.error ?? error?.message ?? 'Erreur inconnue.'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (receptionStore.current) {
|
async function validateWeight() {
|
||||||
const nextStep = receptionStore.current.currentStep + 1
|
localErrorMessage.value = null
|
||||||
await receptionStore.updateReception(receptionStore.current.id, {
|
|
||||||
dsd: weightData.value?.dsd ?? null,
|
const existingEntry = currentWeightEntry.value
|
||||||
weight: weightData.value?.weight ?? null,
|
const baseDsd = weightData.value?.dsd ?? existingEntry?.dsd ?? null
|
||||||
currentStep: nextStep
|
const baseWeight = weightData.value?.weight ?? existingEntry?.weight ?? null
|
||||||
|
const baseWeighedAt = weightData.value?.weighedAt ?? existingEntry?.weighedAt ?? null
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (existingEntry?.id) {
|
||||||
|
await updateWeight(existingEntry.id, {
|
||||||
|
type: props.mode,
|
||||||
|
dsd: baseDsd,
|
||||||
|
weight: baseWeight,
|
||||||
|
weighedAt: baseWeighedAt
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
await createWeight({
|
||||||
|
reception: `/receptions/${storeReception.value.id}`,
|
||||||
|
type: props.mode,
|
||||||
|
dsd: baseDsd,
|
||||||
|
weight: baseWeight,
|
||||||
|
weighedAt: baseWeighedAt
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
errorMessage.value = error.error
|
localErrorMessage.value = error?.data?.error ?? error?.message ?? 'Erreur inconnue.'
|
||||||
} finally {
|
return
|
||||||
isLoading.value = false
|
|
||||||
}
|
}
|
||||||
|
// @TODO Voir comment mettre en place la genération du bon, la validation de la reception et le dernier step
|
||||||
|
const nextStep = storeReception.value.currentStep + 1
|
||||||
|
await receptionStore.updateReception(storeReception.value.id, {
|
||||||
|
currentStep: nextStep,
|
||||||
|
isValid: props.mode === 'tare' ? true : storeReception.value.isValid
|
||||||
|
})
|
||||||
|
await receptionStore.loadReception(storeReception.value.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
50
frontend/components/ui/loading-dots.vue
Normal file
50
frontend/components/ui/loading-dots.vue
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
<template>
|
||||||
|
<div class="flex items-center gap-2 text-sm uppercase">
|
||||||
|
<span class="loader-dots">
|
||||||
|
<span class="loader-dot"></span>
|
||||||
|
<span class="loader-dot"></span>
|
||||||
|
<span class="loader-dot"></span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.loader-dots {
|
||||||
|
display: inline-flex;
|
||||||
|
gap: 4px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loader-dot {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
border-radius: 9999px;
|
||||||
|
background: currentColor;
|
||||||
|
animation: loader-bounce 1s infinite ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loader-dot:nth-child(2) {
|
||||||
|
animation-delay: 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loader-dot:nth-child(3) {
|
||||||
|
animation-delay: 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes loader-bounce {
|
||||||
|
0%,
|
||||||
|
80%,
|
||||||
|
100% {
|
||||||
|
transform: scale(0.6);
|
||||||
|
opacity: 0.4;
|
||||||
|
}
|
||||||
|
40% {
|
||||||
|
transform: scale(1);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
11
frontend/package-lock.json
generated
11
frontend/package-lock.json
generated
@@ -11,7 +11,8 @@
|
|||||||
"nuxt": "^4.2.2",
|
"nuxt": "^4.2.2",
|
||||||
"pinia": "^3.0.4",
|
"pinia": "^3.0.4",
|
||||||
"vue": "^3.5.26",
|
"vue": "^3.5.26",
|
||||||
"vue-router": "^4.6.4"
|
"vue-router": "^4.6.4",
|
||||||
|
"zod": "^4.3.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@nuxtjs/tailwindcss": "^6.14.0"
|
"@nuxtjs/tailwindcss": "^6.14.0"
|
||||||
@@ -11942,6 +11943,14 @@
|
|||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 14"
|
"node": ">= 14"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"node_modules/zod": {
|
||||||
|
"version": "4.3.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/zod/-/zod-4.3.5.tgz",
|
||||||
|
"integrity": "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g==",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,8 @@
|
|||||||
"nuxt": "^4.2.2",
|
"nuxt": "^4.2.2",
|
||||||
"pinia": "^3.0.4",
|
"pinia": "^3.0.4",
|
||||||
"vue": "^3.5.26",
|
"vue": "^3.5.26",
|
||||||
"vue-router": "^4.6.4"
|
"vue-router": "^4.6.4",
|
||||||
|
"zod": "^4.3.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@nuxtjs/tailwindcss": "^6.14.0"
|
"@nuxtjs/tailwindcss": "^6.14.0"
|
||||||
|
|||||||
@@ -1,42 +1,36 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-if="errorMessage" class="text-red-600">{{ errorMessage }}</div>
|
<div v-if="errorMessage" class="text-red-600">{{ errorMessage }}</div>
|
||||||
<div v-if="isLoading" class="text-neutral-600">Chargement...</div>
|
|
||||||
<div v-else>
|
<div v-else>
|
||||||
|
<div class="flex justify-between h-[52px] mb-[90px]">
|
||||||
|
<p class="self-center">Indicateur d’étapes</p>
|
||||||
|
<NuxtLink to="/" class="flex flex-col justify-center uppercase text-xl bg-black text-white h-[50px] w-[272px] text-center">Mettre en pause</NuxtLink>
|
||||||
|
</div>
|
||||||
<ReceptionForm v-if="storeReception?.currentStep === 0"/>
|
<ReceptionForm v-if="storeReception?.currentStep === 0"/>
|
||||||
<ReceptionWeight v-if="storeReception?.currentStep === 1"/>
|
<ReceptionWeight v-if="storeReception?.currentStep === 1" mode="gross"/>
|
||||||
<div v-if="storeReception?.currentStep === 2">Décharger</div>
|
<ReceptionUnloading v-if="storeReception?.currentStep === 2"/>
|
||||||
<ReceptionWeight v-if="storeReception?.currentStep === 3"/>
|
<ReceptionWeight v-if="storeReception?.currentStep === 3" mode="tare"/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { createReception, getReception } from '~/services/reception'
|
|
||||||
import type { ReceptionData } from '~/services/dto/reception-data'
|
|
||||||
import { useReceptionStore } from '~/stores/reception'
|
import { useReceptionStore } from '~/stores/reception'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
const isLoading = ref<boolean>(false)
|
|
||||||
const errorMessage = ref<string | null>(null)
|
|
||||||
const receptionStore = useReceptionStore()
|
const receptionStore = useReceptionStore()
|
||||||
const { current: storeReception } = storeToRefs(receptionStore)
|
const { current: storeReception, isLoading, errorMessage } = storeToRefs(receptionStore)
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
isLoading.value = true
|
|
||||||
const raw = route.params.id
|
const raw = route.params.id
|
||||||
const idStr = Array.isArray(raw) ? raw[0] : raw
|
const idStr = Array.isArray(raw) ? raw[0] : raw
|
||||||
const id = idStr ? Number(idStr) : null
|
const id = idStr ? Number(idStr) : null
|
||||||
|
|
||||||
try {
|
if (id === null) {
|
||||||
const result = id === null ? await createReception() : await getReception(id)
|
await receptionStore.createReception()
|
||||||
if (result) {
|
} else {
|
||||||
receptionStore.setCurrent(result as ReceptionData)
|
await receptionStore.loadReception(id)
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
errorMessage.value = error.error ?? 'Erreur inconnue.'
|
|
||||||
}
|
}
|
||||||
isLoading.value = false
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,9 +1,16 @@
|
|||||||
export interface ReceptionData {
|
export interface ReceptionData {
|
||||||
id: number
|
id: number
|
||||||
dsd: number | null
|
|
||||||
licensePlate: string | null
|
licensePlate: string | null
|
||||||
weight: number | null
|
weights?: WeightEntryData[] | null
|
||||||
receptionDate: string
|
receptionDate: string
|
||||||
currentStep: number
|
currentStep: number
|
||||||
isValid: boolean
|
isValid: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface WeightEntryData {
|
||||||
|
id?: number
|
||||||
|
type: 'gross' | 'tare'
|
||||||
|
dsd: number | null
|
||||||
|
weight: number | null
|
||||||
|
weighedAt: string | null
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
export interface WeightData {
|
export interface WeightData {
|
||||||
weight: number | null
|
weight: number | null
|
||||||
dsd: number | null
|
dsd: number | null
|
||||||
receptionDate: string
|
weighedAt: string | null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,6 +45,6 @@ export async function getWeight(): Promise<WeightData> {
|
|||||||
return await api.get<WeightData>('receptions/weigh')
|
return await api.get<WeightData>('receptions/weigh')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error.message, error)
|
console.error(error.message, error)
|
||||||
return error
|
throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
30
frontend/services/weight.ts
Normal file
30
frontend/services/weight.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import { useApi } from '~/composables/useApi'
|
||||||
|
import type { WeightEntryData } from '~/services/dto/reception-data'
|
||||||
|
|
||||||
|
const api = useApi()
|
||||||
|
|
||||||
|
export type WeightPayload = {
|
||||||
|
reception: string
|
||||||
|
type: 'gross' | 'tare'
|
||||||
|
dsd: number | null
|
||||||
|
weight: number | null
|
||||||
|
weighedAt: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createWeight(payload: WeightPayload) {
|
||||||
|
try {
|
||||||
|
return await api.post<WeightEntryData>('weights', payload)
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error.message, error)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateWeight(id: number, payload: Partial<WeightPayload>) {
|
||||||
|
try {
|
||||||
|
return await api.patch<WeightEntryData>(`weights/${id}`, payload)
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error.message, error)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
17
frontend/utils/zod-errors.ts
Normal file
17
frontend/utils/zod-errors.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import type { ZodError } from 'zod'
|
||||||
|
|
||||||
|
export type FieldErrors<T extends Record<string, unknown>> = Partial<Record<keyof T, string>>
|
||||||
|
|
||||||
|
export const mapZodErrors = <T extends Record<string, unknown>>(error: ZodError<T>): FieldErrors<T> => {
|
||||||
|
const flattened = error.flatten().fieldErrors
|
||||||
|
const result: FieldErrors<T> = {}
|
||||||
|
|
||||||
|
for (const key in flattened) {
|
||||||
|
const message = flattened[key]?.[0]
|
||||||
|
if (message) {
|
||||||
|
result[key as keyof T] = message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
28
migrations/Version20260112000500.php
Normal file
28
migrations/Version20260112000500.php
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace DoctrineMigrations;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
|
|
||||||
|
final class Version20260112000500 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return 'Remove dsd and weight columns from reception';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function up(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->addSql('ALTER TABLE reception DROP dsd');
|
||||||
|
$this->addSql('ALTER TABLE reception DROP weight');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->addSql('ALTER TABLE reception ADD dsd INT DEFAULT NULL');
|
||||||
|
$this->addSql('ALTER TABLE reception ADD weight DOUBLE PRECISION DEFAULT NULL');
|
||||||
|
}
|
||||||
|
}
|
||||||
42
migrations/Version20260112000600.php
Normal file
42
migrations/Version20260112000600.php
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace DoctrineMigrations;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
|
|
||||||
|
final class Version20260112000600 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return 'Update weight table to store single weighings with type';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function up(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->addSql('DROP INDEX UNIQ_7B4E3B2304A72F3F');
|
||||||
|
$this->addSql('ALTER TABLE weight DROP gross_weight');
|
||||||
|
$this->addSql('ALTER TABLE weight DROP tare_weight');
|
||||||
|
$this->addSql('ALTER TABLE weight DROP gross_weighed_at');
|
||||||
|
$this->addSql('ALTER TABLE weight DROP tare_weighed_at');
|
||||||
|
$this->addSql('ALTER TABLE weight ADD dsd INT DEFAULT NULL');
|
||||||
|
$this->addSql('ALTER TABLE weight ADD weight INT DEFAULT NULL');
|
||||||
|
$this->addSql('ALTER TABLE weight ADD weighed_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL');
|
||||||
|
$this->addSql('ALTER TABLE weight ADD type VARCHAR(10) DEFAULT \'gross\' NOT NULL');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->addSql('ALTER TABLE weight DROP dsd');
|
||||||
|
$this->addSql('ALTER TABLE weight DROP weight');
|
||||||
|
$this->addSql('ALTER TABLE weight DROP weighed_at');
|
||||||
|
$this->addSql('ALTER TABLE weight DROP type');
|
||||||
|
$this->addSql('ALTER TABLE weight ADD gross_weight INT DEFAULT NULL');
|
||||||
|
$this->addSql('ALTER TABLE weight ADD tare_weight INT DEFAULT NULL');
|
||||||
|
$this->addSql('ALTER TABLE weight ADD gross_weighed_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL');
|
||||||
|
$this->addSql('ALTER TABLE weight ADD tare_weighed_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL');
|
||||||
|
$this->addSql('CREATE UNIQUE INDEX UNIQ_7B4E3B2304A72F3F ON weight (reception_id)');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,13 +5,20 @@ declare(strict_types=1);
|
|||||||
namespace App\Dto;
|
namespace App\Dto;
|
||||||
|
|
||||||
use DateTimeImmutable;
|
use DateTimeImmutable;
|
||||||
|
use Symfony\Component\Serializer\Attribute\Context;
|
||||||
|
use Symfony\Component\Serializer\Attribute\Groups;
|
||||||
|
use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
|
||||||
|
|
||||||
final readonly class PontBasculeReading
|
final readonly class PontBasculeReading
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
|
#[Groups(['reception:weigh:read'])]
|
||||||
private ?int $dsd,
|
private ?int $dsd,
|
||||||
|
#[Groups(['reception:weigh:read'])]
|
||||||
private ?float $weight,
|
private ?float $weight,
|
||||||
private ?DateTimeImmutable $datetime = null,
|
#[Groups(['reception:weigh:read'])]
|
||||||
|
#[Context([DateTimeNormalizer::FORMAT_KEY => 'Y-m-d'])]
|
||||||
|
private ?DateTimeImmutable $weighedAt = null,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public function getDsd(): ?int
|
public function getDsd(): ?int
|
||||||
@@ -24,8 +31,8 @@ final readonly class PontBasculeReading
|
|||||||
return $this->weight;
|
return $this->weight;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getDatetime(): ?DateTimeImmutable
|
public function getWeighedAt(): ?DateTimeImmutable
|
||||||
{
|
{
|
||||||
return $this->datetime;
|
return $this->weighedAt;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,10 +10,15 @@ use ApiPlatform\Metadata\GetCollection;
|
|||||||
use ApiPlatform\Metadata\Patch;
|
use ApiPlatform\Metadata\Patch;
|
||||||
use ApiPlatform\Metadata\Post;
|
use ApiPlatform\Metadata\Post;
|
||||||
use ApiPlatform\OpenApi\Model\Operation as OpenApiOperation;
|
use ApiPlatform\OpenApi\Model\Operation as OpenApiOperation;
|
||||||
|
use App\Dto\PontBasculeReading;
|
||||||
use App\State\ReceptionWeighingProvider;
|
use App\State\ReceptionWeighingProvider;
|
||||||
use DateTimeImmutable;
|
use DateTimeImmutable;
|
||||||
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
|
use Doctrine\Common\Collections\Collection;
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
use Symfony\Component\Serializer\Attribute\Context;
|
||||||
use Symfony\Component\Serializer\Attribute\Groups;
|
use Symfony\Component\Serializer\Attribute\Groups;
|
||||||
|
use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
|
||||||
|
|
||||||
#[ORM\Entity]
|
#[ORM\Entity]
|
||||||
#[ORM\HasLifecycleCallbacks]
|
#[ORM\HasLifecycleCallbacks]
|
||||||
@@ -21,6 +26,7 @@ use Symfony\Component\Serializer\Attribute\Groups;
|
|||||||
#[ApiResource(
|
#[ApiResource(
|
||||||
operations: [
|
operations: [
|
||||||
new Get(
|
new Get(
|
||||||
|
requirements: ['id' => '\d+'],
|
||||||
normalizationContext: ['groups' => ['reception:read']],
|
normalizationContext: ['groups' => ['reception:read']],
|
||||||
),
|
),
|
||||||
new GetCollection(
|
new GetCollection(
|
||||||
@@ -31,6 +37,7 @@ use Symfony\Component\Serializer\Attribute\Groups;
|
|||||||
denormalizationContext: ['groups' => ['reception:write']],
|
denormalizationContext: ['groups' => ['reception:write']],
|
||||||
),
|
),
|
||||||
new Patch(
|
new Patch(
|
||||||
|
requirements: ['id' => '\d+'],
|
||||||
normalizationContext: ['groups' => ['reception:read']],
|
normalizationContext: ['groups' => ['reception:read']],
|
||||||
denormalizationContext: ['groups' => ['reception:write']],
|
denormalizationContext: ['groups' => ['reception:write']],
|
||||||
),
|
),
|
||||||
@@ -40,7 +47,8 @@ use Symfony\Component\Serializer\Attribute\Groups;
|
|||||||
summary: 'Fetch the current weight reading',
|
summary: 'Fetch the current weight reading',
|
||||||
description: 'Queries the pont-bascule and returns the weight data.',
|
description: 'Queries the pont-bascule and returns the weight data.',
|
||||||
),
|
),
|
||||||
normalizationContext: ['groups' => ['reception:read']],
|
normalizationContext: ['groups' => ['reception:weigh:read']],
|
||||||
|
output: PontBasculeReading::class,
|
||||||
provider: ReceptionWeighingProvider::class,
|
provider: ReceptionWeighingProvider::class,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -53,14 +61,6 @@ class Reception
|
|||||||
#[Groups(['reception:read'])]
|
#[Groups(['reception:read'])]
|
||||||
private ?int $id = null;
|
private ?int $id = null;
|
||||||
|
|
||||||
#[ORM\Column(nullable: true)]
|
|
||||||
#[Groups(['reception:read', 'reception:write'])]
|
|
||||||
private ?int $dsd = null;
|
|
||||||
|
|
||||||
#[ORM\Column(type: 'float', nullable: true)]
|
|
||||||
#[Groups(['reception:read', 'reception:write'])]
|
|
||||||
private ?float $weight = null;
|
|
||||||
|
|
||||||
#[ORM\Column(length: 20, nullable: true)]
|
#[ORM\Column(length: 20, nullable: true)]
|
||||||
#[Groups(['reception:read', 'reception:write'])]
|
#[Groups(['reception:read', 'reception:write'])]
|
||||||
private ?string $licensePlate = null;
|
private ?string $licensePlate = null;
|
||||||
@@ -74,20 +74,19 @@ class Reception
|
|||||||
private bool $isValid = false;
|
private bool $isValid = false;
|
||||||
|
|
||||||
#[ORM\Column(name: 'date_reception', type: 'datetime_immutable')]
|
#[ORM\Column(name: 'date_reception', type: 'datetime_immutable')]
|
||||||
#[Groups(['reception:read'])]
|
#[Groups(['reception:read', 'reception:write'])]
|
||||||
|
#[Context([DateTimeNormalizer::FORMAT_KEY => 'Y-m-d'])]
|
||||||
private ?DateTimeImmutable $receptionDate = null;
|
private ?DateTimeImmutable $receptionDate = null;
|
||||||
|
|
||||||
#[ORM\OneToOne(targetEntity: Weight::class, mappedBy: 'reception', cascade: ['persist', 'remove'])]
|
#[ORM\OneToMany(targetEntity: Weight::class, mappedBy: 'reception', cascade: ['persist', 'remove'], orphanRemoval: true)]
|
||||||
private ?Weight $weightEntry = null;
|
#[Groups(['reception:read'])]
|
||||||
|
private Collection $weights;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
?int $dsd = null,
|
|
||||||
?float $weight = null,
|
|
||||||
?DateTimeImmutable $receptionDate = null,
|
?DateTimeImmutable $receptionDate = null,
|
||||||
) {
|
) {
|
||||||
$this->dsd = $dsd;
|
|
||||||
$this->weight = $weight;
|
|
||||||
$this->receptionDate = $receptionDate;
|
$this->receptionDate = $receptionDate;
|
||||||
|
$this->weights = new ArrayCollection();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getId(): ?int
|
public function getId(): ?int
|
||||||
@@ -95,32 +94,6 @@ class Reception
|
|||||||
return $this->id;
|
return $this->id;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Groups(['reception:read'])]
|
|
||||||
public function getDsd(): ?int
|
|
||||||
{
|
|
||||||
return $this->dsd;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setDsd(?int $dsd): self
|
|
||||||
{
|
|
||||||
$this->dsd = $dsd;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[Groups(['reception:read'])]
|
|
||||||
public function getWeight(): ?float
|
|
||||||
{
|
|
||||||
return $this->weight;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setWeight(?float $weight): self
|
|
||||||
{
|
|
||||||
$this->weight = $weight;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[Groups(['reception:read'])]
|
#[Groups(['reception:read'])]
|
||||||
public function getLicensePlate(): ?string
|
public function getLicensePlate(): ?string
|
||||||
{
|
{
|
||||||
@@ -173,17 +146,30 @@ class Reception
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getWeightEntry(): ?Weight
|
/**
|
||||||
|
* @return Collection<int, Weight>
|
||||||
|
*/
|
||||||
|
public function getWeights(): Collection
|
||||||
{
|
{
|
||||||
return $this->weightEntry;
|
return $this->weights;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setWeightEntry(?Weight $weightEntry): self
|
public function addWeight(Weight $weight): self
|
||||||
{
|
{
|
||||||
$this->weightEntry = $weightEntry;
|
if (!$this->weights->contains($weight)) {
|
||||||
|
$this->weights->add($weight);
|
||||||
|
$weight->setReception($this);
|
||||||
|
}
|
||||||
|
|
||||||
if (null !== $weightEntry && $weightEntry->getReception() !== $this) {
|
return $this;
|
||||||
$weightEntry->setReception($this);
|
}
|
||||||
|
|
||||||
|
public function removeWeight(Weight $weight): self
|
||||||
|
{
|
||||||
|
if ($this->weights->removeElement($weight)) {
|
||||||
|
if ($weight->getReception() === $this) {
|
||||||
|
$weight->setReception(null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
|
|||||||
@@ -4,33 +4,62 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace App\Entity;
|
namespace App\Entity;
|
||||||
|
|
||||||
|
use ApiPlatform\Metadata\ApiResource;
|
||||||
|
use ApiPlatform\Metadata\Get;
|
||||||
|
use ApiPlatform\Metadata\GetCollection;
|
||||||
|
use ApiPlatform\Metadata\Patch;
|
||||||
|
use ApiPlatform\Metadata\Post;
|
||||||
use DateTimeImmutable;
|
use DateTimeImmutable;
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
use Symfony\Component\Serializer\Attribute\Context;
|
||||||
|
use Symfony\Component\Serializer\Attribute\Groups;
|
||||||
|
use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
|
||||||
|
|
||||||
#[ORM\Entity]
|
#[ORM\Entity]
|
||||||
#[ORM\Table(name: 'weight')]
|
#[ORM\Table(name: 'weight')]
|
||||||
|
#[ApiResource(
|
||||||
|
operations: [
|
||||||
|
new Get(normalizationContext: ['groups' => ['weight:read']]),
|
||||||
|
new GetCollection(normalizationContext: ['groups' => ['weight:read']]),
|
||||||
|
new Post(
|
||||||
|
normalizationContext: ['groups' => ['weight:read']],
|
||||||
|
denormalizationContext: ['groups' => ['weight:write']],
|
||||||
|
),
|
||||||
|
new Patch(
|
||||||
|
normalizationContext: ['groups' => ['weight:read']],
|
||||||
|
denormalizationContext: ['groups' => ['weight:write']],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)]
|
||||||
class Weight
|
class Weight
|
||||||
{
|
{
|
||||||
#[ORM\Id]
|
#[ORM\Id]
|
||||||
#[ORM\GeneratedValue]
|
#[ORM\GeneratedValue]
|
||||||
#[ORM\Column]
|
#[ORM\Column]
|
||||||
|
#[Groups(['reception:read', 'weight:read'])]
|
||||||
private ?int $id = null;
|
private ?int $id = null;
|
||||||
|
|
||||||
#[ORM\OneToOne(inversedBy: 'weightEntry')]
|
#[ORM\ManyToOne(inversedBy: 'weights')]
|
||||||
#[ORM\JoinColumn(nullable: false)]
|
#[ORM\JoinColumn(nullable: false)]
|
||||||
|
#[Groups(['weight:read', 'weight:write'])]
|
||||||
private ?Reception $reception = null;
|
private ?Reception $reception = null;
|
||||||
|
|
||||||
#[ORM\Column(nullable: true)]
|
#[ORM\Column(nullable: true)]
|
||||||
private ?int $grossWeight = null;
|
#[Groups(['reception:read', 'weight:read', 'weight:write'])]
|
||||||
|
private ?int $dsd = null;
|
||||||
|
|
||||||
#[ORM\Column(nullable: true)]
|
#[ORM\Column(nullable: true)]
|
||||||
private ?int $tareWeight = null;
|
#[Groups(['reception:read', 'weight:read', 'weight:write'])]
|
||||||
|
private ?int $weight = null;
|
||||||
|
|
||||||
#[ORM\Column(type: 'datetime_immutable', nullable: true)]
|
#[ORM\Column(type: 'datetime_immutable', nullable: true)]
|
||||||
private ?DateTimeImmutable $grossWeighedAt = null;
|
#[Groups(['reception:read', 'weight:read', 'weight:write'])]
|
||||||
|
#[Context([DateTimeNormalizer::FORMAT_KEY => 'Y-m-d'])]
|
||||||
|
private ?DateTimeImmutable $weighedAt = null;
|
||||||
|
|
||||||
#[ORM\Column(type: 'datetime_immutable', nullable: true)]
|
#[ORM\Column(length: 10)]
|
||||||
private ?DateTimeImmutable $tareWeighedAt = null;
|
#[Groups(['reception:read', 'weight:read', 'weight:write'])]
|
||||||
|
private string $type = 'gross';
|
||||||
|
|
||||||
public function getId(): ?int
|
public function getId(): ?int
|
||||||
{
|
{
|
||||||
@@ -46,57 +75,57 @@ class Weight
|
|||||||
{
|
{
|
||||||
$this->reception = $reception;
|
$this->reception = $reception;
|
||||||
|
|
||||||
if (null !== $reception && $reception->getWeightEntry() !== $this) {
|
if (null !== $reception && !$reception->getWeights()->contains($this)) {
|
||||||
$reception->setWeightEntry($this);
|
$reception->addWeight($this);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getGrossWeight(): ?int
|
public function getDsd(): ?int
|
||||||
{
|
{
|
||||||
return $this->grossWeight;
|
return $this->dsd;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setGrossWeight(?int $grossWeight): self
|
public function setDsd(?int $dsd): self
|
||||||
{
|
{
|
||||||
$this->grossWeight = $grossWeight;
|
$this->dsd = $dsd;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getTareWeight(): ?int
|
public function getWeight(): ?int
|
||||||
{
|
{
|
||||||
return $this->tareWeight;
|
return $this->weight;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setTareWeight(?int $tareWeight): self
|
public function setWeight(?int $weight): self
|
||||||
{
|
{
|
||||||
$this->tareWeight = $tareWeight;
|
$this->weight = $weight;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getGrossWeighedAt(): ?DateTimeImmutable
|
public function getWeighedAt(): ?DateTimeImmutable
|
||||||
{
|
{
|
||||||
return $this->grossWeighedAt;
|
return $this->weighedAt;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setGrossWeighedAt(?DateTimeImmutable $grossWeighedAt): self
|
public function setWeighedAt(?DateTimeImmutable $weighedAt): self
|
||||||
{
|
{
|
||||||
$this->grossWeighedAt = $grossWeighedAt;
|
$this->weighedAt = $weighedAt;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getTareWeighedAt(): ?DateTimeImmutable
|
public function getType(): string
|
||||||
{
|
{
|
||||||
return $this->tareWeighedAt;
|
return $this->type;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setTareWeighedAt(?DateTimeImmutable $tareWeighedAt): self
|
public function setType(string $type): self
|
||||||
{
|
{
|
||||||
$this->tareWeighedAt = $tareWeighedAt;
|
$this->type = $type;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ namespace App\State;
|
|||||||
|
|
||||||
use ApiPlatform\Metadata\Operation;
|
use ApiPlatform\Metadata\Operation;
|
||||||
use ApiPlatform\State\ProviderInterface;
|
use ApiPlatform\State\ProviderInterface;
|
||||||
use App\Entity\Reception;
|
use App\Dto\PontBasculeReading;
|
||||||
use App\Exception\PontBasculeException;
|
use App\Exception\PontBasculeException;
|
||||||
use App\Service\PontBasculeService;
|
use App\Service\PontBasculeService;
|
||||||
use Symfony\Component\HttpKernel\Exception\HttpException;
|
use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||||
@@ -17,7 +17,7 @@ final readonly class ReceptionWeighingProvider implements ProviderInterface
|
|||||||
private PontBasculeService $pontBasculeService,
|
private PontBasculeService $pontBasculeService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public function provide(Operation $operation, array $uriVariables = [], array $context = []): ?Reception
|
public function provide(Operation $operation, array $uriVariables = [], array $context = []): ?PontBasculeReading
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$result = $this->pontBasculeService->fetch();
|
$result = $this->pontBasculeService->fetch();
|
||||||
@@ -25,10 +25,6 @@ final readonly class ReceptionWeighingProvider implements ProviderInterface
|
|||||||
throw new HttpException(500, $exception->getMessage(), $exception);
|
throw new HttpException(500, $exception->getMessage(), $exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Reception(
|
return $result;
|
||||||
dsd: $result->getDsd(),
|
|
||||||
weight: $result->getWeight(),
|
|
||||||
receptionDate: $result->getDatetime(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user