feat : Reception.validatedAt + statut entrées + mode consultation
- Backend : champ Reception.validatedAt (timestamp) avec PreUpdate + helpers
isFullyConfirmed/tryValidate ; sync EDNOTIF déclenche tryValidate sur
les receptions impactées ; expose Supplier.name dans le groupe bovine:read.
- Migration : ajout colonne validated_at sans backfill (les receptions
remontent en attente jusqu'au prochain sync).
- Front /entry-exit : remplace Historique par 'Entrées validées' (filtre
exists[validatedAt]=true), ajoute filtres et colonne Statut sur les
deux tableaux, retire Fournisseur, layout 2x2 (entrées + sorties).
- Front /entry-exit/entry/{id} : mode consultation quand entryCompleted=true
(formulaire + actions masqués, colonne EDNOTIF par bovin) ; ajoute
colonnes Vendeur et Cause dans le récap.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="px-[86px]">
|
<div class="px-[86px]">
|
||||||
<div class="flex items-center justify-start gap-6 relative">
|
<div class="flex items-center justify-start gap-6 relative" :class="{ 'mb-8': isConsultationMode }">
|
||||||
<Icon
|
<Icon
|
||||||
@click="router.push('/entry-exit')"
|
@click="router.push('/entry-exit')"
|
||||||
name="gg:arrow-left-o"
|
name="gg:arrow-left-o"
|
||||||
@@ -13,10 +13,11 @@
|
|||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-sm text-slate-600 mt-1 mb-8">
|
<p v-if="!isConsultationMode" class="text-sm text-slate-600 mt-1 mb-8">
|
||||||
{{ reception?.supplier?.name ?? '—' }} · Bovins déclarés : {{ declaredCount }} · Bovins saisis : {{ savedBovinesTotal }}
|
{{ reception?.supplier?.name ?? '—' }} · Bovins déclarés : {{ declaredCount }} · Bovins saisis : {{ savedBovinesTotal }}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<template v-if="!isConsultationMode">
|
||||||
<form
|
<form
|
||||||
class="grid grid-cols-4 gap-x-16 gap-y-8 mb-12 items-end"
|
class="grid grid-cols-4 gap-x-16 gap-y-8 mb-12 items-end"
|
||||||
:class="{ submitted }"
|
:class="{ submitted }"
|
||||||
@@ -36,37 +37,13 @@
|
|||||||
<UiDateMaskedInput
|
<UiDateMaskedInput
|
||||||
v-model="form.arrivalDate"
|
v-model="form.arrivalDate"
|
||||||
label="Date d'entrée"
|
label="Date d'entrée"
|
||||||
|
required
|
||||||
/>
|
/>
|
||||||
<UiSelect
|
<UiSelect
|
||||||
v-model="form.supplierId"
|
v-model="form.supplierId"
|
||||||
label="Vendeur"
|
label="Vendeur"
|
||||||
:options="supplierOptions"
|
:options="supplierOptions"
|
||||||
/>
|
required
|
||||||
<UiSelect
|
|
||||||
v-model="form.buildingId"
|
|
||||||
label="Bâtiment"
|
|
||||||
:options="buildingOptions"
|
|
||||||
/>
|
|
||||||
<UiSelect
|
|
||||||
v-model="form.caseId"
|
|
||||||
label="Case"
|
|
||||||
:options="caseOptions"
|
|
||||||
:disabled="!form.buildingId"
|
|
||||||
/>
|
|
||||||
<UiNumberInput
|
|
||||||
v-model="form.receivedWeight"
|
|
||||||
label="Poids (kg)"
|
|
||||||
wrapperClass="flex-col"
|
|
||||||
labelClass="font-bold uppercase text-xl text-primary-700"
|
|
||||||
:min="1"
|
|
||||||
/>
|
|
||||||
<UiNumberInput
|
|
||||||
v-model="form.pricePerKg"
|
|
||||||
label="Prix au kilo (€)"
|
|
||||||
wrapperClass="flex-col"
|
|
||||||
labelClass="font-bold uppercase text-xl text-primary-700"
|
|
||||||
:min="0"
|
|
||||||
:step="0.01"
|
|
||||||
/>
|
/>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
@@ -81,6 +58,7 @@
|
|||||||
Ajouter
|
Ajouter
|
||||||
</UiButton>
|
</UiButton>
|
||||||
</div>
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
<UiDataTable
|
<UiDataTable
|
||||||
v-model:page="recapPage"
|
v-model:page="recapPage"
|
||||||
@@ -89,29 +67,67 @@
|
|||||||
:items="savedBovines"
|
:items="savedBovines"
|
||||||
:total-items="savedBovinesTotal"
|
:total-items="savedBovinesTotal"
|
||||||
:loading="savedBovinesLoading"
|
:loading="savedBovinesLoading"
|
||||||
:show-actions="true"
|
:show-actions="!isConsultationMode"
|
||||||
>
|
>
|
||||||
|
<template #header-nationalNumber>
|
||||||
|
<UiTextInput v-model="recapFilters.nationalNumber" placeholder="N° National" size="compact" />
|
||||||
|
</template>
|
||||||
|
<template #header-workNumber>
|
||||||
|
<UiTextInput v-model="recapFilters.workNumber" placeholder="N° Travail" size="compact" />
|
||||||
|
</template>
|
||||||
|
<template #header-bovineType.label>
|
||||||
|
<UiTextInput v-model="recapFilters['bovineType.label']" placeholder="Race" size="compact" />
|
||||||
|
</template>
|
||||||
|
<template #header-sex>
|
||||||
|
<UiTextInput v-model="recapFilters.sex" placeholder="Sexe" size="compact" />
|
||||||
|
</template>
|
||||||
|
<template #header-birthDate>
|
||||||
|
<UiDateMaskedInput v-model="birthDateFilter" placeholder="Né le" size="compact" />
|
||||||
|
</template>
|
||||||
|
<template #header-arrivalDate>
|
||||||
|
<UiDateMaskedInput v-model="arrivalDateFilter" placeholder="Entrée le" size="compact" />
|
||||||
|
</template>
|
||||||
|
<template #header-supplier.name>
|
||||||
|
<UiTextInput :model-value="''" placeholder="Vendeur" size="compact" disabled />
|
||||||
|
</template>
|
||||||
|
<template #header-entryCause>
|
||||||
|
<UiTextInput :model-value="''" placeholder="Cause" size="compact" disabled />
|
||||||
|
</template>
|
||||||
|
<template #header-ednotifConfirmedAt>
|
||||||
|
<UiTextInput :model-value="''" placeholder="EDNOTIF" size="compact" disabled />
|
||||||
|
</template>
|
||||||
|
<template #header-actions>
|
||||||
|
<UiTextInput :model-value="''" placeholder="Action" size="compact" disabled />
|
||||||
|
</template>
|
||||||
<template #cell-birthDate="{ item }">
|
<template #cell-birthDate="{ item }">
|
||||||
{{ formatDate(item.birthDate) }}
|
{{ formatDate(item.birthDate) }}
|
||||||
</template>
|
</template>
|
||||||
<template #cell-arrivalDate="{ item }">
|
<template #cell-arrivalDate="{ item }">
|
||||||
{{ formatDate(item.arrivalDate) }}
|
{{ formatDate(item.arrivalDate) }}
|
||||||
</template>
|
</template>
|
||||||
<template #cell-finalPrice="{ item }">
|
|
||||||
{{ formatPrice(item.finalPrice) }}
|
|
||||||
</template>
|
|
||||||
<template #cell-pricePerKg="{ item }">
|
|
||||||
{{ formatPrice(item.pricePerKg) }}
|
|
||||||
</template>
|
|
||||||
<template #cell-buildingCase.building.label="{ item }">
|
|
||||||
{{ item.effectiveBuilding?.label ?? '—' }}
|
|
||||||
</template>
|
|
||||||
<template #cell-buildingCase.caseNumber="{ item }">
|
|
||||||
{{ item.buildingCase?.caseNumber ?? '—' }}
|
|
||||||
</template>
|
|
||||||
<template #cell-bovineType.label="{ item }">
|
<template #cell-bovineType.label="{ item }">
|
||||||
{{ item.bovineType?.label ?? '—' }}
|
{{ item.bovineType?.label ?? '—' }}
|
||||||
</template>
|
</template>
|
||||||
|
<template #cell-supplier.name="{ item }">
|
||||||
|
{{ supplierName(item.supplier) }}
|
||||||
|
</template>
|
||||||
|
<template #cell-entryCause="{ item }">
|
||||||
|
{{ entryCauseLabel(item.entryCause) }}
|
||||||
|
</template>
|
||||||
|
<template #cell-ednotifConfirmedAt="{ item }">
|
||||||
|
<span
|
||||||
|
v-if="item.ednotifConfirmedAt"
|
||||||
|
class="inline-block rounded px-2 py-0.5 text-xs font-semibold bg-green-100 text-green-700"
|
||||||
|
>
|
||||||
|
Validé
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
v-else
|
||||||
|
class="inline-block rounded px-2 py-0.5 text-xs font-semibold bg-orange-100 text-orange-700"
|
||||||
|
>
|
||||||
|
En attente
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
<template #actions="{ item }">
|
<template #actions="{ item }">
|
||||||
<Icon
|
<Icon
|
||||||
name="mdi:delete-outline"
|
name="mdi:delete-outline"
|
||||||
@@ -122,7 +138,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</UiDataTable>
|
</UiDataTable>
|
||||||
|
|
||||||
<div class="flex justify-center mt-8">
|
<div v-if="!isConsultationMode" class="flex justify-center mt-8">
|
||||||
<UiButton
|
<UiButton
|
||||||
type="button"
|
type="button"
|
||||||
class="text-md font-bold uppercase bg-primary-500 text-white h-[50px] px-8"
|
class="text-md font-bold uppercase bg-primary-500 text-white h-[50px] px-8"
|
||||||
@@ -140,9 +156,7 @@
|
|||||||
import type { ReceptionData } from '~/services/dto/reception-data'
|
import type { ReceptionData } from '~/services/dto/reception-data'
|
||||||
import type { BovineData } from '~/services/dto/bovine-data'
|
import type { BovineData } from '~/services/dto/bovine-data'
|
||||||
import type { SupplierData } from '~/services/dto/supplier-data'
|
import type { SupplierData } from '~/services/dto/supplier-data'
|
||||||
import type { BuildingData } from '~/services/dto/building-data'
|
|
||||||
import { getSupplierList } from '~/services/supplier'
|
import { getSupplierList } from '~/services/supplier'
|
||||||
import { getBuildingList } from '~/services/building'
|
|
||||||
import { useDataTableServerState } from '~/composables/useDataTableServerState'
|
import { useDataTableServerState } from '~/composables/useDataTableServerState'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
@@ -153,38 +167,95 @@ const receptionId = computed(() => Number(route.params.id))
|
|||||||
|
|
||||||
const reception = ref<ReceptionData | null>(null)
|
const reception = ref<ReceptionData | null>(null)
|
||||||
const suppliers = ref<SupplierData[]>([])
|
const suppliers = ref<SupplierData[]>([])
|
||||||
const buildings = ref<BuildingData[]>([])
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
items: savedBovines,
|
items: savedBovines,
|
||||||
totalItems: savedBovinesTotal,
|
totalItems: savedBovinesTotal,
|
||||||
page: recapPage,
|
page: recapPage,
|
||||||
perPage: recapPerPage,
|
perPage: recapPerPage,
|
||||||
|
filters: recapFilters,
|
||||||
loading: savedBovinesLoading,
|
loading: savedBovinesLoading,
|
||||||
reload: reloadSavedBovines
|
reload: reloadSavedBovines
|
||||||
} = useDataTableServerState<BovineData>(
|
} = useDataTableServerState<BovineData>(
|
||||||
'bovines',
|
'bovines',
|
||||||
{ reception: receptionId.value },
|
{
|
||||||
{ initialPerPage: 50 }
|
reception: receptionId.value,
|
||||||
|
nationalNumber: '',
|
||||||
|
workNumber: '',
|
||||||
|
'bovineType.label': '',
|
||||||
|
sex: '',
|
||||||
|
'birthDate[after]': '',
|
||||||
|
'birthDate[strictly_before]': '',
|
||||||
|
'arrivalDate[after]': '',
|
||||||
|
'arrivalDate[strictly_before]': ''
|
||||||
|
},
|
||||||
|
{ initialPerPage: 10 }
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const addOneDay = (dateString: string): string => {
|
||||||
|
const [year, month, day] = dateString.split('-').map(Number)
|
||||||
|
const next = new Date(Date.UTC(year, month - 1, day + 1))
|
||||||
|
return next.toISOString().slice(0, 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
const birthDateFilter = computed<string>({
|
||||||
|
get: () => (recapFilters.value['birthDate[after]'] as string) ?? '',
|
||||||
|
set: (value: string) => {
|
||||||
|
if (!value) {
|
||||||
|
recapFilters.value['birthDate[after]'] = ''
|
||||||
|
recapFilters.value['birthDate[strictly_before]'] = ''
|
||||||
|
return
|
||||||
|
}
|
||||||
|
recapFilters.value['birthDate[after]'] = value
|
||||||
|
recapFilters.value['birthDate[strictly_before]'] = addOneDay(value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const arrivalDateFilter = computed<string>({
|
||||||
|
get: () => (recapFilters.value['arrivalDate[after]'] as string) ?? '',
|
||||||
|
set: (value: string) => {
|
||||||
|
if (!value) {
|
||||||
|
recapFilters.value['arrivalDate[after]'] = ''
|
||||||
|
recapFilters.value['arrivalDate[strictly_before]'] = ''
|
||||||
|
return
|
||||||
|
}
|
||||||
|
recapFilters.value['arrivalDate[after]'] = value
|
||||||
|
recapFilters.value['arrivalDate[strictly_before]'] = addOneDay(value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const isAdding = ref(false)
|
const isAdding = ref(false)
|
||||||
const isValidating = ref(false)
|
const isValidating = ref(false)
|
||||||
const submitted = ref(false)
|
const submitted = ref(false)
|
||||||
|
|
||||||
const recapColumns = [
|
const isConsultationMode = computed(() => reception.value?.entryCompleted === true)
|
||||||
{ key: 'nationalNumber', label: 'N° National', width: '110px' },
|
|
||||||
{ key: 'workNumber', label: 'N° Travail', width: '90px' },
|
const recapColumns = computed(() => {
|
||||||
{ key: 'bovineType.label', label: 'Race', width: '110px' },
|
const cols: Array<{ key: string; label: string; width: string }> = [
|
||||||
{ key: 'sex', label: 'Sexe', width: '60px' },
|
{ key: 'nationalNumber', label: 'N° National', width: '100px' },
|
||||||
{ key: 'birthDate', label: 'Né le', width: '90px' },
|
{ key: 'workNumber', label: 'N° Travail', width: '110px' },
|
||||||
{ key: 'receivedWeight', label: 'Poids', width: '70px' },
|
{ key: 'bovineType.label', label: 'Race', width: '1fr' },
|
||||||
{ key: 'arrivalDate', label: 'Entrée le', width: '90px' },
|
{ key: 'sex', label: 'Sexe', width: '70px' },
|
||||||
{ key: 'pricePerKg', label: 'Prix/kg', width: '80px' },
|
{ key: 'birthDate', label: 'Né le', width: '75px' },
|
||||||
{ key: 'finalPrice', label: 'Prix total', width: '90px' },
|
{ key: 'arrivalDate', label: 'Entrée le', width: '75px' },
|
||||||
{ key: 'buildingCase.building.label', label: 'Bâtiment', width: '1fr' },
|
{ key: 'supplier.name', label: 'Vendeur', width: '150px' },
|
||||||
{ key: 'buildingCase.caseNumber', label: 'Case', width: '60px' }
|
{ key: 'entryCause', label: 'Cause', width: '100px' }
|
||||||
]
|
]
|
||||||
|
if (isConsultationMode.value) {
|
||||||
|
cols.push({ key: 'ednotifConfirmedAt', label: 'EDNOTIF', width: '110px' })
|
||||||
|
}
|
||||||
|
return cols
|
||||||
|
})
|
||||||
|
|
||||||
|
const entryCauseLabel = (code: string | null | undefined) => {
|
||||||
|
if (!code) return '—'
|
||||||
|
return entryCauseOptions.find(o => o.value === code)?.label ?? code
|
||||||
|
}
|
||||||
|
|
||||||
|
const supplierName = (supplier: BovineData['supplier']) => {
|
||||||
|
if (supplier && typeof supplier === 'object') return supplier.name
|
||||||
|
return '—'
|
||||||
|
}
|
||||||
|
|
||||||
const formatDate = (date: string | null | undefined) => {
|
const formatDate = (date: string | null | undefined) => {
|
||||||
if (!date) return '—'
|
if (!date) return '—'
|
||||||
@@ -193,11 +264,6 @@ const formatDate = (date: string | null | undefined) => {
|
|||||||
return d.toLocaleDateString('fr-FR', { day: '2-digit', month: '2-digit', year: 'numeric' })
|
return d.toLocaleDateString('fr-FR', { day: '2-digit', month: '2-digit', year: 'numeric' })
|
||||||
}
|
}
|
||||||
|
|
||||||
const formatPrice = (price: number | null | undefined) => {
|
|
||||||
if (price === null || price === undefined) return '—'
|
|
||||||
return `${price.toLocaleString('fr-FR', { minimumFractionDigits: 2, maximumFractionDigits: 2 })} €`
|
|
||||||
}
|
|
||||||
|
|
||||||
const confirmDeleteBovine = async (bovine: BovineData) => {
|
const confirmDeleteBovine = async (bovine: BovineData) => {
|
||||||
const confirmed = window.confirm(`Supprimer le bovin ${bovine.nationalNumber} ?`)
|
const confirmed = window.confirm(`Supprimer le bovin ${bovine.nationalNumber} ?`)
|
||||||
if (!confirmed) return
|
if (!confirmed) return
|
||||||
@@ -231,10 +297,6 @@ interface FormState {
|
|||||||
entryCause: EntryCause
|
entryCause: EntryCause
|
||||||
arrivalDate: string
|
arrivalDate: string
|
||||||
supplierId: string | number | null
|
supplierId: string | number | null
|
||||||
buildingId: string | number | null
|
|
||||||
caseId: string | number | null
|
|
||||||
receivedWeight: number | null
|
|
||||||
pricePerKg: number | null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const entryCauseOptions = [
|
const entryCauseOptions = [
|
||||||
@@ -247,11 +309,7 @@ const initialForm = (): FormState => ({
|
|||||||
nationalNumber: '',
|
nationalNumber: '',
|
||||||
entryCause: 'A',
|
entryCause: 'A',
|
||||||
arrivalDate: reception.value?.receptionDate?.slice(0, 10) ?? '',
|
arrivalDate: reception.value?.receptionDate?.slice(0, 10) ?? '',
|
||||||
supplierId: reception.value?.supplier?.id ?? null,
|
supplierId: reception.value?.supplier?.id ?? null
|
||||||
buildingId: reception.value?.buildings?.[0]?.id ?? null,
|
|
||||||
caseId: null,
|
|
||||||
receivedWeight: null,
|
|
||||||
pricePerKg: null
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const form = reactive<FormState>(initialForm())
|
const form = reactive<FormState>(initialForm())
|
||||||
@@ -260,30 +318,13 @@ const supplierOptions = computed(() =>
|
|||||||
suppliers.value.map(s => ({ value: s.id, label: s.name }))
|
suppliers.value.map(s => ({ value: s.id, label: s.name }))
|
||||||
)
|
)
|
||||||
|
|
||||||
const buildingOptions = computed(() =>
|
|
||||||
buildings.value.map(b => ({ value: b.id, label: b.label }))
|
|
||||||
)
|
|
||||||
|
|
||||||
const caseOptions = computed(() => {
|
|
||||||
const building = buildings.value.find(b => b.id === Number(form.buildingId))
|
|
||||||
if (!building?.buildingCases) return []
|
|
||||||
return [...building.buildingCases]
|
|
||||||
.sort((a, b) => (a.caseNumber ?? 0) - (b.caseNumber ?? 0))
|
|
||||||
.map(c => ({
|
|
||||||
value: c.id,
|
|
||||||
label: `Case ${c.caseNumber ?? c.code ?? c.id}`
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
|
|
||||||
watch(() => form.buildingId, (newVal, oldVal) => {
|
|
||||||
if (newVal !== oldVal) form.caseId = null
|
|
||||||
})
|
|
||||||
|
|
||||||
const declaredCount = computed(() => reception.value?.declaredBovineCount ?? 0)
|
const declaredCount = computed(() => reception.value?.declaredBovineCount ?? 0)
|
||||||
|
|
||||||
const isFormValid = computed(() =>
|
const isFormValid = computed(() =>
|
||||||
form.nationalNumber.trim() !== ''
|
form.nationalNumber.trim() !== ''
|
||||||
&& !!form.entryCause
|
&& !!form.entryCause
|
||||||
|
&& !!form.arrivalDate
|
||||||
|
&& form.supplierId !== null
|
||||||
)
|
)
|
||||||
|
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
@@ -306,16 +347,13 @@ const addBovine = async () => {
|
|||||||
|
|
||||||
isAdding.value = true
|
isAdding.value = true
|
||||||
try {
|
try {
|
||||||
const payload: Record<string, unknown> = {
|
const payload = {
|
||||||
nationalNumber: form.nationalNumber.trim(),
|
nationalNumber: form.nationalNumber.trim(),
|
||||||
entryCause: form.entryCause,
|
entryCause: form.entryCause,
|
||||||
|
arrivalDate: form.arrivalDate,
|
||||||
|
supplier: `/api/suppliers/${form.supplierId}`,
|
||||||
reception: `/api/receptions/${receptionId.value}`
|
reception: `/api/receptions/${receptionId.value}`
|
||||||
}
|
}
|
||||||
if (form.receivedWeight !== null) payload.receivedWeight = form.receivedWeight
|
|
||||||
if (form.pricePerKg !== null) payload.pricePerKg = form.pricePerKg
|
|
||||||
if (form.arrivalDate) payload.arrivalDate = form.arrivalDate
|
|
||||||
if (form.supplierId !== null) payload.supplier = `/api/suppliers/${form.supplierId}`
|
|
||||||
if (form.caseId !== null) payload.buildingCase = `/api/building_cases/${form.caseId}`
|
|
||||||
|
|
||||||
await api.post<BovineData>('bovines', payload, {
|
await api.post<BovineData>('bovines', payload, {
|
||||||
headers: { 'Content-Type': 'application/ld+json' }
|
headers: { 'Content-Type': 'application/ld+json' }
|
||||||
@@ -332,10 +370,7 @@ const addBovine = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
[suppliers.value, buildings.value] = await Promise.all([
|
suppliers.value = await getSupplierList()
|
||||||
getSupplierList(),
|
|
||||||
getBuildingList()
|
|
||||||
])
|
|
||||||
await loadReception()
|
await loadReception()
|
||||||
reloadSavedBovines()
|
reloadSavedBovines()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -23,6 +23,25 @@
|
|||||||
row-clickable
|
row-clickable
|
||||||
@row-click="goToEntry"
|
@row-click="goToEntry"
|
||||||
>
|
>
|
||||||
|
<template #header-identificationNumber>
|
||||||
|
<UiTextInput
|
||||||
|
v-model="entryFilters.identificationNumber"
|
||||||
|
placeholder="Numéro"
|
||||||
|
size="compact"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template #header-receptionDate>
|
||||||
|
<UiDateMaskedInput v-model="entryDateFilter" placeholder="Date" size="compact" />
|
||||||
|
</template>
|
||||||
|
<template #header-declaredCount>
|
||||||
|
<UiTextInput :model-value="''" placeholder="Déclarés" size="compact" disabled />
|
||||||
|
</template>
|
||||||
|
<template #header-registeredBovineCount>
|
||||||
|
<UiTextInput :model-value="''" placeholder="Saisis" size="compact" disabled />
|
||||||
|
</template>
|
||||||
|
<template #header-status>
|
||||||
|
<UiTextInput :model-value="''" placeholder="Statut" size="compact" disabled />
|
||||||
|
</template>
|
||||||
<template #cell-identificationNumber="{ item }">
|
<template #cell-identificationNumber="{ item }">
|
||||||
{{ item.identificationNumber }}
|
{{ item.identificationNumber }}
|
||||||
</template>
|
</template>
|
||||||
@@ -35,49 +54,48 @@
|
|||||||
<template #cell-registeredBovineCount="{ item }">
|
<template #cell-registeredBovineCount="{ item }">
|
||||||
{{ item.registeredBovineCount ?? 0 }}
|
{{ item.registeredBovineCount ?? 0 }}
|
||||||
</template>
|
</template>
|
||||||
|
<template #cell-status="{ item }">
|
||||||
|
<span
|
||||||
|
v-if="!item.entryCompleted"
|
||||||
|
class="inline-block rounded px-2 py-0.5 text-xs font-semibold bg-yellow-100 text-yellow-700"
|
||||||
|
>
|
||||||
|
Attente saisie
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
v-else
|
||||||
|
class="inline-block rounded px-2 py-0.5 text-xs font-semibold bg-orange-100 text-orange-700"
|
||||||
|
>
|
||||||
|
Attente EDNOTIF
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
</UiDataTable>
|
</UiDataTable>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<h2 class="text-xl font-bold uppercase text-primary-500 mb-4">Sorties en attente</h2>
|
<h2 class="text-xl font-bold uppercase text-primary-500 mb-4">Entrées validées</h2>
|
||||||
<div class="rounded border border-dashed border-slate-300 p-8 text-center text-slate-500">
|
|
||||||
À venir
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<section class="mt-12 mb-16">
|
|
||||||
<h2 class="text-xl font-bold uppercase text-primary-500 mb-4">Historique</h2>
|
|
||||||
<UiDataTable
|
<UiDataTable
|
||||||
v-model:page="historyPage"
|
v-model:page="validatedPage"
|
||||||
v-model:per-page="historyPerPage"
|
v-model:per-page="validatedPerPage"
|
||||||
:columns="historyColumns"
|
:columns="validatedColumns"
|
||||||
:items="history"
|
:items="validated"
|
||||||
:total-items="totalHistory"
|
:total-items="totalValidated"
|
||||||
:loading="historyLoading"
|
:loading="validatedLoading"
|
||||||
>
|
>
|
||||||
<template #header-identificationNumber>
|
<template #header-identificationNumber>
|
||||||
<UiTextInput
|
<UiTextInput
|
||||||
v-model="historyFilters.identificationNumber"
|
v-model="validatedFilters.identificationNumber"
|
||||||
placeholder="Numéro"
|
placeholder="Numéro"
|
||||||
size="compact"
|
size="compact"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<template #header-receptionDate>
|
<template #header-receptionDate>
|
||||||
<UiDateMaskedInput v-model="historyDateFilter" placeholder="Date" size="compact" />
|
<UiDateMaskedInput v-model="validatedDateFilter" placeholder="Date" size="compact" />
|
||||||
</template>
|
|
||||||
<template #header-supplier.name>
|
|
||||||
<UiTextInput
|
|
||||||
v-model="historyFilters['supplier.name']"
|
|
||||||
placeholder="Fournisseur"
|
|
||||||
size="compact"
|
|
||||||
/>
|
|
||||||
</template>
|
</template>
|
||||||
<template #header-registeredBovineCount>
|
<template #header-registeredBovineCount>
|
||||||
<UiTextInput :model-value="''" placeholder="Saisis" size="compact" disabled />
|
<UiTextInput :model-value="''" placeholder="Saisis" size="compact" disabled />
|
||||||
</template>
|
</template>
|
||||||
<template #header-confirmedBovineCount>
|
<template #header-validatedAt>
|
||||||
<UiTextInput :model-value="''" placeholder="Confirmés" size="compact" disabled />
|
<UiTextInput :model-value="''" placeholder="Validée le" size="compact" disabled />
|
||||||
</template>
|
</template>
|
||||||
<template #header-status>
|
<template #header-status>
|
||||||
<UiTextInput :model-value="''" placeholder="Statut" size="compact" disabled />
|
<UiTextInput :model-value="''" placeholder="Statut" size="compact" disabled />
|
||||||
@@ -91,26 +109,36 @@
|
|||||||
<template #cell-registeredBovineCount="{ item }">
|
<template #cell-registeredBovineCount="{ item }">
|
||||||
{{ item.registeredBovineCount ?? 0 }}
|
{{ item.registeredBovineCount ?? 0 }}
|
||||||
</template>
|
</template>
|
||||||
<template #cell-confirmedBovineCount="{ item }">
|
<template #cell-validatedAt="{ item }">
|
||||||
{{ item.confirmedBovineCount ?? 0 }} / {{ item.registeredBovineCount ?? 0 }}
|
{{ formatDate(item.validatedAt) }}
|
||||||
</template>
|
</template>
|
||||||
<template #cell-status="{ item }">
|
<template #cell-status>
|
||||||
<span
|
<span
|
||||||
v-if="(item.confirmedBovineCount ?? 0) >= (item.registeredBovineCount ?? 0) && (item.registeredBovineCount ?? 0) > 0"
|
|
||||||
class="inline-block rounded px-2 py-0.5 text-xs font-semibold bg-green-100 text-green-700"
|
class="inline-block rounded px-2 py-0.5 text-xs font-semibold bg-green-100 text-green-700"
|
||||||
>
|
>
|
||||||
Confirmée
|
Validée
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
v-else
|
|
||||||
class="inline-block rounded px-2 py-0.5 text-xs font-semibold bg-yellow-100 text-yellow-700"
|
|
||||||
>
|
|
||||||
EDNOTIF en attente
|
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</UiDataTable>
|
</UiDataTable>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-12 mb-16 grid grid-cols-2 gap-8">
|
||||||
|
<section>
|
||||||
|
<h2 class="text-xl font-bold uppercase text-primary-500 mb-4">Sorties en attente</h2>
|
||||||
|
<div class="rounded border border-dashed border-slate-300 p-8 text-center text-slate-500">
|
||||||
|
À venir
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2 class="text-xl font-bold uppercase text-primary-500 mb-4">Sorties validées</h2>
|
||||||
|
<div class="rounded border border-dashed border-slate-300 p-8 text-center text-slate-500">
|
||||||
|
À venir
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
@@ -124,38 +152,41 @@ const {
|
|||||||
totalItems: totalEntries,
|
totalItems: totalEntries,
|
||||||
page: entryPage,
|
page: entryPage,
|
||||||
perPage: entryPerPage,
|
perPage: entryPerPage,
|
||||||
|
filters: entryFilters,
|
||||||
loading: entriesLoading,
|
loading: entriesLoading,
|
||||||
reload
|
reload
|
||||||
} = useDataTableServerState<ReceptionData>(
|
} = useDataTableServerState<ReceptionData>(
|
||||||
'receptions',
|
'receptions',
|
||||||
{
|
{
|
||||||
'isValid': 'true',
|
'isValid': 'true',
|
||||||
'entryCompleted': 'false',
|
'exists[validatedAt]': 'false',
|
||||||
'receptionType.code': 'BOVINS'
|
'receptionType.code': 'BOVINS',
|
||||||
|
'identificationNumber': '',
|
||||||
|
'receptionDate[after]': '',
|
||||||
|
'receptionDate[strictly_before]': ''
|
||||||
},
|
},
|
||||||
{ initialPerPage: 5 }
|
{ initialPerPage: 5 }
|
||||||
)
|
)
|
||||||
|
|
||||||
const {
|
const {
|
||||||
items: history,
|
items: validated,
|
||||||
totalItems: totalHistory,
|
totalItems: totalValidated,
|
||||||
page: historyPage,
|
page: validatedPage,
|
||||||
perPage: historyPerPage,
|
perPage: validatedPerPage,
|
||||||
filters: historyFilters,
|
filters: validatedFilters,
|
||||||
loading: historyLoading,
|
loading: validatedLoading,
|
||||||
reload: reloadHistory
|
reload: reloadValidated
|
||||||
} = useDataTableServerState<ReceptionData>(
|
} = useDataTableServerState<ReceptionData>(
|
||||||
'receptions',
|
'receptions',
|
||||||
{
|
{
|
||||||
'isValid': 'true',
|
'isValid': 'true',
|
||||||
'entryCompleted': 'true',
|
'exists[validatedAt]': 'true',
|
||||||
'receptionType.code': 'BOVINS',
|
'receptionType.code': 'BOVINS',
|
||||||
'identificationNumber': '',
|
'identificationNumber': '',
|
||||||
'supplier.name': '',
|
|
||||||
'receptionDate[after]': '',
|
'receptionDate[after]': '',
|
||||||
'receptionDate[strictly_before]': ''
|
'receptionDate[strictly_before]': ''
|
||||||
},
|
},
|
||||||
{ initialPerPage: 10 }
|
{ initialPerPage: 5 }
|
||||||
)
|
)
|
||||||
|
|
||||||
const addOneDay = (dateString: string): string => {
|
const addOneDay = (dateString: string): string => {
|
||||||
@@ -164,37 +195,49 @@ const addOneDay = (dateString: string): string => {
|
|||||||
return next.toISOString().slice(0, 10)
|
return next.toISOString().slice(0, 10)
|
||||||
}
|
}
|
||||||
|
|
||||||
const historyDateFilter = computed<string>({
|
const entryDateFilter = computed<string>({
|
||||||
get: () => (historyFilters.value['receptionDate[after]'] as string) ?? '',
|
get: () => (entryFilters.value['receptionDate[after]'] as string) ?? '',
|
||||||
set: (value: string) => {
|
set: (value: string) => {
|
||||||
if (!value) {
|
if (!value) {
|
||||||
historyFilters.value['receptionDate[after]'] = ''
|
entryFilters.value['receptionDate[after]'] = ''
|
||||||
historyFilters.value['receptionDate[strictly_before]'] = ''
|
entryFilters.value['receptionDate[strictly_before]'] = ''
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
historyFilters.value['receptionDate[after]'] = value
|
entryFilters.value['receptionDate[after]'] = value
|
||||||
historyFilters.value['receptionDate[strictly_before]'] = addOneDay(value)
|
entryFilters.value['receptionDate[strictly_before]'] = addOneDay(value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const validatedDateFilter = computed<string>({
|
||||||
|
get: () => (validatedFilters.value['receptionDate[after]'] as string) ?? '',
|
||||||
|
set: (value: string) => {
|
||||||
|
if (!value) {
|
||||||
|
validatedFilters.value['receptionDate[after]'] = ''
|
||||||
|
validatedFilters.value['receptionDate[strictly_before]'] = ''
|
||||||
|
return
|
||||||
|
}
|
||||||
|
validatedFilters.value['receptionDate[after]'] = value
|
||||||
|
validatedFilters.value['receptionDate[strictly_before]'] = addOneDay(value)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const entryColumns = [
|
const entryColumns = [
|
||||||
{ key: 'identificationNumber', label: 'Numéro', width: '80px' },
|
{ key: 'identificationNumber', label: 'Numéro', width: '75px' },
|
||||||
{ key: 'receptionDate', label: 'Date', width: '75px' },
|
{ key: 'receptionDate', label: 'Date', width: '75px' },
|
||||||
{ key: 'supplier.name', label: 'Fournisseur', width: '1fr' },
|
{ key: 'declaredCount', label: 'Déclarés', width: '75px' },
|
||||||
{ key: 'declaredCount', label: 'Déclarés', width: '85px' },
|
{ key: 'registeredBovineCount', label: 'Saisis', width: '70px' },
|
||||||
{ key: 'registeredBovineCount', label: 'Saisis', width: '50px' }
|
{ key: 'status', label: 'Statut', width: '1fr' }
|
||||||
]
|
]
|
||||||
|
|
||||||
const historyColumns = [
|
const validatedColumns = [
|
||||||
{ key: 'identificationNumber', label: 'Numéro', width: '110px' },
|
{ key: 'identificationNumber', label: 'Numéro', width: '75px' },
|
||||||
{ key: 'receptionDate', label: 'Date', width: '110px' },
|
{ key: 'receptionDate', label: 'Date', width: '75px' },
|
||||||
{ key: 'supplier.name', label: 'Fournisseur', width: '1fr' },
|
{ key: 'registeredBovineCount', label: 'Saisis', width: '50px' },
|
||||||
{ key: 'registeredBovineCount', label: 'Saisis', width: '80px' },
|
{ key: 'validatedAt', label: 'Validée le', width: '75px' },
|
||||||
{ key: 'confirmedBovineCount', label: 'Confirmés', width: '110px' },
|
{ key: 'status', label: 'Statut', width: '1fr' }
|
||||||
{ key: 'status', label: 'Statut', width: '170px' }
|
|
||||||
]
|
]
|
||||||
|
|
||||||
const formatDate = (date: string | null) => {
|
const formatDate = (date: string | null | undefined) => {
|
||||||
if (!date) return '—'
|
if (!date) return '—'
|
||||||
const d = new Date(date.replace(' ', 'T'))
|
const d = new Date(date.replace(' ', 'T'))
|
||||||
if (isNaN(d.getTime())) return date
|
if (isNaN(d.getTime())) return date
|
||||||
@@ -211,6 +254,6 @@ const goToEntry = (reception: ReceptionData) => {
|
|||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
reload()
|
reload()
|
||||||
reloadHistory()
|
reloadValidated()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export interface BovineData {
|
|||||||
buildingCase: BovineBuildingCaseRef | null
|
buildingCase: BovineBuildingCaseRef | null
|
||||||
building: BovineBuildingRef | null
|
building: BovineBuildingRef | null
|
||||||
effectiveBuilding: BovineBuildingRef | null
|
effectiveBuilding: BovineBuildingRef | null
|
||||||
supplier: string | null
|
supplier: { id: number; name: string } | string | null
|
||||||
workNumber: string | null
|
workNumber: string | null
|
||||||
birthDate: string | null
|
birthDate: string | null
|
||||||
bovineType: { id: number; label: string; code: string } | null
|
bovineType: { id: number; label: string; code: string } | null
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ export interface ReceptionData {
|
|||||||
currentStep: number
|
currentStep: number
|
||||||
isValid: boolean
|
isValid: boolean
|
||||||
entryCompleted?: boolean
|
entryCompleted?: boolean
|
||||||
|
validatedAt?: string | null
|
||||||
registeredBovineCount?: number
|
registeredBovineCount?: number
|
||||||
confirmedBovineCount?: number
|
confirmedBovineCount?: number
|
||||||
declaredBovineCount?: number
|
declaredBovineCount?: number
|
||||||
|
|||||||
26
migrations/Version20260430090000.php
Normal file
26
migrations/Version20260430090000.php
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace DoctrineMigrations;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
|
|
||||||
|
final class Version20260430090000 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return 'Ajout de reception.validated_at (timestamp de validation complète : entrée terminée + tous bovins confirmés EDNOTIF).';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function up(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->addSql('ALTER TABLE reception ADD validated_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->addSql('ALTER TABLE reception DROP validated_at');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -118,6 +118,7 @@ class Bovine
|
|||||||
|
|
||||||
#[ORM\ManyToOne]
|
#[ORM\ManyToOne]
|
||||||
#[Groups(['bovine:read', 'bovine:write', 'building_case:read'])]
|
#[Groups(['bovine:read', 'bovine:write', 'building_case:read'])]
|
||||||
|
#[ApiProperty(readableLink: true)]
|
||||||
private ?Supplier $supplier = null;
|
private ?Supplier $supplier = null;
|
||||||
|
|
||||||
#[ORM\Column(length: 50, nullable: true)]
|
#[ORM\Column(length: 50, nullable: true)]
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ namespace App\Entity;
|
|||||||
|
|
||||||
use ApiPlatform\Doctrine\Orm\Filter\BooleanFilter;
|
use ApiPlatform\Doctrine\Orm\Filter\BooleanFilter;
|
||||||
use ApiPlatform\Doctrine\Orm\Filter\DateFilter;
|
use ApiPlatform\Doctrine\Orm\Filter\DateFilter;
|
||||||
|
use ApiPlatform\Doctrine\Orm\Filter\ExistsFilter;
|
||||||
use ApiPlatform\Doctrine\Orm\Filter\SearchFilter;
|
use ApiPlatform\Doctrine\Orm\Filter\SearchFilter;
|
||||||
use ApiPlatform\Metadata\ApiFilter;
|
use ApiPlatform\Metadata\ApiFilter;
|
||||||
use ApiPlatform\Metadata\ApiProperty;
|
use ApiPlatform\Metadata\ApiProperty;
|
||||||
@@ -32,6 +33,7 @@ use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
|
|||||||
#[ORM\HasLifecycleCallbacks]
|
#[ORM\HasLifecycleCallbacks]
|
||||||
#[ORM\Table(name: 'reception')]
|
#[ORM\Table(name: 'reception')]
|
||||||
#[ApiFilter(BooleanFilter::class, properties: ['isValid', 'entryCompleted'])]
|
#[ApiFilter(BooleanFilter::class, properties: ['isValid', 'entryCompleted'])]
|
||||||
|
#[ApiFilter(ExistsFilter::class, properties: ['validatedAt'])]
|
||||||
#[ApiFilter(SearchFilter::class, properties: [
|
#[ApiFilter(SearchFilter::class, properties: [
|
||||||
'identificationNumber' => 'ipartial',
|
'identificationNumber' => 'ipartial',
|
||||||
'supplier.name' => 'ipartial',
|
'supplier.name' => 'ipartial',
|
||||||
@@ -115,6 +117,10 @@ class Reception
|
|||||||
#[Groups(['reception:read', 'reception:write', 'reception-bovine:read'])]
|
#[Groups(['reception:read', 'reception:write', 'reception-bovine:read'])]
|
||||||
private bool $entryCompleted = false;
|
private bool $entryCompleted = false;
|
||||||
|
|
||||||
|
#[ORM\Column(type: 'datetime_immutable', nullable: true)]
|
||||||
|
#[Groups(['reception:read', 'reception-bovine:read'])]
|
||||||
|
private ?DateTimeImmutable $validatedAt = null;
|
||||||
|
|
||||||
#[ORM\Column(name: 'date_reception', type: 'datetime_immutable')]
|
#[ORM\Column(name: 'date_reception', type: 'datetime_immutable')]
|
||||||
#[Groups(['reception:read', 'reception:write', 'reception-bovine:read'])]
|
#[Groups(['reception:read', 'reception:write', 'reception-bovine:read'])]
|
||||||
#[Context(
|
#[Context(
|
||||||
@@ -295,6 +301,45 @@ class Reception
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getValidatedAt(): ?DateTimeImmutable
|
||||||
|
{
|
||||||
|
return $this->validatedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setValidatedAt(?DateTimeImmutable $validatedAt): self
|
||||||
|
{
|
||||||
|
$this->validatedAt = $validatedAt;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isFullyConfirmed(): bool
|
||||||
|
{
|
||||||
|
if ($this->bovines->isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
foreach ($this->bovines as $bovine) {
|
||||||
|
if (null === $bovine->getEdnotifConfirmedAt()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function tryValidate(): void
|
||||||
|
{
|
||||||
|
if ($this->entryCompleted && null === $this->validatedAt && $this->isFullyConfirmed()) {
|
||||||
|
$this->validatedAt = new DateTimeImmutable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[ORM\PreUpdate]
|
||||||
|
public function onPreUpdate(): void
|
||||||
|
{
|
||||||
|
$this->tryValidate();
|
||||||
|
}
|
||||||
|
|
||||||
#[Groups(['reception:read'])]
|
#[Groups(['reception:read'])]
|
||||||
public function getRegisteredBovineCount(): int
|
public function getRegisteredBovineCount(): int
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -54,11 +54,11 @@ class Supplier
|
|||||||
#[ORM\Id]
|
#[ORM\Id]
|
||||||
#[ORM\GeneratedValue]
|
#[ORM\GeneratedValue]
|
||||||
#[ORM\Column]
|
#[ORM\Column]
|
||||||
#[Groups(['supplier:read', 'reception:read'])]
|
#[Groups(['supplier:read', 'reception:read', 'bovine:read'])]
|
||||||
private ?int $id = null;
|
private ?int $id = null;
|
||||||
|
|
||||||
#[ORM\Column(length: 180)]
|
#[ORM\Column(length: 180)]
|
||||||
#[Groups(['supplier:read', 'reception:read', 'supplier:write'])]
|
#[Groups(['supplier:read', 'reception:read', 'supplier:write', 'bovine:read'])]
|
||||||
private string $name = '';
|
private string $name = '';
|
||||||
|
|
||||||
#[ORM\Column(length: 180, nullable: true)]
|
#[ORM\Column(length: 180, nullable: true)]
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ final class BovineSyncInventoryProcessor implements ProcessorInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
$seen = [];
|
$seen = [];
|
||||||
|
$impactedReceptions = [];
|
||||||
foreach ($inventory->animals as $animal) {
|
foreach ($inventory->animals as $animal) {
|
||||||
$nationalNumber = $animal->identification?->bovin?->nationalNumber;
|
$nationalNumber = $animal->identification?->bovin?->nationalNumber;
|
||||||
if (null === $nationalNumber || '' === $nationalNumber) {
|
if (null === $nationalNumber || '' === $nationalNumber) {
|
||||||
@@ -77,8 +78,16 @@ final class BovineSyncInventoryProcessor implements ProcessorInterface
|
|||||||
// voit ce bovin remonter dans l'inventaire.
|
// voit ce bovin remonter dans l'inventaire.
|
||||||
if (null === $bovine->getEdnotifConfirmedAt()) {
|
if (null === $bovine->getEdnotifConfirmedAt()) {
|
||||||
$bovine->setEdnotifConfirmedAt(new DateTimeImmutable());
|
$bovine->setEdnotifConfirmedAt(new DateTimeImmutable());
|
||||||
|
$reception = $bovine->getReception();
|
||||||
|
if (null !== $reception) {
|
||||||
|
$impactedReceptions[$reception->getId()] = $reception;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($impactedReceptions as $reception) {
|
||||||
|
$reception->tryValidate();
|
||||||
|
}
|
||||||
|
|
||||||
$now = new DateTimeImmutable();
|
$now = new DateTimeImmutable();
|
||||||
foreach ($existingByNationalNumber as $nationalNumber => $bovine) {
|
foreach ($existingByNationalNumber as $nationalNumber => $bovine) {
|
||||||
|
|||||||
Reference in New Issue
Block a user