Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
| Numéro du ticket | Titre du ticket | |------------------|-----------------| | | | ## Description de la PR ## Modification du .env ## Check list - [ ] Pas de régression - [x] TU/TI/TF rédigée - [x] TU/TI/TF OK - [ ] CHANGELOG modifié Reviewed-on: #53 Co-authored-by: tristan <tristan@yuno.malio.fr> Co-committed-by: tristan <tristan@yuno.malio.fr>
1068 lines
35 KiB
Vue
1068 lines
35 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">
|
|
<UiTabs
|
|
v-model="activeTab"
|
|
:tabs="[
|
|
{ key: 'weights', label: 'pesée à plein', error: hasGrossWeightError },
|
|
{ key: 'weightsEmpty', label: 'pesée à vide', error: hasTareWeightError },
|
|
{ key: 'merchandise', label: isMerchandise ? 'Marchandise' : 'Bovins', error: hasMerchandiseTabError }
|
|
]"
|
|
/>
|
|
<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 || ' ' }}
|
|
</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 || ' ' }}
|
|
</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">
|
|
useHead({ title: 'Modifier réception' })
|
|
|
|
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>
|