[#203] Réceptions — Parcours de pesée multi-étapes #3

Merged
tristan merged 10 commits from feat/203-reception-parcours-pesee-multi-etapas into develop 2026-01-14 07:17:34 +00:00
6 changed files with 157 additions and 80 deletions
Showing only changes of commit 46af62483f - Show all commits

29
.idea/workspace.xml generated
View File

@@ -4,23 +4,13 @@
<option name="autoReloadType" value="SELECTIVE" /> <option name="autoReloadType" value="SELECTIVE" />
</component> </component>
<component name="ChangeListManager"> <component name="ChangeListManager">
<list default="true" id="7c107abe-5995-4428-8429-b146aaca8386" name="Changes" comment="feat : Ajout de pinia, création de la table weight et reception mise en place du système de step pour les receptions (WIP)"> <list default="true" id="7c107abe-5995-4428-8429-b146aaca8386" name="Changes" comment="feat : Ajout de zod, création d'un composant de chargement loading-dots.vue et finalisation du flow d'une reception">
<change afterPath="$PROJECT_DIR$/frontend/components/reception/reception-unloading.vue" afterDir="false" />
<change afterPath="$PROJECT_DIR$/frontend/components/ui/loading-dots.vue" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" /> <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/config/reference.php" beforeDir="false" afterPath="$PROJECT_DIR$/config/reference.php" afterDir="false" /> <change beforePath="$PROJECT_DIR$/config/reference.php" beforeDir="false" afterPath="$PROJECT_DIR$/config/reference.php" afterDir="false" />
<change beforePath="$PROJECT_DIR$/frontend/components/reception/reception-form.vue" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/components/reception/reception-form.vue" afterDir="false" />
<change beforePath="$PROJECT_DIR$/frontend/components/reception/reception-weight.vue" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/components/reception/reception-weight.vue" afterDir="false" /> <change beforePath="$PROJECT_DIR$/frontend/components/reception/reception-weight.vue" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/components/reception/reception-weight.vue" afterDir="false" />
<change beforePath="$PROJECT_DIR$/frontend/package-lock.json" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/package-lock.json" afterDir="false" /> <change beforePath="$PROJECT_DIR$/frontend/layouts/default.vue" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/layouts/default.vue" afterDir="false" />
<change beforePath="$PROJECT_DIR$/frontend/package.json" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/package.json" 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/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/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" />
@@ -255,7 +245,7 @@
<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" /> <workItem from="1768287908317" duration="21021000" />
</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)"> <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" /> <option name="closed" value="true" />
@@ -265,7 +255,15 @@
<option name="project" value="LOCAL" /> <option name="project" value="LOCAL" />
<updated>1768237763998</updated> <updated>1768237763998</updated>
</task> </task>
<option name="localTasksCounter" value="2" /> <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>
<option name="localTasksCounter" value="3" />
<servers /> <servers />
</component> </component>
<component name="TypeScriptGeneratedFilesManager"> <component name="TypeScriptGeneratedFilesManager">
@@ -286,7 +284,8 @@
<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 : (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)" />
<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)" /> <MESSAGE value="feat : Ajout de zod, création d'un composant de chargement loading-dots.vue et finalisation du flow d'une reception" />
<option name="LAST_COMMIT_MESSAGE" value="feat : Ajout de zod, création d'un composant de chargement loading-dots.vue et finalisation du flow d'une reception" />
</component> </component>
<component name="XDebuggerManager"> <component name="XDebuggerManager">
<breakpoint-manager> <breakpoint-manager>

View File

