Compare commits
5 Commits
v0.0.28
...
feat/test-
| Author | SHA1 | Date | |
|---|---|---|---|
| 92354d9060 | |||
| 33563addbe | |||
| 81c2a5802b | |||
| 015a8c49fb | |||
| 505f67d475 |
2
.idea/dataSources.xml
generated
2
.idea/dataSources.xml
generated
@@ -16,4 +16,4 @@
|
||||
<working-dir>$ProjectFileDir$</working-dir>
|
||||
</data-source>
|
||||
</component>
|
||||
</project>
|
||||
</project>
|
||||
110
.idea/workspace.xml
generated
110
.idea/workspace.xml
generated
@@ -4,10 +4,15 @@
|
||||
<option name="autoReloadType" value="SELECTIVE" />
|
||||
</component>
|
||||
<component name="ChangeListManager">
|
||||
<list default="true" id="7c107abe-5995-4428-8429-b146aaca8386" name="Changes" comment="feat : ajout du responsive sur la navbar et la page d'accueil">
|
||||
<list default="true" id="7c107abe-5995-4428-8429-b146aaca8386" name="Changes" comment="feat : Ajout de la sélection des bovins étape 3 d'une réception (WIP)">
|
||||
<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$/frontend/layouts/default.vue" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/layouts/default.vue" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/frontend/components/reception/reception-bovine-received.vue" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/components/reception/reception-bovine-received.vue" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/frontend/components/reception/reception-form.vue" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/components/reception/reception-form.vue" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/frontend/components/reception/reception-product-received.vue" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/components/reception/reception-product-received.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$/src/Entity/Reception.php" beforeDir="false" afterPath="$PROJECT_DIR$/src/Entity/Reception.php" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/Entity/ReceptionBovine.php" beforeDir="false" afterPath="$PROJECT_DIR$/src/Entity/ReceptionBovine.php" afterDir="false" />
|
||||
</list>
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||
@@ -38,7 +43,7 @@
|
||||
<component name="Git.Settings">
|
||||
<option name="RECENT_BRANCH_BY_REPOSITORY">
|
||||
<map>
|
||||
<entry key="$PROJECT_DIR$" value="feat/256-reception-etape-3-bovin" />
|
||||
<entry key="$PROJECT_DIR$" value="fix/makefile" />
|
||||
</map>
|
||||
</option>
|
||||
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
||||
@@ -219,36 +224,36 @@
|
||||
<option name="hideEmptyMiddlePackages" value="true" />
|
||||
<option name="showLibraryContents" value="true" />
|
||||
</component>
|
||||
<component name="PropertiesComponent"><![CDATA[{
|
||||
"keyToString": {
|
||||
"RunOnceActivity.MCP Project settings loaded": "true",
|
||||
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||
"RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true",
|
||||
"RunOnceActivity.git.unshallow": "true",
|
||||
"RunOnceActivity.typescript.service.memoryLimit.init": "true",
|
||||
"git-widget-placeholder": "develop",
|
||||
"last_opened_file_path": "/home/sroy/Documents/test/Ferme",
|
||||
"node.js.detected.package.eslint": "true",
|
||||
"node.js.detected.package.tslint": "true",
|
||||
"node.js.selected.package.eslint": "(autodetect)",
|
||||
"node.js.selected.package.tslint": "(autodetect)",
|
||||
"nodejs_package_manager_path": "npm",
|
||||
"settings.editor.selected.configurable": "configurable.tailwindcss",
|
||||
"ts.external.directory.path": "/opt/phpstorm/plugins/javascript-plugin/jsLanguageServicesImpl/external",
|
||||
"vue.rearranger.settings.migration": "true"
|
||||
<component name="PropertiesComponent">{
|
||||
"keyToString": {
|
||||
"RunOnceActivity.MCP Project settings loaded": "true",
|
||||
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||
"RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true",
|
||||
"RunOnceActivity.git.unshallow": "true",
|
||||
"RunOnceActivity.typescript.service.memoryLimit.init": "true",
|
||||
"git-widget-placeholder": "feat/256-reception-etape-3-bovin",
|
||||
"last_opened_file_path": "/home/sroy/Documents/test/Ferme",
|
||||
"node.js.detected.package.eslint": "true",
|
||||
"node.js.detected.package.tslint": "true",
|
||||
"node.js.selected.package.eslint": "(autodetect)",
|
||||
"node.js.selected.package.tslint": "(autodetect)",
|
||||
"nodejs_package_manager_path": "npm",
|
||||
"settings.editor.selected.configurable": "configurable.tailwindcss",
|
||||
"ts.external.directory.path": "/opt/phpstorm/plugins/javascript-plugin/jsLanguageServicesImpl/external",
|
||||
"vue.rearranger.settings.migration": "true"
|
||||
},
|
||||
"keyToStringList": {
|
||||
"DatabaseDriversLRU": [
|
||||
"postgresql"
|
||||
"keyToStringList": {
|
||||
"DatabaseDriversLRU": [
|
||||
"postgresql"
|
||||
],
|
||||
"com.intellij.ide.scratch.ScratchImplUtil$2/New Scratch File": [
|
||||
"TEXT"
|
||||
"com.intellij.ide.scratch.ScratchImplUtil$2/New Scratch File": [
|
||||
"TEXT"
|
||||
],
|
||||
"vue.recent.templates": [
|
||||
"Vue Composition API Component"
|
||||
"vue.recent.templates": [
|
||||
"Vue Composition API Component"
|
||||
]
|
||||
}
|
||||
}]]></component>
|
||||
}</component>
|
||||
<component name="RecentsManager">
|
||||
<key name="MoveFile.RECENT_KEYS">
|
||||
<recent name="\\wsl.localhost\Ubuntu-24.04\home\m-tristan\workspace\Ferme" />
|
||||
@@ -296,6 +301,22 @@
|
||||
<workItem from="1770195959162" duration="18915000" />
|
||||
<workItem from="1770274844804" duration="3940000" />
|
||||
</task>
|
||||
<task id="LOCAL-00001" summary="feat : Ajout de pinia, création de la table weight et reception mise en place du système de step pour les receptions (WIP)">
|
||||
<option name="closed" value="true" />
|
||||
<created>1768237763998</created>
|
||||
<option name="number" value="00001" />
|
||||
<option name="presentableId" value="LOCAL-00001" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1768237763998</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00002" summary="feat : Ajout de zod, création d'un composant de chargement loading-dots.vue et finalisation du flow d'une reception">
|
||||
<option name="closed" value="true" />
|
||||
<created>1768316052474</created>
|
||||
<option name="number" value="00002" />
|
||||
<option name="presentableId" value="LOCAL-00002" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1768316052474</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00003" summary="feat : Ajout d'un composable pour la pesée qui sera réutilisable pour l'expédition, ajout de contrainte sur les entity de reception et weight pour plus de robustesse et correction de la class active des liens dans la nav">
|
||||
<option name="closed" value="true" />
|
||||
<created>1768316835575</created>
|
||||
@@ -672,23 +693,7 @@
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1770217875423</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00050" summary="feat : Ajout de la sélection des bovins étape 3 d'une réception (WIP)">
|
||||
<option name="closed" value="true" />
|
||||
<created>1770283622425</created>
|
||||
<option name="number" value="00050" />
|
||||
<option name="presentableId" value="LOCAL-00050" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1770283622425</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00051" summary="feat : ajout du responsive sur la navbar et la page d'accueil">
|
||||
<option name="closed" value="true" />
|
||||
<created>1770308927948</created>
|
||||
<option name="number" value="00051" />
|
||||
<option name="presentableId" value="LOCAL-00051" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1770308927948</updated>
|
||||
</task>
|
||||
<option name="localTasksCounter" value="52" />
|
||||
<option name="localTasksCounter" value="50" />
|
||||
<servers />
|
||||
</component>
|
||||
<component name="TypeScriptGeneratedFilesManager">
|
||||
@@ -738,6 +743,7 @@
|
||||
</option>
|
||||
</component>
|
||||
<component name="VcsManagerConfiguration">
|
||||
<MESSAGE value="fix : correction du path URI pour la création d'un poids dans une réception" />
|
||||
<MESSAGE value="feat : Ajout du bundle Monolog pour la gestion des logs" />
|
||||
<MESSAGE value="fix : affiche plus détail dans les logs en recette/prod" />
|
||||
<MESSAGE value="fix : modification du script de déploiement pour corriger le problème d'écriture des logs de prod" />
|
||||
@@ -762,8 +768,18 @@
|
||||
<MESSAGE value="feat : ajout de colonne pour les Supplier, Address. Modification du numéro de réception et ajout de fixtures" />
|
||||
<MESSAGE value="feat : mise à jour du bon de réception" />
|
||||
<MESSAGE value="feat : Ajout de la sélection des bovins étape 3 d'une réception (WIP)" />
|
||||
<MESSAGE value="feat : ajout du responsive sur la navbar et la page d'accueil" />
|
||||
<option name="LAST_COMMIT_MESSAGE" value="feat : ajout du responsive sur la navbar et la page d'accueil" />
|
||||
<option name="LAST_COMMIT_MESSAGE" value="feat : Ajout de la sélection des bovins étape 3 d'une réception (WIP)" />
|
||||
</component>
|
||||
<component name="XDebuggerManager">
|
||||
<breakpoint-manager>
|
||||
<breakpoints>
|
||||
<line-breakpoint enabled="true" type="php">
|
||||
<url>file://$PROJECT_DIR$/src/Entity/ReceptionPelletBuilding.php</url>
|
||||
<line>6</line>
|
||||
<option name="timeStamp" value="3" />
|
||||
</line-breakpoint>
|
||||
</breakpoints>
|
||||
</breakpoint-manager>
|
||||
</component>
|
||||
<component name="XSLT-Support.FileAssociations.UIState">
|
||||
<expand />
|
||||
|
||||
@@ -27,8 +27,6 @@ Ajouter dans le fichier .env du frontend
|
||||
* Ajout du bundle malio/ednotif-bundle
|
||||
* Ajout de composant UI
|
||||
* Finalisation de la partie réception de marchandise
|
||||
* [#267] Lister les réceptions en attente
|
||||
* [#268] Lister les réceptions terminées
|
||||
|
||||
### Changed
|
||||
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
|
||||
|
||||
<template>
|
||||
<NuxtLink :to="link">
|
||||
<div class="w-[324px] h-[228px] border border-black rounded-md p-6 flex flex-col justify-between">
|
||||
<div class="flex justify-between">
|
||||
<div class="rounded-full w-[80px] h-[80px] bg-neutral-400 flex justify-center items-center">
|
||||
<Icon :name="iconName" style="color: black" size="44" />
|
||||
</div>
|
||||
<div>
|
||||
<Icon name="mdi:plus" style="color: black" size="44" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="uppercase font-bold">
|
||||
<p class="text-3xl"> {{ label }} </p>
|
||||
</div>
|
||||
</div>
|
||||
</NuxtLink>
|
||||
|
||||
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const props = defineProps<{
|
||||
link: string
|
||||
iconName: string
|
||||
label: string
|
||||
}>()
|
||||
</script>
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
<script setup lang="ts">
|
||||
import type {BovineTypeData} from "~/services/dto/bovine-type-data";
|
||||
import {getBovineTypeList} from "~/services/bovine-type";
|
||||
import {RECEPTION_TYPE_CODES} from "~/utils/constants";
|
||||
import {MERCHANDISE_TYPE_CODES, RECEPTION_TYPE_CODES} from "~/utils/constants";
|
||||
import {useReceptionStore} from '~/stores/reception'
|
||||
import {
|
||||
createReceptionBovine,
|
||||
@@ -46,7 +46,6 @@ import {
|
||||
} from "~/services/reception-bovine";
|
||||
import {computed, onMounted, reactive, ref, watch} from "vue";
|
||||
|
||||
const toast = useToast()
|
||||
const isLoadingBovineType = ref(false)
|
||||
const bovineType = ref<BovineTypeData[]>([])
|
||||
const receptionStore = useReceptionStore()
|
||||
@@ -56,13 +55,19 @@ const receptionId = computed(() => receptionStore.current?.id ?? null)
|
||||
const receptionIri = computed(() =>
|
||||
receptionId.value ? `/api/receptions/${receptionId.value}` : null
|
||||
)
|
||||
const totalBovines = computed(() => {
|
||||
const base = Object.values(bovineQuantities).reduce((sum, value) => {
|
||||
return sum + (value ?? 0)
|
||||
const toast = useToast()
|
||||
const nuxtApp = useNuxtApp()
|
||||
const i18n = nuxtApp.$i18n as { t: (key: string) => string } |
|
||||
undefined
|
||||
const t = (key: string) => (i18n?.t ? String(i18n.t(key)) : key)
|
||||
const totalBovineQuantity = computed(() => {
|
||||
const baseTotal = Object.values(bovineQuantities).reduce((sum, value) => {
|
||||
const n = typeof value === 'number' ? value : 0
|
||||
return sum + n
|
||||
}, 0)
|
||||
return base + (otherQuantity.value ?? 0)
|
||||
const other = typeof otherQuantity.value === 'number' ? otherQuantity.value : 0
|
||||
return baseTotal + other
|
||||
})
|
||||
|
||||
const loadBovineType = async () => {
|
||||
isLoadingBovineType.value = true
|
||||
try {
|
||||
@@ -112,7 +117,6 @@ watch(
|
||||
async function syncBovineSelections(receptionIri: string) {
|
||||
const existing = await getReceptionBovineList(receptionIri)
|
||||
const existingMap = new Map<string, { id: number; quantity: number | null }>()
|
||||
|
||||
for (const selection of existing) {
|
||||
const bovineTypeId = String(selection.bovineType.id)
|
||||
existingMap.set(bovineTypeId, {
|
||||
@@ -155,21 +159,37 @@ async function syncBovineSelections(receptionIri: string) {
|
||||
})
|
||||
}
|
||||
}
|
||||
const hasNegativeQuantity = computed(() => {
|
||||
const anyNegativeInTypes =
|
||||
Object.values(bovineQuantities).some((value) => {
|
||||
return typeof value === 'number' && value < 0
|
||||
})
|
||||
|
||||
const otherNegative =
|
||||
typeof otherQuantity.value === 'number' &&
|
||||
otherQuantity.value < 0
|
||||
|
||||
return anyNegativeInTypes || otherNegative
|
||||
})
|
||||
async function goNext() {
|
||||
if (!receptionStore.current || !receptionIri.value) {
|
||||
return
|
||||
}
|
||||
|
||||
// @TODO Ajouter un composable pour le toaster qui gère les key i18n
|
||||
if (totalBovines.value > 52) {
|
||||
if (hasNegativeQuantity.value) {
|
||||
toast.error({
|
||||
title: 'Erreur',
|
||||
message: ("La quantité de bovins ne peut pas être négative.")
|
||||
})
|
||||
return
|
||||
}
|
||||
// Le 52 à vérifier
|
||||
if (totalBovineQuantity.value > 52) {
|
||||
toast.error({
|
||||
title: 'Erreur',
|
||||
message: ('Le total des bovins ne peut pas dépasser 52.')
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const nextStep = receptionStore.current.currentStep + 1
|
||||
await syncBovineSelections(receptionIri.value)
|
||||
|
||||
|
||||
@@ -222,12 +222,10 @@ const filteredVehicles = computed<VehicleData[]>(() => {
|
||||
(!form.truckId || String(vehicle.truck?.id) === form.truckId)
|
||||
)
|
||||
})
|
||||
|
||||
const selectedReceptionType = computed(() =>
|
||||
receptionTypes.value.find((type) => String(type.id) === form.receptionTypeId) ?? null
|
||||
)
|
||||
|
||||
// Supprime les données bovines si on change de type de réception
|
||||
const clearReceptionBovines = async (receptionIri: string) => {
|
||||
const existing = await getReceptionBovineList(receptionIri)
|
||||
for (const selection of existing) {
|
||||
@@ -473,6 +471,9 @@ async function validate() {
|
||||
const normalizedTruckId = form.truckId.trim()
|
||||
const normalizedCarrierId = form.carrierId.trim()
|
||||
const normalizedDriverId = form.driverId.trim()
|
||||
const previousTypeCode = receptionStore.current.receptionType?.code ?? null
|
||||
const nextTypeCode = selectedReceptionType.value?.code ?? null
|
||||
const receptionIri = `/api/receptions/${receptionStore.current.id}`
|
||||
const receptionTypeIri = normalizedReceptionTypeId
|
||||
? `/api/reception_types/${normalizedReceptionTypeId}`
|
||||
: null
|
||||
@@ -521,11 +522,6 @@ async function validate() {
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
const previousTypeCode = receptionStore.current.receptionType?.code ?? null
|
||||
const nextTypeCode = selectedReceptionType.value?.code ?? null
|
||||
const receptionIri = `/api/receptions/${receptionStore.current.id}`
|
||||
|
||||
if (
|
||||
previousTypeCode === RECEPTION_TYPE_CODES.BOVINS &&
|
||||
nextTypeCode === RECEPTION_TYPE_CODES.MERCHANDISES
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<div class="flex flex-col items-center gap-16">
|
||||
<!-- @TODO voir pour séparer dans un composant au moment de l'implémentation des Bovins -->
|
||||
<div
|
||||
v-if="receptionStore.current?.receptionType?.code === RECEPTION_TYPE_CODES.MERCHANDISES"
|
||||
class="flex flex-col gap-16 items-center w-full">
|
||||
@@ -98,6 +99,7 @@ const selectedBuildingIds = ref<string[]>([])
|
||||
const selectedPelletBuildingIds = ref<Record<string, string[]>>({})
|
||||
const merchandiseDetail = ref('')
|
||||
|
||||
|
||||
// Extrait l'ID d'une relation depuis un IRI ou un objet complet.
|
||||
const getRelationId = (value: unknown): string | null => {
|
||||
if (!value) {
|
||||
|
||||
@@ -30,7 +30,6 @@
|
||||
disabled ? 'cursor-not-allowed' : 'cursor-text',
|
||||
inputClass
|
||||
]"
|
||||
@keydown="onKeydown"
|
||||
@input="onInput"
|
||||
>
|
||||
</div>
|
||||
@@ -79,13 +78,7 @@ const onInput = (event: Event) => {
|
||||
emit('update:modelValue', null)
|
||||
return
|
||||
}
|
||||
const numeric = Math.max(0, Number(target.value))
|
||||
const numeric = Number(target.value)
|
||||
emit('update:modelValue', Number.isNaN(numeric) ? null : numeric)
|
||||
}
|
||||
|
||||
const onKeydown = (event: KeyboardEvent) => {
|
||||
if (event.key === '-' || event.key === 'e' || event.key === 'E') {
|
||||
event.preventDefault()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
<template>
|
||||
<div class="min-h-screen bg-white text-neutral-900">
|
||||
<header class="w-full border-b border-neutral-200 bg-primary-500">
|
||||
<div class="flex w-full items-center justify-center px-6 py-4">
|
||||
<button
|
||||
type="button"
|
||||
class="inline-flex items-center justify-center text-3xl text-white md:hidden"
|
||||
aria-label="Ouvrir le menu"
|
||||
@click="toggleMenu"
|
||||
>
|
||||
<span aria-hidden="true" class="flex items-center"><Icon name="mdi:menu" size="44"/></span>
|
||||
</button>
|
||||
<nav class="ml-4 hidden items-center gap-8 text-2xl font-bold uppercase text-white md:flex">
|
||||
<div class="flex w-full items-center px-6 py-4">
|
||||
<NuxtLink to="/" class="flex items-center gap-3">
|
||||
<span
|
||||
class="flex items-center justify-center bg-white text-xl font-bold uppercase text-primary-500 p-4"
|
||||
>
|
||||
LOGO
|
||||
</span>
|
||||
</NuxtLink>
|
||||
<nav class="mx-8 flex flex-1 gap-8 text-2xl font-bold uppercase text-white">
|
||||
<NuxtLink to="/" custom v-slot="{ href, navigate, isExactActive }">
|
||||
<a
|
||||
:href="href"
|
||||
@@ -20,76 +19,26 @@
|
||||
Accueil
|
||||
</a>
|
||||
</NuxtLink>
|
||||
<NuxtLink to="/reception" custom v-slot="{ href, navigate, isActive }">
|
||||
<a
|
||||
:href="href"
|
||||
@click="navigate"
|
||||
:class="isReceptionActive ? 'opacity-100' : 'opacity-50'"
|
||||
>
|
||||
Reception
|
||||
</a>
|
||||
</NuxtLink>
|
||||
</nav>
|
||||
<NuxtLink to="/" class="flex flex-1 items-center justify-center gap-3">
|
||||
<span
|
||||
class="flex items-center justify-center bg-white text-xl font-bold uppercase text-primary-500 p-4"
|
||||
>
|
||||
LOGO
|
||||
</span>
|
||||
</NuxtLink>
|
||||
<div class="w-[44px] md:hidden"></div>
|
||||
<button
|
||||
type="button"
|
||||
class="ml-auto hidden text-xl font-bold uppercase text-white transition hover:opacity-80 md:inline-flex"
|
||||
class="ml-auto text-xl font-bold uppercase text-white transition hover:opacity-80"
|
||||
@click="handleLogout"
|
||||
>
|
||||
Déconnexion
|
||||
</button>
|
||||
</div>
|
||||
<transition
|
||||
enter-active-class="transition duration-200 ease-out"
|
||||
enter-from-class="opacity-0"
|
||||
enter-to-class="opacity-100"
|
||||
leave-active-class="transition duration-150 ease-in"
|
||||
leave-from-class="opacity-100"
|
||||
leave-to-class="opacity-0"
|
||||
>
|
||||
<div
|
||||
v-if="isMenuOpen"
|
||||
class="fixed inset-0 z-40 bg-black/40 md:hidden"
|
||||
@click="closeMenu"
|
||||
/>
|
||||
</transition>
|
||||
<transition
|
||||
enter-active-class="transition duration-200 ease-out"
|
||||
enter-from-class="-translate-x-full"
|
||||
enter-to-class="translate-x-0"
|
||||
leave-active-class="transition duration-150 ease-in"
|
||||
leave-from-class="translate-x-0"
|
||||
leave-to-class="-translate-x-full"
|
||||
>
|
||||
<aside
|
||||
v-if="isMenuOpen"
|
||||
class="fixed left-0 top-0 z-50 h-full w-full bg-primary-600 px-6 pb-8 pt-6 text-white shadow-xl md:hidden"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
>
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-2xl font-bold uppercase">Menu</span>
|
||||
<button
|
||||
type="button"
|
||||
class="text-2xl"
|
||||
aria-label="Fermer le menu"
|
||||
@click="closeMenu"
|
||||
>
|
||||
<Icon name="mdi:close" size="44"/>
|
||||
</button>
|
||||
</div>
|
||||
<nav class="mt-8 flex flex-col gap-6 text-xl font-bold uppercase">
|
||||
<NuxtLink to="/" class="opacity-100" @click="closeMenu">Accueil</NuxtLink>
|
||||
</nav>
|
||||
<button
|
||||
type="button"
|
||||
class="mt-5 text-xl font-bold uppercase"
|
||||
@click="handleLogout"
|
||||
>
|
||||
Déconnexion
|
||||
</button>
|
||||
</aside>
|
||||
</transition>
|
||||
</header>
|
||||
<main class="mx-auto w-full max-w-[1280px] pb-0">
|
||||
<main class="mx-auto w-full max-w-[1280px] px-6 pt-[85px] pb-0">
|
||||
<slot/>
|
||||
</main>
|
||||
</div>
|
||||
@@ -101,21 +50,11 @@ import { useAuthStore } from '~/stores/auth'
|
||||
const route = useRoute()
|
||||
const auth = useAuthStore()
|
||||
const isReceptionActive = computed(() => route.path.startsWith('/reception'))
|
||||
const isMenuOpen = ref(false)
|
||||
|
||||
const closeMenu = () => {
|
||||
isMenuOpen.value = false
|
||||
}
|
||||
|
||||
const toggleMenu = () => {
|
||||
isMenuOpen.value = !isMenuOpen.value
|
||||
}
|
||||
|
||||
const handleLogout = async () => {
|
||||
try {
|
||||
await auth.logout()
|
||||
} finally {
|
||||
closeMenu()
|
||||
await navigateTo('/login')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,8 +9,7 @@ export default defineNuxtConfig({
|
||||
'@nuxtjs/tailwindcss',
|
||||
'@pinia/nuxt',
|
||||
'nuxt-toast',
|
||||
'@nuxtjs/i18n',
|
||||
'@nuxt/icon'
|
||||
'@nuxtjs/i18n'
|
||||
],
|
||||
css: ['~/assets/css/main.css', '~/assets/css/toast.css'],
|
||||
runtimeConfig: {
|
||||
|
||||
77
frontend/package-lock.json
generated
77
frontend/package-lock.json
generated
@@ -7,7 +7,6 @@
|
||||
"name": "frontend",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@nuxt/icon": "^2.2.1",
|
||||
"@nuxtjs/i18n": "^10.2.1",
|
||||
"@pinia/nuxt": "^0.11.3",
|
||||
"izitoast": "^1.4.0",
|
||||
@@ -36,19 +35,6 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/@antfu/install-pkg": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-1.1.0.tgz",
|
||||
"integrity": "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"package-manager-detector": "^1.3.0",
|
||||
"tinyexec": "^1.0.1"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame": {
|
||||
"version": "7.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
|
||||
@@ -1262,47 +1248,6 @@
|
||||
"url": "https://github.com/sponsors/nzakas"
|
||||
}
|
||||
},
|
||||
"node_modules/@iconify/collections": {
|
||||
"version": "1.0.646",
|
||||
"resolved": "https://registry.npmjs.org/@iconify/collections/-/collections-1.0.646.tgz",
|
||||
"integrity": "sha512-zA5Gr1MJm1SI0TjOUl7wu4kvBWXQ6Uh8ALEtqQ5ucXyUxP2M8m2bk2hfVtGykSdMlDB+Xs2AHbJ9pQqayz9WGQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@iconify/types": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@iconify/types": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz",
|
||||
"integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@iconify/utils": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-3.1.0.tgz",
|
||||
"integrity": "sha512-Zlzem1ZXhI1iHeeERabLNzBHdOa4VhQbqAcOQaMKuTuyZCpwKbC2R4Dd0Zo3g9EAc+Y4fiarO8HIHRAth7+skw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@antfu/install-pkg": "^1.1.0",
|
||||
"@iconify/types": "^2.0.0",
|
||||
"mlly": "^1.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@iconify/vue": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@iconify/vue/-/vue-5.0.0.tgz",
|
||||
"integrity": "sha512-C+KuEWIF5nSBrobFJhT//JS87OZ++QDORB6f2q2Wm6fl2mueSTpFBeBsveK0KW9hWiZ4mNiPjsh6Zs4jjdROSg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@iconify/types": "^2.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/cyberalien"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": ">=3"
|
||||
}
|
||||
},
|
||||
"node_modules/@intlify/bundle-utils": {
|
||||
"version": "11.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@intlify/bundle-utils/-/bundle-utils-11.0.3.tgz",
|
||||
@@ -2323,28 +2268,6 @@
|
||||
"devtools-wizard": "cli.mjs"
|
||||
}
|
||||
},
|
||||
"node_modules/@nuxt/icon": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@nuxt/icon/-/icon-2.2.1.tgz",
|
||||
"integrity": "sha512-GI840yYGuvHI0BGDQ63d6rAxGzG96jQcWrnaWIQKlyQo/7sx9PjXkSHckXUXyX1MCr9zY6U25Td6OatfY6Hklw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@iconify/collections": "^1.0.641",
|
||||
"@iconify/types": "^2.0.0",
|
||||
"@iconify/utils": "^3.1.0",
|
||||
"@iconify/vue": "^5.0.0",
|
||||
"@nuxt/devtools-kit": "^3.1.1",
|
||||
"@nuxt/kit": "^4.2.2",
|
||||
"consola": "^3.4.2",
|
||||
"local-pkg": "^1.1.2",
|
||||
"mlly": "^1.8.0",
|
||||
"ohash": "^2.0.11",
|
||||
"pathe": "^2.0.3",
|
||||
"picomatch": "^4.0.3",
|
||||
"std-env": "^3.10.0",
|
||||
"tinyglobby": "^0.2.15"
|
||||
}
|
||||
},
|
||||
"node_modules/@nuxt/kit": {
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@nuxt/kit/-/kit-4.2.2.tgz",
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
"build:dist": "nuxt generate && rm -rf dist && cp -R .output/public dist"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nuxt/icon": "^2.2.1",
|
||||
"@nuxtjs/i18n": "^10.2.1",
|
||||
"@pinia/nuxt": "^0.11.3",
|
||||
"izitoast": "^1.4.0",
|
||||
|
||||
@@ -1,15 +1,55 @@
|
||||
<script setup lang="ts">
|
||||
</script>
|
||||
<template>
|
||||
<div class="flex flex-wrap justify-center mt-8 gap-8 mb-8 md:mb-0">
|
||||
<card-link label="NOUVELLE RÉCEPTION" link="/reception" iconName="mdi:truck-outline" />
|
||||
<card-link label="NOUVELLE EXPÉDITION" link="/" iconName="mdi:truck-fast-outline" />
|
||||
<card-link label="PLAN DE SITE" link="/" iconName="mdi:warehouse" />
|
||||
<card-link label="RÉCEPTIONS EN ATTENTE" link="/reception/waiting-reception" iconName="mdi:truck-remove-outline" />
|
||||
<card-link label="EXPÉDITIONS EN ATTENTE" link="/" iconName="mdi:truck-cargo-container" />
|
||||
<card-link label="CASES" link="/" iconName="mdi:cube-outline" />
|
||||
<card-link label="RÉCEPTIONS FINIES" link="/reception/finish-reception" iconName="mdi:truck-check-outline" />
|
||||
<card-link label="EXPÉDITIONS FINIES" link="/" iconName="mdi:truck-delivery-outline" />
|
||||
<card-link label="PASSEPORT DU BOVIN" link="/" iconName="mdi:cow" />
|
||||
<div>
|
||||
<h1 class="text-3xl font-bold">Liste des receptions</h1>
|
||||
<div class="mt-6 border border-slate-200">
|
||||
<div class="grid grid-cols-6 gap-4 bg-slate-100 px-4 py-3 text-sm font-semibold uppercase tracking-wide">
|
||||
<div>ID</div>
|
||||
<div>Immatriculation</div>
|
||||
<div>Pesée plein</div>
|
||||
<div>Pesée vide</div>
|
||||
<div>Etape</div>
|
||||
<div>Date</div>
|
||||
</div>
|
||||
<div
|
||||
v-for="reception in receptionList"
|
||||
:key="reception.id"
|
||||
class="grid grid-cols-6 gap-4 px-4 py-3 text-sm hover:bg-slate-50 cursor-pointer border-t border-slate-200"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
@click="goToReception(reception.id)"
|
||||
@keydown.enter="goToReception(reception.id)"
|
||||
>
|
||||
<div>{{ reception.id }}</div>
|
||||
<div>{{ reception.licensePlate }}</div>
|
||||
<div>{{ formatWeighing(reception, 'gross') }}</div>
|
||||
<div>{{ formatWeighing(reception, 'tare') }}</div>
|
||||
<div>{{ reception.currentStep }}</div>
|
||||
<div>{{ reception.receptionDate }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type {ReceptionData} from "~/services/dto/reception-data";
|
||||
import {getReceptionList} from "~/services/reception";
|
||||
|
||||
const receptionList = ref<ReceptionData[]>()
|
||||
const router = useRouter()
|
||||
|
||||
const goToReception = (id: number) => {
|
||||
router.push(`/reception/${id}`)
|
||||
}
|
||||
|
||||
const formatWeighing = (reception: ReceptionData, type: 'gross' | 'tare') => {
|
||||
const entry = reception.weights?.find((weight) => weight.type === type)
|
||||
if (!entry || entry.weight == null || entry.dsd == null) {
|
||||
return '—'
|
||||
}
|
||||
return `${entry.weight} kg / ${entry.dsd} dsd`
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
receptionList.value = await getReceptionList()
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
<template>
|
||||
<div class="flex items-center justify-start gap-10">
|
||||
<Icon @click="router.push('/')" name="gg:arrow-left-o" style="color: black" size="44" />
|
||||
<h1 class="text-3xl font-bold uppercase">listes des réceptions finie</h1>
|
||||
</div>
|
||||
|
||||
<div class="ps-20 " >
|
||||
<div class="mt-6 border border-slate-200 mb-16 ">
|
||||
<div class="grid grid-cols-6 gap-4 bg-slate-100 px-4 py-3 text-sm font-semibold uppercase tracking-wide">
|
||||
<div>Numéro</div>
|
||||
<div>Date</div>
|
||||
<div>Fournisseur</div>
|
||||
<div>Adresse</div>
|
||||
<div>Type réception</div>
|
||||
<div>Poids</div>
|
||||
</div>
|
||||
<div
|
||||
v-for="reception in receptionList"
|
||||
:key="reception.id"
|
||||
class="grid grid-cols-6 gap-4 px-4 py-3 text-sm hover:bg-slate-50 cursor-pointer border-t border-slate-200"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div>{{ reception.identificationNumber}}</div>
|
||||
<div>{{ reception.receptionDate}}</div>
|
||||
<div>{{ reception.supplier?.name }}</div>
|
||||
<div>{{ reception.address?.fullAddress }}</div>
|
||||
<div>{{ reception.receptionType?.label }}</div>
|
||||
<div>{{ formatWeighing(reception, 'gross') }} | {{ formatWeighing(reception, 'tare') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type {ReceptionData} from "~/services/dto/reception-data";
|
||||
import {getReceptionList} from "~/services/reception";
|
||||
|
||||
const receptionList = ref<ReceptionData[]>()
|
||||
const router = useRouter()
|
||||
|
||||
const formatWeighing = (reception: ReceptionData, type: 'gross' | 'tare') => {
|
||||
const entry = reception.weights?.find((weight) => weight.type === type)
|
||||
if (!entry || entry.weight == null || entry.dsd == null) {
|
||||
return '—'
|
||||
}
|
||||
return `${entry.weight} kg`
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
receptionList.value = await getReceptionList(true)
|
||||
})
|
||||
</script>
|
||||
@@ -1,51 +0,0 @@
|
||||
<template>
|
||||
<div class="flex items-center justify-between ">
|
||||
<div class="flex items-center gap-10">
|
||||
<Icon @click="router.push('/')" name="gg:arrow-left-o" style="color: black" size="44" />
|
||||
<h1 class="text-3xl font-bold uppercase">listes des réceptions en attente</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ps-20 " >
|
||||
<div class="mt-6 border border-slate-200 mb-16 ">
|
||||
<div class="grid grid-cols-5 gap-4 bg-slate-100 px-4 py-3 text-sm font-semibold uppercase tracking-wide">
|
||||
<div>Fournisseur</div>
|
||||
<div>Adresse</div>
|
||||
<div>Type réception</div>
|
||||
<div>Transporteur</div>
|
||||
<div>Immatriculation</div>
|
||||
</div>
|
||||
<div
|
||||
v-for="reception in receptionList"
|
||||
:key="reception.id"
|
||||
class="grid grid-cols-5 gap-4 px-4 py-3 text-sm hover:bg-slate-50 cursor-pointer border-t border-slate-200"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
@click="goToReception(reception.id)"
|
||||
@keydown.enter="goToReception(reception.id)"
|
||||
>
|
||||
<div>{{ reception.supplier?.name }}</div>
|
||||
<div>{{ reception.address?.fullAddress }}</div>
|
||||
<div>{{ reception.receptionType?.label }}</div>
|
||||
<div>{{ reception.carrier?.name }}</div>
|
||||
<div>{{ reception.licensePlate }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type {ReceptionData} from "~/services/dto/reception-data";
|
||||
import {getReceptionList} from "~/services/reception";
|
||||
|
||||
const receptionList = ref<ReceptionData[]>()
|
||||
const router = useRouter()
|
||||
|
||||
const goToReception = (id: number) => {
|
||||
router.push(`/reception/${id}`)
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
receptionList.value = await getReceptionList(false)
|
||||
})
|
||||
</script>
|
||||
@@ -2,15 +2,13 @@ import {useApi} from '~/composables/useApi'
|
||||
import type {ReceptionData, ReceptionPayload} from '~/services/dto/reception-data'
|
||||
import type {WeightData} from '~/services/dto/weight-data'
|
||||
|
||||
export async function getReceptionList(isValid: boolean|null = null) {
|
||||
export async function getReceptionList() {
|
||||
const api = useApi()
|
||||
const query = isValid !== null ? { isValid: isValid} : {}
|
||||
return api.get<ReceptionData[]>('receptions', query, {
|
||||
return api.get<ReceptionData>(`receptions`, {}, {
|
||||
toastErrorKey: 'errors.reception.list'
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
export async function getReception(id: number) {
|
||||
const api = useApi()
|
||||
return api.get<ReceptionData>(`receptions/${id}`, {}, {
|
||||
|
||||
@@ -4,8 +4,6 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use ApiPlatform\Doctrine\Orm\Filter\BooleanFilter;
|
||||
use ApiPlatform\Metadata\ApiFilter;
|
||||
use ApiPlatform\Metadata\ApiProperty;
|
||||
use ApiPlatform\Metadata\ApiResource;
|
||||
use ApiPlatform\Metadata\Get;
|
||||
@@ -28,7 +26,6 @@ use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
|
||||
#[ORM\Entity]
|
||||
#[ORM\HasLifecycleCallbacks]
|
||||
#[ORM\Table(name: 'reception')]
|
||||
#[ApiFilter(BooleanFilter::class, properties: ['isValid'])]
|
||||
#[ApiResource(
|
||||
operations: [
|
||||
new Get(
|
||||
|
||||
0
src/Repository/.gitignore
vendored
Normal file
0
src/Repository/.gitignore
vendored
Normal file
45
src/Repository/BovineTypeRepository.php
Normal file
45
src/Repository/BovineTypeRepository.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Repository;
|
||||
|
||||
use App\Entity\BovineType;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
/**
|
||||
* @extends ServiceEntityRepository<BovineType>
|
||||
*/
|
||||
class BovineTypeRepository extends ServiceEntityRepository
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
{
|
||||
parent::__construct($registry, BovineType::class);
|
||||
}
|
||||
|
||||
// /**
|
||||
// * @return BovineType[] Returns an array of BovineType objects
|
||||
// */
|
||||
// public function findByExampleField($value): array
|
||||
// {
|
||||
// return $this->createQueryBuilder('b')
|
||||
// ->andWhere('b.exampleField = :val')
|
||||
// ->setParameter('val', $value)
|
||||
// ->orderBy('b.id', 'ASC')
|
||||
// ->setMaxResults(10)
|
||||
// ->getQuery()
|
||||
// ->getResult()
|
||||
// ;
|
||||
// }
|
||||
|
||||
// public function findOneBySomeField($value): ?BovineType
|
||||
// {
|
||||
// return $this->createQueryBuilder('b')
|
||||
// ->andWhere('b.exampleField = :val')
|
||||
// ->setParameter('val', $value)
|
||||
// ->getQuery()
|
||||
// ->getOneOrNullResult()
|
||||
// ;
|
||||
// }
|
||||
}
|
||||
45
src/Repository/ReceptionBovineRepository.php
Normal file
45
src/Repository/ReceptionBovineRepository.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Repository;
|
||||
|
||||
use App\Entity\ReceptionBovine;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
/**
|
||||
* @extends ServiceEntityRepository<ReceptionBovine>
|
||||
*/
|
||||
class ReceptionBovineRepository extends ServiceEntityRepository
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
{
|
||||
parent::__construct($registry, ReceptionBovine::class);
|
||||
}
|
||||
|
||||
// /**
|
||||
// * @return ReceptionBovine[] Returns an array of ReceptionBovine objects
|
||||
// */
|
||||
// public function findByExampleField($value): array
|
||||
// {
|
||||
// return $this->createQueryBuilder('r')
|
||||
// ->andWhere('r.exampleField = :val')
|
||||
// ->setParameter('val', $value)
|
||||
// ->orderBy('r.id', 'ASC')
|
||||
// ->setMaxResults(10)
|
||||
// ->getQuery()
|
||||
// ->getResult()
|
||||
// ;
|
||||
// }
|
||||
|
||||
// public function findOneBySomeField($value): ?ReceptionBovine
|
||||
// {
|
||||
// return $this->createQueryBuilder('r')
|
||||
// ->andWhere('r.exampleField = :val')
|
||||
// ->setParameter('val', $value)
|
||||
// ->getQuery()
|
||||
// ->getOneOrNullResult()
|
||||
// ;
|
||||
// }
|
||||
}
|
||||
102
tests/BovinIdentificationProviderTest.php
Normal file
102
tests/BovinIdentificationProviderTest.php
Normal file
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Tests\State;
|
||||
|
||||
use ApiPlatform\Metadata\Operation;
|
||||
use App\State\BovinIdentificationProvider;
|
||||
use DateTimeImmutable;
|
||||
use Malio\EdnotifBundle\Bovin\Api\BovinApiInterface;
|
||||
use Malio\EdnotifBundle\Bovin\Dto\AnimalFileDto;
|
||||
use Malio\EdnotifBundle\Bovin\Dto\BovinIdentificationDto;
|
||||
use Malio\EdnotifBundle\Bovin\Dto\BovinRef;
|
||||
use Malio\EdnotifBundle\Bovin\Dto\DateValueDto;
|
||||
use Malio\EdnotifBundle\Bovin\Dto\ExploitationRef;
|
||||
use Malio\EdnotifBundle\Bovin\Dto\MovementDto;
|
||||
use Malio\EdnotifBundle\Bovin\Dto\ParentInfoDto;
|
||||
use Malio\EdnotifBundle\Bovin\Dto\PresencePeriodDto;
|
||||
use Malio\EdnotifBundle\Shared\Dto\StandardResponseDto;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class BovinIdentificationProviderTest extends TestCase
|
||||
{
|
||||
public function testReturnsNullWhenNumeroNationalMissing(): void
|
||||
{
|
||||
$api = $this->createMock(BovinApiInterface::class);
|
||||
$api->expects(self::never())->method('getAnimalFile');
|
||||
|
||||
$provider = new BovinIdentificationProvider($api);
|
||||
|
||||
$result = $provider->provide($this->createStub(Operation::class), []);
|
||||
|
||||
self::assertNull($result);
|
||||
}
|
||||
|
||||
public function testMapsIdentificationAndPresencePeriods(): void
|
||||
{
|
||||
$api = $this->createMock(BovinApiInterface::class);
|
||||
|
||||
$identification = new BovinIdentificationDto(
|
||||
bovin: new BovinRef('FR', 'IGNORED'),
|
||||
sex: 'F',
|
||||
breedType: 'LIM',
|
||||
birthDate: new DateValueDto(new DateTimeImmutable('2024-01-02'), 'Y'),
|
||||
workNumber: 'W123',
|
||||
isFilie: true,
|
||||
motherCarrier: new ParentInfoDto(new BovinRef('FR', 'MOM1'), 'CHA'),
|
||||
fatherIpg: new ParentInfoDto(new BovinRef('FR', 'DAD1'), 'BBB'),
|
||||
birthExploitation: new ExploitationRef('FR', 'EXP1'),
|
||||
);
|
||||
|
||||
$presencePeriods = [
|
||||
new PresencePeriodDto(
|
||||
new MovementDto(new DateTimeImmutable('2024-03-01'), 'ENTRY', null),
|
||||
new MovementDto(new DateTimeImmutable('2024-03-10'), 'EXIT', null),
|
||||
),
|
||||
];
|
||||
|
||||
$animalFile = new AnimalFileDto(
|
||||
new StandardResponseDto(true, null),
|
||||
$identification,
|
||||
$presencePeriods,
|
||||
null
|
||||
);
|
||||
|
||||
$api->expects(self::once())
|
||||
->method('getAnimalFile')
|
||||
->with('FR123', 'FR')
|
||||
->willReturn($animalFile)
|
||||
;
|
||||
|
||||
$provider = new BovinIdentificationProvider($api);
|
||||
|
||||
$result = $provider->provide(
|
||||
$this->createStub(Operation::class),
|
||||
['numeroNational' => 'FR123']
|
||||
);
|
||||
|
||||
self::assertNotNull($result);
|
||||
self::assertSame('FR123', $result->numeroNational);
|
||||
self::assertSame('F', $result->sex);
|
||||
self::assertSame('LIM', $result->breedType);
|
||||
self::assertSame('W123', $result->workNumber);
|
||||
self::assertSame('2024-01-02', $result->birthDate);
|
||||
self::assertSame('Y', $result->birthDateCompletenessFlag);
|
||||
self::assertTrue($result->isFilie);
|
||||
self::assertSame('MOM1', $result->motherNationalNumber);
|
||||
self::assertSame('CHA', $result->motherBreedType);
|
||||
self::assertSame('DAD1', $result->fatherNationalNumber);
|
||||
self::assertSame('BBB', $result->fatherBreedType);
|
||||
self::assertSame('EXP1', $result->birthExploitationNumber);
|
||||
|
||||
self::assertCount(1, $result->presencePeriods);
|
||||
self::assertSame('2024-03-01', $result->presencePeriods[0]->entryDate);
|
||||
self::assertSame('ENTRY', $result->presencePeriods[0]->entryCause);
|
||||
self::assertSame('2024-03-10', $result->presencePeriods[0]->exitDate);
|
||||
self::assertSame('EXIT', $result->presencePeriods[0]->exitCause);
|
||||
}
|
||||
}
|
||||
48
tests/State/MeProviderTest.php
Normal file
48
tests/State/MeProviderTest.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Tests\State;
|
||||
|
||||
use ApiPlatform\Metadata\Operation;
|
||||
use App\Entity\User;
|
||||
use App\State\MeProvider;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class MeProviderTest extends TestCase
|
||||
{
|
||||
public function testProvideReturnUser(): void
|
||||
{
|
||||
$user = new User();
|
||||
|
||||
$security = $this->createStub(Security::class);
|
||||
$security->method('getUser')->willReturn($user);
|
||||
|
||||
$provider = new MeProvider($security);
|
||||
|
||||
$result = $provider->provide($this->createStub(Operation::class));
|
||||
|
||||
self::assertSame($user, $result);
|
||||
self::assertInstanceOf(User::class, $result);
|
||||
}
|
||||
|
||||
public function testProvideThrowAccessDeniedException(): void
|
||||
{
|
||||
$user = null;
|
||||
|
||||
$security = $this->createStub(Security::class);
|
||||
$security->method('getUser')->willReturn($user);
|
||||
|
||||
$provider = new MeProvider($security);
|
||||
|
||||
$this->expectException(AccessDeniedException::class);
|
||||
$this->expectExceptionMessage('User not authenticated.');
|
||||
|
||||
$provider->provide($this->createStub(Operation::class));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user