feat : Ajout de la sélection des bovins étape 3 d'une réception

This commit is contained in:
2026-02-05 09:08:30 +01:00
parent 81c2a5802b
commit 33563addbe
8 changed files with 164 additions and 52 deletions

80
.idea/workspace.xml generated
View File

@@ -8,11 +8,9 @@
<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/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/components/ui/UiNumberInput.vue" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/components/ui/UiNumberInput.vue" afterDir="false" />
<change beforePath="$PROJECT_DIR$/frontend/pages/reception/[[id]].vue" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/pages/reception/[[id]].vue" afterDir="false" />
<change beforePath="$PROJECT_DIR$/frontend/services/reception-bovine.ts" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/services/reception-bovine.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/Entity/BovineType.php" beforeDir="false" afterPath="$PROJECT_DIR$/src/Entity/BovineType.php" 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>
@@ -226,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": "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"
<component name="PropertiesComponent">{
&quot;keyToString&quot;: {
&quot;RunOnceActivity.MCP Project settings loaded&quot;: &quot;true&quot;,
&quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,
&quot;RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252&quot;: &quot;true&quot;,
&quot;RunOnceActivity.git.unshallow&quot;: &quot;true&quot;,
&quot;RunOnceActivity.typescript.service.memoryLimit.init&quot;: &quot;true&quot;,
&quot;git-widget-placeholder&quot;: &quot;feat/256-reception-etape-3-bovin&quot;,
&quot;last_opened_file_path&quot;: &quot;/home/sroy/Documents/test/Ferme&quot;,
&quot;node.js.detected.package.eslint&quot;: &quot;true&quot;,
&quot;node.js.detected.package.tslint&quot;: &quot;true&quot;,
&quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;,
&quot;node.js.selected.package.tslint&quot;: &quot;(autodetect)&quot;,
&quot;nodejs_package_manager_path&quot;: &quot;npm&quot;,
&quot;settings.editor.selected.configurable&quot;: &quot;configurable.tailwindcss&quot;,
&quot;ts.external.directory.path&quot;: &quot;/opt/phpstorm/plugins/javascript-plugin/jsLanguageServicesImpl/external&quot;,
&quot;vue.rearranger.settings.migration&quot;: &quot;true&quot;
},
"keyToStringList": {
"DatabaseDriversLRU": [
"postgresql"
&quot;keyToStringList&quot;: {
&quot;DatabaseDriversLRU&quot;: [
&quot;postgresql&quot;
],
"com.intellij.ide.scratch.ScratchImplUtil$2/New Scratch File": [
"TEXT"
&quot;com.intellij.ide.scratch.ScratchImplUtil$2/New Scratch File&quot;: [
&quot;TEXT&quot;
],
"vue.recent.templates": [
"Vue Composition API Component"
&quot;vue.recent.templates&quot;: [
&quot;Vue Composition API Component&quot;
]
}
}]]></component>
}</component>
<component name="RecentsManager">
<key name="MoveFile.RECENT_KEYS">
<recent name="\\wsl.localhost\Ubuntu-24.04\home\m-tristan\workspace\Ferme" />
@@ -300,7 +298,8 @@
<workItem from="1770102495553" duration="2280000" />
<workItem from="1770195604082" duration="90000" />
<workItem from="1770195718952" duration="215000" />
<workItem from="1770195959162" duration="18482000" />
<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" />
@@ -686,7 +685,15 @@
<option name="project" value="LOCAL" />
<updated>1770206668867</updated>
</task>
<option name="localTasksCounter" value="49" />
<task id="LOCAL-00049" summary="feat : Ajout de la sélection des bovins étape 3 d'une réception (WIP)">
<option name="closed" value="true" />
<created>1770217875423</created>
<option name="number" value="00049" />
<option name="presentableId" value="LOCAL-00049" />
<option name="project" value="LOCAL" />
<updated>1770217875423</updated>
</task>
<option name="localTasksCounter" value="50" />
<servers />
</component>
<component name="TypeScriptGeneratedFilesManager">
@@ -771,17 +778,6 @@
<line>6</line>
<option name="timeStamp" value="3" />
</line-breakpoint>
<line-breakpoint enabled="true" type="javascript">
<url>file://$PROJECT_DIR$/frontend/components/reception/reception-product-received.vue</url>
<properties lambdaOrdinal="-1" />
<option name="timeStamp" value="7" />
</line-breakpoint>
<line-breakpoint enabled="true" type="javascript">
<url>file://$PROJECT_DIR$/frontend/components/reception/reception-product-received.vue</url>
<line>103</line>
<properties lambdaOrdinal="-1" />
<option name="timeStamp" value="9" />
</line-breakpoint>
</breakpoints>
</breakpoint-manager>
</component>

View File

@@ -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,
@@ -55,6 +55,19 @@ const receptionId = computed(() => receptionStore.current?.id ?? null)
const receptionIri = computed(() =>
receptionId.value ? `/api/receptions/${receptionId.value}` : null
)
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)
const other = typeof otherQuantity.value === 'number' ? otherQuantity.value : 0
return baseTotal + other
})
const loadBovineType = async () => {
isLoadingBovineType.value = true
try {
@@ -90,6 +103,13 @@ watch(
delete bovineQuantities[key]
}
Object.assign(bovineQuantities, selectionMap)
const existingOther = receptionStore.current?.bovineDetail
const parsedOther =
typeof existingOther === 'string' && existingOther.trim() !== ''
? Number(existingOther)
: 0
otherQuantity.value = Number.isFinite(parsedOther) ? parsedOther : 0
},
{immediate: true}
)
@@ -115,7 +135,7 @@ async function syncBovineSelections(receptionIri: string) {
}
if (selectedQuantity !== entry.quantity) {
await updateReceptionBovine(entry.id, { quantity: selectedQuantity })
await updateReceptionBovine(entry.id, {quantity: selectedQuantity})
existingMap.set(bovineTypeId, {
id: entry.id,
quantity: selectedQuantity
@@ -139,18 +159,44 @@ 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
}
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)
await receptionStore.updateReception(receptionStore.current.id, {
merchandiseType: null,
merchandiseDetail: null,
bovineDetail: otherQuantity.value ? String(otherQuantity.value) : null,
currentStep: nextStep
})
}

