diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 31f36e9..c6457e4 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -5,16 +5,16 @@ - - - + + + - { - "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": "preferences.pluginManager", - "ts.external.directory.path": "/opt/phpstorm/plugins/javascript-plugin/jsLanguageServicesImpl/external", - "vue.rearranger.settings.migration": "true" + +}]]> @@ -300,7 +300,7 @@ - + - diff --git a/frontend/components/reception/reception-bovine-received.vue b/frontend/components/reception/reception-bovine-received.vue index a81d28d..4f8b12b 100644 --- a/frontend/components/reception/reception-bovine-received.vue +++ b/frontend/components/reception/reception-bovine-received.vue @@ -4,7 +4,7 @@ class="flex flex-col items-center gap-16">

Sélection des marchandises réceptionnnées

+ class="flex flex-row gap-8 items-center">
@@ -38,14 +41,20 @@ import {useReceptionStore} from '~/stores/reception' import { createReceptionBovine, deleteReceptionBovine, - getReceptionBovineList + getReceptionBovineList, + updateReceptionBovine } from "~/services/reception-bovine"; -import {ref} from "vue"; +import {computed, onMounted, reactive, ref, watch} from "vue"; const isLoadingBovineType = ref(false) const bovineType = ref([]) const receptionStore = useReceptionStore() -const selectedBovineTypeIds = ref +const bovineQuantities = reactive>({}) +const otherQuantity = ref(0) +const receptionId = computed(() => receptionStore.current?.id ?? null) +const receptionIri = computed(() => + receptionId.value ? `/api/receptions/${receptionId.value}` : null +) const loadBovineType = async () => { isLoadingBovineType.value = true try { @@ -55,28 +64,94 @@ const loadBovineType = async () => { } } - onMounted(async () => { - await loadBovineType() - selectedBovineTypeIds.value=bovineType.value.map((type) => String(type.id)) }) +watch( + () => receptionId.value, + async (id) => { + if (!id || !receptionIri.value) { + return + } + + const selectionMap: Record = {} + for (const type of bovineType.value) { + selectionMap[String(type.id)] = 0 + } + + const existing = await getReceptionBovineList(receptionIri.value) + for (const selection of existing) { + const bovineTypeId = String(selection.bovineType.id) + selectionMap[bovineTypeId] = selection.quantity ?? 0 + } + + for (const key of Object.keys(bovineQuantities)) { + delete bovineQuantities[key] + } + Object.assign(bovineQuantities, selectionMap) + }, + {immediate: true} +) + +async function syncBovineSelections(receptionIri: string) { + const existing = await getReceptionBovineList(receptionIri) + const existingMap = new Map() + for (const selection of existing) { + const bovineTypeId = String(selection.bovineType.id) + existingMap.set(bovineTypeId, { + id: selection.id, + quantity: selection.quantity ?? 0 + }) + } + + // Supprime les entrées supprimées ou modifiées + for (const [bovineTypeId, entry] of existingMap.entries()) { + const selectedQuantity = bovineQuantities[bovineTypeId] ?? 0 + if (!selectedQuantity) { + await deleteReceptionBovine(entry.id) + existingMap.delete(bovineTypeId) + continue + } + + if (selectedQuantity !== entry.quantity) { + await updateReceptionBovine(entry.id, { quantity: selectedQuantity }) + existingMap.set(bovineTypeId, { + id: entry.id, + quantity: selectedQuantity + }) + } + } + + // Crée les entrées manquantes + for (const [bovineTypeId, quantity] of Object.entries(bovineQuantities)) { + if (!quantity) { + continue + } + if (existingMap.has(bovineTypeId)) { + // Déjà à jour + continue + } + await createReceptionBovine({ + reception: receptionIri, + bovineType: `/api/bovine_types/${bovineTypeId}`, + quantity + }) + } +} + async function goNext() { - if (!receptionStore.current) { + if (!receptionStore.current || !receptionIri.value) { return } const nextStep = receptionStore.current.currentStep + 1 - const receptionIri = `/api/receptions/${receptionStore.current.id}` - console.log(selectedBovineTypeIds.value) + + await syncBovineSelections(receptionIri.value) + await receptionStore.updateReception(receptionStore.current.id, { merchandiseType: null, merchandiseDetail: null, - buildings: null, - bovinesTypes : selectedBovineTypeIds.value.map((id) => `/api/bovines_types/${id}`), currentStep: nextStep }) } - - diff --git a/frontend/components/reception/reception-product-received.vue b/frontend/components/reception/reception-product-received.vue index 12c7437..48b17d1 100644 --- a/frontend/components/reception/reception-product-received.vue +++ b/frontend/components/reception/reception-product-received.vue @@ -175,7 +175,6 @@ onMounted(async () => { } selectedPelletBuildingIds.value = selectionMap }) - // Enregistre les sélections et passe à l'étape suivante async function goNext() { if (!receptionStore.current) { @@ -193,6 +192,7 @@ async function goNext() { buildings: isGranule.value ? [] : selectedBuildingIds.value.map((id) => `/api/buildings/${id}`), + bovinesTypes: null, currentStep: nextStep }) @@ -210,7 +210,6 @@ async function clearPelletSelections(receptionIri: string) { await deleteReceptionPelletBuilding(selection.id) } } - // Synchronise les associations granulés/bâtiments avec l'état du formulaire async function syncPelletSelections(receptionIri: string) { const existing = await getReceptionPelletBuildingList(receptionIri) diff --git a/frontend/components/ui/UiNumberInput.vue b/frontend/components/ui/UiNumberInput.vue index 146648f..5c073cb 100644 --- a/frontend/components/ui/UiNumberInput.vue +++ b/frontend/components/ui/UiNumberInput.vue @@ -1,12 +1,19 @@ diff --git a/frontend/services/reception-bovine.ts b/frontend/services/reception-bovine.ts index 4ae5265..3d166d6 100644 --- a/frontend/services/reception-bovine.ts +++ b/frontend/services/reception-bovine.ts @@ -47,3 +47,13 @@ export async function deleteReceptionBovine(id: number): Promise { 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/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/src/Entity/BovineType.php b/src/Entity/BovineType.php index 106fdfd..ee12732 100644 --- a/src/Entity/BovineType.php +++ b/src/Entity/BovineType.php @@ -28,15 +28,15 @@ class BovineType #[ORM\Id] #[ORM\GeneratedValue] #[ORM\Column] - #[Groups(['bovine-type:read', 'reception:read'])] + #[Groups(['bovine-type:read', 'reception:read', 'reception-bovine:read'])] private ?int $id = null; #[ORM\Column(length: 120)] - #[Groups(['bovine-type:read', 'reception:read'])] + #[Groups(['bovine-type:read', 'reception:read', 'reception-bovine:read'])] private ?string $label = null; #[ORM\Column(length: 50)] - #[Groups(['bovine-type:read', 'reception:read'])] + #[Groups(['bovine-type:read', 'reception:read', 'reception-bovine:read'])] private ?string $code = null; public function getId(): ?int diff --git a/src/Entity/Reception.php b/src/Entity/Reception.php index 6682ae0..eb86405 100644 --- a/src/Entity/Reception.php +++ b/src/Entity/Reception.php @@ -72,27 +72,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; diff --git a/src/Entity/ReceptionBovine.php b/src/Entity/ReceptionBovine.php index 94938bb..20b109e 100644 --- a/src/Entity/ReceptionBovine.php +++ b/src/Entity/ReceptionBovine.php @@ -4,15 +4,21 @@ declare(strict_types=1); namespace App\Entity; +use ApiPlatform\Doctrine\Orm\Filter\SearchFilter; +use ApiPlatform\Metadata\ApiFilter; +use ApiPlatform\Metadata\ApiProperty; use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\Delete; use ApiPlatform\Metadata\Get; use ApiPlatform\Metadata\GetCollection; +use ApiPlatform\Metadata\Patch; use ApiPlatform\Metadata\Post; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Serializer\Attribute\Groups; #[ORM\Entity] +#[ApiFilter(SearchFilter::class, properties: ['reception' => 'exact'])] +#[ORM\UniqueConstraint(name: 'uniq_reception_bovine_type', columns: ['reception_id', 'bovine_type_id'])] #[ApiResource( operations: [ new Get( @@ -26,6 +32,10 @@ use Symfony\Component\Serializer\Attribute\Groups; 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')", @@ -36,20 +46,27 @@ class ReceptionBovine #[ORM\GeneratedValue] #[ORM\Column] #[Groups(['reception-bovine:read', 'reception:read'])] + private ?int $id = null; #[ORM\ManyToOne(inversedBy: 'bovines_types')] - #[Groups(['reception-bovine:read'])] + #[Groups(['reception-bovine:read', 'reception-bovine:write'])] private ?Reception $reception = null; #[ORM\ManyToOne] #[ORM\JoinColumn(nullable: false)] - #[Groups(['reception-bovine:read', 'reception:read'])] - private ?BovineType $BovineType = null; + #[Groups(['reception-bovine:read', 'reception-bovine:write', 'reception:read'])] + #[ApiProperty(readableLink: true)] + private ?BovineType $bovineType = null; #[ORM\Column(options: ['default' => 0])] - #[Groups(['reception-bovine:read', 'reception:read'])] - private ?int $Quantity = null; + // #[Assert\Range( + // min: 0, + // max: 10, + // notInRangeMessage: 'La quantité doit être comprise entre {{ min }} et {{ max }}.' + // )] + #[Groups(['reception-bovine:read', 'reception-bovine:write', 'reception:read'])] + private ?int $quantity = null; public function getId(): ?int { @@ -70,24 +87,24 @@ class ReceptionBovine public function getBovineType(): ?BovineType { - return $this->BovineType; + return $this->bovineType; } - public function setBovineType(?BovineType $BovineType): static + public function setBovineType(?BovineType $bovineType): static { - $this->BovineType = $BovineType; + $this->bovineType = $bovineType; return $this; } public function getQuantity(): ?int { - return $this->Quantity; + return $this->quantity; } - public function setQuantity(int $Quantity): static + public function setQuantity(int $quantity): static { - $this->Quantity = $Quantity; + $this->quantity = $quantity; return $this; }