Compare commits
2 Commits
486247bf86
...
v0.0.94
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
961fa63f3d | ||
| bebfabcacc |
@@ -1,2 +1,2 @@
|
|||||||
parameters:
|
parameters:
|
||||||
app.version: '0.0.93'
|
app.version: '0.0.94'
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,199 +0,0 @@
|
|||||||
# Entrée / Sortie des bovins — Design
|
|
||||||
|
|
||||||
## Contexte
|
|
||||||
|
|
||||||
Aujourd'hui, l'application gère les **réceptions** (arrivée d'un camion) qui déclarent un nombre de bovins par race (ex : 5 charolais + 3 limousine + 2 autres). Une fois la réception terminée, ces déclarations sont des indicateurs imprécis et il manque l'étape de saisie individuelle des bovins (numéro national, poids, prix…).
|
|
||||||
|
|
||||||
L'objectif est d'introduire un **workflow d'entrée** qui transforme une réception bovins finie en saisies individuelles enrichies via EDNOTIF, et de poser les fondations pour un futur workflow de sortie symétrique.
|
|
||||||
|
|
||||||
Pour ce lot, **les sorties sont hors scope** mais l'écran liste prévoit déjà leur emplacement.
|
|
||||||
|
|
||||||
## Décisions structurantes
|
|
||||||
|
|
||||||
| Décision | Choix |
|
|
||||||
| --- | --- |
|
|
||||||
| Distinction "en attente" vs "terminée" | Flag explicite `entryCompleted: bool` sur `Reception` |
|
|
||||||
| Lien Bovine → Reception | FK 1-N, `Bovine.reception` ManyToOne **nullable** |
|
|
||||||
| Rendu de l'écran de saisie | UN formulaire (2 lignes) + tableau récap dessous |
|
|
||||||
| Bâtiment + Case | Choisis **par bovin** dans le formulaire |
|
|
||||||
| Persistance | Save individuel à chaque "Ajouter" (POST /bovines) |
|
|
||||||
| Enrichissement EDNOTIF | Au backend via le `BovineProcessor` existant (pas de lookup live) |
|
|
||||||
|
|
||||||
## Modèle de données
|
|
||||||
|
|
||||||
### `Reception` — modification
|
|
||||||
|
|
||||||
Nouveau champ :
|
|
||||||
- `entryCompleted: bool`, default `false`, non nullable.
|
|
||||||
- Pertinent uniquement quand `receptionType.code === 'BOVINS'`. Pour les autres types, reste `false` et ignoré côté UI.
|
|
||||||
- Inclus dans les groupes `reception:read` et `reception:write`.
|
|
||||||
|
|
||||||
Migration : `ALTER TABLE reception ADD COLUMN entry_completed BOOLEAN NOT NULL DEFAULT false`.
|
|
||||||
|
|
||||||
Ajout d'un `BooleanFilter` sur `entryCompleted` dans `#[ApiFilter]`.
|
|
||||||
|
|
||||||
### `Bovine` — modification
|
|
||||||
|
|
||||||
Nouveau champ :
|
|
||||||
- `reception: Reception` (ManyToOne, **nullable**).
|
|
||||||
- Inclus dans `bovine:read` et `bovine:write`.
|
|
||||||
|
|
||||||
Migration : `ALTER TABLE bovine ADD COLUMN reception_id INTEGER NULL` + index + FK contrainte. Bovins existants restent à `NULL` — aucune migration de données.
|
|
||||||
|
|
||||||
Ajout d'un `SearchFilter` exact sur `reception` dans `#[ApiFilter]` pour permettre `GET /bovines?reception={id}`.
|
|
||||||
|
|
||||||
### `Reception` — relation inverse pour le compteur
|
|
||||||
|
|
||||||
Pour permettre l'affichage du compteur "bovins saisis" dans la liste sans N+1 :
|
|
||||||
|
|
||||||
- Ajouter `bovines: Collection<Bovine>` côté `Reception` (OneToMany inverse, `mappedBy: 'reception'`, fetch lazy).
|
|
||||||
- Exposer un getter calculé `getRegisteredBovineCount(): int` dans le groupe `reception:read`.
|
|
||||||
- L'implémentation côté provider/list peut utiliser un `addSelect('COUNT(b.id) AS bovineCount')` via un `QueryExtension` API Platform si le N+1 devient un problème (à mesurer).
|
|
||||||
|
|
||||||
### Aucune autre entité
|
|
||||||
|
|
||||||
Pas de table de jointure (un bovin entre une seule fois via une réception unique). Pas de nouvelle entité `Entry` (la `Reception` joue ce rôle). Pas d'entité `Exit` pour ce lot — la symétrie sera traitée plus tard.
|
|
||||||
|
|
||||||
## Endpoints API
|
|
||||||
|
|
||||||
Tous les endpoints réutilisent les ressources existantes ; **aucun endpoint custom n'est créé**.
|
|
||||||
|
|
||||||
### Liste des entrées en attente
|
|
||||||
|
|
||||||
`GET /api/receptions?receptionType.code=BOVINS&isValid=true&entryCompleted=false`
|
|
||||||
|
|
||||||
### Validation finale d'une entrée
|
|
||||||
|
|
||||||
`PATCH /api/receptions/{id}` avec `{ entryCompleted: true }`.
|
|
||||||
|
|
||||||
### Création d'un bovin lié
|
|
||||||
|
|
||||||
`POST /api/bovines` (Content-Type `application/ld+json`) avec :
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"nationalNumber": "FR1234567890",
|
|
||||||
"receivedWeight": 368,
|
|
||||||
"pricePerKg": 5.7,
|
|
||||||
"arrivalDate": "2026-04-29",
|
|
||||||
"supplier": "/api/suppliers/12",
|
|
||||||
"reception": "/api/receptions/45",
|
|
||||||
"buildingCase": "/api/building_cases/8"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Le `BovineProcessor` enrichit automatiquement (workNumber, birthDate, race auto-créée via `BovineType`).
|
|
||||||
|
|
||||||
**Nettoyage en passant** : le `BovineProcessor` actuel appelle `setBreedCode()` qui n'existe plus (héritage avant la migration vers `BovineType` FK). À corriger pour qu'il fasse `setBovineType()` avec auto-create d'un `BovineType` si la race retournée par EDNOTIF n'existe pas en base.
|
|
||||||
|
|
||||||
### Suppression d'un bovin
|
|
||||||
|
|
||||||
`DELETE /api/bovines/{id}` — sécurité actuelle `ROLE_ADMIN` à abaisser à `ROLE_USER` pour permettre la correction immédiate depuis le tableau.
|
|
||||||
|
|
||||||
## Front-end
|
|
||||||
|
|
||||||
### Home (`pages/index.vue`)
|
|
||||||
|
|
||||||
- Card "CASES" → renommée "ENTRÉE / SORTIE" (multi-ligne `Entrée<br>Sortie`).
|
|
||||||
- Lien : `/entry-exit`.
|
|
||||||
- Icône : `mdi:swap-horizontal-bold` (à finaliser à l'implémentation).
|
|
||||||
|
|
||||||
### Page liste — `pages/entry-exit/index.vue`
|
|
||||||
|
|
||||||
Deux sections empilées :
|
|
||||||
|
|
||||||
**Entrées en attente**
|
|
||||||
- Composant : `UiDataTable`.
|
|
||||||
- Filtres serveur : `receptionType.code=BOVINS`, `isValid=true`, `entryCompleted=false`.
|
|
||||||
- Colonnes :
|
|
||||||
- Date réception
|
|
||||||
- Fournisseur (`supplier.name`)
|
|
||||||
- Total déclaré (calculé côté front : `sum(bovines_types.quantity) + parseInt(bovineDetail ?? '0')`)
|
|
||||||
- Bovins saisis (depuis `getRegisteredBovineCount` exposé sur Reception)
|
|
||||||
- Action (rangée cliquable)
|
|
||||||
- Click row → `/entry-exit/entry/{receptionId}`.
|
|
||||||
|
|
||||||
**Sorties en attente**
|
|
||||||
- Tableau placeholder vide avec message "À venir".
|
|
||||||
|
|
||||||
### Écran de saisie — `pages/entry-exit/entry/[id].vue`
|
|
||||||
|
|
||||||
**Header**
|
|
||||||
- Titre : "Entrée bovins #N-BR-XXXX — Fournisseur YYY"
|
|
||||||
- Sous-titre : "Bovins déclarés : 8 · Bovins saisis : 3"
|
|
||||||
- Icône retour à gauche.
|
|
||||||
|
|
||||||
**Formulaire (2 lignes)**
|
|
||||||
|
|
||||||
Ligne 1 : Numéro national · Poids à l'arrivée · Date d'arrivée · Vendeur (Supplier select)
|
|
||||||
Ligne 2 : Prix au kilo · Bâtiment (Building select) · Case (BuildingCase select dépendant du bâtiment) · Bouton **Ajouter**
|
|
||||||
|
|
||||||
**Pré-remplissage** (au chargement et après chaque add) :
|
|
||||||
- Date d'arrivée = `reception.receptionDate` (date seule, modifiable)
|
|
||||||
- Vendeur = `reception.supplier` (modifiable)
|
|
||||||
- Bâtiment = premier de `reception.buildings` si dispo, sinon vide
|
|
||||||
- Case = vide (à choisir explicitement)
|
|
||||||
- Numéro national, poids, prix : vides
|
|
||||||
|
|
||||||
**Comportement bouton "Ajouter"**
|
|
||||||
- Disabled si form invalide (n° national vide, poids ≤ 0, prix ≤ 0, building/case manquants).
|
|
||||||
- Click → `POST /api/bovines` avec `application/ld+json`.
|
|
||||||
- Succès → reload du tableau, reset form (en gardant les pré-remplissages), focus sur Numéro national.
|
|
||||||
- Erreur 409 (doublon n° national) → toast "Ce bovin existe déjà".
|
|
||||||
- Erreur EDNOTIF → bovin créé sans enrichissement (race/naissance vides), toast warning.
|
|
||||||
|
|
||||||
**Tableau récap (dessous)**
|
|
||||||
|
|
||||||
Colonnes : N° national · N° travail · Race · Sexe · Date naissance · Poids arrivée · Date arrivée · Prix/kg · Prix total · Bâtiment · Case · Action (icône poubelle).
|
|
||||||
|
|
||||||
Source : `GET /api/bovines?reception={id}` au mount + après chaque add/delete.
|
|
||||||
|
|
||||||
Suppression : `DELETE /api/bovines/{id}` avec `window.confirm`.
|
|
||||||
|
|
||||||
**Footer**
|
|
||||||
- Bouton **Valider l'entrée** (à droite).
|
|
||||||
- Si `bovins saisis < bovins déclarés` → `window.confirm("Vous n'avez saisi que X/Y bovins. Confirmer la fermeture ?")`.
|
|
||||||
- Disabled si 0 bovin saisi.
|
|
||||||
- Click → `PATCH /api/receptions/{id}` avec `{ entryCompleted: true }` → toast succès → redirection `/entry-exit`.
|
|
||||||
|
|
||||||
## Sécurité (rôles)
|
|
||||||
|
|
||||||
| Action | Rôle requis |
|
|
||||||
| --- | --- |
|
|
||||||
| Voir la page entrée/sortie | `ROLE_USER` |
|
|
||||||
| Ajouter un bovin (POST /bovines) | `ROLE_USER` (actuellement `ROLE_ADMIN` — à abaisser, ce flux est métier opérationnel) |
|
|
||||||
| Supprimer un bovin (DELETE /bovines) | `ROLE_USER` (idem, à abaisser) |
|
|
||||||
| Valider l'entrée (PATCH receptions) | `ROLE_USER` |
|
|
||||||
|
|
||||||
L'abaissement à `ROLE_USER` sur `Bovine::Post`, `Bovine::Patch` et `Bovine::Delete` est **délibéré** : ce flux fait partie des opérations métier quotidiennes, pas de l'administration. À confirmer pendant l'implémentation.
|
|
||||||
|
|
||||||
## Cas limites
|
|
||||||
|
|
||||||
- **Total saisi > déclaré** : autorisé (les déclarations en réception sont des indicateurs imprécis).
|
|
||||||
- **Doublon n° national** : la `UniqueConstraint` BDD le rejette → toast.
|
|
||||||
- **EDNOTIF indisponible** : bovin créé sans enrich, comportement actuel du processor.
|
|
||||||
- **Réception supprimée pendant la saisie** : impossible côté UI tant qu'on est dans l'écran. Si ça arrive (autre user), les `POST /bovines` suivants échoueront en 404 sur l'IRI reception → toast.
|
|
||||||
- **Sortie d'un bovin** : non géré dans ce lot. Le futur workflow de sortie viendra basculer `Bovine.exitedAt`.
|
|
||||||
|
|
||||||
## Critères d'acceptation
|
|
||||||
|
|
||||||
- [ ] Migration `entry_completed` sur Reception passe sans erreur.
|
|
||||||
- [ ] Migration `reception_id` sur Bovine passe sans erreur, bovins existants intacts.
|
|
||||||
- [ ] Card "CASES" sur home remplacée par "ENTRÉE / SORTIE".
|
|
||||||
- [ ] `/entry-exit` affiche les entrées en attente et un placeholder sorties.
|
|
||||||
- [ ] Click sur une entrée → écran saisie avec form pré-rempli.
|
|
||||||
- [ ] "Ajouter" → bovin créé, ligne au tableau, form reset (pré-remplissages restaurés).
|
|
||||||
- [ ] Suppression d'une ligne fonctionne avec confirmation.
|
|
||||||
- [ ] "Valider l'entrée" bascule `entryCompleted` et redirige.
|
|
||||||
- [ ] Une réception fermée disparaît de la liste.
|
|
||||||
- [ ] `BovineProcessor` corrigé pour utiliser `setBovineType()` avec auto-create.
|
|
||||||
- [ ] `make test` passe sans régression.
|
|
||||||
|
|
||||||
## Mode d'implémentation
|
|
||||||
|
|
||||||
Sur ce projet, l'utilisateur souhaite **valider chaque étape du plan** avant exécution. À chaque étape du plan d'implémentation, l'agent doit :
|
|
||||||
|
|
||||||
1. Présenter ce qu'il s'apprête à faire (fichiers, changements).
|
|
||||||
2. Attendre la validation explicite de l'utilisateur.
|
|
||||||
3. Exécuter, puis présenter l'étape suivante.
|
|
||||||
|
|
||||||
Cette discipline permet des retours en direct et des ajustements fins en cours de route.
|
|
||||||
@@ -32,6 +32,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
useHead({ title: 'Type de bovin' })
|
||||||
|
|
||||||
import {createBovin, getBovin, updateBovin} from "~/services/bovine-type";
|
import {createBovin, getBovin, updateBovin} from "~/services/bovine-type";
|
||||||
import type {BovineTypeData, BovinFormData} from "~/services/dto/bovine-type-data";
|
import type {BovineTypeData, BovinFormData} from "~/services/dto/bovine-type-data";
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|||||||
@@ -38,6 +38,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
useHead({ title: 'Types de bovins' })
|
||||||
|
|
||||||
import type { BovineTypeData } from '~/services/dto/bovine-type-data'
|
import type { BovineTypeData } from '~/services/dto/bovine-type-data'
|
||||||
import { useAuthStore } from '~/stores/auth'
|
import { useAuthStore } from '~/stores/auth'
|
||||||
import { useDataTableServerState } from '~/composables/useDataTableServerState'
|
import { useDataTableServerState } from '~/composables/useDataTableServerState'
|
||||||
|
|||||||
@@ -44,6 +44,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
useHead({ title: 'Transporteur' })
|
||||||
|
|
||||||
import {createCarrier, getCarrier, updateCarrier} from "~/services/carrier";
|
import {createCarrier, getCarrier, updateCarrier} from "~/services/carrier";
|
||||||
import type {CarrierData, CarrierFormData} from "~/services/dto/carrier-data";
|
import type {CarrierData, CarrierFormData} from "~/services/dto/carrier-data";
|
||||||
import {computed} from "vue";
|
import {computed} from "vue";
|
||||||
|
|||||||
@@ -34,6 +34,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
useHead({ title: 'Transporteurs' })
|
||||||
|
|
||||||
import type { CarrierData } from '~/services/dto/carrier-data'
|
import type { CarrierData } from '~/services/dto/carrier-data'
|
||||||
import { useDataTableServerState } from '~/composables/useDataTableServerState'
|
import { useDataTableServerState } from '~/composables/useDataTableServerState'
|
||||||
|
|
||||||
|
|||||||
@@ -96,6 +96,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
useHead({ title: 'Client' })
|
||||||
|
|
||||||
import {computed, reactive, ref, watch} from "vue"
|
import {computed, reactive, ref, watch} from "vue"
|
||||||
import {createCustomer, getCustomer, updateCustomer} from "~/services/customer"
|
import {createCustomer, getCustomer, updateCustomer} from "~/services/customer"
|
||||||
import type {CustomerData, CustomerFormData, CustomerPayload} from "~/services/dto/customer-data"
|
import type {CustomerData, CustomerFormData, CustomerPayload} from "~/services/dto/customer-data"
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
useHead({ title: 'Adresse client' })
|
||||||
|
|
||||||
import type { AddressData, AddressPayload } from "~/services/address"
|
import type { AddressData, AddressPayload } from "~/services/address"
|
||||||
import { createAddress, getAddress, updateAddress } from "~/services/address"
|
import { createAddress, getAddress, updateAddress } from "~/services/address"
|
||||||
import { getCustomer, updateCustomer } from "~/services/customer"
|
import { getCustomer, updateCustomer } from "~/services/customer"
|
||||||
|
|||||||
@@ -44,6 +44,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
useHead({ title: 'Clients' })
|
||||||
|
|
||||||
import type { CustomerData } from '~/services/dto/customer-data'
|
import type { CustomerData } from '~/services/dto/customer-data'
|
||||||
import { useAuthStore } from '~/stores/auth'
|
import { useAuthStore } from '~/stores/auth'
|
||||||
import { useDataTableServerState } from '~/composables/useDataTableServerState'
|
import { useDataTableServerState } from '~/composables/useDataTableServerState'
|
||||||
|
|||||||
@@ -97,6 +97,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
useHead({ title: 'Fournisseur' })
|
||||||
|
|
||||||
import {computed, reactive, ref, watch} from "vue"
|
import {computed, reactive, ref, watch} from "vue"
|
||||||
import {createSupplier, getSupplier, updateSupplier} from "~/services/supplier"
|
import {createSupplier, getSupplier, updateSupplier} from "~/services/supplier"
|
||||||
import type {SupplierData, SupplierFormData, SupplierPayload} from "~/services/dto/supplier-data"
|
import type {SupplierData, SupplierFormData, SupplierPayload} from "~/services/dto/supplier-data"
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
useHead({ title: 'Adresse fournisseur' })
|
||||||
|
|
||||||
import type {AddressData, AddressPayload} from "~/services/address";
|
import type {AddressData, AddressPayload} from "~/services/address";
|
||||||
import {createAddress, getAddress, updateAddress} from "~/services/address";
|
import {createAddress, getAddress, updateAddress} from "~/services/address";
|
||||||
import {getSupplier, updateSupplier} from "~/services/supplier";
|
import {getSupplier, updateSupplier} from "~/services/supplier";
|
||||||
|
|||||||
@@ -44,6 +44,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
useHead({ title: 'Fournisseurs' })
|
||||||
|
|
||||||
import type { SupplierData } from '~/services/dto/supplier-data'
|
import type { SupplierData } from '~/services/dto/supplier-data'
|
||||||
import { useAuthStore } from '~/stores/auth'
|
import { useAuthStore } from '~/stores/auth'
|
||||||
import { useDataTableServerState } from '~/composables/useDataTableServerState'
|
import { useDataTableServerState } from '~/composables/useDataTableServerState'
|
||||||
|
|||||||
@@ -74,6 +74,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
useHead({ title: 'Utilisateur' })
|
||||||
|
|
||||||
import { computed, reactive, ref, watch } from 'vue'
|
import { computed, reactive, ref, watch } from 'vue'
|
||||||
import { ROLE } from '~/utils/constants'
|
import { ROLE } from '~/utils/constants'
|
||||||
import { createUser, updateUser, getUser } from '~/services/auth'
|
import { createUser, updateUser, getUser } from '~/services/auth'
|
||||||
|
|||||||
@@ -63,6 +63,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
useHead({ title: 'Utilisateurs' })
|
||||||
|
|
||||||
import type { UserData } from '~/services/dto/user-data'
|
import type { UserData } from '~/services/dto/user-data'
|
||||||
import { ROLE } from '~/utils/constants'
|
import { ROLE } from '~/utils/constants'
|
||||||
import { useAuthStore } from '~/stores/auth'
|
import { useAuthStore } from '~/stores/auth'
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
useHead({ title: 'Accueil' })
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div class="flex flex-wrap justify-center pb-16 gap-12">
|
<div class="flex flex-wrap justify-center pb-16 gap-12">
|
||||||
|
|||||||
@@ -69,6 +69,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
useHead({ title: 'Bovins' })
|
||||||
|
|
||||||
import { createBovine, getBovine, updateBovine } from '~/services/bovine'
|
import { createBovine, getBovine, updateBovine } from '~/services/bovine'
|
||||||
import type { BovinePayload } from '~/services/dto/bovine-data'
|
import type { BovinePayload } from '~/services/dto/bovine-data'
|
||||||
import type { SupplierData } from '~/services/dto/supplier-data'
|
import type { SupplierData } from '~/services/dto/supplier-data'
|
||||||
|
|||||||
@@ -80,6 +80,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
useHead({ title: 'Bâtiments' })
|
||||||
|
|
||||||
import type {BuildingData} from "~/services/dto/building-data"
|
import type {BuildingData} from "~/services/dto/building-data"
|
||||||
import type {BuildingLayoutData} from "~/services/dto/building-layout-data"
|
import type {BuildingLayoutData} from "~/services/dto/building-layout-data"
|
||||||
import type {BuildingCasePositionData} from "~/services/dto/building-case-position-data"
|
import type {BuildingCasePositionData} from "~/services/dto/building-case-position-data"
|
||||||
|
|||||||
@@ -130,6 +130,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
useHead({ title: 'Cases' })
|
||||||
|
|
||||||
import type { BuildingCaseData } from '~/services/dto/building-case-data'
|
import type { BuildingCaseData } from '~/services/dto/building-case-data'
|
||||||
import type { BovineData } from '~/services/dto/bovine-data'
|
import type { BovineData } from '~/services/dto/bovine-data'
|
||||||
import { useAuthStore } from '~/stores/auth'
|
import { useAuthStore } from '~/stores/auth'
|
||||||
|
|||||||
@@ -147,6 +147,8 @@
|
|||||||
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
useHead({ title: 'Inventaire' })
|
||||||
|
|
||||||
import type { BovineData } from '~/services/dto/bovine-data'
|
import type { BovineData } from '~/services/dto/bovine-data'
|
||||||
import type { InventoryExportFilters } from '~/components/inventory/inventory-export-modal.vue'
|
import type { InventoryExportFilters } from '~/components/inventory/inventory-export-modal.vue'
|
||||||
import { useAuthStore } from '~/stores/auth'
|
import { useAuthStore } from '~/stores/auth'
|
||||||
|
|||||||
@@ -53,6 +53,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
useHead({ title: 'Connexion' })
|
||||||
|
|
||||||
import type { UserData } from '~/services/dto/user-data'
|
import type { UserData } from '~/services/dto/user-data'
|
||||||
import { getUsers } from '~/services/auth'
|
import { getUsers } from '~/services/auth'
|
||||||
import { useAuthStore } from '~/stores/auth'
|
import { useAuthStore } from '~/stores/auth'
|
||||||
|
|||||||
@@ -54,6 +54,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
useHead({ title: 'Réception' })
|
||||||
|
|
||||||
import { useReceptionStore } from '~/stores/reception'
|
import { useReceptionStore } from '~/stores/reception'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useWorkflowSteps } from '~/composables/useWorkflowSteps'
|
import { useWorkflowSteps } from '~/composables/useWorkflowSteps'
|
||||||
|
|||||||
@@ -73,6 +73,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
useHead({ title: 'Validation réception' })
|
||||||
|
|
||||||
import type { ReceptionData } from '~/services/dto/reception-data'
|
import type { ReceptionData } from '~/services/dto/reception-data'
|
||||||
import type { ReceptionTypeData } from '~/services/dto/reception-type-data'
|
import type { ReceptionTypeData } from '~/services/dto/reception-type-data'
|
||||||
import { getReceptionTypeList } from '~/services/reception-type'
|
import { getReceptionTypeList } from '~/services/reception-type'
|
||||||
|
|||||||
@@ -226,6 +226,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
useHead({ title: 'Modifier réception' })
|
||||||
|
|
||||||
import { usePdfPrinter } from '#imports'
|
import { usePdfPrinter } from '#imports'
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import UpdateBovin from '~/components/reception/update-bovin.vue'
|
import UpdateBovin from '~/components/reception/update-bovin.vue'
|
||||||
|
|||||||
@@ -72,6 +72,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
useHead({ title: 'Réceptions en attente' })
|
||||||
|
|
||||||
import type { ReceptionData } from '~/services/dto/reception-data'
|
import type { ReceptionData } from '~/services/dto/reception-data'
|
||||||
import type { ReceptionTypeData } from '~/services/dto/reception-type-data'
|
import type { ReceptionTypeData } from '~/services/dto/reception-type-data'
|
||||||
import { deleteReception } from '~/services/reception'
|
import { deleteReception } from '~/services/reception'
|
||||||
|
|||||||
@@ -125,6 +125,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
useHead({ title: 'Scanner' })
|
||||||
|
|
||||||
import { ref, computed, nextTick, onMounted, watch } from 'vue'
|
import { ref, computed, nextTick, onMounted, watch } from 'vue'
|
||||||
import { useBarcodeScanner } from '~/composables/useBarcodeScanner'
|
import { useBarcodeScanner } from '~/composables/useBarcodeScanner'
|
||||||
import { createBovine } from '~/services/bovine'
|
import { createBovine } from '~/services/bovine'
|
||||||
|
|||||||
@@ -51,6 +51,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
useHead({ title: 'Expédition' })
|
||||||
|
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useShipmentStore } from '~/stores/shipment'
|
import { useShipmentStore } from '~/stores/shipment'
|
||||||
import { useWorkflowSteps } from '~/composables/useWorkflowSteps'
|
import { useWorkflowSteps } from '~/composables/useWorkflowSteps'
|
||||||
|
|||||||
@@ -71,6 +71,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
useHead({ title: 'Validation expédition' })
|
||||||
|
|
||||||
import type { ShipmentData } from '~/services/dto/shipment-data'
|
import type { ShipmentData } from '~/services/dto/shipment-data'
|
||||||
import type { ShipmentTypeData } from '~/services/dto/shipment-type-data'
|
import type { ShipmentTypeData } from '~/services/dto/shipment-type-data'
|
||||||
import { getShipmentTypeList } from '~/services/shipment-type'
|
import { getShipmentTypeList } from '~/services/shipment-type'
|
||||||
|
|||||||
@@ -197,6 +197,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
useHead({ title: 'Modifier expédition' })
|
||||||
|
|
||||||
import { usePdfPrinter } from '#imports'
|
import { usePdfPrinter } from '#imports'
|
||||||
import { computed, onMounted, reactive, ref, watch } from 'vue'
|
import { computed, onMounted, reactive, ref, watch } from 'vue'
|
||||||
import UpdateWeight from '~/components/commun/update-weight.vue'
|
import UpdateWeight from '~/components/commun/update-weight.vue'
|
||||||
|
|||||||
@@ -84,6 +84,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
useHead({ title: 'Expéditions en attente' })
|
||||||
|
|
||||||
import type { ShipmentData } from '~/services/dto/shipment-data'
|
import type { ShipmentData } from '~/services/dto/shipment-data'
|
||||||
import type { ShipmentTypeData } from '~/services/dto/shipment-type-data'
|
import type { ShipmentTypeData } from '~/services/dto/shipment-type-data'
|
||||||
import { deleteShipment } from '~/services/shipment'
|
import { deleteShipment } from '~/services/shipment'
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace DoctrineMigrations;
|
|
||||||
|
|
||||||
use Doctrine\DBAL\Schema\Schema;
|
|
||||||
use Doctrine\Migrations\AbstractMigration;
|
|
||||||
|
|
||||||
final class Version20260429073108 extends AbstractMigration
|
|
||||||
{
|
|
||||||
public function getDescription(): string
|
|
||||||
{
|
|
||||||
return 'Workflow entrée/sortie : ajout entry_completed sur reception et reception_id sur bovine.';
|
|
||||||
}
|
|
||||||
|
|
||||||
public function up(Schema $schema): void
|
|
||||||
{
|
|
||||||
// Reception : flag de fermeture d'une entrée bovins.
|
|
||||||
$this->addSql('ALTER TABLE reception ADD entry_completed BOOLEAN NOT NULL DEFAULT FALSE');
|
|
||||||
|
|
||||||
// Bovine : FK nullable vers la réception qui a fait entrer le bovin.
|
|
||||||
$this->addSql('ALTER TABLE bovine ADD reception_id INT DEFAULT NULL');
|
|
||||||
$this->addSql('CREATE INDEX IDX_BOVINE_RECEPTION ON bovine (reception_id)');
|
|
||||||
$this->addSql('ALTER TABLE bovine ADD CONSTRAINT FK_BOVINE_RECEPTION FOREIGN KEY (reception_id) REFERENCES reception (id) ON DELETE SET NULL');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function down(Schema $schema): void
|
|
||||||
{
|
|
||||||
$this->addSql('ALTER TABLE bovine DROP CONSTRAINT FK_BOVINE_RECEPTION');
|
|
||||||
$this->addSql('DROP INDEX IDX_BOVINE_RECEPTION');
|
|
||||||
$this->addSql('ALTER TABLE bovine DROP reception_id');
|
|
||||||
$this->addSql('ALTER TABLE reception DROP entry_completed');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -10,7 +10,6 @@ use ApiPlatform\Doctrine\Orm\Filter\SearchFilter;
|
|||||||
use ApiPlatform\Metadata\ApiFilter;
|
use ApiPlatform\Metadata\ApiFilter;
|
||||||
use ApiPlatform\Metadata\ApiProperty;
|
use ApiPlatform\Metadata\ApiProperty;
|
||||||
use ApiPlatform\Metadata\ApiResource;
|
use ApiPlatform\Metadata\ApiResource;
|
||||||
use ApiPlatform\Metadata\Delete;
|
|
||||||
use ApiPlatform\Metadata\Get;
|
use ApiPlatform\Metadata\Get;
|
||||||
use ApiPlatform\Metadata\GetCollection;
|
use ApiPlatform\Metadata\GetCollection;
|
||||||
use ApiPlatform\Metadata\Patch;
|
use ApiPlatform\Metadata\Patch;
|
||||||
@@ -35,7 +34,6 @@ use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
|
|||||||
'sex' => 'exact',
|
'sex' => 'exact',
|
||||||
'buildingCase' => 'exact',
|
'buildingCase' => 'exact',
|
||||||
'receivedWeight' => 'exact',
|
'receivedWeight' => 'exact',
|
||||||
'reception' => 'exact',
|
|
||||||
])]
|
])]
|
||||||
#[ApiFilter(DateFilter::class, properties: ['arrivalDate', 'birthDate', 'exitDate'])]
|
#[ApiFilter(DateFilter::class, properties: ['arrivalDate', 'birthDate', 'exitDate'])]
|
||||||
#[ApiFilter(ExistsFilter::class, properties: ['exitedAt'])]
|
#[ApiFilter(ExistsFilter::class, properties: ['exitedAt'])]
|
||||||
@@ -52,20 +50,16 @@ use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
|
|||||||
new Post(
|
new Post(
|
||||||
normalizationContext: ['groups' => ['bovine:read']],
|
normalizationContext: ['groups' => ['bovine:read']],
|
||||||
denormalizationContext: ['groups' => ['bovine:write']],
|
denormalizationContext: ['groups' => ['bovine:write']],
|
||||||
security: "is_granted('ROLE_USER')",
|
security: "is_granted('ROLE_ADMIN')",
|
||||||
processor: BovineProcessor::class,
|
processor: BovineProcessor::class,
|
||||||
),
|
),
|
||||||
new Patch(
|
new Patch(
|
||||||
requirements: ['id' => '\d+'],
|
requirements: ['id' => '\d+'],
|
||||||
normalizationContext: ['groups' => ['bovine:read']],
|
normalizationContext: ['groups' => ['bovine:read']],
|
||||||
denormalizationContext: ['groups' => ['bovine:write']],
|
denormalizationContext: ['groups' => ['bovine:write']],
|
||||||
security: "is_granted('ROLE_USER')",
|
security: "is_granted('ROLE_ADMIN')",
|
||||||
processor: BovineProcessor::class,
|
processor: BovineProcessor::class,
|
||||||
),
|
),
|
||||||
new Delete(
|
|
||||||
requirements: ['id' => '\d+'],
|
|
||||||
security: "is_granted('ROLE_USER')",
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
security: "is_granted('ROLE_USER')",
|
security: "is_granted('ROLE_USER')",
|
||||||
)]
|
)]
|
||||||
@@ -100,12 +94,6 @@ class Bovine
|
|||||||
#[ApiProperty(readableLink: true)]
|
#[ApiProperty(readableLink: true)]
|
||||||
private ?BuildingCase $buildingCase = null;
|
private ?BuildingCase $buildingCase = null;
|
||||||
|
|
||||||
#[ORM\ManyToOne(inversedBy: 'bovines')]
|
|
||||||
#[ORM\JoinColumn(nullable: true, onDelete: 'SET NULL')]
|
|
||||||
#[Groups(['bovine:read', 'bovine:write'])]
|
|
||||||
#[ApiProperty(readableLink: false)]
|
|
||||||
private ?Reception $reception = null;
|
|
||||||
|
|
||||||
#[ORM\ManyToOne]
|
#[ORM\ManyToOne]
|
||||||
#[Groups(['bovine:read'])]
|
#[Groups(['bovine:read'])]
|
||||||
#[ApiProperty(readableLink: true)]
|
#[ApiProperty(readableLink: true)]
|
||||||
@@ -223,18 +211,6 @@ class Bovine
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getReception(): ?Reception
|
|
||||||
{
|
|
||||||
return $this->reception;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setReception(?Reception $reception): static
|
|
||||||
{
|
|
||||||
$this->reception = $reception;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getBuilding(): ?Building
|
public function getBuilding(): ?Building
|
||||||
{
|
{
|
||||||
return $this->building;
|
return $this->building;
|
||||||
|
|||||||
@@ -31,14 +31,13 @@ use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
|
|||||||
#[ORM\Entity]
|
#[ORM\Entity]
|
||||||
#[ORM\HasLifecycleCallbacks]
|
#[ORM\HasLifecycleCallbacks]
|
||||||
#[ORM\Table(name: 'reception')]
|
#[ORM\Table(name: 'reception')]
|
||||||
#[ApiFilter(BooleanFilter::class, properties: ['isValid', 'entryCompleted'])]
|
#[ApiFilter(BooleanFilter::class, properties: ['isValid'])]
|
||||||
#[ApiFilter(SearchFilter::class, properties: [
|
#[ApiFilter(SearchFilter::class, properties: [
|
||||||
'identificationNumber' => 'ipartial',
|
'identificationNumber' => 'ipartial',
|
||||||
'supplier.name' => 'ipartial',
|
'supplier.name' => 'ipartial',
|
||||||
'carrier.name' => 'ipartial',
|
'carrier.name' => 'ipartial',
|
||||||
'licensePlate' => 'ipartial',
|
'licensePlate' => 'ipartial',
|
||||||
'receptionType.id' => 'exact',
|
'receptionType.id' => 'exact',
|
||||||
'receptionType.code' => 'exact',
|
|
||||||
])]
|
])]
|
||||||
#[ApiFilter(DateFilter::class, properties: ['receptionDate'])]
|
#[ApiFilter(DateFilter::class, properties: ['receptionDate'])]
|
||||||
#[ApiResource(
|
#[ApiResource(
|
||||||
@@ -111,10 +110,6 @@ class Reception
|
|||||||
#[Groups(['reception:read', 'reception:write', 'reception-bovine:read'])]
|
#[Groups(['reception:read', 'reception:write', 'reception-bovine:read'])]
|
||||||
private bool $isValid = false;
|
private bool $isValid = false;
|
||||||
|
|
||||||
#[ORM\Column(options: ['default' => false])]
|
|
||||||
#[Groups(['reception:read', 'reception:write', 'reception-bovine:read'])]
|
|
||||||
private bool $entryCompleted = false;
|
|
||||||
|
|
||||||
#[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(
|
||||||
@@ -209,12 +204,6 @@ class Reception
|
|||||||
#[Groups(['reception:read', 'reception:write'])]
|
#[Groups(['reception:read', 'reception:write'])]
|
||||||
private ?string $bovineDetail = null;
|
private ?string $bovineDetail = null;
|
||||||
|
|
||||||
/**
|
|
||||||
* @var Collection<int, Bovine>
|
|
||||||
*/
|
|
||||||
#[ORM\OneToMany(targetEntity: Bovine::class, mappedBy: 'reception')]
|
|
||||||
private Collection $bovines;
|
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
?DateTimeImmutable $receptionDate = null,
|
?DateTimeImmutable $receptionDate = null,
|
||||||
) {
|
) {
|
||||||
@@ -223,7 +212,6 @@ class Reception
|
|||||||
$this->buildings = new ArrayCollection();
|
$this->buildings = new ArrayCollection();
|
||||||
$this->pelletBuildings = new ArrayCollection();
|
$this->pelletBuildings = new ArrayCollection();
|
||||||
$this->bovines_types = new ArrayCollection();
|
$this->bovines_types = new ArrayCollection();
|
||||||
$this->bovines = new ArrayCollection();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getId(): ?int
|
public function getId(): ?int
|
||||||
@@ -282,25 +270,6 @@ class Reception
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Groups(['reception:read'])]
|
|
||||||
public function isEntryCompleted(): bool
|
|
||||||
{
|
|
||||||
return $this->entryCompleted;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setEntryCompleted(bool $entryCompleted): self
|
|
||||||
{
|
|
||||||
$this->entryCompleted = $entryCompleted;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[Groups(['reception:read'])]
|
|
||||||
public function getRegisteredBovineCount(): int
|
|
||||||
{
|
|
||||||
return $this->bovines->count();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[Groups(['reception:read'])]
|
#[Groups(['reception:read'])]
|
||||||
public function getReceptionDate(): ?DateTimeImmutable
|
public function getReceptionDate(): ?DateTimeImmutable
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user