View File

@@ -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
@@ -221,6 +222,16 @@ 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
)
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(
@@ -460,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
@@ -508,7 +522,12 @@ async function validate() {
}
return
}
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,

View File

@@ -192,6 +192,7 @@ async function goNext() {
buildings: isGranule.value
? []
: selectedBuildingIds.value.map((id) => `/api/buildings/${id}`),
bovineDetail: null,
bovinesTypes: null,
currentStep: nextStep
})

View File

@@ -21,6 +21,7 @@ export interface ReceptionData {
receptionType?: ReceptionTypeData | null
merchandiseType?: MerchandiseTypeData | null
merchandiseDetail?: string | null
bovineDetail?: string | null
buildings?: BuildingData[] | null
bovinesTypes?: BovineTypeData[] | null
pelletBuildings?: ReceptionPelletBuildingData[] | null
@@ -48,6 +49,7 @@ export type ReceptionPayload = {
receptionType?: string | null
merchandiseType?: string | null
merchandiseDetail?: string | null
bovineDetail?: string | null
buildings?: string[] | null
bovinesTypes?: string[] | null
user?: string | null

View File

@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20260205070819 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->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');
}
}

View File

@@ -172,9 +172,16 @@ class Reception
* @var Collection<int, ReceptionBovine>
*/
#[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,
) {
@@ -507,4 +514,16 @@ class Reception
return $this;
}
public function getBovineDetail(): ?string
{
return $this->bovineDetail;
}
public function setBovineDetail(?string $bovineDetail): static
{
$this->bovineDetail = $bovineDetail;
return $this;
}
}

View File

@@ -60,11 +60,9 @@ class ReceptionBovine
private ?BovineType $bovineType = null;
#[ORM\Column(options: ['default' => 0])]
// #[Assert\Range(
// min: 0,
// max: 10,
// notInRangeMessage: 'La quantité doit être comprise entre {{ min }} et {{ max }}.'
// )]
#[Assert\Range(
min: 0
)]
#[Groups(['reception-bovine:read', 'reception-bovine:write', 'reception:read'])]
private ?int $quantity = null;