Files
Ferme/frontend/pages/reception/update/[[id]].vue
tristan a905c6a1de
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
fix : correction des retours de la V0
2026-03-18 14:47:03 +01:00

1090 lines
37 KiB
Vue

<template>
<form :class="{ submitted }" @submit.prevent="validate">
<div class="grid grid-cols-2 items-start gap-y-8 gap-x-40 mb-[60px]">
<div class="flex items-center justify-between gap-10 relative">
<div class="flex flex-row absolute -left-[60px] justify-between">
<Icon @click="router.push('/reception/finish-reception')" name="gg:arrow-left-o" size="44" class="cursor-pointer text-primary-500"/>
</div>
<h1 class="font-bold text-4xl col-start-1 row-start-1 text-primary-500 uppercase">Réception {{ form.identificationNumber }}</h1>
<div class="bg-primary-500 p-1 rounded-md flex items-center" title="Imprimer" @click="printReceipt">
<Icon name="mdi:printer-outline" size="32" class="cursor-pointer text-white"/>
</div>
</div>
<!-- Nom de l'utilisateur -->
<UiSelect
id="reception-user"
:disabled="!auth.isAdmin"
v-model="form.userId"
label="Nom de l'utilisateur"
:options="users.map((user) => ({
value: String(user.id),
label: user.username
}))"
:loading="isLoadingUsers"
wrapper-class="col-start-1 row-start-2"
required
/>
<!-- Date de réception -->
<UiDateInput
id="reception-date"
:disabled="!auth.isAdmin"
v-model="form.receptionDate"
label="Date de réception"
wrapper-class="col-start-1 row-start-3"
required
/>
<!-- type de reception -->
<UiSelect
id="reception-supplier"
v-model="form.receptionTypeId"
:disabled="!auth.isAdmin"
label="Type de Réception"
:options="receptionTypes.map((receptionType) => ({
value: String(receptionType.id),
label: receptionType.label
}))"
:loading="isLoadingSuppliers"
wrapper-class="col-start-1 row-start-4"
required
/>
<!-- Fournisseur -->
<UiSelect
id="reception-supplier"
v-model="form.supplierId"
:disabled="!auth.isAdmin"
label="Fournisseur"
:options="suppliers.map((supplier) => ({
value: String(supplier.id),
label: supplier.name
}))"
:loading="isLoadingSuppliers"
wrapper-class="col-start-1 row-start-5"
required
/>
<!-- Adresse fournisseur -->
<UiSelect
id="reception-address"
v-model="form.addressId"
label="Adresse"
:options="supplierAddresses.map((address) => ({
value: String(address.id),
label: address.fullAddress
}))"
:disabled="(isLoadingSuppliers || supplierAddresses.length === 0) && !auth.isAdmin"
wrapper-class="col-start-2 row-start-1"
required
/>
<!-- Camion -->
<UiSelect
id="reception-truck"
v-model="form.truckId"
:disabled="!auth.isAdmin"
label="Camion"
:options="trucks.map((truck) => ({
value: String(truck.id),
label: truck.name
}))"
:loading="isLoadingTrucks"
wrapper-class="col-start-2 row-start-2"
required
/>
<!-- Transporteur -->
<UiSelect
id="reception-carrier"
v-model="form.carrierId"
label="Transporteur"
:disabled="!auth.isAdmin"
:options="carriers.map((carrier) => ({
value: String(carrier.id),
label: carrier.name
}))"
:loading="isLoadingCarriers"
select-class="h-[34px]"
wrapper-class="col-start-2 row-start-3"
required
/>
<!-- Chauffeur (LIOT) -->
<UiSelect
id="reception-driver"
v-model="form.driverId"
:disabled="!auth.isAdmin"
label="Nom du chauffeur si LIOT"
:options="filteredDrivers.map((driver) => ({
value: String(driver.id),
label: driver.name
}))"
:loading="isLoadingDrivers"
wrapper-class="col-start-2 row-start-5"
v-if="isLiotCarrier"
required
/>
<!-- Plaque d'immatriculation -->
<div v-if="!isLiotCarrier" class="col-start-2 row-start-4">
<UiLicensePlateInput
:disabled="!auth.isAdmin"
v-model="form.licensePlate"
v-model:allowAny="allowAnyLicensePlate"
required
/>
</div>
<!-- Immatriculation (LIOT) -->
<UiSelect
v-if="isLiotCarrier"
id="reception-vehicle"
v-model="form.vehicleId"
label="Immatriculation"
:options="filteredVehicles.map((vehicle) => ({
value: String(vehicle.id),
label: vehicle.plate
}))"
:loading="isLoadingVehicles"
:disabled="(isLoadingVehicles || filteredVehicles.length === 0) && !auth.isAdmin"
wrapper-class="col-start-2 row-start-4"
required
/>
</div>
<div v-if="formIsLoading">
<div class="flex justify-evenly gap-y-8 gap-x-41 mb-10 border-b border-primary-500/60">
<h1
class="font-bold text-3xl uppercase px-12 col-start-1 row-start-1 cursor-pointer"
:class="[
activeTab === 'weights' ? 'border-b-[6px] border-primary-500 text-primary-500' : 'text-primary-500/50',
hasGrossWeightError ? '!text-red-500 !border-red-500' : ''
]"
@click="activeTab = 'weights'"
>
pesée à plein
</h1>
<h1
class="font-bold text-3xl uppercase col-start-1 row-start-1 px-12 cursor-pointer"
:class="[
activeTab === 'weightsEmpty' ? 'border-b-[6px] border-primary-500 text-primary-500' : 'text-primary-500/50',
hasTareWeightError ? '!text-red-500 !border-red-500' : ''
]"
@click="activeTab = 'weightsEmpty'"
>
pesée à vide
</h1>
<h1
class="font-bold text-3xl uppercase px-12 col-start-2 row-start-1 cursor-pointer"
:class="[
activeTab === 'merchandise' ? 'border-b-[6px] border-primary-500 text-primary-500' : 'text-primary-500/50',
hasMerchandiseTabError ? '!text-red-500 !border-red-500' : ''
]"
@click="activeTab = 'merchandise'"
>
{{ isMerchandise ? "Marchandise" : "Bovins" }}
</h1>
</div>
<div class="mb-12 ">
<update-weight
v-show="activeTab === 'weights'"
v-model="grossWeight"
v-if="grossWeight"
:isAdmin="auth.isAdmin"
/>
<update-weight
v-show="activeTab === 'weightsEmpty'"
v-model="tareWeight"
v-if="tareWeight"
:isAdmin="auth.isAdmin"
/>
<update-merchandise
v-if="activeTab === 'merchandise' && isMerchandise"
v-model="merchandiseForm"
:isAdmin="auth.isAdmin"
/>
<p v-if="activeTab === 'merchandise' && isMerchandise" class="text-red-500 text-sm mt-2" :class="showMerchandiseError ? '' : 'invisible'">
{{ merchandiseErrorMessage || '&nbsp;' }}
</p>
<update-bovin
v-if="activeTab === 'merchandise' && !isMerchandise"
v-model="bovineEntries"
v-model:otherQuantity="bovineOtherQuantity"
:isAdmin="auth.isAdmin"
/>
<p v-if="activeTab === 'merchandise' && !isMerchandise" class="text-red-500 text-sm mt-2" :class="showMerchandiseError ? '' : 'invisible'">
{{ merchandiseErrorMessage || '&nbsp;' }}
</p>
</div>
<div class="flex justify-center">
<UiButton
v-if="auth.isAdmin"
type="submit"
class="inline-flex mb-16 items-center justify-center text-xl text-white uppercase bg-primary-500 h-[50px] px-8 rounded hover:opacity-80 gap-2 justify-self-end"
@click="submitted = true"
>
Valider
</UiButton>
</div>
</div>
</form>
</template>
<script setup lang="ts">
import { usePdfPrinter } from '#imports'
import { computed } from 'vue'
import UpdateBovin from '~/components/reception/update-bovin.vue'
import UpdateMerchandise from '~/components/reception/update-merchandise.vue'
import UpdateWeight from '~/components/commun/update-weight.vue'
import { getUsers } from '~/services/auth'
import { getCarrierList } from '~/services/carrier'
import type { CarrierData } from '~/services/dto/carrier-data'
import type { DriverData } from '~/services/dto/driver-data'
import type { ReceptionBovineTypeData } from '~/services/dto/reception-bovine-data'
import type {
MerchandiseEntryData,
ReceptionData,
ReceptionFormData,
} from '~/services/dto/reception-data'
import type { WeightEntryData } from '~/services/dto/weight-data'
import type { ReceptionTypeData } from '~/services/dto/reception-type-data'
import type { SupplierData } from '~/services/dto/supplier-data'
import type { TruckData } from '~/services/dto/truck-data'
import type { UserData } from '~/services/dto/user-data'
import type { VehicleData } from '~/services/dto/vehicle-data'
import { getDriverList } from '~/services/driver'
import {
createReceptionBovine,
deleteReceptionBovine,
getReceptionBovineList,
updateReceptionBovine
} from '~/services/reception-bovine'
import {
createReceptionPelletBuilding,
deleteReceptionPelletBuilding,
getReceptionPelletBuildingList
} from '~/services/reception-pellet-building'
import { getReception, updateReception } from '~/services/reception'
import { getReceptionTypeList } from '~/services/reception-type'
import { getSupplierList } from '~/services/supplier'
import { getTruckList } from '~/services/truck'
import { getVehicleList } from '~/services/vehicle'
import { createWeight, updateWeight } from '~/services/weight'
import { useAuthStore } from '~/stores/auth'
import { useReceptionStore } from '~/stores/reception'
import { MERCHANDISE_TYPE_CODES, RECEPTION_TYPE_CODES, SUPPLIER_CODE } from '~/utils/constants'
import { getMerchandiseTypeList } from '~/services/merchandise-type'
import type { MerchandiseTypeData } from '~/services/dto/merchandise-type-data'
const router = useRouter()
const route = useRoute()
const auth = useAuthStore()
const authStore = useAuthStore()
const receptionStore = useReceptionStore()
const { printPdf } = usePdfPrinter()
const activeTab = ref<'weightsEmpty' | 'weights' | 'merchandise'>('weights')
const grossWeight = ref<WeightEntryData>(createEmptyWeightEntry('gross'))
const tareWeight = ref<WeightEntryData>(createEmptyWeightEntry('tare'))
const bovineEntries = ref<ReceptionBovineTypeData[]>([])
const bovineOtherQuantity = ref<number | null>(0)
const merchandiseForm = ref<MerchandiseEntryData>({
merchandiseTypeId: '',
merchandiseDetail: '',
selectedBuildingIds: [],
selectedPelletBuildingIds: {}
})
const allowAnyLicensePlate = ref(false)
const submitted = ref(false)
const showMerchandiseError = ref(false)
const merchandiseErrorMessage = ref('')
const hasGrossWeightError = computed(() =>
submitted.value && (grossWeight.value.weight === null || grossWeight.value.weighedAt === null || grossWeight.value.dsd === null)
)
const hasTareWeightError = computed(() =>
submitted.value && (tareWeight.value.weight === null || tareWeight.value.weighedAt === null || tareWeight.value.dsd === null)
)
const hasMerchandiseTabError = computed(() => submitted.value && showMerchandiseError.value)
const isLoading = ref(false)
const users = ref<UserData[]>([])
const isLoadingUsers = ref(false)
const isLoadingTypes = ref(false)
const suppliers = ref<SupplierData[]>([])
const receptionTypes = ref<ReceptionTypeData[]>([])
const isLoadingSuppliers = ref(false)
const trucks = ref<TruckData[]>([])
const isLoadingTrucks = ref(false)
const carriers = ref<CarrierData[]>([])
const isLoadingCarriers = ref(false)
const drivers = ref<DriverData[]>([])
const isLoadingDrivers = ref(false)
const vehicles = ref<VehicleData[]>([])
const isLoadingVehicles = ref(false)
const formIsLoading = ref(false)
const isMerchandise = ref(false)
const merchandiseTypesList = ref<MerchandiseTypeData[]>([])
const isHydrating = ref(false)
const vehicleSyncLock = ref(false)
const idReception = Number(route.params.id)
function runWithVehicleSyncLock(mutator: () => void) {
if (vehicleSyncLock.value) {
return
}
vehicleSyncLock.value = true
try {
mutator()
} finally {
queueMicrotask(() => {
vehicleSyncLock.value = false
})
}
}
const form = reactive<ReceptionFormData>({
identificationNumber: null,
licensePlate: '',
receptionDate: new Date().toISOString().slice(0, 10),
receptionTypeId: '',
userId: '',
supplierId: '',
addressId: '',
truckId: '',
carrierId: '',
driverId: '',
vehicleId: ''
})
const selectedCarrier = computed(() =>
carriers.value.find((carrier) => String(carrier.id) === form.carrierId) ?? null
)
const isLiotCarrier = computed(() => selectedCarrier.value?.code === SUPPLIER_CODE.LIOT)
const supplierAddresses = computed(() => {
const supplierId = Number(form.supplierId)
if (!Number.isFinite(supplierId)) {
return []
}
return suppliers.value.find((supplier) => supplier.id === supplierId)?.addresses ?? []
})
const filteredDrivers = computed<DriverData[]>(() => {
if (!form.carrierId) {
return []
}
return drivers.value.filter((driver) => String(driver.carrier?.id) === form.carrierId)
})
const filteredVehicles = computed<VehicleData[]>(() => {
if (!form.carrierId) {
return []
}
return vehicles.value.filter(
(vehicle) =>
String(vehicle.carrier?.id) === form.carrierId &&
(!form.truckId || String(vehicle.truck?.id) === form.truckId)
)
})
watch(
() => idReception,
async (id) => {
if (!Number.isFinite(id) || id <= 0) {
return
}
isLoading.value = true
try {
const reception = await getReception(id)
hydrateFromReception(reception)
await loadBovineEntries(id)
} finally {
isLoading.value = false
}
},
{immediate: true}
)
watch(
() => form.receptionTypeId,
() => {
const receptionType = receptionTypes.value.find((type) => String(type.id) === form.receptionTypeId) ?? null
isMerchandise.value = receptionType?.code === RECEPTION_TYPE_CODES.MERCHANDISES
}
)
function createEmptyWeightEntry(type: 'gross' | 'tare'): WeightEntryData {
return {
type,
dsd: null,
weight: null,
weighedAt: null
}
}
async function clearReceptionBovines(receptionId: number) {
const receptionIri = `/api/receptions/${receptionId}`
const existing = await getReceptionBovineList(receptionIri)
for (const selection of existing) {
await deleteReceptionBovine(selection.id)
}
}
async function loadBovineEntries(receptionId: number) {
const receptionIri = `/api/receptions/${receptionId}`
bovineEntries.value = await getReceptionBovineList(receptionIri)
}
function syncMerchandiseFlag() {
const receptionType =
receptionTypes.value.find((type) => String(type.id) === form.receptionTypeId) ?? null
isMerchandise.value = receptionType?.code === RECEPTION_TYPE_CODES.MERCHANDISES
}
function getRelationId(value: unknown): string | null {
if (!value) {
return null
}
if (typeof value === 'string') {
const match = value.match(/\/(\d+)$/)
return match ? match[1] : null
}
if (typeof value === 'object' && 'id' in value) {
const relation = value as { id?: number | string }
if (typeof relation.id === 'number') {
return String(relation.id)
}
if (typeof relation.id === 'string') {
return relation.id
}
}
return null
}
async function clearReceptionMerchandise(receptionId: number) {
const receptionIri = `/api/receptions/${receptionId}`
const existing = await getReceptionPelletBuildingList(receptionIri)
for (const selection of existing) {
await deleteReceptionPelletBuilding(selection.id)
}
await updateReception(receptionId, {
merchandiseType: null,
merchandiseDetail: null,
buildings: []
})
}
function hydrateFromReception(reception: ReceptionData | null) {
if (!reception) {
return
}
isHydrating.value = true
form.identificationNumber = reception?.identificationNumber ?? ''
form.licensePlate = reception?.licensePlate ?? ''
form.receptionDate = reception?.receptionDate?.slice(0, 10) ?? new Date().toISOString().slice(0, 10)
form.userId = reception?.user?.id
? String(reception.user.id)
: form.userId
form.supplierId = reception?.supplier?.id
? String(reception.supplier.id)
: ''
form.addressId = reception?.address?.id
? String(reception.address.id)
: ''
form.truckId = reception?.truck?.id
? String(reception.truck.id)
: ''
form.carrierId = reception?.carrier?.id
? String(reception.carrier.id)
: ''
form.driverId = reception?.driver?.id
? String(reception.driver.id)
: ''
form.receptionTypeId = reception?.receptionType?.id
? String(reception.receptionType.id)
: ''
const selectionMap: Record<string, string[]> = {}
for (const selection of reception?.pelletBuildings ?? []) {
const pelletTypeId = getRelationId(selection.pelletType)
const buildingId = getRelationId(selection.building)
if (!pelletTypeId || !buildingId) {
continue
}
if (!selectionMap[pelletTypeId]) {
selectionMap[pelletTypeId] = []
}
selectionMap[pelletTypeId].push(buildingId)
}
merchandiseForm.value = {
merchandiseTypeId: reception?.merchandiseType?.id ? String(reception.merchandiseType.id) : '',
merchandiseDetail: reception?.merchandiseDetail ?? '',
selectedBuildingIds: reception?.buildings?.map((building) => String(building.id)) ?? [],
selectedPelletBuildingIds: selectionMap
}
const parsedOther =
typeof reception?.bovineDetail === 'string' && reception.bovineDetail.trim() !== ''
? Number(reception.bovineDetail)
: 0
bovineOtherQuantity.value = Number.isFinite(parsedOther) ? parsedOther : 0
const gross = reception.weights?.find((weight) => weight.type === 'gross') ?? null
const tare = reception.weights?.find((weight) => weight.type === 'tare') ?? null
grossWeight.value = gross ? { ...gross } : createEmptyWeightEntry('gross')
tareWeight.value = tare ? { ...tare } : createEmptyWeightEntry('tare')
isHydrating.value = false
syncMerchandiseFlag()
}
async function loadUsers() {
isLoadingUsers.value = true
try {
users.value = await getUsers()
} finally {
isLoadingUsers.value = false
}
}
async function loadSuppliers() {
isLoadingSuppliers.value = true
try {
suppliers.value = await getSupplierList()
} finally {
isLoadingSuppliers.value = false
}
}
async function loadTypes() {
isLoadingTypes.value = true
try {
receptionTypes.value = await getReceptionTypeList()
} finally {
isLoadingTypes.value = false
}
}
async function loadTrucks() {
isLoadingTrucks.value = true
try {
trucks.value = await getTruckList()
} finally {
isLoadingTrucks.value = false
}
}
async function loadCarriers() {
isLoadingCarriers.value = true
try {
carriers.value = await getCarrierList()
} finally {
isLoadingCarriers.value = false
}
}
async function loadDrivers() {
isLoadingDrivers.value = true
try {
drivers.value = await getDriverList()
} finally {
isLoadingDrivers.value = false
}
}
async function loadVehicles() {
isLoadingVehicles.value = true
try {
vehicles.value = await getVehicleList()
} finally {
isLoadingVehicles.value = false
}
}
function setDefaultUser() {
if (form.userId) {
return
}
if (authStore.user?.id) {
form.userId = String(authStore.user.id)
}
}
async function printReceipt() {
if (!import.meta.client || !Number.isFinite(idReception) || idReception <= 0) {
return
}
const supplierName =
suppliers.value.find((supplier) => String(supplier.id) === form.supplierId)?.name ??
'fournisseur'
const filename = `${form.identificationNumber || idReception}_${supplierName}_${form.licensePlate || 'immat'}.pdf`
await printPdf(`/receptions/${idReception}/receipt`, filename)
// Laisse le temps a la boite de dialogue d'impression de s'ouvrir.
await new Promise((resolve) => setTimeout(resolve, 600))
}
async function saveWeightEntry(entry: WeightEntryData) {
if (!idReception || entry.weight === null) {
return
}
const payload = {
type: entry.type,
dsd: entry.dsd ?? null,
weight: entry.weight,
weighedAt: entry.weighedAt ?? null
}
if (entry.id) {
await updateWeight(entry.id, payload)
return
}
// Fallback: if id is missing in local state, reuse existing weight by type.
const reception = await getReception(idReception)
const existingEntry = reception?.weights?.find((weight) => weight.type === entry.type) ?? null
if (existingEntry?.id) {
await updateWeight(existingEntry.id, payload)
return
}
await createWeight({
reception: `/api/receptions/${idReception}`,
...payload
})
}
async function saveBovineEntry(entry: ReceptionBovineTypeData) {
if (!idReception || !entry.bovineType || entry.quantity === null || entry.quantity <= 0) {
return
}
const payload = {
quantity: entry.quantity
}
if (entry.id) {
await updateReceptionBovine(entry.id, payload)
return
}
await createReceptionBovine({
reception: `/api/receptions/${idReception}`,
bovineType: `/api/bovine_types/${entry.bovineType.id}`,
...payload
})
}
function getTotalBovines() {
const totalTypes = bovineEntries.value.reduce(
(sum, entry) => sum + (entry.quantity ?? 0),
0
)
return totalTypes +
(bovineOtherQuantity.value ?? 0)
}
async function syncBovineEntries() {
if (!idReception) {
return
}
const receptionIri = `/api/receptions/${idReception}`
const existing = await getReceptionBovineList(receptionIri)
const currentPositive = bovineEntries.value.filter(
(entry) => (entry.quantity ?? 0) > 0
)
for (const existingEntry of existing) {
const stillPresent = currentPositive.some(
(entry) => entry.bovineType.id === existingEntry.bovineType.id
)
if (!stillPresent) {
await deleteReceptionBovine(existingEntry.id)
}
}
for (const entry of currentPositive) {
await saveBovineEntry(entry)
}
}
async function saveMerchandiseEntry(
receptionIri: string,
pelletTypeId: string,
buildingId: string,
existingKeys: Set<string>
) {
const key = `${pelletTypeId}:${buildingId}`
if (existingKeys.has(key)) {
return
}
await createReceptionPelletBuilding({
reception: receptionIri,
pelletType: `/api/pellet_types/${pelletTypeId}`,
building: `/api/buildings/${buildingId}`
})
}
async function syncMerchandiseEntries() {
if (!idReception) {
return
}
const receptionIri = `/api/receptions/${idReception}`
const existing = await getReceptionPelletBuildingList(receptionIri)
const existingMap = new Map<string, number>()
for (const selection of existing) {
const pelletTypeId = getRelationId(selection.pelletType)
const buildingId = getRelationId(selection.building)
if (!pelletTypeId || !buildingId) {
continue
}
existingMap.set(`${pelletTypeId}:${buildingId}`, selection.id)
}
const desiredEntries: Array<{ pelletTypeId: string; buildingId: string }> = []
for (const [pelletTypeId, buildingIds] of Object.entries(
merchandiseForm.value.selectedPelletBuildingIds
)) {
for (const buildingId of buildingIds) {
desiredEntries.push({ pelletTypeId, buildingId })
}
}
const desiredKeys = new Set(
desiredEntries.map((entry) => `${entry.pelletTypeId}:${entry.buildingId}`)
)
for (const [key, selectionId] of existingMap.entries()) {
if (!desiredKeys.has(key)) {
await deleteReceptionPelletBuilding(selectionId)
}
}
const existingKeys = new Set(existingMap.keys())
for (const entry of desiredEntries) {
await saveMerchandiseEntry(
receptionIri,
entry.pelletTypeId,
entry.buildingId,
existingKeys
)
}
}
async function validate() {
const normalizedLicensePlate = form.licensePlate.trim()
const normalizedReceptionDate = form.receptionDate.trim()
const normalizedReceptionTypeId = form.receptionTypeId.trim()
const normalizedUserId = form.userId.trim()
const normalizedSupplierId = form.supplierId.trim()
const normalizedAddressId = form.addressId.trim()
const normalizedTruckId = form.truckId.trim()
const normalizedCarrierId = form.carrierId.trim()
const normalizedDriverId = form.driverId.trim()
const userIri = normalizedUserId
? `/api/users/${normalizedUserId}`
: null
const supplierIri = normalizedSupplierId
? `/api/suppliers/${normalizedSupplierId}`
: null
const addressIri = normalizedAddressId
? `/api/addresses/${normalizedAddressId}`
: null
const truckIri = normalizedTruckId
? `/api/trucks/${normalizedTruckId}`
: null
const carrierIri = normalizedCarrierId
? `/api/carriers/${normalizedCarrierId}`
: null
const driverIri = normalizedDriverId
? `/api/drivers/${normalizedDriverId}`
: null
const typeIri = normalizedReceptionTypeId
? `/api/reception_types/${normalizedReceptionTypeId}`
: null
const basePayload = {
licensePlate: normalizedLicensePlate,
receptionDate: normalizedReceptionDate,
receptionType: typeIri,
user: userIri,
supplier: supplierIri,
address: addressIri,
truck: truckIri,
carrier: carrierIri
}
const payload = {
...basePayload,
...(isLiotCarrier.value && driverIri ? { driver: driverIri } : {}),
}
if (idReception) {
const hasInvalidWeights =
grossWeight.value.weight === null || grossWeight.value.weighedAt === null || grossWeight.value.dsd === null ||
tareWeight.value.weight === null || tareWeight.value.weighedAt === null || tareWeight.value.dsd === null
if (hasInvalidWeights) {
return
}
showMerchandiseError.value = false
merchandiseErrorMessage.value = ''
if (!isMerchandise.value && getTotalBovines() === 0) {
showMerchandiseError.value = true
merchandiseErrorMessage.value = 'Veuillez saisir au moins une race bovine.'
return
}
if (isMerchandise.value) {
const selectedType = merchandiseTypesList.value.find(
(t) => String(t.id) === merchandiseForm.value.merchandiseTypeId
)
const isAutresType = selectedType?.code === MERCHANDISE_TYPE_CODES.AUTRES
const isGranuleType = selectedType?.code === MERCHANDISE_TYPE_CODES.GRANULE
if (isAutresType && !merchandiseForm.value.merchandiseDetail.trim()) {
showMerchandiseError.value = true
merchandiseErrorMessage.value = 'Veuillez préciser le type de marchandise.'
return
}
if (!isAutresType && !isGranuleType && merchandiseForm.value.selectedBuildingIds.length === 0) {
showMerchandiseError.value = true
merchandiseErrorMessage.value = 'Veuillez sélectionner au moins un bâtiment.'
return
}
if (isGranuleType) {
const hasAny = Object.values(merchandiseForm.value.selectedPelletBuildingIds)
.some((ids) => ids.length > 0)
if (!hasAny) {
showMerchandiseError.value = true
merchandiseErrorMessage.value = 'Veuillez sélectionner au moins un bâtiment.'
return
}
}
}
await receptionStore.updateReception(idReception, {
...payload
})
await saveWeightEntry(grossWeight.value)
await saveWeightEntry(tareWeight.value)
if (isMerchandise.value) {
await clearReceptionBovines(idReception)
await updateReception(idReception, {
merchandiseType: merchandiseForm.value.merchandiseTypeId
? `/api/merchandise_types/${merchandiseForm.value.merchandiseTypeId}`
: null,
merchandiseDetail: merchandiseForm.value.merchandiseDetail.trim() || null,
buildings: merchandiseForm.value.selectedBuildingIds.map(
(buildingId) => `/api/buildings/${buildingId}`
),
bovineDetail: null,
bovinesTypes: null
})
await syncMerchandiseEntries()
} else {
if (getTotalBovines() > 52) {
// toast/erreur UI
return
}
await clearReceptionMerchandise(idReception)
await syncBovineEntries()
await updateReception(idReception, {
bovineDetail: bovineOtherQuantity.value ? String(bovineOtherQuantity.value) : null
})
}
// Évite une réhydratation complète après save (source de cascades de watchers).
// On recharge uniquement les bovins quand on est en mode bovins.
if (!isMerchandise.value) {
await loadBovineEntries(idReception)
}
return
}
}
onMounted(async () => {
await loadTypes()
merchandiseTypesList.value = await getMerchandiseTypeList()
syncMerchandiseFlag()
formIsLoading.value = true
await loadUsers()
await loadSuppliers()
await loadTrucks()
await loadCarriers()
await loadDrivers()
await loadVehicles()
await authStore.ensureSession()
setDefaultUser()
})
watch(
() => [form.supplierId, form.addressId, suppliers.value],
() => {
if (isHydrating.value) {
return
}
if (!form.supplierId) {
if (form.addressId !== '') {
form.addressId = ''
}
return
}
if (!form.addressId && supplierAddresses.value.length === 1) {
const nextAddressId = String(supplierAddresses.value[0].id)
if (form.addressId !== nextAddressId) {
form.addressId = nextAddressId
}
return
}
if (!form.addressId) {
return
}
const matches = supplierAddresses.value.some(
(address) => String(address.id) === form.addressId
)
if (!matches) {
if (supplierAddresses.value.length === 1) {
const nextAddressId = String(supplierAddresses.value[0].id)
if (form.addressId !== nextAddressId) {
form.addressId = nextAddressId
}
} else {
if (form.addressId !== '') {
form.addressId = ''
}
}
}
},
{ immediate: true }
)
watch(
() => form.carrierId,
() => {
if (isHydrating.value || vehicleSyncLock.value) {
return
}
if (!form.carrierId && idReception == null) {
runWithVehicleSyncLock(() => {
form.driverId = ''
form.vehicleId = ''
})
return
}
if (!isLiotCarrier.value && idReception == null) {
runWithVehicleSyncLock(() => {
form.driverId = ''
form.vehicleId = ''
})
return
}
if (filteredDrivers.value.length === 1) {
const nextDriverId = String(filteredDrivers.value[0].id)
if (form.driverId !== nextDriverId) {
form.driverId = nextDriverId
}
}
if (filteredVehicles.value.length === 1) {
const nextVehicleId = String(filteredVehicles.value[0].id)
if (form.vehicleId !== nextVehicleId) {
runWithVehicleSyncLock(() => {
form.vehicleId = nextVehicleId
})
}
}
},
{ immediate: true }
)
watch(
() => [form.truckId, form.carrierId, vehicles.value],
() => {
if (isHydrating.value || vehicleSyncLock.value) {
return
}
if (!isLiotCarrier.value) {
return
}
if (filteredVehicles.value.length === 1) {
const nextVehicleId = String(filteredVehicles.value[0].id)
if (form.vehicleId !== nextVehicleId) {
runWithVehicleSyncLock(() => {
form.vehicleId = nextVehicleId
})
}
return
}
if (!form.vehicleId) {
return
}
const matches = filteredVehicles.value.some(
(vehicle) => String(vehicle.id) === form.vehicleId
)
if (!matches) {
if (form.vehicleId !== '') {
runWithVehicleSyncLock(() => {
form.vehicleId = ''
})
}
}
},
{ immediate: true }
)
watch(
() => [form.vehicleId, form.carrierId, vehicles.value],
() => {
if (vehicleSyncLock.value) {
return
}
if (!isLiotCarrier.value) {
return
}
if (isHydrating.value) {
return
}
const selected = filteredVehicles.value.find(
(vehicle) => String(vehicle.id) === form.vehicleId
)
if (selected && form.licensePlate !== selected.plate) {
runWithVehicleSyncLock(() => {
form.licensePlate = selected.plate
allowAnyLicensePlate.value = false
})
}
}
)
watch(
() => [form.licensePlate, form.carrierId, vehicles.value],
() => {
if (vehicleSyncLock.value) {
return
}
if (isHydrating.value) {
return
}
if (!isLiotCarrier.value || form.vehicleId) {
return
}
const match = filteredVehicles.value.find(
(vehicle) => vehicle.plate === form.licensePlate
)
if (match) {
const nextVehicleId = String(match.id)
if (form.vehicleId !== nextVehicleId) {
runWithVehicleSyncLock(() => {
form.vehicleId = nextVehicleId
})
}
}
}
)
</script>