feat : Ajout de la sélection des bovins étape 3 d'une réception (WIP)

This commit is contained in:
2026-02-04 16:11:08 +01:00
parent 015a8c49fb
commit 81c2a5802b
10 changed files with 230 additions and 76 deletions

View File

@@ -4,7 +4,7 @@
class="flex flex-col items-center gap-16">
<h1 class="text-4xl uppercase font-bold">Sélection des marchandises réceptionnnées</h1>
<div
class="flex flex-row gap-16 items-center w-full">
class="flex flex-row gap-8 items-center">
<div
v-for="type in bovineType"
:key="type.id"
@@ -12,14 +12,17 @@
<UiNumberInput
:label="type.label"
:code="type.code"
v-model="selectedBovineTypeIds[type.id]"
:value="String(type.id)"
v-model="bovineQuantities[String(type.id)]"
:placeholder="0"
:min="0"
:max="10"
/>
</div>
<div
class="mt-8 flex flex-row mb-2 gap-6">
<UiNumberInput
label="Autres"
v-model="otherQuantity"
/>
</div>
</div>
@@ -38,14 +41,20 @@ import {useReceptionStore} from '~/stores/reception'
import {
createReceptionBovine,
deleteReceptionBovine,
getReceptionBovineList
getReceptionBovineList,
updateReceptionBovine
} from "~/services/reception-bovine";
import {ref} from "vue";
import {computed, onMounted, reactive, ref, watch} from "vue";
const isLoadingBovineType = ref(false)
const bovineType = ref<BovineTypeData[]>([])
const receptionStore = useReceptionStore()
const selectedBovineTypeIds = ref<string[]>
const bovineQuantities = reactive<Record<string, number | null>>({})
const otherQuantity = ref<number | null>(0)
const receptionId = computed(() => receptionStore.current?.id ?? null)
const receptionIri = computed(() =>
receptionId.value ? `/api/receptions/${receptionId.value}` : null
)
const loadBovineType = async () => {
isLoadingBovineType.value = true
try {
@@ -55,28 +64,94 @@ const loadBovineType = async () => {
}
}
onMounted(async () => {
await loadBovineType()
selectedBovineTypeIds.value=bovineType.value.map((type) => String(type.id))
})
watch(
() => receptionId.value,
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)
},
{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 goNext() {
if (!receptionStore.current) {
if (!receptionStore.current || !receptionIri.value) {
return
}
const nextStep = receptionStore.current.currentStep + 1
const receptionIri = `/api/receptions/${receptionStore.current.id}`
console.log(selectedBovineTypeIds.value)
await syncBovineSelections(receptionIri.value)
await receptionStore.updateReception(receptionStore.current.id, {
merchandiseType: null,
merchandiseDetail: null,
buildings: null,
bovinesTypes : selectedBovineTypeIds.value.map((id) => `/api/bovines_types/${id}`),
currentStep: nextStep
})
}
</script>

View File

@@ -175,7 +175,6 @@ onMounted(async () => {
}
selectedPelletBuildingIds.value = selectionMap
})
// Enregistre les sélections et passe à l'étape suivante
async function goNext() {
if (!receptionStore.current) {
@@ -193,6 +192,7 @@ async function goNext() {
buildings: isGranule.value
? []
: selectedBuildingIds.value.map((id) => `/api/buildings/${id}`),
bovinesTypes: null,
currentStep: nextStep
})
@@ -210,7 +210,6 @@ async function clearPelletSelections(receptionIri: string) {
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)

View File

@@ -1,12 +1,19 @@
<template>
<div :class="['flex items-center gap-4', wrapperClass]">
<div :class="['flex flex-row items-center gap-2', wrapperClass]">
<label
v-if="label"
:for="id"
class="font-bold uppercase text-xl"
class="text-xl text-bold flex items-center gap-2"
:class="labelClass"
>
{{ label }}
<span
v-if="label">
{{ label }}
</span>
<span
v-if="code" class="text-neutral-600">
({{ code }})
</span>
</label>
<input
:id="id"
@@ -17,7 +24,7 @@
:step="step"
:disabled="disabled"
v-bind="attrs"
class="border-b border-black text-xl pb-[6px] bg-transparent text-right"
class="border-b border-black text-xl bg-transparent w-48"
:class="[
isEmpty ? 'text-neutral-400' : 'text-black',
disabled ? 'cursor-not-allowed' : 'cursor-text',
@@ -29,14 +36,15 @@
</template>
<script setup lang="ts">
import { computed, useAttrs } from 'vue'
import {computed, useAttrs} from 'vue'
defineOptions({ inheritAttrs: false })
defineOptions({inheritAttrs: false})
const props = withDefaults(
defineProps<{
id?: string
label?: string
code?: string
modelValue: number | string | null | undefined
min?: number | string
max?: number | string

View File

@@ -16,8 +16,12 @@
</div>
<ReceptionForm v-if="!storeReception || storeReception.currentStep === 0"/>
<ReceptionWeight v-if="storeReception?.currentStep === 1" mode="gross"/>
<ReceptionProductReceived v-if="storeReception?.currentStep === 2 && receptionStore.current?.receptionType?.code === RECEPTION_TYPE_CODES.MERCHANDISES"/>
<ReceptionBovineReceived v-if="storeReception?.currentStep === 2 && receptionStore.current?.receptionType?.code === RECEPTION_TYPE_CODES.BOVINS"/>
<ReceptionProductReceived
v-if="storeReception?.currentStep === 2 &&
receptionStore.current?.receptionType?.code === RECEPTION_TYPE_CODES.MERCHANDISES"/>
<ReceptionBovineReceived
v-if="storeReception?.currentStep === 2 &&
receptionStore.current?.receptionType?.code === RECEPTION_TYPE_CODES.BOVINS"/>
<ReceptionWeight v-if="storeReception?.currentStep !== null && storeReception?.currentStep >= 3" mode="tare"/>
</div>
</template>

View File

@@ -47,3 +47,13 @@ export async function deleteReceptionBovine(id: number): Promise<void> {
toastErrorKey: 'errors.receptionBovine.delete'
})
}
export async function updateReceptionBovine(
id: number,
payload: Partial<ReceptionBovinePayload>
): Promise<ReceptionBovineTypeData> {
const api = useApi()
return api.patch<ReceptionBovineTypeData>(`reception_bovines/${id}`, payload, {
toastErrorKey: 'errors.receptionBovine.update'
})
}