diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml index 15f93b9..4fee92a 100644 --- a/.idea/dataSources.xml +++ b/.idea/dataSources.xml @@ -16,4 +16,4 @@ $ProjectFileDir$ - \ No newline at end of file + diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 8f94591..968a9a5 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -4,15 +4,19 @@ + @@ -752,9 +771,19 @@ - - - + + + + + file://$PROJECT_DIR$/src/Entity/ReceptionPelletBuilding.php + 6 + + + @@ -768,4 +797,4 @@ - \ No newline at end of file + diff --git a/frontend/components/reception/reception-bovine-received.vue b/frontend/components/reception/reception-bovine-received.vue new file mode 100644 index 0000000..4a928d2 --- /dev/null +++ b/frontend/components/reception/reception-bovine-received.vue @@ -0,0 +1,183 @@ + + diff --git a/frontend/components/reception/reception-form.vue b/frontend/components/reception/reception-form.vue index 14e684e..393de8f 100644 --- a/frontend/components/reception/reception-form.vue +++ b/frontend/components/reception/reception-form.vue @@ -142,7 +142,8 @@ import type {DriverData} from '~/services/dto/driver-data' import {getDriverList} from '~/services/driver' import type {VehicleData} from '~/services/dto/vehicle-data' import {getVehicleList} from '~/services/vehicle' -import {SUPLLIER_CODE} from "~/utils/constants"; +import {RECEPTION_TYPE_CODES, SUPLLIER_CODE} from "~/utils/constants"; +import {deleteReceptionBovine, getReceptionBovineList} from "~/services/reception-bovine"; type ReceptionFormData = { licensePlate: string @@ -222,6 +223,18 @@ const filteredVehicles = computed(() => { ) }) +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) { + await deleteReceptionBovine(selection.id) + } +} + // Hydrate le formulaire depuis la réception en cours watch( () => receptionStore.current, @@ -509,6 +522,16 @@ 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 + ) { + await clearReceptionBovines(receptionIri) + } const nextStep = receptionStore.current.currentStep + 1 await receptionStore.updateReception(receptionStore.current.id, { currentStep: nextStep, diff --git a/frontend/components/reception/reception-product-received.vue b/frontend/components/reception/reception-product-received.vue index aed56c6..79c01e2 100644 --- a/frontend/components/reception/reception-product-received.vue +++ b/frontend/components/reception/reception-product-received.vue @@ -1,10 +1,9 @@ diff --git a/frontend/i18n/locales/fr.json b/frontend/i18n/locales/fr.json index 6f9026f..cc66034 100644 --- a/frontend/i18n/locales/fr.json +++ b/frontend/i18n/locales/fr.json @@ -1,64 +1,72 @@ { - "errors": { - "http": { - "get": "Impossible de récupérer les données.", - "post": "Impossible de créer la ressource.", - "put": "Impossible de mettre à jour la ressource.", - "patch": "Impossible de mettre à jour la ressource.", - "delete": "Impossible de supprimer la ressource." + "errors": { + "http": { + "get": "Impossible de récupérer les données.", + "post": "Impossible de créer la ressource.", + "put": "Impossible de mettre à jour la ressource.", + "patch": "Impossible de mettre à jour la ressource.", + "delete": "Impossible de supprimer la ressource." + }, + "reception": { + "list": "Impossible de récupérer la liste des réceptions.", + "fetch": "Impossible de récupérer la réception.", + "create": "Impossible de créer la réception.", + "update": "Impossible de mettre à jour la réception.", + "weigh": "Impossible de récupérer la pesée." + }, + "receptionType": { + "list": "Impossible de récupérer la liste des types de réception." + }, + "merchandiseType": { + "list": "Impossible de récupérer la liste des types de marchandises." + }, + "building": { + "list": "Impossible de récupérer la liste des bâtiments." + }, + "pelletType": { + "list": "Impossible de récupérer la liste des types de granulés." + }, + "receptionPelletBuilding": { + "list": "Impossible de récupérer la liste des dépôts de granulés.", + "create": "Impossible d'enregistrer le dépôt de granulés.", + "delete": "Impossible de supprimer le dépôt de granulés." + }, + "receptionBovine": { + "list": "Impossible de récupérer la liste des bovins de la réception.", + "create": "Impossible d'enregistrer le bovin.", + "delete": "Impossible de supprimer le bovin." + }, + "supplier": { + "list": "Impossible de récupérer la liste des fournisseurs." + }, + "truck": { + "list": "Impossible de récupérer la liste des camions." + }, + "bovin": { + "list": "Impossible de récupérer la liste des races de bovins." + }, + "carrier": { + "list": "Impossible de récupérer la liste des transporteurs." + }, + "driver": { + "list": "Impossible de récupérer la liste des chauffeurs." + }, + "vehicle": { + "list": "Impossible de récupérer la liste des immatriculations." + }, + "auth": { + "login": "Identifiants invalides.", + "users": "Impossible de récupérer les utilisateurs.", + "logout": "Impossible de se déconnecter." + } }, - "reception": { - "list": "Impossible de récupérer la liste des réceptions.", - "fetch": "Impossible de récupérer la réception.", - "create": "Impossible de créer la réception.", - "update": "Impossible de mettre à jour la réception.", - "weigh": "Impossible de récupérer la pesée." - }, - "receptionType": { - "list": "Impossible de récupérer la liste des types de réception." - }, - "merchandiseType": { - "list": "Impossible de récupérer la liste des types de marchandises." - }, - "building": { - "list": "Impossible de récupérer la liste des bâtiments." - }, - "pelletType": { - "list": "Impossible de récupérer la liste des types de granulés." - }, - "receptionPelletBuilding": { - "list": "Impossible de récupérer la liste des dépôts de granulés.", - "create": "Impossible d'enregistrer le dépôt de granulés.", - "delete": "Impossible de supprimer le dépôt de granulés." - }, - "supplier": { - "list": "Impossible de récupérer la liste des fournisseurs." - }, - "truck": { - "list": "Impossible de récupérer la liste des camions." - }, - "carrier": { - "list": "Impossible de récupérer la liste des transporteurs." - }, - "driver": { - "list": "Impossible de récupérer la liste des chauffeurs." - }, - "vehicle": { - "list": "Impossible de récupérer la liste des immatriculations." - }, - "auth": { - "login": "Identifiants invalides.", - "users": "Impossible de récupérer les utilisateurs.", - "logout": "Impossible de se déconnecter." + "success": { + "reception": { + "update": "Réception mise à jour avec succès." + }, + "auth": { + "login": "Connexion réussie.", + "logout": "Déconnexion réussie." + } } - }, - "success": { - "reception": { - "update": "Réception mise à jour avec succès." - }, - "auth": { - "login": "Connexion réussie.", - "logout": "Déconnexion réussie." - } - } } diff --git a/frontend/pages/reception/[[id]].vue b/frontend/pages/reception/[[id]].vue index 9be5d9a..e90b226 100644 --- a/frontend/pages/reception/[[id]].vue +++ b/frontend/pages/reception/[[id]].vue @@ -16,7 +16,12 @@ - + + @@ -25,6 +30,7 @@ import {useReceptionStore} from '~/stores/reception' import {storeToRefs} from 'pinia' import {RECEPTION_STEP_LABELS} from '~/constants/steps' +import {RECEPTION_TYPE_CODES} from "~/utils/constants"; const route = useRoute() const router = useRouter() diff --git a/frontend/services/bovine-type.ts b/frontend/services/bovine-type.ts new file mode 100644 index 0000000..4e4dafd --- /dev/null +++ b/frontend/services/bovine-type.ts @@ -0,0 +1,23 @@ +import { useApi } from '~/composables/useApi' +import type {BovineTypeData} from "~/services/dto/bovine-type-data"; + +export type BovineTypeListResponse = + | BovineTypeData[] + | { 'hydra:member'?: BovineTypeData[] } + +export async function getBovineTypeList(): Promise { + const api = useApi() + const response = await api.get('bovine_types', {}, { + toastErrorKey: 'errors.bovin.list' + }) + + if (Array.isArray(response)) { + return response + } + + if (response && typeof response === 'object' && Array.isArray(response['hydra:member'])) { + return response['hydra:member'] + } + + return [] +} diff --git a/frontend/services/dto/bovine-type-data.ts b/frontend/services/dto/bovine-type-data.ts new file mode 100644 index 0000000..c12ca7c --- /dev/null +++ b/frontend/services/dto/bovine-type-data.ts @@ -0,0 +1,5 @@ +export interface BovineTypeData{ + id: number + label: string + code: string +} diff --git a/frontend/services/dto/reception-bovine-data.ts b/frontend/services/dto/reception-bovine-data.ts new file mode 100644 index 0000000..d23f984 --- /dev/null +++ b/frontend/services/dto/reception-bovine-data.ts @@ -0,0 +1,8 @@ +import type {BovineTypeData} from "~/services/dto/bovine-type-data"; + +export interface ReceptionBovineTypeData{ + id: number + quantity : number + reception?: string + bovineType: BovineTypeData +} diff --git a/frontend/services/dto/reception-data.ts b/frontend/services/dto/reception-data.ts index 857fa07..f1fe901 100644 --- a/frontend/services/dto/reception-data.ts +++ b/frontend/services/dto/reception-data.ts @@ -8,6 +8,7 @@ import type { AddressData } from '~/services/dto/address-data' import type { TruckData } from '~/services/dto/truck-data' import type { CarrierData } from '~/services/dto/carrier-data' import type { DriverData } from '~/services/dto/driver-data' +import type {BovineTypeData} from "~/services/dto/bovine-type-data"; export interface ReceptionData { id: number @@ -20,7 +21,9 @@ export interface ReceptionData { receptionType?: ReceptionTypeData | null merchandiseType?: MerchandiseTypeData | null merchandiseDetail?: string | null + bovineDetail?: string | null buildings?: BuildingData[] | null + bovinesTypes?: BovineTypeData[] | null pelletBuildings?: ReceptionPelletBuildingData[] | null user?: UserData | null supplier?: SupplierData | null @@ -46,7 +49,9 @@ export type ReceptionPayload = { receptionType?: string | null merchandiseType?: string | null merchandiseDetail?: string | null + bovineDetail?: string | null buildings?: string[] | null + bovinesTypes?: string[] | null user?: string | null supplier?: string | null address?: string | null diff --git a/frontend/services/reception-bovine.ts b/frontend/services/reception-bovine.ts new file mode 100644 index 0000000..3d166d6 --- /dev/null +++ b/frontend/services/reception-bovine.ts @@ -0,0 +1,59 @@ +import { useApi } from '~/composables/useApi' +import type { ReceptionBovineTypeData } from '~/services/dto/reception-bovine-data' + +export type ReceptionBovineListResponse = + | ReceptionBovineTypeData[] + | { 'hydra:member'?: ReceptionBovineTypeData[] } + +export type ReceptionBovinePayload = { + quantity: number + reception: string + bovineType: string +} + +export async function getReceptionBovineList( + receptionIri: string +): Promise { + const api = useApi() + const response = await api.get( + 'reception_bovines', + { reception: receptionIri }, + { + toastErrorKey: 'errors.receptionBovine.list' + } + ) + + if (Array.isArray(response)) { + return response + } + if (response && typeof response === 'object' && Array.isArray(response['hydra:member'])) { + return response['hydra:member'] + } + return [] +} + +export async function createReceptionBovine( + payload: ReceptionBovinePayload +): Promise { + const api = useApi() + return api.post('reception_bovines', payload, { + toastErrorKey: 'errors.receptionBovine.create' + }) +} + +export async function deleteReceptionBovine(id: number): Promise { + const api = useApi() + await api.delete(`reception_bovines/${id}`, {}, { + toastErrorKey: 'errors.receptionBovine.delete' + }) +} + +export async function updateReceptionBovine( + id: number, + payload: Partial +): Promise { + const api = useApi() + return api.patch(`reception_bovines/${id}`, payload, { + toastErrorKey: 'errors.receptionBovine.update' + }) +} diff --git a/frontend/utils/constants.ts b/frontend/utils/constants.ts index 3676886..507b268 100644 --- a/frontend/utils/constants.ts +++ b/frontend/utils/constants.ts @@ -1,5 +1,6 @@ export const RECEPTION_TYPE_CODES = { - MERCHANDISES: 'MARCHANDISES' + MERCHANDISES: 'MARCHANDISES', + BOVINS: 'BOVINS' } as const export const MERCHANDISE_TYPE_CODES = { diff --git a/migrations/Version20260203123833.php b/migrations/Version20260203123833.php new file mode 100644 index 0000000..05296c7 --- /dev/null +++ b/migrations/Version20260203123833.php @@ -0,0 +1,39 @@ +addSql('CREATE TABLE bovine_type (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, label VARCHAR(120) NOT NULL, code VARCHAR(50) NOT NULL, PRIMARY KEY (id))'); + $this->addSql('CREATE TABLE reception_bovine (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, quantity INT NOT NULL, reception_id INT DEFAULT NULL, bovine_type_id INT NOT NULL, PRIMARY KEY (id))'); + $this->addSql('CREATE INDEX IDX_636B9DB97C14DF52 ON reception_bovine (reception_id)'); + $this->addSql('CREATE INDEX IDX_636B9DB97899F32E ON reception_bovine (bovine_type_id)'); + $this->addSql('ALTER TABLE reception_bovine ADD CONSTRAINT FK_636B9DB97C14DF52 FOREIGN KEY (reception_id) REFERENCES reception (id)'); + $this->addSql('ALTER TABLE reception_bovine ADD CONSTRAINT FK_636B9DB97899F32E FOREIGN KEY (bovine_type_id) REFERENCES bovine_type (id) NOT DEFERRABLE'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE reception_bovine DROP CONSTRAINT FK_636B9DB97C14DF52'); + $this->addSql('ALTER TABLE reception_bovine DROP CONSTRAINT FK_636B9DB97899F32E'); + $this->addSql('DROP TABLE bovine_type'); + $this->addSql('DROP TABLE reception_bovine'); + } +} diff --git a/migrations/Version20260204141406.php b/migrations/Version20260204141406.php new file mode 100644 index 0000000..3c64fab --- /dev/null +++ b/migrations/Version20260204141406.php @@ -0,0 +1,33 @@ +addSql('ALTER TABLE reception_bovine ALTER quantity SET DEFAULT 0'); + $this->addSql('CREATE UNIQUE INDEX uniq_reception_bovine_type ON reception_bovine (reception_id, bovine_type_id)'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('DROP INDEX uniq_reception_bovine_type'); + $this->addSql('ALTER TABLE reception_bovine ALTER quantity DROP DEFAULT'); + } +} diff --git a/migrations/Version20260205070819.php b/migrations/Version20260205070819.php new file mode 100644 index 0000000..4116413 --- /dev/null +++ b/migrations/Version20260205070819.php @@ -0,0 +1,31 @@ +addSql('ALTER TABLE reception ADD bovine_detail VARCHAR(255) DEFAULT NULL'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE reception DROP bovine_detail'); + } +} diff --git a/src/Entity/BovineType.php b/src/Entity/BovineType.php new file mode 100644 index 0000000..ee12732 --- /dev/null +++ b/src/Entity/BovineType.php @@ -0,0 +1,70 @@ + '\d+'], + normalizationContext: ['groups' => ['bovine-type:read']], + ), + new GetCollection( + normalizationContext: ['groups' => ['bovine-type:read']], + ), + ], + security: "is_granted('ROLE_USER')", +)] +class BovineType +{ + #[ORM\Id] + #[ORM\GeneratedValue] + #[ORM\Column] + #[Groups(['bovine-type:read', 'reception:read', 'reception-bovine:read'])] + private ?int $id = null; + + #[ORM\Column(length: 120)] + #[Groups(['bovine-type:read', 'reception:read', 'reception-bovine:read'])] + private ?string $label = null; + + #[ORM\Column(length: 50)] + #[Groups(['bovine-type:read', 'reception:read', 'reception-bovine:read'])] + private ?string $code = null; + + public function getId(): ?int + { + return $this->id; + } + + public function getLabel(): ?string + { + return $this->label; + } + + public function setLabel(string $label): static + { + $this->label = $label; + + return $this; + } + + public function getCode(): ?string + { + return $this->code; + } + + public function setCode(string $code): static + { + $this->code = $code; + + return $this; + } +} diff --git a/src/Entity/Reception.php b/src/Entity/Reception.php index dfaf42a..66305a0 100644 --- a/src/Entity/Reception.php +++ b/src/Entity/Reception.php @@ -75,27 +75,27 @@ class Reception #[ORM\Id] #[ORM\GeneratedValue] #[ORM\Column] - #[Groups(['reception:read'])] + #[Groups(['reception:read', 'reception-bovine:read'])] private ?int $id = null; #[ORM\Column(length: 20, nullable: true)] - #[Groups(['reception:read', 'reception:write'])] + #[Groups(['reception:read', 'reception:write', 'reception-bovine:read'])] private ?string $licensePlate = null; #[ORM\Column(length: 20, unique: true, nullable: true)] - #[Groups(['reception:read'])] + #[Groups(['reception:read', 'reception-bovine:read'])] private ?string $identificationNumber = null; #[ORM\Column(options: ['default' => 0])] - #[Groups(['reception:read', 'reception:write'])] + #[Groups(['reception:read', 'reception:write', 'reception-bovine:read'])] private int $currentStep = 0; #[ORM\Column(options: ['default' => false])] - #[Groups(['reception:read', 'reception:write'])] + #[Groups(['reception:read', 'reception:write', 'reception-bovine:read'])] private bool $isValid = false; #[ORM\Column(name: 'date_reception', type: 'datetime_immutable')] - #[Groups(['reception:read', 'reception:write'])] + #[Groups(['reception:read', 'reception:write', 'reception-bovine:read'])] #[Context([DateTimeNormalizer::FORMAT_KEY => 'Y-m-d'])] private ?DateTimeImmutable $receptionDate = null; @@ -171,6 +171,20 @@ class Reception #[ApiProperty(readableLink: true)] private ?Driver $driver = null; + /** + * @var Collection + */ + #[ORM\OneToMany(targetEntity: ReceptionBovine::class, mappedBy: 'reception', cascade: ['persist', 'remove'], orphanRemoval: true)] + #[Assert\Range( + min: 0 + )] + #[Groups(['reception:read', 'reception:write'])] + private Collection $bovines_types; + + #[ORM\Column(length: 255, nullable: true)] + #[Groups(['reception:read', 'reception:write'])] + private ?string $bovineDetail = null; + public function __construct( ?DateTimeImmutable $receptionDate = null, ) { @@ -178,6 +192,7 @@ class Reception $this->weights = new ArrayCollection(); $this->buildings = new ArrayCollection(); $this->pelletBuildings = new ArrayCollection(); + $this->bovines_types = new ArrayCollection(); } public function getId(): ?int @@ -472,4 +487,46 @@ class Reception ) ; } + + /** + * @return Collection + */ + public function getBovinesTypes(): Collection + { + return $this->bovines_types; + } + + public function addBovinesType(ReceptionBovine $bovinesType): static + { + if (!$this->bovines_types->contains($bovinesType)) { + $this->bovines_types->add($bovinesType); + $bovinesType->setReception($this); + } + + return $this; + } + + public function removeBovinesType(ReceptionBovine $bovinesType): static + { + if ($this->bovines_types->removeElement($bovinesType)) { + // set the owning side to null (unless already changed) + if ($bovinesType->getReception() === $this) { + $bovinesType->setReception(null); + } + } + + return $this; + } + + public function getBovineDetail(): ?string + { + return $this->bovineDetail; + } + + public function setBovineDetail(?string $bovineDetail): static + { + $this->bovineDetail = $bovineDetail; + + return $this; + } } diff --git a/src/Entity/ReceptionBovine.php b/src/Entity/ReceptionBovine.php new file mode 100644 index 0000000..f58d592 --- /dev/null +++ b/src/Entity/ReceptionBovine.php @@ -0,0 +1,109 @@ + 'exact'])] +#[ORM\UniqueConstraint(name: 'uniq_reception_bovine_type', columns: ['reception_id', 'bovine_type_id'])] +#[ApiResource( + operations: [ + new Get( + requirements: ['id' => '\d+'], + normalizationContext: ['groups' => ['reception-bovine:read']], + ), + new GetCollection( + normalizationContext: ['groups' => ['reception-bovine:read']], + ), + new Post( + normalizationContext: ['groups' => ['reception-bovine:read']], + denormalizationContext: ['groups' => ['reception-bovine:write']], + ), + new Patch( + normalizationContext: ['groups' => ['reception-bovine:read']], + denormalizationContext: ['groups' => ['reception-bovine:write']], + ), + new Delete(), + ], + security: "is_granted('ROLE_USER')", +)] +class ReceptionBovine +{ + #[ORM\Id] + #[ORM\GeneratedValue] + #[ORM\Column] + #[Groups(['reception-bovine:read', 'reception:read'])] + + private ?int $id = null; + + #[ORM\ManyToOne(inversedBy: 'bovines_types')] + #[Groups(['reception-bovine:read', 'reception-bovine:write'])] + private ?Reception $reception = null; + + #[ORM\ManyToOne] + #[ORM\JoinColumn(nullable: false)] + #[Groups(['reception-bovine:read', 'reception-bovine:write', 'reception:read'])] + #[ApiProperty(readableLink: true)] + private ?BovineType $bovineType = null; + + #[ORM\Column(options: ['default' => 0])] + #[Assert\Range( + min: 0 + )] + #[Groups(['reception-bovine:read', 'reception-bovine:write', 'reception:read'])] + private ?int $quantity = null; + + public function getId(): ?int + { + return $this->id; + } + + public function getReception(): ?Reception + { + return $this->reception; + } + + public function setReception(?Reception $reception): static + { + $this->reception = $reception; + + return $this; + } + + public function getBovineType(): ?BovineType + { + return $this->bovineType; + } + + public function setBovineType(?BovineType $bovineType): static + { + $this->bovineType = $bovineType; + + return $this; + } + + public function getQuantity(): ?int + { + return $this->quantity; + } + + public function setQuantity(int $quantity): static + { + $this->quantity = $quantity; + + return $this; + } +} diff --git a/src/Repository/.gitignore b/src/Repository/.gitignore deleted file mode 100644 index e69de29..0000000