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:
@@ -23,6 +23,25 @@
|
||||
row-clickable
|
||||
@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 }">
|
||||
{{ item.identificationNumber }}
|
||||
</template>
|
||||
@@ -35,81 +54,90 @@
|
||||
<template #cell-registeredBovineCount="{ item }">
|
||||
{{ item.registeredBovineCount ?? 0 }}
|
||||
</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>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 class="text-xl font-bold uppercase text-primary-500 mb-4">Entrées validées</h2>
|
||||
<UiDataTable
|
||||
v-model:page="validatedPage"
|
||||
v-model:per-page="validatedPerPage"
|
||||
:columns="validatedColumns"
|
||||
:items="validated"
|
||||
:total-items="totalValidated"
|
||||
:loading="validatedLoading"
|
||||
>
|
||||
<template #header-identificationNumber>
|
||||
<UiTextInput
|
||||
v-model="validatedFilters.identificationNumber"
|
||||
placeholder="Numéro"
|
||||
size="compact"
|
||||
/>
|
||||
</template>
|
||||
<template #header-receptionDate>
|
||||
<UiDateMaskedInput v-model="validatedDateFilter" placeholder="Date" size="compact" />
|
||||
</template>
|
||||
<template #header-registeredBovineCount>
|
||||
<UiTextInput :model-value="''" placeholder="Saisis" size="compact" disabled />
|
||||
</template>
|
||||
<template #header-validatedAt>
|
||||
<UiTextInput :model-value="''" placeholder="Validée le" size="compact" disabled />
|
||||
</template>
|
||||
<template #header-status>
|
||||
<UiTextInput :model-value="''" placeholder="Statut" size="compact" disabled />
|
||||
</template>
|
||||
<template #cell-identificationNumber="{ item }">
|
||||
{{ item.identificationNumber }}
|
||||
</template>
|
||||
<template #cell-receptionDate="{ item }">
|
||||
{{ formatDate(item.receptionDate) }}
|
||||
</template>
|
||||
<template #cell-registeredBovineCount="{ item }">
|
||||
{{ item.registeredBovineCount ?? 0 }}
|
||||
</template>
|
||||
<template #cell-validatedAt="{ item }">
|
||||
{{ formatDate(item.validatedAt) }}
|
||||
</template>
|
||||
<template #cell-status>
|
||||
<span
|
||||
class="inline-block rounded px-2 py-0.5 text-xs font-semibold bg-green-100 text-green-700"
|
||||
>
|
||||
Validée
|
||||
</span>
|
||||
</template>
|
||||
</UiDataTable>
|
||||
</section>
|
||||
</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>
|
||||
</div>
|
||||
|
||||
<section class="mt-12 mb-16">
|
||||
<h2 class="text-xl font-bold uppercase text-primary-500 mb-4">Historique</h2>
|
||||
<UiDataTable
|
||||
v-model:page="historyPage"
|
||||
v-model:per-page="historyPerPage"
|
||||
:columns="historyColumns"
|
||||
:items="history"
|
||||
:total-items="totalHistory"
|
||||
:loading="historyLoading"
|
||||
>
|
||||
<template #header-identificationNumber>
|
||||
<UiTextInput
|
||||
v-model="historyFilters.identificationNumber"
|
||||
placeholder="Numéro"
|
||||
size="compact"
|
||||
/>
|
||||
</template>
|
||||
<template #header-receptionDate>
|
||||
<UiDateMaskedInput v-model="historyDateFilter" placeholder="Date" size="compact" />
|
||||
</template>
|
||||
<template #header-supplier.name>
|
||||
<UiTextInput
|
||||
v-model="historyFilters['supplier.name']"
|
||||
placeholder="Fournisseur"
|
||||
size="compact"
|
||||
/>
|
||||
</template>
|
||||
<template #header-registeredBovineCount>
|
||||
<UiTextInput :model-value="''" placeholder="Saisis" size="compact" disabled />
|
||||
</template>
|
||||
<template #header-confirmedBovineCount>
|
||||
<UiTextInput :model-value="''" placeholder="Confirmés" size="compact" disabled />
|
||||
</template>
|
||||
<template #header-status>
|
||||
<UiTextInput :model-value="''" placeholder="Statut" size="compact" disabled />
|
||||
</template>
|
||||
<template #cell-identificationNumber="{ item }">
|
||||
{{ item.identificationNumber }}
|
||||
</template>
|
||||
<template #cell-receptionDate="{ item }">
|
||||
{{ formatDate(item.receptionDate) }}
|
||||
</template>
|
||||
<template #cell-registeredBovineCount="{ item }">
|
||||
{{ item.registeredBovineCount ?? 0 }}
|
||||
</template>
|
||||
<template #cell-confirmedBovineCount="{ item }">
|
||||
{{ item.confirmedBovineCount ?? 0 }} / {{ item.registeredBovineCount ?? 0 }}
|
||||
</template>
|
||||
<template #cell-status="{ item }">
|
||||
<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"
|
||||
>
|
||||
Confirmé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>
|
||||
</template>
|
||||
</UiDataTable>
|
||||
</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>
|
||||
|
||||
@@ -124,38 +152,41 @@ const {
|
||||
totalItems: totalEntries,
|
||||
page: entryPage,
|
||||
perPage: entryPerPage,
|
||||
filters: entryFilters,
|
||||
loading: entriesLoading,
|
||||
reload
|
||||
} = useDataTableServerState<ReceptionData>(
|
||||
'receptions',
|
||||
{
|
||||
'isValid': 'true',
|
||||
'entryCompleted': 'false',
|
||||
'receptionType.code': 'BOVINS'
|
||||
'exists[validatedAt]': 'false',
|
||||
'receptionType.code': 'BOVINS',
|
||||
'identificationNumber': '',
|
||||
'receptionDate[after]': '',
|
||||
'receptionDate[strictly_before]': ''
|
||||
},
|
||||
{ initialPerPage: 5 }
|
||||
)
|
||||
|
||||
const {
|
||||
items: history,
|
||||
totalItems: totalHistory,
|
||||
page: historyPage,
|
||||
perPage: historyPerPage,
|
||||
filters: historyFilters,
|
||||
loading: historyLoading,
|
||||
reload: reloadHistory
|
||||
items: validated,
|
||||
totalItems: totalValidated,
|
||||
page: validatedPage,
|
||||
perPage: validatedPerPage,
|
||||
filters: validatedFilters,
|
||||
loading: validatedLoading,
|
||||
reload: reloadValidated
|
||||
} = useDataTableServerState<ReceptionData>(
|
||||
'receptions',
|
||||
{
|
||||
'isValid': 'true',
|
||||
'entryCompleted': 'true',
|
||||
'exists[validatedAt]': 'true',
|
||||
'receptionType.code': 'BOVINS',
|
||||
'identificationNumber': '',
|
||||
'supplier.name': '',
|
||||
'receptionDate[after]': '',
|
||||
'receptionDate[strictly_before]': ''
|
||||
},
|
||||
{ initialPerPage: 10 }
|
||||
{ initialPerPage: 5 }
|
||||
)
|
||||
|
||||
const addOneDay = (dateString: string): string => {
|
||||
@@ -164,37 +195,49 @@ const addOneDay = (dateString: string): string => {
|
||||
return next.toISOString().slice(0, 10)
|
||||
}
|
||||
|
||||
const historyDateFilter = computed<string>({
|
||||
get: () => (historyFilters.value['receptionDate[after]'] as string) ?? '',
|
||||
const entryDateFilter = computed<string>({
|
||||
get: () => (entryFilters.value['receptionDate[after]'] as string) ?? '',
|
||||
set: (value: string) => {
|
||||
if (!value) {
|
||||
historyFilters.value['receptionDate[after]'] = ''
|
||||
historyFilters.value['receptionDate[strictly_before]'] = ''
|
||||
entryFilters.value['receptionDate[after]'] = ''
|
||||
entryFilters.value['receptionDate[strictly_before]'] = ''
|
||||
return
|
||||
}
|
||||
historyFilters.value['receptionDate[after]'] = value
|
||||
historyFilters.value['receptionDate[strictly_before]'] = addOneDay(value)
|
||||
entryFilters.value['receptionDate[after]'] = 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 = [
|
||||
{ key: 'identificationNumber', label: 'Numéro', width: '80px' },
|
||||
{ key: 'identificationNumber', label: 'Numéro', width: '75px' },
|
||||
{ key: 'receptionDate', label: 'Date', width: '75px' },
|
||||
{ key: 'supplier.name', label: 'Fournisseur', width: '1fr' },
|
||||
{ key: 'declaredCount', label: 'Déclarés', width: '85px' },
|
||||
{ key: 'registeredBovineCount', label: 'Saisis', width: '50px' }
|
||||
{ key: 'declaredCount', label: 'Déclarés', width: '75px' },
|
||||
{ key: 'registeredBovineCount', label: 'Saisis', width: '70px' },
|
||||
{ key: 'status', label: 'Statut', width: '1fr' }
|
||||
]
|
||||
|
||||
const historyColumns = [
|
||||
{ key: 'identificationNumber', label: 'Numéro', width: '110px' },
|
||||
{ key: 'receptionDate', label: 'Date', width: '110px' },
|
||||
{ key: 'supplier.name', label: 'Fournisseur', width: '1fr' },
|
||||
{ key: 'registeredBovineCount', label: 'Saisis', width: '80px' },
|
||||
{ key: 'confirmedBovineCount', label: 'Confirmés', width: '110px' },
|
||||
{ key: 'status', label: 'Statut', width: '170px' }
|
||||
const validatedColumns = [
|
||||
{ key: 'identificationNumber', label: 'Numéro', width: '75px' },
|
||||
{ key: 'receptionDate', label: 'Date', width: '75px' },
|
||||
{ key: 'registeredBovineCount', label: 'Saisis', width: '50px' },
|
||||
{ key: 'validatedAt', label: 'Validée le', width: '75px' },
|
||||
{ key: 'status', label: 'Statut', width: '1fr' }
|
||||
]
|
||||
|
||||
const formatDate = (date: string | null) => {
|
||||
const formatDate = (date: string | null | undefined) => {
|
||||
if (!date) return '—'
|
||||
const d = new Date(date.replace(' ', 'T'))
|
||||
if (isNaN(d.getTime())) return date
|
||||
@@ -211,6 +254,6 @@ const goToEntry = (reception: ReceptionData) => {
|
||||
|
||||
onMounted(() => {
|
||||
reload()
|
||||
reloadHistory()
|
||||
reloadValidated()
|
||||
})
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user