fix : trie du code et reorganisation composant parent enfant
This commit is contained in:
5
.idea/workspace.xml
generated
5
.idea/workspace.xml
generated
@@ -7,7 +7,8 @@
|
|||||||
<list default="true" id="7c107abe-5995-4428-8429-b146aaca8386" name="Changes" comment="fix : correction du retour sur la liste des réceptions + suppression de la redirection après enregistrement">
|
<list default="true" id="7c107abe-5995-4428-8429-b146aaca8386" name="Changes" comment="fix : correction du retour sur la liste des réceptions + suppression de la redirection après enregistrement">
|
||||||
<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/update-weight.vue" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/components/reception/update-weight.vue" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/frontend/components/reception/update-bovin.vue" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/components/reception/update-bovin.vue" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/frontend/components/reception/update-merchandise.vue" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/components/reception/update-merchandise.vue" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/frontend/pages/reception/update/[[id]].vue" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/pages/reception/update/[[id]].vue" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/frontend/pages/reception/update/[[id]].vue" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/pages/reception/update/[[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/reception-data.ts" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/services/dto/reception-data.ts" afterDir="false" />
|
||||||
</list>
|
</list>
|
||||||
@@ -232,7 +233,7 @@
|
|||||||
"RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true",
|
"RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true",
|
||||||
"RunOnceActivity.git.unshallow": "true",
|
"RunOnceActivity.git.unshallow": "true",
|
||||||
"RunOnceActivity.typescript.service.memoryLimit.init": "true",
|
"RunOnceActivity.typescript.service.memoryLimit.init": "true",
|
||||||
"git-widget-placeholder": "fit/332-refonte-reception-terminee",
|
"git-widget-placeholder": "feat/332-refonte-reception-terminee",
|
||||||
"last_opened_file_path": "//wsl.localhost/Ubuntu-24.04/home/kevin/Stage/Ferme/frontend/pages/shipment",
|
"last_opened_file_path": "//wsl.localhost/Ubuntu-24.04/home/kevin/Stage/Ferme/frontend/pages/shipment",
|
||||||
"node.js.detected.package.eslint": "true",
|
"node.js.detected.package.eslint": "true",
|
||||||
"node.js.detected.package.tslint": "true",
|
"node.js.detected.package.tslint": "true",
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<form>
|
<form>
|
||||||
<div class="flex flex-row justify-between gap-x-12 font-bold uppercase mb-8">
|
<div class="flex flex-row justify-between gap-x-12 font-bold uppercase mb-8">
|
||||||
<div
|
<div
|
||||||
v-for="type in bovineType"
|
v-for="type in bovineTypes"
|
||||||
:key="type.id"
|
:key="type.id"
|
||||||
>
|
>
|
||||||
<UiNumberInput
|
<UiNumberInput
|
||||||
:label="type.label"
|
:label="type.label"
|
||||||
:code="type.code"
|
:code="type.code"
|
||||||
v-model="bovineQuantities[String(type.id)]"
|
v-model="localQuantities[String(type.id)]"
|
||||||
:disabled="!auth.isAdmin"
|
:disabled="!isAdmin"
|
||||||
:placeholder="0"
|
:placeholder="0"
|
||||||
:min="0"
|
:min="0"
|
||||||
:max="10"
|
:max="10"
|
||||||
@@ -18,187 +18,96 @@
|
|||||||
</div>
|
</div>
|
||||||
<UiNumberInput
|
<UiNumberInput
|
||||||
label="Autres"
|
label="Autres"
|
||||||
v-model="otherQuantity"
|
v-model="localOtherQuantity"
|
||||||
:disabled="!auth.isAdmin"
|
:disabled="!isAdmin"
|
||||||
wrapperClass="w-44 flex-col"
|
wrapperClass="w-44 flex-col"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
|
||||||
import type {BovineTypeData} from "~/services/dto/bovine-type-data";
|
|
||||||
import {getBovineTypeList} from "~/services/bovine-type";
|
|
||||||
import {createReceptionBovine, deleteReceptionBovine, getReceptionBovineList, updateReceptionBovine} from "~/services/reception-bovine";
|
|
||||||
import {computed, onMounted, reactive, ref, watch} from "vue";
|
|
||||||
import {getReception, updateReception} from "~/services/reception";
|
|
||||||
|
|
||||||
const toast = useToast()
|
<script setup lang="ts">
|
||||||
const isLoadingBovineType = ref(false)
|
import { onMounted, reactive, ref, watch } from 'vue'
|
||||||
const bovineType = ref<BovineTypeData[]>([])
|
import { getBovineTypeList } from '~/services/bovine-type'
|
||||||
const bovineQuantities = reactive<Record<string, number | null>>({})
|
import type { BovineTypeData } from '~/services/dto/bovine-type-data'
|
||||||
const otherQuantity = ref<number | null>(0)
|
import type { ReceptionBovineTypeData } from '~/services/dto/reception-bovine-data'
|
||||||
const initialBovineQuantities = ref<Record<string, number | null>>({})
|
|
||||||
const initialOtherQuantity = ref<number | null>(0)
|
|
||||||
const auth = useAuthStore()
|
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
idReception: number
|
modelValue: ReceptionBovineTypeData[]
|
||||||
isValidate: boolean
|
otherQuantity: number | null
|
||||||
|
isAdmin: boolean
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const receptionId = props.idReception
|
const emit = defineEmits<{
|
||||||
const reception = await getReception(receptionId)
|
(event: 'update:modelValue', value: ReceptionBovineTypeData[]): void
|
||||||
|
(event: 'update:otherQuantity', value: number | null): void
|
||||||
|
}>()
|
||||||
|
|
||||||
const receptionIri = computed(() =>
|
const bovineTypes = ref<BovineTypeData[]>([])
|
||||||
receptionId ? `/api/receptions/${receptionId}` : null
|
const localQuantities = reactive<Record<string, number | null>>({})
|
||||||
|
const localOtherQuantity = ref<number | null>(props.otherQuantity ?? 0)
|
||||||
|
const isSyncing = ref(false)
|
||||||
|
|
||||||
|
function buildEntriesFromLocal(): ReceptionBovineTypeData[] {
|
||||||
|
return bovineTypes.value.map((type) => {
|
||||||
|
const existing = props.modelValue.find((entry) => entry.bovineType.id === type.id)
|
||||||
|
return {
|
||||||
|
id: existing?.id ?? 0,
|
||||||
|
bovineType: type,
|
||||||
|
quantity: localQuantities[String(type.id)] ?? 0
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncLocalFromProps() {
|
||||||
|
isSyncing.value = true
|
||||||
|
try {
|
||||||
|
for (const key of Object.keys(localQuantities)) {
|
||||||
|
delete localQuantities[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const type of bovineTypes.value) {
|
||||||
|
const existing = props.modelValue.find((entry) => entry.bovineType.id === type.id)
|
||||||
|
localQuantities[String(type.id)] = existing?.quantity ?? 0
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
isSyncing.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.otherQuantity,
|
||||||
|
(value) => {
|
||||||
|
localOtherQuantity.value = value ?? 0
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const totalBovines = computed(() => {
|
watch(localOtherQuantity, (value) => {
|
||||||
const base = Object.values(bovineQuantities).reduce((sum, value) => {
|
emit('update:otherQuantity', value ?? 0)
|
||||||
return sum + (value ?? 0)
|
|
||||||
}, 0)
|
|
||||||
return base + (otherQuantity.value ?? 0)
|
|
||||||
})
|
|
||||||
|
|
||||||
const hasBovineChanged = () => {
|
|
||||||
if ((initialOtherQuantity.value ?? 0) !== (otherQuantity.value ?? 0)) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const [key, value] of Object.entries(bovineQuantities)) {
|
|
||||||
if ((initialBovineQuantities.value[key] ?? 0) !== (value ?? 0)) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
const loadBovineType = async () => {
|
|
||||||
isLoadingBovineType.value = true
|
|
||||||
try {
|
|
||||||
bovineType.value = await getBovineTypeList()
|
|
||||||
} finally {
|
|
||||||
isLoadingBovineType.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
await loadBovineType()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
[() => receptionId, () => bovineType.value],
|
() => props.modelValue,
|
||||||
async ([id, types]) => {
|
() => {
|
||||||
if (!id || !receptionIri.value || types.length === 0) {
|
syncLocalFromProps()
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
localQuantities,
|
||||||
|
() => {
|
||||||
|
if (isSyncing.value) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
emit('update:modelValue', buildEntriesFromLocal())
|
||||||
const selectionMap: Record<string, number | null> = {}
|
|
||||||
for (const type of types) {
|
|
||||||
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)
|
|
||||||
|
|
||||||
const existingOther = reception.bovineDetail
|
|
||||||
const parsedOther =
|
|
||||||
typeof existingOther === 'string' && existingOther.trim() !== ''
|
|
||||||
? Number(existingOther)
|
|
||||||
: 0
|
|
||||||
otherQuantity.value = Number.isFinite(parsedOther) ? parsedOther : 0
|
|
||||||
initialBovineQuantities.value = {...selectionMap}
|
|
||||||
initialOtherQuantity.value = otherQuantity.value ?? 0
|
|
||||||
},
|
},
|
||||||
{immediate: true}
|
{ deep: true }
|
||||||
)
|
)
|
||||||
|
|
||||||
async function syncBovineSelections(receptionIri: string) {
|
onMounted(async () => {
|
||||||
const existing = await getReceptionBovineList(receptionIri)
|
bovineTypes.value = await getBovineTypeList()
|
||||||
const existingMap = new Map<string, { id: number; quantity: number | null }>()
|
syncLocalFromProps()
|
||||||
|
emit('update:modelValue', buildEntriesFromLocal())
|
||||||
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)) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
await createReceptionBovine({
|
|
||||||
reception: receptionIri,
|
|
||||||
bovineType: `/api/bovine_types/${bovineTypeId}`,
|
|
||||||
quantity
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.isValidate,
|
|
||||||
async (val) => {
|
|
||||||
if (!val) return
|
|
||||||
await runValidate()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
const runValidate = async () => {
|
|
||||||
if (!hasBovineChanged()) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const receptionIri = `/api/receptions/${reception.id}`
|
|
||||||
// @TODO Ajouter un composable pour le toaster qui gère les key i18n
|
|
||||||
if (totalBovines.value > 52) {
|
|
||||||
toast.error({
|
|
||||||
title: 'Erreur',
|
|
||||||
message: ('Le total des bovins ne peut pas dépasser 52.')
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
await syncBovineSelections(receptionIri)
|
|
||||||
|
|
||||||
await updateReception(receptionId, {
|
|
||||||
merchandiseType: null,
|
|
||||||
merchandiseDetail: null,
|
|
||||||
bovineDetail: otherQuantity.value ? String(otherQuantity.value) : null,
|
|
||||||
})
|
|
||||||
|
|
||||||
initialBovineQuantities.value = {...bovineQuantities}
|
|
||||||
initialOtherQuantity.value = otherQuantity.value ?? 0
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<form>
|
<form>
|
||||||
<div
|
<div class="flex flex-col">
|
||||||
class="flex flex-col">
|
<div class="w-full col-start-1 row-start-1">
|
||||||
<div class="w-full col-start-1 row-start-1">
|
|
||||||
<UiRadioGroup
|
<UiRadioGroup
|
||||||
id="merchandise-type"
|
id="merchandise-type"
|
||||||
v-model="selectedMerchandiseTypeId"
|
v-model="selectedMerchandiseTypeId"
|
||||||
@@ -14,14 +13,14 @@
|
|||||||
input-class="accent-primary-700 focus:ring-primary-700"
|
input-class="accent-primary-700 focus:ring-primary-700"
|
||||||
option-label-class="uppercase"
|
option-label-class="uppercase"
|
||||||
wrapper-class="w-full uppercase"
|
wrapper-class="w-full uppercase"
|
||||||
group-class="grid grid-cols-[336px_336px_355px_200px] w-[160px_160px_200px_180px] mt-9 mb-7"
|
group-class="grid grid-cols-[336px_336px_355px_200px] w-[160px_160px_200px_180px] mt-9 mb-7"
|
||||||
:disabled="!auth.isAdmin"
|
:disabled="!isAdmin"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="w-full grid grid-cols-[3fr_1fr] gap-12 col-start-2 row-start-1">
|
<div class="w-full grid grid-cols-[3fr_1fr] gap-12 col-start-2 row-start-1">
|
||||||
<div
|
<div
|
||||||
v-if="merchandiseTypeId && !isGranule"
|
v-if="selectedMerchandiseTypeId && !isGranule"
|
||||||
class="flex gap-[218px]"
|
class="flex gap-[218px]"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@@ -32,172 +31,174 @@
|
|||||||
v-model="selectedBuildingIds"
|
v-model="selectedBuildingIds"
|
||||||
:value="String(building.id)"
|
:value="String(building.id)"
|
||||||
:label="building.label"
|
:label="building.label"
|
||||||
:disabled="!auth.isAdmin"
|
:disabled="!isAdmin"
|
||||||
input-class="accent-primary-700 focus:ring-primary-700"
|
input-class="accent-primary-700 focus:ring-primary-700"
|
||||||
label-class="text-xl uppercase"
|
label-class="text-xl uppercase"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="merchandiseTypeId && isAutres"
|
v-if="selectedMerchandiseTypeId && isAutres"
|
||||||
class="flex flex-col justify-self-end max-w-[182px]"
|
class="flex flex-col justify-self-end max-w-[182px]"
|
||||||
>
|
>
|
||||||
<UiTextInput
|
<UiTextInput
|
||||||
id="merchandise-detail"
|
id="merchandise-detail"
|
||||||
:disabled="!auth.isAdmin"
|
:disabled="!isAdmin"
|
||||||
v-model="merchandiseDetail"
|
v-model="merchandiseDetail"
|
||||||
placeholder="Préciser"
|
placeholder="Préciser"
|
||||||
:maxlength="255"
|
:maxlength="255"
|
||||||
class="h-6"
|
class="h-6"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
|
||||||
v-if="merchandiseTypeId && isGranule"
|
|
||||||
class="flex flex-col gap-10 w-full col-start-2 row-start-1 "
|
|
||||||
>
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-[max-content_max-content_max-content_max-content] justify-between">
|
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="selectedMerchandiseTypeId && isGranule"
|
||||||
|
class="flex flex-col gap-10 w-full col-start-2 row-start-1"
|
||||||
|
>
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-[max-content_max-content_max-content_max-content] justify-between">
|
||||||
<div v-for="type in pelletTypes" :key="type.id" class="flex flex-col gap-4">
|
<div v-for="type in pelletTypes" :key="type.id" class="flex flex-col gap-4">
|
||||||
<p class="mb-1 font-medium uppercase">{{ type.label }}</p>
|
<p class="mb-1 font-medium uppercase">{{ type.label }}</p>
|
||||||
<div
|
<div
|
||||||
v-for="building in buildings"
|
v-for="building in buildings"
|
||||||
:key="building.id"
|
:key="building.id"
|
||||||
class="flex text-lg"
|
class="flex text-lg"
|
||||||
>
|
>
|
||||||
<UiCheckbox
|
<UiCheckbox
|
||||||
v-model="selectedPelletBuildingIds[String(type.id)]"
|
v-model="selectedPelletBuildingIds[String(type.id)]"
|
||||||
:value="String(building.id)"
|
:value="String(building.id)"
|
||||||
:label="building.label"
|
:label="building.label"
|
||||||
:disabled="!auth.isAdmin"
|
:disabled="!isAdmin"
|
||||||
input-class="accent-primary-700 focus:ring-primary-700"
|
input-class="accent-primary-700 focus:ring-primary-700"
|
||||||
label-class="text-lg"
|
label-class="text-lg"
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {computed, onMounted, ref, watch} from 'vue'
|
import { computed, onMounted, ref, watch } from 'vue'
|
||||||
import {getBuildingList} from '~/services/building'
|
import type { BuildingData } from '~/services/dto/building-data'
|
||||||
import {getMerchandiseTypeList} from '~/services/merchandise-type'
|
import type { MerchandiseTypeData } from '~/services/dto/merchandise-type-data'
|
||||||
import type {MerchandiseTypeData} from '~/services/dto/merchandise-type-data'
|
import type { PelletTypeData } from '~/services/dto/pellet-type-data'
|
||||||
import type {BuildingData} from '~/services/dto/building-data'
|
import type { MerchandiseEntryData } from '~/services/dto/reception-data'
|
||||||
import type {PelletTypeData} from '~/services/dto/pellet-type-data'
|
import { getBuildingList } from '~/services/building'
|
||||||
import {getPelletTypeList} from '~/services/pellet-type'
|
import { getMerchandiseTypeList } from '~/services/merchandise-type'
|
||||||
import {
|
import { getPelletTypeList } from '~/services/pellet-type'
|
||||||
createReceptionPelletBuilding,
|
import { MERCHANDISE_TYPE_CODES } from '~/utils/constants'
|
||||||
deleteReceptionPelletBuilding,
|
|
||||||
getReceptionPelletBuildingList
|
const props = defineProps<{
|
||||||
} from '~/services/reception-pellet-building'
|
modelValue: MerchandiseEntryData
|
||||||
import {MERCHANDISE_TYPE_CODES} from '~/utils/constants'
|
isAdmin: boolean
|
||||||
import {getReception, updateReception} from "~/services/reception";
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(event: 'update:modelValue', value: MerchandiseEntryData): void
|
||||||
|
}>()
|
||||||
|
|
||||||
const merchandiseTypes = ref<MerchandiseTypeData[]>([])
|
const merchandiseTypes = ref<MerchandiseTypeData[]>([])
|
||||||
const buildings = ref<BuildingData[]>([])
|
const buildings = ref<BuildingData[]>([])
|
||||||
const pelletTypes = ref<PelletTypeData[]>([])
|
const pelletTypes = ref<PelletTypeData[]>([])
|
||||||
|
|
||||||
const selectedMerchandiseTypeId = ref('')
|
const selectedMerchandiseTypeId = ref('')
|
||||||
const selectedBuildingIds = ref<string[]>([])
|
const selectedBuildingIds = ref<string[]>([])
|
||||||
const selectedPelletBuildingIds = ref<Record<string, string[]>>({})
|
const selectedPelletBuildingIds = ref<Record<string, string[]>>({})
|
||||||
const merchandiseDetail = ref('')
|
const merchandiseDetail = ref('')
|
||||||
const auth = useAuthStore()
|
const isHydrating = ref(false)
|
||||||
const initialMerchandiseTypeId = ref<string | null>(null)
|
const isReady = ref(false)
|
||||||
const initialMerchandiseDetail = ref<string | null>(null)
|
|
||||||
const initialBuildingIds = ref<string[]>([])
|
|
||||||
const initialPelletSelections = ref<Record<string, string[]>>({})
|
|
||||||
const props = defineProps<{
|
|
||||||
idReception: number
|
|
||||||
isValidate: boolean
|
|
||||||
}>()
|
|
||||||
const receptionId = props.idReception
|
|
||||||
const reception = await getReception(receptionId)
|
|
||||||
const merchandiseTypeId = reception.receptionType?.id
|
|
||||||
|
|
||||||
// Extrait l'ID d'une relation depuis un IRI ou un objet complet.
|
|
||||||
const 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 record = value as { id?: number | string }
|
|
||||||
if (typeof record.id === 'number') {
|
|
||||||
return String(record.id)
|
|
||||||
}
|
|
||||||
if (typeof record.id === 'string') {
|
|
||||||
return record.id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
// Type de marchandise sélectionné dans le select
|
|
||||||
const selectedMerchandiseType = computed(() =>
|
const selectedMerchandiseType = computed(() =>
|
||||||
merchandiseTypes.value.find((type) => String(type.id) === selectedMerchandiseTypeId.value)
|
merchandiseTypes.value.find((type) => String(type.id) === selectedMerchandiseTypeId.value) ?? null
|
||||||
|
)
|
||||||
|
const isGranule = computed(
|
||||||
|
() => selectedMerchandiseType.value?.code === MERCHANDISE_TYPE_CODES.GRANULE
|
||||||
|
)
|
||||||
|
const isAutres = computed(
|
||||||
|
() => selectedMerchandiseType.value?.code === MERCHANDISE_TYPE_CODES.AUTRES
|
||||||
)
|
)
|
||||||
// Indique si le type est "Granulé"
|
|
||||||
const isGranule = computed(() => selectedMerchandiseType.value?.code === MERCHANDISE_TYPE_CODES.GRANULE)
|
|
||||||
// Indique si le type est "Autres"
|
|
||||||
const isAutres = computed(() => selectedMerchandiseType.value?.code === MERCHANDISE_TYPE_CODES.AUTRES)
|
|
||||||
|
|
||||||
const hasMerchandiseChanged = () => {
|
function clonePelletSelections(value: Record<string, string[]>) {
|
||||||
const currentTypeId = selectedMerchandiseTypeId.value || null
|
const clone: Record<string, string[]> = {}
|
||||||
if (initialMerchandiseTypeId.value !== currentTypeId) {
|
for (const [key, buildingIds] of Object.entries(value)) {
|
||||||
return true
|
clone[key] = [...buildingIds]
|
||||||
}
|
}
|
||||||
|
return clone
|
||||||
const currentDetail = isAutres.value ? merchandiseDetail.value.trim() : ''
|
|
||||||
if ((initialMerchandiseDetail.value ?? '') !== currentDetail) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentBuildings = isGranule.value ? [] : [...selectedBuildingIds.value].sort()
|
|
||||||
const initialBuildings = [...initialBuildingIds.value].sort()
|
|
||||||
if (currentBuildings.length !== initialBuildings.length) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
for (let i = 0; i < currentBuildings.length; i += 1) {
|
|
||||||
if (currentBuildings[i] !== initialBuildings[i]) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentPellets = normalizePelletSelections(selectedPelletBuildingIds.value)
|
|
||||||
const initialPellets = normalizePelletSelections(initialPelletSelections.value)
|
|
||||||
if (currentPellets.length !== initialPellets.length) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
for (let i = 0; i < currentPellets.length; i += 1) {
|
|
||||||
if (currentPellets[i] !== initialPellets[i]) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const normalizePelletSelections = (selections: Record<string, string[]>) => {
|
function ensurePelletKeys() {
|
||||||
const pairs: string[] = []
|
for (const pelletType of pelletTypes.value) {
|
||||||
for (const [pelletTypeId, buildingIds] of Object.entries(selections)) {
|
const key = String(pelletType.id)
|
||||||
for (const buildingId of buildingIds) {
|
if (!selectedPelletBuildingIds.value[key]) {
|
||||||
pairs.push(`${pelletTypeId}:${buildingId}`)
|
selectedPelletBuildingIds.value[key] = []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return pairs.sort()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Charge les référentiels et hydrate le formulaire depuis la réception
|
function hydrateFromModelValue(value: MerchandiseEntryData) {
|
||||||
|
isHydrating.value = true
|
||||||
|
try {
|
||||||
|
selectedMerchandiseTypeId.value = value.merchandiseTypeId ?? ''
|
||||||
|
merchandiseDetail.value = value.merchandiseDetail ?? ''
|
||||||
|
selectedBuildingIds.value = [...(value.selectedBuildingIds ?? [])]
|
||||||
|
selectedPelletBuildingIds.value = clonePelletSelections(
|
||||||
|
value.selectedPelletBuildingIds ?? {}
|
||||||
|
)
|
||||||
|
ensurePelletKeys()
|
||||||
|
} finally {
|
||||||
|
isHydrating.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function emitModelValue() {
|
||||||
|
emit('update:modelValue', {
|
||||||
|
merchandiseTypeId: selectedMerchandiseTypeId.value,
|
||||||
|
merchandiseDetail: merchandiseDetail.value,
|
||||||
|
selectedBuildingIds: [...selectedBuildingIds.value],
|
||||||
|
selectedPelletBuildingIds: clonePelletSelections(selectedPelletBuildingIds.value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.modelValue,
|
||||||
|
(value) => {
|
||||||
|
hydrateFromModelValue(value)
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
[selectedMerchandiseTypeId, selectedBuildingIds, selectedPelletBuildingIds, merchandiseDetail],
|
||||||
|
() => {
|
||||||
|
if (isHydrating.value || !isReady.value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isGranule.value) {
|
||||||
|
if (selectedBuildingIds.value.length > 0) {
|
||||||
|
selectedBuildingIds.value = []
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (const key of Object.keys(selectedPelletBuildingIds.value)) {
|
||||||
|
if (selectedPelletBuildingIds.value[key].length > 0) {
|
||||||
|
selectedPelletBuildingIds.value[key] = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isAutres.value && merchandiseDetail.value !== '') {
|
||||||
|
merchandiseDetail.value = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
emitModelValue()
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
)
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
const [merchandiseTypeList, buildingList, pelletTypeList] = await Promise.all([
|
const [merchandiseTypeList, buildingList, pelletTypeList] = await Promise.all([
|
||||||
getMerchandiseTypeList(),
|
getMerchandiseTypeList(),
|
||||||
@@ -208,133 +209,8 @@ onMounted(async () => {
|
|||||||
buildings.value = buildingList
|
buildings.value = buildingList
|
||||||
pelletTypes.value = pelletTypeList
|
pelletTypes.value = pelletTypeList
|
||||||
|
|
||||||
const currentId = reception.merchandiseType?.id
|
hydrateFromModelValue(props.modelValue)
|
||||||
if (currentId) {
|
isReady.value = true
|
||||||
selectedMerchandiseTypeId.value = String(currentId)
|
emitModelValue()
|
||||||
}
|
|
||||||
merchandiseDetail.value = reception.merchandiseDetail ?? ''
|
|
||||||
|
|
||||||
selectedBuildingIds.value =
|
|
||||||
reception.buildings?.map((building) => String(building.id)) ?? []
|
|
||||||
|
|
||||||
const existingPelletSelections = reception.pelletBuildings ?? []
|
|
||||||
const selectionMap: Record<string, string[]> = {}
|
|
||||||
for (const selection of existingPelletSelections) {
|
|
||||||
// L'API peut renvoyer les relations comme IRI ou comme objets selon le contexte.
|
|
||||||
const pelletTypeId = getRelationId(selection.pelletType)
|
|
||||||
const buildingId = getRelationId(selection.building)
|
|
||||||
if (!pelletTypeId || !buildingId) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if (!selectionMap[pelletTypeId]) {
|
|
||||||
selectionMap[pelletTypeId] = []
|
|
||||||
}
|
|
||||||
selectionMap[pelletTypeId].push(buildingId)
|
|
||||||
}
|
|
||||||
for (const pelletType of pelletTypes.value) {
|
|
||||||
const key = String(pelletType.id)
|
|
||||||
if (!selectionMap[key]) {
|
|
||||||
selectionMap[key] = []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
selectedPelletBuildingIds.value = selectionMap
|
|
||||||
initialMerchandiseTypeId.value = selectedMerchandiseTypeId.value || null
|
|
||||||
initialMerchandiseDetail.value = isAutres.value ? merchandiseDetail.value.trim() : ''
|
|
||||||
initialBuildingIds.value = [...selectedBuildingIds.value]
|
|
||||||
initialPelletSelections.value = JSON.parse(JSON.stringify(selectedPelletBuildingIds.value))
|
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.isValidate,
|
|
||||||
async (val) => {
|
|
||||||
if (!val) return
|
|
||||||
await runValidate()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const runValidate = async () => {
|
|
||||||
if (!hasMerchandiseChanged()) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const receptionIri = `/api/receptions/${reception.id}`
|
|
||||||
|
|
||||||
// 1) supprimer toutes les anciennes associations
|
|
||||||
await clearPelletSelections(receptionIri)
|
|
||||||
|
|
||||||
// 2) update reception (type + détails + buildings non-granulé)
|
|
||||||
await updateReception(reception.id, {
|
|
||||||
merchandiseType: selectedMerchandiseTypeId.value
|
|
||||||
? `/api/merchandise_types/${selectedMerchandiseTypeId.value}`
|
|
||||||
: null,
|
|
||||||
merchandiseDetail: isAutres.value ? merchandiseDetail.value.trim() :
|
|
||||||
null,
|
|
||||||
buildings: isGranule.value
|
|
||||||
? []
|
|
||||||
: selectedBuildingIds.value.map((id) => `/api/buildings/${id}`),
|
|
||||||
bovineDetail: null,
|
|
||||||
bovinesTypes: null,
|
|
||||||
})
|
|
||||||
|
|
||||||
// 3) si granulé ALORS créer les nouvelles associations
|
|
||||||
if (isGranule.value) {
|
|
||||||
await syncPelletSelections(receptionIri)
|
|
||||||
}
|
|
||||||
|
|
||||||
initialMerchandiseTypeId.value = selectedMerchandiseTypeId.value || null
|
|
||||||
initialMerchandiseDetail.value = isAutres.value ? merchandiseDetail.value.trim() : ''
|
|
||||||
initialBuildingIds.value = [...selectedBuildingIds.value]
|
|
||||||
initialPelletSelections.value = JSON.parse(JSON.stringify(selectedPelletBuildingIds.value))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Supprime toutes les associations granulés/bâtiments existantes
|
|
||||||
async function clearPelletSelections(receptionIri: string) {
|
|
||||||
const existing = await getReceptionPelletBuildingList(receptionIri)
|
|
||||||
for (const selection of existing) {
|
|
||||||
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)
|
|
||||||
const existingMap = new Map<string, number>()
|
|
||||||
for (const selection of existing) {
|
|
||||||
// Construit la table de correspondance avec des IDs normalisés pour éviter les doublons.
|
|
||||||
const pelletTypeId = getRelationId(selection.pelletType)
|
|
||||||
const buildingId = getRelationId(selection.building)
|
|
||||||
if (!pelletTypeId || !buildingId) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
const key = `${pelletTypeId}:${buildingId}`
|
|
||||||
existingMap.set(key, selection.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
const desiredEntries: Array<{ pelletTypeId: string; buildingId: string }> = []
|
|
||||||
for (const [pelletTypeId, buildingIds] of Object.entries(selectedPelletBuildingIds.value)) {
|
|
||||||
for (const buildingId of buildingIds) {
|
|
||||||
desiredEntries.push({pelletTypeId, buildingId})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const desiredKeys = new Set(desiredEntries.map(
|
|
||||||
(entry) => `${entry.pelletTypeId}:${entry.buildingId}`
|
|
||||||
))
|
|
||||||
|
|
||||||
for (const [key, id] of existingMap.entries()) {
|
|
||||||
if (!desiredKeys.has(key)) {
|
|
||||||
await deleteReceptionPelletBuilding(id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const entry of desiredEntries) {
|
|
||||||
const key = `${entry.pelletTypeId}:${entry.buildingId}`
|
|
||||||
if (!existingMap.has(key)) {
|
|
||||||
await createReceptionPelletBuilding({
|
|
||||||
reception: receptionIri,
|
|
||||||
pelletType: `/api/pellet_types/${entry.pelletTypeId}`,
|
|
||||||
building: `/api/buildings/${entry.buildingId}`
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -173,17 +173,15 @@
|
|||||||
|
|
||||||
<update-merchandise
|
<update-merchandise
|
||||||
v-show="activeTab === 'merchandise' && isMerchandise"
|
v-show="activeTab === 'merchandise' && isMerchandise"
|
||||||
:idReception="idReception"
|
v-model="merchandiseForm"
|
||||||
:disabled="!auth.isAdmin"
|
:isAdmin="auth.isAdmin"
|
||||||
:isValidate="isValidMerchandise"
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<update-bovin
|
<update-bovin
|
||||||
v-show="activeTab === 'merchandise' && !isMerchandise"
|
v-show="activeTab === 'merchandise' && !isMerchandise"
|
||||||
:idReception="idReception"
|
v-model="bovineEntries"
|
||||||
:disabled="!auth.isAdmin"
|
v-model:otherQuantity="bovineOtherQuantity"
|
||||||
:isValidate="isValidBovin"
|
:isAdmin="auth.isAdmin"
|
||||||
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-center">
|
<div class="flex justify-center">
|
||||||
@@ -201,62 +199,67 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {usePdfPrinter} from "#imports"
|
import { usePdfPrinter } from '#imports'
|
||||||
import {useReceptionStore} from '~/stores/reception'
|
import { computed } from 'vue'
|
||||||
import type {UserData} from '~/services/dto/user-data'
|
import UpdateBovin from '~/components/reception/update-bovin.vue'
|
||||||
import {getUsers} from '~/services/auth'
|
import UpdateMerchandise from '~/components/reception/update-merchandise.vue'
|
||||||
import {useAuthStore} from '~/stores/auth'
|
import UpdateWeight from '~/components/reception/update-weight.vue'
|
||||||
import type {SupplierData} from '~/services/dto/supplier-data'
|
import { getUsers } from '~/services/auth'
|
||||||
import {getSupplierList} from '~/services/supplier'
|
import { getCarrierList } from '~/services/carrier'
|
||||||
import type {TruckData} from '~/services/dto/truck-data'
|
import type { CarrierData } from '~/services/dto/carrier-data'
|
||||||
import {getTruckList} from '~/services/truck'
|
import type { DriverData } from '~/services/dto/driver-data'
|
||||||
import type {CarrierData} from '~/services/dto/carrier-data'
|
import type { ReceptionBovineTypeData } from '~/services/dto/reception-bovine-data'
|
||||||
import {getCarrierList} from '~/services/carrier'
|
import type {
|
||||||
import type {DriverData} from '~/services/dto/driver-data'
|
MerchandiseEntryData,
|
||||||
import {getDriverList} from '~/services/driver'
|
ReceptionData,
|
||||||
import type {VehicleData} from '~/services/dto/vehicle-data'
|
ReceptionFormData,
|
||||||
import {getVehicleList} from '~/services/vehicle'
|
WeightEntryData
|
||||||
import {RECEPTION_TYPE_CODES, SUPPLIER_CODE} from "~/utils/constants";
|
} from '~/services/dto/reception-data'
|
||||||
import {deleteReceptionBovine, getReceptionBovineList,} from "~/services/reception-bovine";
|
import type { ReceptionTypeData } from '~/services/dto/reception-type-data'
|
||||||
import type {ReceptionData, ReceptionFormData, WeightEntryData} from "~/services/dto/reception-data";
|
import type { SupplierData } from '~/services/dto/supplier-data'
|
||||||
import {getReception, updateReception} from "~/services/reception";
|
import type { TruckData } from '~/services/dto/truck-data'
|
||||||
import UpdateWeight from "~/components/reception/update-weight.vue";
|
import type { UserData } from '~/services/dto/user-data'
|
||||||
import UpdateMerchandise from "~/components/reception/update-merchandise.vue";
|
import type { VehicleData } from '~/services/dto/vehicle-data'
|
||||||
import UpdateBovin from "~/components/reception/update-bovin.vue";
|
import { getDriverList } from '~/services/driver'
|
||||||
import {getReceptionTypeList} from "~/services/reception-type";
|
import {
|
||||||
import type {ReceptionTypeData} from "~/services/dto/reception-type-data";
|
createReceptionBovine,
|
||||||
import {computed} from "vue";
|
deleteReceptionBovine,
|
||||||
import {createWeight, updateWeight} from "~/services/weight";
|
getReceptionBovineList,
|
||||||
import {deleteReceptionPelletBuilding, getReceptionPelletBuildingList} from "~/services/reception-pellet-building";
|
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 { RECEPTION_TYPE_CODES, SUPPLIER_CODE } from '~/utils/constants'
|
||||||
|
|
||||||
const form = reactive<ReceptionFormData>({
|
const router = useRouter()
|
||||||
identificationNumber: null,
|
const route = useRoute()
|
||||||
licensePlate: '',
|
const auth = useAuthStore()
|
||||||
receptionDate: new Date().toISOString().slice(0, 10),
|
const authStore = useAuthStore()
|
||||||
receptionTypeId: '',
|
const receptionStore = useReceptionStore()
|
||||||
userId: '',
|
const { printPdf } = usePdfPrinter()
|
||||||
supplierId: '',
|
|
||||||
addressId: '',
|
|
||||||
truckId: '',
|
|
||||||
carrierId: '',
|
|
||||||
driverId: '',
|
|
||||||
vehicleId: ''
|
|
||||||
})
|
|
||||||
|
|
||||||
const createEmptyWeightEntry = (type: 'gross' | 'tare'): WeightEntryData => ({
|
|
||||||
type,
|
|
||||||
dsd: null,
|
|
||||||
weight: null,
|
|
||||||
weighedAt: null
|
|
||||||
})
|
|
||||||
|
|
||||||
|
const activeTab = ref<'weightsEmpty' | 'weights' | 'merchandise'>('weights')
|
||||||
const grossWeight = ref<WeightEntryData>(createEmptyWeightEntry('gross'))
|
const grossWeight = ref<WeightEntryData>(createEmptyWeightEntry('gross'))
|
||||||
const tareWeight = ref<WeightEntryData>(createEmptyWeightEntry('tare'))
|
const tareWeight = ref<WeightEntryData>(createEmptyWeightEntry('tare'))
|
||||||
|
const bovineEntries = ref<ReceptionBovineTypeData[]>([])
|
||||||
const { printPdf } = usePdfPrinter()
|
const bovineOtherQuantity = ref<number | null>(0)
|
||||||
const activeTab = ref<'weightsEmpty' | 'weights' | 'merchandise'>('weights')
|
const merchandiseForm = ref<MerchandiseEntryData>({
|
||||||
const router = useRouter()
|
merchandiseTypeId: '',
|
||||||
const receptionStore = useReceptionStore()
|
merchandiseDetail: '',
|
||||||
|
selectedBuildingIds: [],
|
||||||
|
selectedPelletBuildingIds: {}
|
||||||
|
})
|
||||||
const allowAnyLicensePlate = ref(false)
|
const allowAnyLicensePlate = ref(false)
|
||||||
const isLoading = ref(false)
|
const isLoading = ref(false)
|
||||||
const users = ref<UserData[]>([])
|
const users = ref<UserData[]>([])
|
||||||
@@ -273,28 +276,30 @@ const drivers = ref<DriverData[]>([])
|
|||||||
const isLoadingDrivers = ref(false)
|
const isLoadingDrivers = ref(false)
|
||||||
const vehicles = ref<VehicleData[]>([])
|
const vehicles = ref<VehicleData[]>([])
|
||||||
const isLoadingVehicles = ref(false)
|
const isLoadingVehicles = ref(false)
|
||||||
const authStore = useAuthStore()
|
|
||||||
const isValid = ref(false)
|
|
||||||
const isValidBovin = ref(false)
|
|
||||||
const isValidMerchandise = ref(false)
|
|
||||||
const formIsLoading = ref(false)
|
const formIsLoading = ref(false)
|
||||||
const route = useRoute()
|
|
||||||
const idReception = Number(route.params.id)
|
|
||||||
const auth = useAuthStore()
|
|
||||||
const isMerchandise = ref(false)
|
const isMerchandise = ref(false)
|
||||||
|
|
||||||
// Empêche les watchers de reset des champs pendant le remplissage initial
|
|
||||||
const isHydrating = ref(false)
|
const isHydrating = ref(false)
|
||||||
|
|
||||||
// Transporteur sélectionné dans le formulaire
|
const idReception = Number(route.params.id)
|
||||||
|
|
||||||
|
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(() =>
|
const selectedCarrier = computed(() =>
|
||||||
carriers.value.find((carrier) => String(carrier.id) === form.carrierId) ?? null
|
carriers.value.find((carrier) => String(carrier.id) === form.carrierId) ?? null
|
||||||
)
|
)
|
||||||
|
|
||||||
// Indique si le transporteur est LIOT
|
|
||||||
const isLiotCarrier = computed(() => selectedCarrier.value?.code === SUPPLIER_CODE.LIOT)
|
const isLiotCarrier = computed(() => selectedCarrier.value?.code === SUPPLIER_CODE.LIOT)
|
||||||
|
|
||||||
// Adresses disponibles pour le fournisseur sélectionné
|
|
||||||
const supplierAddresses = computed(() => {
|
const supplierAddresses = computed(() => {
|
||||||
const supplierId = Number(form.supplierId)
|
const supplierId = Number(form.supplierId)
|
||||||
if (!Number.isFinite(supplierId)) {
|
if (!Number.isFinite(supplierId)) {
|
||||||
@@ -302,16 +307,12 @@ const supplierAddresses = computed(() => {
|
|||||||
}
|
}
|
||||||
return suppliers.value.find((supplier) => supplier.id === supplierId)?.addresses ?? []
|
return suppliers.value.find((supplier) => supplier.id === supplierId)?.addresses ?? []
|
||||||
})
|
})
|
||||||
|
|
||||||
// Chauffeurs filtrés par transporteur (LIOT)
|
|
||||||
const filteredDrivers = computed<DriverData[]>(() => {
|
const filteredDrivers = computed<DriverData[]>(() => {
|
||||||
if (!form.carrierId) {
|
if (!form.carrierId) {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
return drivers.value.filter((driver) => String(driver.carrier?.id) === form.carrierId)
|
return drivers.value.filter((driver) => String(driver.carrier?.id) === form.carrierId)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Véhicules filtrés par transporteur + type de camion
|
|
||||||
const filteredVehicles = computed<VehicleData[]>(() => {
|
const filteredVehicles = computed<VehicleData[]>(() => {
|
||||||
if (!form.carrierId) {
|
if (!form.carrierId) {
|
||||||
return []
|
return []
|
||||||
@@ -323,8 +324,42 @@ const filteredVehicles = computed<VehicleData[]>(() => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Supprime les données bovines si on change de type de réception
|
watch(
|
||||||
const clearReceptionBovines = async (receptionId: number) => {
|
() => idReception,
|
||||||
|
async (id) => {
|
||||||
|
if (id === null) {
|
||||||
|
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 receptionIri = `/api/receptions/${receptionId}`
|
||||||
const existing = await getReceptionBovineList(receptionIri)
|
const existing = await getReceptionBovineList(receptionIri)
|
||||||
for (const selection of existing) {
|
for (const selection of existing) {
|
||||||
@@ -332,22 +367,46 @@ const clearReceptionBovines = async (receptionId: number) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const syncMerchandiseFlag = () => {
|
async function loadBovineEntries(receptionId: number) {
|
||||||
|
const receptionIri = `/api/receptions/${receptionId}`
|
||||||
|
bovineEntries.value = await getReceptionBovineList(receptionIri)
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncMerchandiseFlag() {
|
||||||
const receptionType =
|
const receptionType =
|
||||||
receptionTypes.value.find((type) => String(type.id) === form.receptionTypeId) ?? null
|
receptionTypes.value.find((type) => String(type.id) === form.receptionTypeId) ?? null
|
||||||
isMerchandise.value = receptionType?.code === RECEPTION_TYPE_CODES.MERCHANDISES
|
isMerchandise.value = receptionType?.code === RECEPTION_TYPE_CODES.MERCHANDISES
|
||||||
}
|
}
|
||||||
|
|
||||||
const clearReceptionMerchandise = async (receptionId: number) => {
|
function getRelationId(value: unknown): string | null {
|
||||||
const receptionIri = `/api/receptions/${receptionId}`
|
if (!value) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
// supprime toutes les associations granulés/bâtiments
|
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)
|
const existing = await getReceptionPelletBuildingList(receptionIri)
|
||||||
for (const selection of existing) {
|
for (const selection of existing) {
|
||||||
await deleteReceptionPelletBuilding(selection.id)
|
await deleteReceptionPelletBuilding(selection.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
// reset des champs marchandise
|
|
||||||
await updateReception(receptionId, {
|
await updateReception(receptionId, {
|
||||||
merchandiseType: null,
|
merchandiseType: null,
|
||||||
merchandiseDetail: null,
|
merchandiseDetail: null,
|
||||||
@@ -355,7 +414,7 @@ const clearReceptionMerchandise = async (receptionId: number) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const hydrateFromReception = (reception: ReceptionData | null) => {
|
function hydrateFromReception(reception: ReceptionData | null) {
|
||||||
if (!reception) {
|
if (!reception) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -384,41 +443,41 @@ const hydrateFromReception = (reception: ReceptionData | null) => {
|
|||||||
form.receptionTypeId = reception?.receptionType?.id
|
form.receptionTypeId = reception?.receptionType?.id
|
||||||
? String(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 gross = reception.weights?.find((weight) => weight.type === 'gross') ?? null
|
||||||
const tare = reception.weights?.find((weight) => weight.type === 'tare') ?? null
|
const tare = reception.weights?.find((weight) => weight.type === 'tare') ?? null
|
||||||
grossWeight.value = gross ? {...gross} : createEmptyWeightEntry('gross')
|
grossWeight.value = gross ? { ...gross } : createEmptyWeightEntry('gross')
|
||||||
tareWeight.value = tare ? {...tare} : createEmptyWeightEntry('tare')
|
tareWeight.value = tare ? { ...tare } : createEmptyWeightEntry('tare')
|
||||||
isHydrating.value = false
|
isHydrating.value = false
|
||||||
syncMerchandiseFlag()
|
syncMerchandiseFlag()
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(
|
async function loadUsers() {
|
||||||
() => idReception,
|
|
||||||
async (id) => {
|
|
||||||
if (id === null) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
isLoading.value = true
|
|
||||||
try {
|
|
||||||
const reception = await getReception(id)
|
|
||||||
hydrateFromReception(reception)
|
|
||||||
} 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
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// Charge la liste des users pour le select
|
|
||||||
const loadUsers = async () => {
|
|
||||||
isLoadingUsers.value = true
|
isLoadingUsers.value = true
|
||||||
try {
|
try {
|
||||||
users.value = await getUsers()
|
users.value = await getUsers()
|
||||||
@@ -427,8 +486,7 @@ const loadUsers = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Charge la liste des fournisseurs pour le select
|
async function loadSuppliers() {
|
||||||
const loadSuppliers = async () => {
|
|
||||||
isLoadingSuppliers.value = true
|
isLoadingSuppliers.value = true
|
||||||
try {
|
try {
|
||||||
suppliers.value = await getSupplierList()
|
suppliers.value = await getSupplierList()
|
||||||
@@ -437,8 +495,7 @@ const loadSuppliers = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//charge la liste des types pour le select
|
async function loadTypes() {
|
||||||
const loadTypes = async () => {
|
|
||||||
isLoadingTypes.value = true
|
isLoadingTypes.value = true
|
||||||
try {
|
try {
|
||||||
receptionTypes.value = await getReceptionTypeList()
|
receptionTypes.value = await getReceptionTypeList()
|
||||||
@@ -447,8 +504,7 @@ const loadTypes = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Charge la liste des camions pour le select
|
async function loadTrucks() {
|
||||||
const loadTrucks = async () => {
|
|
||||||
isLoadingTrucks.value = true
|
isLoadingTrucks.value = true
|
||||||
try {
|
try {
|
||||||
trucks.value = await getTruckList()
|
trucks.value = await getTruckList()
|
||||||
@@ -457,8 +513,7 @@ const loadTrucks = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Charge la liste des transporteurs pour le select
|
async function loadCarriers() {
|
||||||
const loadCarriers = async () => {
|
|
||||||
isLoadingCarriers.value = true
|
isLoadingCarriers.value = true
|
||||||
try {
|
try {
|
||||||
carriers.value = await getCarrierList()
|
carriers.value = await getCarrierList()
|
||||||
@@ -467,8 +522,7 @@ const loadCarriers = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Charge la liste des chauffeurs pour le select
|
async function loadDrivers() {
|
||||||
const loadDrivers = async () => {
|
|
||||||
isLoadingDrivers.value = true
|
isLoadingDrivers.value = true
|
||||||
try {
|
try {
|
||||||
drivers.value = await getDriverList()
|
drivers.value = await getDriverList()
|
||||||
@@ -477,8 +531,7 @@ const loadDrivers = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Charge la liste des véhicules pour le select
|
async function loadVehicles() {
|
||||||
const loadVehicles = async () => {
|
|
||||||
isLoadingVehicles.value = true
|
isLoadingVehicles.value = true
|
||||||
try {
|
try {
|
||||||
vehicles.value = await getVehicleList()
|
vehicles.value = await getVehicleList()
|
||||||
@@ -487,8 +540,7 @@ const loadVehicles = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// On met l'user connecté par défaut dans le select
|
function setDefaultUser() {
|
||||||
const setDefaultUser = () => {
|
|
||||||
if (form.userId) {
|
if (form.userId) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -497,136 +549,7 @@ const setDefaultUser = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// On récupère toutes les données des selects au chargement du composant
|
async function printReceipt() {
|
||||||
onMounted(async () => {
|
|
||||||
await loadTypes()
|
|
||||||
syncMerchandiseFlag()
|
|
||||||
formIsLoading.value =true
|
|
||||||
await loadUsers()
|
|
||||||
await loadSuppliers()
|
|
||||||
await loadTrucks()
|
|
||||||
await loadCarriers()
|
|
||||||
await loadDrivers()
|
|
||||||
await loadVehicles()
|
|
||||||
await authStore.ensureSession()
|
|
||||||
setDefaultUser()
|
|
||||||
})
|
|
||||||
|
|
||||||
// Ajuste driver/vehicle quand le transporteur change (logique LIOT)
|
|
||||||
watch(
|
|
||||||
() => [form.supplierId, form.addressId, suppliers.value],
|
|
||||||
() => {
|
|
||||||
if (!form.supplierId) {
|
|
||||||
form.addressId = ''
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!form.addressId && supplierAddresses.value.length === 1) {
|
|
||||||
form.addressId = String(supplierAddresses.value[0].id)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!form.addressId) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const matches = supplierAddresses.value.some(
|
|
||||||
(address) => String(address.id) === form.addressId
|
|
||||||
)
|
|
||||||
if (!matches) {
|
|
||||||
if (supplierAddresses.value.length === 1) {
|
|
||||||
form.addressId = String(supplierAddresses.value[0].id)
|
|
||||||
} else {
|
|
||||||
form.addressId = ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{immediate: true}
|
|
||||||
)
|
|
||||||
|
|
||||||
// Valide/auto-sélectionne le véhicule selon camion + transporteur (LIOT)
|
|
||||||
watch(
|
|
||||||
() => form.carrierId,
|
|
||||||
() => {
|
|
||||||
if (isHydrating.value) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!form.carrierId && idReception == null) {
|
|
||||||
form.driverId = ''
|
|
||||||
form.vehicleId = ''
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!isLiotCarrier.value && idReception == null) {
|
|
||||||
form.driverId = ''
|
|
||||||
form.vehicleId = ''
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (filteredDrivers.value.length === 1) {
|
|
||||||
form.driverId = String(filteredDrivers.value[0].id)
|
|
||||||
}
|
|
||||||
if (filteredVehicles.value.length === 1) {
|
|
||||||
form.vehicleId = String(filteredVehicles.value[0].id)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{immediate: true}
|
|
||||||
)
|
|
||||||
|
|
||||||
// Récupère la plaque depuis le véhicule choisi (LIOT)
|
|
||||||
watch(
|
|
||||||
() => [form.truckId, form.carrierId, vehicles.value],
|
|
||||||
() => {
|
|
||||||
if (!isLiotCarrier.value) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (filteredVehicles.value.length === 1) {
|
|
||||||
form.vehicleId = String(filteredVehicles.value[0].id)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!form.vehicleId) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const matches = filteredVehicles.value.some(
|
|
||||||
(vehicle) => String(vehicle.id) === form.vehicleId
|
|
||||||
)
|
|
||||||
if (!matches) {
|
|
||||||
form.vehicleId = ''
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{immediate: true}
|
|
||||||
)
|
|
||||||
|
|
||||||
// Auto-renseigne le véhicule si la plaque correspond (LIOT)
|
|
||||||
watch(
|
|
||||||
() => [form.vehicleId, form.carrierId, vehicles.value],
|
|
||||||
() => {
|
|
||||||
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
|
|
||||||
allowAnyLicensePlate.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => [form.licensePlate, form.carrierId, vehicles.value],
|
|
||||||
() => {
|
|
||||||
if (!isLiotCarrier.value || form.vehicleId) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const match = filteredVehicles.value.find(
|
|
||||||
(vehicle) => vehicle.plate === form.licensePlate
|
|
||||||
)
|
|
||||||
if (match) {
|
|
||||||
form.vehicleId = String(match.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
const printReceipt = async () => {
|
|
||||||
if (!import.meta.client || !Number.isFinite(idReception) || idReception <= 0) {
|
if (!import.meta.client || !Number.isFinite(idReception) || idReception <= 0) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -641,7 +564,7 @@ const printReceipt = async () => {
|
|||||||
await new Promise((resolve) => setTimeout(resolve, 600))
|
await new Promise((resolve) => setTimeout(resolve, 600))
|
||||||
}
|
}
|
||||||
|
|
||||||
const saveWeightEntry = async (entry: WeightEntryData) => {
|
async function saveWeightEntry(entry: WeightEntryData) {
|
||||||
if (!idReception || entry.weight === null) {
|
if (!idReception || entry.weight === null) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -663,6 +586,114 @@ const saveWeightEntry = async (entry: WeightEntryData) => {
|
|||||||
...payload
|
...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
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
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() {
|
async function validate() {
|
||||||
const normalizedLicensePlate = form.licensePlate.trim()
|
const normalizedLicensePlate = form.licensePlate.trim()
|
||||||
@@ -709,32 +740,168 @@ async function validate() {
|
|||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
...basePayload,
|
...basePayload,
|
||||||
...(isLiotCarrier.value && driverIri ? {driver: driverIri} : {}),
|
...(isLiotCarrier.value && driverIri ? { driver: driverIri } : {}),
|
||||||
}
|
}
|
||||||
|
|
||||||
if (idReception) {
|
if (idReception) {
|
||||||
isValid.value = true
|
|
||||||
isMerchandise.value ? (isValidMerchandise.value = true) : (isValidBovin.value = true)
|
|
||||||
await receptionStore.updateReception(idReception, {
|
await receptionStore.updateReception(idReception, {
|
||||||
...payload
|
...payload
|
||||||
})
|
})
|
||||||
await saveWeightEntry(grossWeight.value)
|
await saveWeightEntry(grossWeight.value)
|
||||||
await saveWeightEntry(tareWeight.value)
|
await saveWeightEntry(tareWeight.value)
|
||||||
const refreshedReception = await getReception(idReception)
|
|
||||||
hydrateFromReception(refreshedReception)
|
|
||||||
|
|
||||||
if (isMerchandise.value) {
|
if (isMerchandise.value) {
|
||||||
await clearReceptionBovines(idReception)
|
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 {
|
} else {
|
||||||
await clearReceptionMerchandise(idReception)
|
await clearReceptionMerchandise(idReception)
|
||||||
|
await syncBovineEntries()
|
||||||
|
await updateReception(idReception, {
|
||||||
|
bovineDetail: bovineOtherQuantity.value ? String(bovineOtherQuantity.value) : null
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
isValid.value = false
|
const refreshedReception = await getReception(idReception)
|
||||||
isValidBovin.value = false
|
hydrateFromReception(refreshedReception)
|
||||||
isValidMerchandise.value = false
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await loadTypes()
|
||||||
|
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 (!form.supplierId) {
|
||||||
|
form.addressId = ''
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!form.addressId && supplierAddresses.value.length === 1) {
|
||||||
|
form.addressId = String(supplierAddresses.value[0].id)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!form.addressId) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const matches = supplierAddresses.value.some(
|
||||||
|
(address) => String(address.id) === form.addressId
|
||||||
|
)
|
||||||
|
if (!matches) {
|
||||||
|
if (supplierAddresses.value.length === 1) {
|
||||||
|
form.addressId = String(supplierAddresses.value[0].id)
|
||||||
|
} else {
|
||||||
|
form.addressId = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => form.carrierId,
|
||||||
|
() => {
|
||||||
|
if (isHydrating.value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!form.carrierId && idReception == null) {
|
||||||
|
form.driverId = ''
|
||||||
|
form.vehicleId = ''
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!isLiotCarrier.value && idReception == null) {
|
||||||
|
form.driverId = ''
|
||||||
|
form.vehicleId = ''
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (filteredDrivers.value.length === 1) {
|
||||||
|
form.driverId = String(filteredDrivers.value[0].id)
|
||||||
|
}
|
||||||
|
if (filteredVehicles.value.length === 1) {
|
||||||
|
form.vehicleId = String(filteredVehicles.value[0].id)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => [form.truckId, form.carrierId, vehicles.value],
|
||||||
|
() => {
|
||||||
|
if (!isLiotCarrier.value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (filteredVehicles.value.length === 1) {
|
||||||
|
form.vehicleId = String(filteredVehicles.value[0].id)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!form.vehicleId) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const matches = filteredVehicles.value.some(
|
||||||
|
(vehicle) => String(vehicle.id) === form.vehicleId
|
||||||
|
)
|
||||||
|
if (!matches) {
|
||||||
|
form.vehicleId = ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => [form.vehicleId, form.carrierId, vehicles.value],
|
||||||
|
() => {
|
||||||
|
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
|
||||||
|
allowAnyLicensePlate.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => [form.licensePlate, form.carrierId, vehicles.value],
|
||||||
|
() => {
|
||||||
|
if (!isLiotCarrier.value || form.vehicleId) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const match = filteredVehicles.value.find(
|
||||||
|
(vehicle) => vehicle.plate === form.licensePlate
|
||||||
|
)
|
||||||
|
if (match) {
|
||||||
|
form.vehicleId = String(match.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -41,6 +41,13 @@ export interface WeightEntryData {
|
|||||||
weighedAt: string | null
|
weighedAt: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface MerchandiseEntryData {
|
||||||
|
merchandiseTypeId: string
|
||||||
|
merchandiseDetail: string
|
||||||
|
selectedBuildingIds: string[]
|
||||||
|
selectedPelletBuildingIds: Record<string, string[]>
|
||||||
|
}
|
||||||
|
|
||||||
export interface WeightFormData {
|
export interface WeightFormData {
|
||||||
id: number
|
id: number
|
||||||
weight: number
|
weight: number
|
||||||
|
|||||||
Reference in New Issue
Block a user