@@ -25,12 +25,12 @@
<div class="flex justify-center mt-[54px]"> <div class="flex justify-center mt-[54px]">
<button <button
class="text-xl uppercase bg-primary-500 text-white h-[50px] w-[272px]" class="text-xl uppercase bg-primary-500 text-white h-[50px] w-[272px]"
@click="getReceptionWeight" @click="fetchWeight"
>{{ displayWeight !== null ? 'refaire une pesee' : 'peser' }}</button> >{{ displayWeight !== null ? 'refaire une pesee' : 'peser' }}</button>
<button <button
v-if="displayWeight !== null" v-if="displayWeight !== null"
class="text-xl uppercase bg-primary-500 text-white h-[50px] w-[272px] ml-4" class="text-xl uppercase bg-primary-500 text-white h-[50px] w-[272px] ml-4"
@click="validateWeight" @click="saveWeight"
>Valider la pesée</button> >Valider la pesée</button>
</div> </div>
@@ -38,73 +38,30 @@
<script setup lang="ts"> <script setup lang="ts">
import { storeToRefs } from 'pinia' import { storeToRefs } from 'pinia'
import { getWeight } from '~/services/reception' import { useWeighing } from '~/composables/useWeighing'
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 props = defineProps<{ const props = defineProps<{
mode: 'gross' | 'tare' mode: 'gross' | 'tare'
}>() }>()
const weightData = ref<WeightData | null>(null)
const localErrorMessage = ref<string | null>(null)
const receptionStore = useReceptionStore() const receptionStore = useReceptionStore()
const { current: storeReception, errorMessage: storeErrorMessage } = storeToRefs(receptionStore) const { current: storeReception, errorMessage: storeErrorMessage } = storeToRefs(receptionStore)
const errorMessage = computed(() => localErrorMessage.value ?? storeErrorMessage.value) const {
const currentWeightEntry = computed( displayWeight,
() => storeReception.value?.weights?.find((entry) => entry.type === props.mode) ?? null displayDsd,
) title,
const displayWeight = computed(() => weightData.value?.weight ?? currentWeightEntry.value?.weight ?? null) errorMessage,
const displayDsd = computed(() => weightData.value?.dsd ?? currentWeightEntry.value?.dsd ?? '-') showLoadingBox,
const title = computed(() => (props.mode === 'gross' ? 'Pesée à plein' : 'Pesée à vide')) fetchWeight,
const showLoadingBox = computed(() => displayWeight.value === null && !errorMessage.value) saveWeight
} = useWeighing({
async function getReceptionWeight() { mode: props.mode,
localErrorMessage.value = null reception: storeReception,
try { updateReception: receptionStore.updateReception,
weightData.value = await getWeight() loadReception: receptionStore.loadReception,
} catch (error) { storeError: storeErrorMessage
localErrorMessage.value = error?.data?.error ?? error?.message ?? 'Erreur inconnue.'
}
}
async function validateWeight() {
localErrorMessage.value = null
const existingEntry = currentWeightEntry.value
const baseDsd = weightData.value?.dsd ?? existingEntry?.dsd ?? null
const baseWeight = weightData.value?.weight ?? existingEntry?.weight ?? null
const baseWeighedAt = weightData.value?.weighedAt ?? existingEntry?.weighedAt ?? null
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) {
localErrorMessage.value = error?.data?.error ?? error?.message ?? 'Erreur inconnue.'
return
}
// @TODO Voir comment mettre en place la genération du bon, la validation de la reception et le dernier step // @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>

View File

@@ -0,0 +1,109 @@
import { computed, ref } from 'vue'
import type { Ref } from 'vue'
import type { ReceptionData, WeightEntryData } from '~/services/dto/reception-data'
import type { WeightData } from '~/services/dto/weight-data'
import { getWeight } from '~/services/reception'
import { createWeight, updateWeight } from '~/services/weight'
export type WeighingMode = 'gross' | 'tare'
type UseWeighingOptions = {
mode: WeighingMode
reception: Ref<ReceptionData | null>
updateReception: (id: number, payload: Partial<ReceptionData>) => Promise<ReceptionData | null>
loadReception?: (id: number) => Promise<ReceptionData | null>
storeError?: Ref<string | null>
}
export const useWeighing = ({
mode,
reception,
updateReception,
loadReception,
storeError
}: UseWeighingOptions) => {
const weightData = ref<WeightData | null>(null)
const localErrorMessage = ref<string | null>(null)
const currentWeightEntry = computed<WeightEntryData | null>(() => {
const weights = reception.value?.weights ?? []
return weights.find((entry) => entry.type === mode) ?? null
})
const displayWeight = computed(() => weightData.value?.weight ?? currentWeightEntry.value?.weight ?? null)
const displayDsd = computed(() => weightData.value?.dsd ?? currentWeightEntry.value?.dsd ?? '-')
const title = computed(() => (mode === 'gross' ? 'Pesée à plein' : 'Pesée à vide'))
const errorMessage = computed(() => localErrorMessage.value ?? storeError?.value ?? null)
const showLoadingBox = computed(() => displayWeight.value === null && !errorMessage.value)
const fetchWeight = async () => {
localErrorMessage.value = null
try {
weightData.value = await getWeight()
} catch (error) {
localErrorMessage.value = error?.data?.error ?? error?.message ?? 'Erreur inconnue.'
}
}
const saveWeight = async () => {
localErrorMessage.value = null
if (!reception.value) {
localErrorMessage.value = 'Réception introuvable.'
return
}
const existingEntry = currentWeightEntry.value
const baseDsd = weightData.value?.dsd ?? existingEntry?.dsd ?? null
const baseWeight = weightData.value?.weight ?? existingEntry?.weight ?? null
const baseWeighedAt = weightData.value?.weighedAt ?? existingEntry?.weighedAt ?? null
if (baseWeight === null) {
localErrorMessage.value = 'Veuillez dabord peser.'
Review

Possible d'avoir un system de trad pour les avoir toute au même endroit ? pour le moment que fr mais ont en sait jamais

Possible d'avoir un system de trad pour les avoir toute au même endroit ? pour le moment que fr mais ont en sait jamais
return
}
try {
if (existingEntry?.id) {
await updateWeight(existingEntry.id, {
type: mode,
dsd: baseDsd,
weight: baseWeight,
weighedAt: baseWeighedAt
})
} else {
await createWeight({
reception: `/receptions/${reception.value.id}`,
type: mode,
dsd: baseDsd,
weight: baseWeight,
weighedAt: baseWeighedAt
})
}
} catch (error) {
localErrorMessage.value = error?.data?.error ?? error?.message ?? 'Erreur inconnue.'
return
}
const nextStep = reception.value.currentStep + 1
await updateReception(reception.value.id, {
currentStep: nextStep,
isValid: mode === 'tare' ? true : reception.value.isValid
})
if (loadReception) {
await loadReception(reception.value.id)
}
}
return {
weightData,
currentWeightEntry,
displayWeight,
displayDsd,
title,
errorMessage,
showLoadingBox,
fetchWeight,
saveWeight
}
}

View File

@@ -23,7 +23,7 @@
<a <a
:href="href" :href="href"
@click="navigate" @click="navigate"
:class="isActive ? 'opacity-100' : 'opacity-50'" :class="isReceptionActive ? 'opacity-100' : 'opacity-50'"
> >
Reception Reception
</a> </a>
@@ -36,3 +36,8 @@
</main> </main>
</div> </div>
</template> </template>
<script setup lang="ts">
const route = useRoute()
const isReceptionActive = computed(() => route.path.startsWith('/reception'))
</script>

View File

@@ -3,7 +3,7 @@
<div v-else> <div v-else>
<div class="flex justify-between h-[52px] mb-[90px]"> <div class="flex justify-between h-[52px] mb-[90px]">
<p class="self-center">Indicateur détapes</p> <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> <NuxtLink to="/" class="flex flex-col justify-center uppercase text-xl bg-black text-white h-[50px] w-[272px] text-center">Mettre en attente</NuxtLink>
</div> </div>
<ReceptionForm v-if="storeReception?.currentStep === 0"/> <ReceptionForm v-if="storeReception?.currentStep === 0"/>
<ReceptionWeight v-if="storeReception?.currentStep === 1" mode="gross"/> <ReceptionWeight v-if="storeReception?.currentStep === 1" mode="gross"/>
@@ -20,7 +20,7 @@ const route = useRoute()
const router = useRouter() const router = useRouter()
const receptionStore = useReceptionStore() const receptionStore = useReceptionStore()
const { current: storeReception, isLoading, errorMessage } = storeToRefs(receptionStore) const { current: storeReception, errorMessage } = storeToRefs(receptionStore)
onMounted(async () => { onMounted(async () => {
const raw = route.params.id const raw = route.params.id

View File

@@ -11,9 +11,11 @@ use ApiPlatform\Metadata\Patch;
use ApiPlatform\Metadata\Post; use ApiPlatform\Metadata\Post;
use DateTimeImmutable; use DateTimeImmutable;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Serializer\Attribute\Context; use Symfony\Component\Serializer\Attribute\Context;
use Symfony\Component\Serializer\Attribute\Groups; use Symfony\Component\Serializer\Attribute\Groups;
use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer; use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
use Symfony\Component\Validator\Constraints as Assert;
#[ORM\Entity] #[ORM\Entity]
#[ORM\Table(name: 'weight')] #[ORM\Table(name: 'weight')]
@@ -31,6 +33,7 @@ use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
), ),
], ],
)] )]
#[UniqueEntity(fields: ['reception', 'type'], message: 'A weighing already exists for this type.')]
class Weight class Weight
{ {
#[ORM\Id] #[ORM\Id]
@@ -46,10 +49,12 @@ class Weight
#[ORM\Column(nullable: true)] #[ORM\Column(nullable: true)]
#[Groups(['reception:read', 'weight:read', 'weight:write'])] #[Groups(['reception:read', 'weight:read', 'weight:write'])]
#[Assert\PositiveOrZero]
private ?int $dsd = null; private ?int $dsd = null;
#[ORM\Column(nullable: true)] #[ORM\Column(nullable: true)]
#[Groups(['reception:read', 'weight:read', 'weight:write'])] #[Groups(['reception:read', 'weight:read', 'weight:write'])]
#[Assert\PositiveOrZero]
private ?int $weight = null; private ?int $weight = null;
#[ORM\Column(type: 'datetime_immutable', nullable: true)] #[ORM\Column(type: 'datetime_immutable', nullable: true)]
@@ -59,6 +64,8 @@ class Weight
#[ORM\Column(length: 10)] #[ORM\Column(length: 10)]
#[Groups(['reception:read', 'weight:read', 'weight:write'])] #[Groups(['reception:read', 'weight:read', 'weight:write'])]
#[Assert\NotBlank]
#[Assert\Choice(choices: ['gross', 'tare'])]
private string $type = 'gross'; private string $type = 'gross';
public function getId(): ?int public function getId(): ?int