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

This commit is contained in:
2026-01-13 16:05:49 +01:00
parent 6dab1d789a
commit 46af62483f
6 changed files with 157 additions and 80 deletions

29
.idea/workspace.xml generated
View File

@@ -4,23 +4,13 @@
<option name="autoReloadType" value="SELECTIVE" />
</component>
<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)">
<change afterPath="$PROJECT_DIR$/frontend/components/reception/reception-unloading.vue" afterDir="false" />
<change afterPath="$PROJECT_DIR$/frontend/components/ui/loading-dots.vue" afterDir="false" />
<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 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$/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/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/layouts/default.vue" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/layouts/default.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>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
@@ -255,7 +245,7 @@
<updated>1767956826164</updated>
<workItem from="1767956827666" duration="7866000" />
<workItem from="1768201706520" duration="13383000" />
<workItem from="1768287908317" duration="20308000" />
<workItem from="1768287908317" duration="21021000" />
</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" />
@@ -265,7 +255,15 @@
<option name="project" value="LOCAL" />
<updated>1768237763998</updated>
</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 />
</component>
<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 : 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 name="XDebuggerManager">
<breakpoint-manager>

View File

@@ -25,12 +25,12 @@
<div class="flex justify-center mt-[54px]">
<button
class="text-xl uppercase bg-primary-500 text-white h-[50px] w-[272px]"
@click="getReceptionWeight"
@click="fetchWeight"
>{{ 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"
@click="saveWeight"
>Valider la pesée</button>
</div>
@@ -38,73 +38,30 @@
<script setup lang="ts">
import { storeToRefs } from 'pinia'
import { getWeight } from '~/services/reception'
import type { WeightData } from '~/services/dto/weight-data'
import { createWeight, updateWeight } from '~/services/weight'
import { useWeighing } from '~/composables/useWeighing'
import { useReceptionStore } from '~/stores/reception'
const props = defineProps<{
mode: 'gross' | 'tare'
}>()
const weightData = ref<WeightData | null>(null)
const localErrorMessage = ref<string | null>(null)
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() {
localErrorMessage.value = null
try {
weightData.value = await getWeight()
} catch (error) {
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
const {
displayWeight,
displayDsd,
title,
errorMessage,
showLoadingBox,
fetchWeight,
saveWeight
} = useWeighing({
mode: props.mode,
reception: storeReception,
updateReception: receptionStore.updateReception,
loadReception: receptionStore.loadReception,
storeError: storeErrorMessage
})
} 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
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>

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.'
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
:href="href"
@click="navigate"
:class="isActive ? 'opacity-100' : 'opacity-50'"
:class="isReceptionActive ? 'opacity-100' : 'opacity-50'"
>
Reception
</a>
@@ -36,3 +36,8 @@
</main>
</div>
</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 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>
<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>
<ReceptionForm v-if="storeReception?.currentStep === 0"/>
<ReceptionWeight v-if="storeReception?.currentStep === 1" mode="gross"/>
@@ -20,7 +20,7 @@ const route = useRoute()
const router = useRouter()
const receptionStore = useReceptionStore()
const { current: storeReception, isLoading, errorMessage } = storeToRefs(receptionStore)
const { current: storeReception, errorMessage } = storeToRefs(receptionStore)
onMounted(async () => {
const raw = route.params.id

View File

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