- Champ entryCause sur Bovine (enum App\Enum\CauseEntree : Achat/Naissance/PretOuPension) - Sélecteur "Cause d'entrée" sur le formulaire de saisie (default Achat, required) - ednotif_confirmed_at sur Bovine : timestamp set par le sync EDNOTIF la première fois qu'un bovin remonte dans getInventory. Backfill des bovins existants au jour de la migration. - Inventaire (page + export + stats) filtre les bovins encore "en attente EDNOTIF" : ils n'apparaissent qu'une fois confirmés par le sync. - getter getConfirmedBovineCount sur Reception, exposé en reception:read. - Tableau Historique full-width sur /entry-exit listant les entrées validées, avec filtres de colonnes (numéro, date, fournisseur), compteur Confirmés/Saisis, et badge de statut "Confirmée" / "EDNOTIF en attente". - Tableau récap de l'écran de saisie passé en useDataTableServerState pour bénéficier du loading et de la pagination serveur. - Validation entrée : confirm window obligatoire, message renforcé en cas d'écart entre saisis et déclarés. - Pattern projet "submitted" sur le formulaire d'ajout pour le visuel required. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
217 lines
8.0 KiB
Vue
217 lines
8.0 KiB
Vue
<template>
|
|
<div class="px-[86px]">
|
|
<div class="flex items-center justify-start gap-10 relative">
|
|
<Icon
|
|
@click="router.push('/')"
|
|
name="gg:arrow-left-o"
|
|
size="44"
|
|
class="cursor-pointer text-primary-500 absolute -left-[60px]"
|
|
/>
|
|
<h1 class="font-bold text-3xl uppercase text-primary-500">Entrée / Sortie</h1>
|
|
</div>
|
|
|
|
<div class="mt-8 grid grid-cols-2 gap-8">
|
|
<section>
|
|
<h2 class="text-xl font-bold uppercase text-primary-500 mb-4">Entrées en attente</h2>
|
|
<UiDataTable
|
|
v-model:page="entryPage"
|
|
v-model:per-page="entryPerPage"
|
|
:columns="entryColumns"
|
|
:items="entries"
|
|
:total-items="totalEntries"
|
|
:loading="entriesLoading"
|
|
row-clickable
|
|
@row-click="goToEntry"
|
|
>
|
|
<template #cell-identificationNumber="{ item }">
|
|
{{ item.identificationNumber }}
|
|
</template>
|
|
<template #cell-receptionDate="{ item }">
|
|
{{ formatDate(item.receptionDate) }}
|
|
</template>
|
|
<template #cell-declaredCount="{ item }">
|
|
{{ item.declaredBovineCount ?? 0 }}
|
|
</template>
|
|
<template #cell-registeredBovineCount="{ item }">
|
|
{{ item.registeredBovineCount ?? 0 }}
|
|
</template>
|
|
</UiDataTable>
|
|
</section>
|
|
|
|
<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>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import type { ReceptionData } from '~/services/dto/reception-data'
|
|
import { useDataTableServerState } from '~/composables/useDataTableServerState'
|
|
|
|
const router = useRouter()
|
|
|
|
const {
|
|
items: entries,
|
|
totalItems: totalEntries,
|
|
page: entryPage,
|
|
perPage: entryPerPage,
|
|
loading: entriesLoading,
|
|
reload
|
|
} = useDataTableServerState<ReceptionData>(
|
|
'receptions',
|
|
{
|
|
'isValid': 'true',
|
|
'entryCompleted': 'false',
|
|
'receptionType.code': 'BOVINS'
|
|
},
|
|
{ initialPerPage: 5 }
|
|
)
|
|
|
|
const {
|
|
items: history,
|
|
totalItems: totalHistory,
|
|
page: historyPage,
|
|
perPage: historyPerPage,
|
|
filters: historyFilters,
|
|
loading: historyLoading,
|
|
reload: reloadHistory
|
|
} = useDataTableServerState<ReceptionData>(
|
|
'receptions',
|
|
{
|
|
'isValid': 'true',
|
|
'entryCompleted': 'true',
|
|
'receptionType.code': 'BOVINS',
|
|
'identificationNumber': '',
|
|
'supplier.name': '',
|
|
'receptionDate[after]': '',
|
|
'receptionDate[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 historyDateFilter = computed<string>({
|
|
get: () => (historyFilters.value['receptionDate[after]'] as string) ?? '',
|
|
set: (value: string) => {
|
|
if (!value) {
|
|
historyFilters.value['receptionDate[after]'] = ''
|
|
historyFilters.value['receptionDate[strictly_before]'] = ''
|
|
return
|
|
}
|
|
historyFilters.value['receptionDate[after]'] = value
|
|
historyFilters.value['receptionDate[strictly_before]'] = addOneDay(value)
|
|
}
|
|
})
|
|
|
|
const entryColumns = [
|
|
{ key: 'identificationNumber', label: 'Numéro', width: '80px' },
|
|
{ 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' }
|
|
]
|
|
|
|
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 formatDate = (date: string | null) => {
|
|
if (!date) return '—'
|
|
const d = new Date(date.replace(' ', 'T'))
|
|
if (isNaN(d.getTime())) return date
|
|
return d.toLocaleDateString('fr-FR', {
|
|
day: '2-digit',
|
|
month: '2-digit',
|
|
year: 'numeric'
|
|
})
|
|
}
|
|
|
|
const goToEntry = (reception: ReceptionData) => {
|
|
router.push(`/entry-exit/entry/${reception.id}`)
|
|
}
|
|
|
|
onMounted(() => {
|
|
reload()
|
|
reloadHistory()
|
|
})
|
|
</script>
|