Compare commits

...

3 Commits

Author SHA1 Message Date
ae87f2b568 fix : Correctifs 2026-02-25 10:12:30 +01:00
gitea-actions
c52f22472d chore: bump version to v0.0.52
All checks were successful
Auto Tag Develop / tag (push) Successful in 4s
Build Release Artefact / build (push) Successful in 1m19s
2026-02-19 07:47:19 +00:00
e7421e985e [#331] Mettre à jour l'entité Shipment et bovin_shipment (!30)
All checks were successful
Auto Tag Develop / tag (push) Successful in 6s
| Numéro du ticket | Titre du ticket |
|------------------|-----------------|
|      #331           |        Mettre à jour l'entité Shipment et bovin_shipment         |

## Description de la PR

## Modification du .env

## Check list

- [x] Pas de régression
- [ ] TU/TI/TF rédigée
- [x] TU/TI/TF OK
- [x] CHANGELOG modifié

Reviewed-on: #30
Reviewed-by: Autin <tristan@yuno.malio.fr>
Co-authored-by: kevin <kevin@yuno.malio.fr>
Co-committed-by: kevin <kevin@yuno.malio.fr>
2026-02-19 07:47:13 +00:00
33 changed files with 242 additions and 435 deletions

View File

@@ -49,6 +49,8 @@ Ajouter dans le fichier .env du frontend
* fix layout admin * fix layout admin
* Creation page admin listing bovins * Creation page admin listing bovins
* Creation page admin ajout/modification bovins * Creation page admin ajout/modification bovins
* [#331] Mettre à jour l'entité Shipment et bovin_shipment
* [#334] Correctifs
### Changed ### Changed
### Fixed ### Fixed

View File

@@ -1,2 +1,2 @@
parameters: parameters:
app.version: '0.0.51' app.version: '0.0.52'

View File

@@ -8,11 +8,12 @@
</div> </div>
<button <button
class="text-xl uppercase bg-primary-500 text-white h-[50px] w-[272px]" class="inline-flex items-center justify-center text-xl text-white uppercase bg-primary-500 h-[50px] px-8 rounded hover:opacity-80 gap-2"
type="submit" type="submit"
:disabled="isLoading" :disabled="isLoading"
> >
{{ props.address? "Sauvegarder" : "Ajouter" }} <Icon :name="props.address ? 'mdi:check' : 'mdi:plus'" size="28" />
{{ props.address? "Valider" : "Ajouter" }}
</button> </button>
</div> </div>
@@ -22,7 +23,7 @@
<UiTextInput id="address-street2" v-model="form.street2" label="Complément" /> <UiTextInput id="address-street2" v-model="form.street2" label="Complément" />
<UiTextInput id="address-postalCode" v-model="form.postalCode" label="Code postal" /> <UiTextInput id="address-postalCode" v-model="form.postalCode" label="Code postal" />
<UiTextInput id="address-city" v-model="form.city" label="Ville" /> <UiTextInput id="address-city" v-model="form.city" label="Ville" />
<UiTextInput id="address-country" v-model="form.countryCode" label="Pays" /> <UiTextInput id="address-country" v-model="form.countryCode" label="Pays (code)" />
</div> </div>
</form> </form>
</template> </template>

View File

@@ -27,11 +27,14 @@
/> />
</div> </div>
</div> </div>
<button <div class="flex justify-center">
class="text-xl uppercase bg-primary-500 text-white h-[50px] w-[272px]" <UiButton
@click="goNext" type="submit"
>Valider class="text-xl uppercase bg-primary-500 text-white h-[50px] w-[272px] justify-self-end"
</button> @click="goNext"
>Valider
</UiButton>
</div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">

View File

@@ -64,11 +64,14 @@
</div> </div>
</div> </div>
</div> </div>
<button <div class="flex justify-center">
class="text-xl uppercase bg-primary-500 text-white h-[50px] w-[272px]" <UiButton
@click="goNext" type="submit"
>Valider class="text-xl uppercase bg-primary-500 text-white h-[50px] w-[272px] justify-self-end"
</button> @click="goNext"
>Valider
</UiButton>
</div>
</div> </div>
</template> </template>

View File

@@ -92,7 +92,7 @@
<!-- Plaque d'immatriculation (hors LIOT) --> <!-- Plaque d'immatriculation (hors LIOT) -->
<div v-if="!isLiotCarrier" class="col-start-2 row-start-4"> <div v-if="!isLiotCarrier" class="col-start-2 row-start-4">
<UiLicensePlateInput <UiLicensePlateInput
v-model="form.licencePlate" v-model="form.licensePlate"
v-model:allowAny="allowAnyLicensePlate" v-model:allowAny="allowAnyLicensePlate"
/> />
</div> </div>
@@ -152,15 +152,9 @@ import type {ShipmentFormData} from '~/services/dto/shipment-data'
import {SUPPLIER_CODE} from "~/utils/constants" import {SUPPLIER_CODE} from "~/utils/constants"
import {useAuthStore} from '~/stores/auth' import {useAuthStore} from '~/stores/auth'
import {useShipmentStore} from '~/stores/shipment' import {useShipmentStore} from '~/stores/shipment'
import { computed, reactive, ref, watch, onMounted } from 'vue' import {computed, reactive, ref, watch, onMounted} from 'vue'
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";
import {
createShipmentBovine,
deleteShipmentBovine,
getBovinShipmentList,
updateShipmentBovine
} from "~/services/bovin-shipment";
const users = ref<UserData[]>([]) const users = ref<UserData[]>([])
const customers = ref<CustomerData[]>([]) const customers = ref<CustomerData[]>([])
@@ -199,7 +193,7 @@ const form = reactive<ShipmentFormData>({
carrierId: '', carrierId: '',
driverId: '', driverId: '',
vehicleId: '', vehicleId: '',
licencePlate: '', licensePlate: '',
}) })
// Adresses liées au client sélectionné // Adresses liées au client sélectionné
const customerAddresses = computed<AddressData[]>(() => { const customerAddresses = computed<AddressData[]>(() => {
@@ -321,7 +315,7 @@ watch(
() => shipmentStore.current, () => shipmentStore.current,
(shipment) => { (shipment) => {
isHydrating.value = true isHydrating.value = true
form.licencePlate = shipment?.licencePlate ?? '' form.licensePlate = shipment?.licensePlate ?? ''
form.shipmentDate = shipment?.shipmentDate ?? new Date().toISOString().slice(0, 10) form.shipmentDate = shipment?.shipmentDate ?? new Date().toISOString().slice(0, 10)
form.userId = shipment?.user?.id ? String(shipment.user.id) : form.userId = shipment?.user?.id ? String(shipment.user.id) :
form.userId form.userId
@@ -332,23 +326,15 @@ watch(
form.carrierId = shipment?.carrier?.id ? String(shipment.carrier.id) : '' form.carrierId = shipment?.carrier?.id ? String(shipment.carrier.id) : ''
form.driverId = shipment?.driver?.id ? String(shipment.driver.id) : '' form.driverId = shipment?.driver?.id ? String(shipment.driver.id) : ''
form.vehicleId = shipment?.vehicle?.id ? String(shipment.vehicle.id) : '' form.vehicleId = shipment?.vehicle?.id ? String(shipment.vehicle.id) : ''
if (!shipment || !shipment.bovinShipments) {
selectedShipmentTypeId.value = ''
shipmentQuantity.value = 0
} else {
const selectedEntry = shipment.bovinShipments.find((entry) => {
const typeId = entry.shipmentType?.id
return Boolean(typeId) && Number(entry.nbBovinSend ?? 0) > 0
}) ?? shipment.bovinShipments.find((entry) => Boolean(entry.shipmentType?.id))
if (!selectedEntry?.shipmentType?.id) {
selectedShipmentTypeId.value = '' selectedShipmentTypeId.value = shipment?.shipmentType?.id
shipmentQuantity.value = 0 ? String(shipment.shipmentType.id)
} else { : ''
selectedShipmentTypeId.value = String(selectedEntry.shipmentType.id)
shipmentQuantity.value = selectedEntry.nbBovinSend ?? 0 shipmentQuantity.value = shipment?.nbBovinSend ?? 0
}
}
isHydrating.value = false isHydrating.value = false
}, },
{immediate: true} {immediate: true}
@@ -455,89 +441,28 @@ watch(
(vehicle) => String(vehicle.id) === form.vehicleId (vehicle) => String(vehicle.id) === form.vehicleId
) )
if (selected) { if (selected) {
form.licencePlate = selected.plate form.licensePlate = selected.plate
allowAnyLicensePlate.value = false allowAnyLicensePlate.value = false
} }
} }
) )
watch( watch(
() => [form.licencePlate, form.carrierId, vehicles.value], () => [form.licensePlate, form.carrierId, vehicles.value],
() => { () => {
if (!isLiotCarrier.value || form.vehicleId) { if (!isLiotCarrier.value || form.vehicleId) {
return return
} }
const match = filteredVehicles.value.find( const match = filteredVehicles.value.find(
(vehicle) => vehicle.plate === form.licencePlate (vehicle) => vehicle.plate === form.licensePlate
) )
if (match) { if (match) {
form.vehicleId = String(match.id) form.vehicleId = String(match.id)
} }
} }
) )
const buildDesiredBovinShipments = () => {
const typeId = Number(selectedShipmentTypeId.value)
if (!Number.isFinite(typeId)) {
return []
}
const type = bovineShipment.value.find((entry) => entry.id === typeId)
if (!type) {
return []
}
const raw = shipmentQuantity.value
const quantity = raw === null || raw === undefined ? 0 : Number(raw)
const normalizedQuantity = Number.isFinite(quantity) ? Math.max(0, Math.trunc(quantity)) : 0
if (normalizedQuantity <= 0) {
return []
}
return [{type, quantity: normalizedQuantity}]
}
const syncBovinShipments = async (
shipmentId: number,
existing: Array<{ id?: number; nbBovinSend: number | null; shipmentType?: unknown }> = []
) => {
const shipmentIri = `/api/shipments/${shipmentId}`
const desired = buildDesiredBovinShipments()
const desiredByTypeId = new Map<number, number>()
for (const entry of desired) {
desiredByTypeId.set(entry.type.id, entry.quantity)
}
for (const entry of existing) {
if (!entry.id) {
continue
}
const rawType = entry.shipmentType
let typeId: number | null = null
if (rawType && typeof rawType === 'object' && 'id' in rawType) {
typeId = Number((rawType as { id: number }).id)
} else if (typeof rawType === 'string') {
const match = rawType.match(/\/shipment_types\/(\\d+)$/)
typeId = match ? Number(match[1]) : null
}
if (!typeId) {
continue
}
const desiredQuantity = desiredByTypeId.get(typeId)
if (!desiredQuantity) {
await deleteShipmentBovine(entry.id)
continue
}
if (entry.nbBovinSend !== desiredQuantity) {
await updateShipmentBovine(entry.id, {nbBovinSend: desiredQuantity})
}
desiredByTypeId.delete(typeId)
}
for (const [typeId, quantity] of desiredByTypeId.entries()) {
await createShipmentBovine({
shipment: shipmentIri,
shipmentType: `/api/shipment_types/${typeId}`,
nbBovinSend: quantity
})
}
}
const buildPayload = () => { const buildPayload = () => {
const normalizedLicensePlate = form.licencePlate.trim() const normalizedLicensePlate = form.licensePlate.trim()
const normalizedShipmentDate = form.shipmentDate.trim() const normalizedShipmentDate = form.shipmentDate.trim()
const normalizedCustomerId = form.customerId.trim() const normalizedCustomerId = form.customerId.trim()
const normalizedTruckId = form.truckId.trim() const normalizedTruckId = form.truckId.trim()
@@ -563,29 +488,36 @@ const buildPayload = () => {
const addressIri = normalizedAddressId const addressIri = normalizedAddressId
? `/api/addresses/${normalizedAddressId}` ? `/api/addresses/${normalizedAddressId}`
: null : null
const normalizedShipmentTypeId = selectedShipmentTypeId.value.trim()
const shipmentTypeIri = normalizedShipmentTypeId
? `/api/shipment_types/${normalizedShipmentTypeId}`
: null
const rawQuantity = Number(shipmentQuantity.value ?? 0)
const normalizedQuantity = Number.isFinite(rawQuantity) ? Math.max(0,
Math.trunc(rawQuantity)) : 0
return { return {
licencePlate: normalizedLicensePlate, licensePlate: normalizedLicensePlate,
shipmentDate: normalizedShipmentDate, shipmentDate: normalizedShipmentDate,
customer: customerIri, customer: customerIri,
truck: truckIri, truck: truckIri,
carrier: carrierIri, carrier: carrierIri,
driver: driverIri, driver: driverIri,
user: userIri, user: userIri,
address: addressIri address: addressIri,
shipmentType: shipmentTypeIri,
nbBovinSend: normalizedQuantity,
} }
} }
const saveDraft = async () => { const saveDraft = async () => {
const payload = buildPayload() const payload = buildPayload()
if (!shipmentStore.current) { if (!shipmentStore.current) {
const created = await shipmentStore.createShipment({ await shipmentStore.createShipment({
currentStep: 0, currentStep: 0,
...payload ...payload
}) })
if (created) {
await syncBovinShipments(created.id, [])
}
return return
} }
@@ -593,10 +525,6 @@ const saveDraft = async () => {
currentStep: shipmentStore.current.currentStep, currentStep: shipmentStore.current.currentStep,
...payload ...payload
}) })
await syncBovinShipments(
shipmentStore.current.id,
shipmentStore.current?.bovinShipments ?? []
)
} }
defineExpose({saveDraft}) defineExpose({saveDraft})
@@ -610,7 +538,6 @@ const validate = async () => {
}) })
if (created) { if (created) {
await shipmentStore.loadShipment(created.id) await shipmentStore.loadShipment(created.id)
await syncBovinShipments(created.id, shipmentStore.current?.bovinShipments ?? [])
await router.push(`/shipment/${created.id}`) await router.push(`/shipment/${created.id}`)
} }
return return
@@ -621,6 +548,5 @@ const validate = async () => {
...payload ...payload
}) })
await shipmentStore.loadShipment(shipmentStore.current.id) await shipmentStore.loadShipment(shipmentStore.current.id)
await syncBovinShipments(shipmentStore.current.id, shipmentStore.current?.bovinShipments ?? [])
} }
</script> </script>

View File

@@ -1,6 +1,6 @@
<template> <template>
<div class="flex flex-col items-center gap-[118px]"> <div class="flex flex-col items-center gap-[118px]">
<h1 class="font-bold text-5xl uppercase text-primary-500">Charment des bovins</h1> <h1 class="font-bold text-5xl uppercase text-primary-500">Chargement des bovins</h1>
<div <div
class="w-full flex flex-col items-center justify-center"> class="w-full flex flex-col items-center justify-center">
<UiLoadingDots /> <UiLoadingDots />

View File

@@ -75,7 +75,7 @@ const printReceipt = async () => {
await saveWeight() await saveWeight()
const shipment = shipmentStore.current const shipment = shipmentStore.current
const filename = `${shipment.identificationNumber ?? shipment.id}_${shipment.customer?.label ?? 'client'}_${shipment.licencePlate ?? 'immat'}.pdf` const filename = `${shipment.identificationNumber ?? shipment.id}_${shipment.customer?.label ?? 'client'}_${shipment.licensePlate ?? 'immat'}.pdf`
await printPdf(`/shipments/${shipment.id}/receipt`, filename) await printPdf(`/shipments/${shipment.id}/receipt`, filename)
// Laisse le temps a la boite de dialogue d'impression de s'ouvrir. // Laisse le temps a la boite de dialogue d'impression de s'ouvrir.

View File

@@ -111,7 +111,7 @@ export const useWeighingShipment = ({
const displayWeight = computed(() => weightData.value?.weight ?? currentWeightEntry.value?.weight ?? null) const displayWeight = computed(() => weightData.value?.weight ?? currentWeightEntry.value?.weight ?? null)
const displayDsd = computed(() => weightData.value?.dsd ?? currentWeightEntry.value?.dsd ?? '-') const displayDsd = computed(() => weightData.value?.dsd ?? currentWeightEntry.value?.dsd ?? '-')
const title = computed(() => (modeShipment === 'gross' ? 'Pesée à plein' : 'Pesée à vide')) const title = computed(() => (modeShipment === 'gross' ? 'Pesée à vide' : 'Pesée à plein'))
const showLoadingBox = computed( const showLoadingBox = computed(
() => isFetching.value || (displayWeight.value === null && currentWeightEntry.value === null) () => isFetching.value || (displayWeight.value === null && currentWeightEntry.value === null)
) )

View File

@@ -8,10 +8,10 @@
<UiButton <UiButton
type="submit" type="submit"
:disabled="isLoading || isHydrating" :disabled="isLoading || isHydrating"
class="text-xl uppercase bg-primary-500 text-white h-[50px] w-[272px] hover:opacity-80" class="inline-flex items-center justify-center text-xl text-white uppercase bg-primary-500 h-[50px] px-8 rounded hover:opacity-80 gap-2"
> >
<Icon name="mdi:check" size="28" /> <Icon :name="isEdit ? 'mdi:check' : 'mdi:plus'" size="28" />
{{ isEdit ? 'Modifier' : 'Ajouter' }} {{ isEdit ? 'Valider' : 'Ajouter' }}
</UiButton> </UiButton>
</div> </div>

View File

@@ -8,8 +8,10 @@
<UiButton <UiButton
type="submit" type="submit"
class="text-xl uppercase bg-primary-500 text-white h-[50px] w-[272px] justify-self-end" class="inline-flex items-center justify-center text-xl text-white uppercase bg-primary-500 h-[50px] px-8 rounded hover:opacity-80 gap-2 justify-self-end"
>Enregistrer >
<Icon name="mdi:check" size="28" />
Valider
</UiButton> </UiButton>
</div> </div>
@@ -52,10 +54,6 @@ const form = reactive<CarrierFormData>({
name:'' name:''
}) })
definePageMeta({
layout: 'default'
})
const hydrateFromUser = (carrier: CarrierData | null) => { const hydrateFromUser = (carrier: CarrierData | null) => {
if (!carrier) { if (!carrier) {
return return

View File

@@ -4,7 +4,7 @@
<h1 class="text-3xl font-bold uppercase text-primary-500">listes des transporteurs</h1> <h1 class="text-3xl font-bold uppercase text-primary-500">listes des transporteurs</h1>
<NuxtLink <NuxtLink
to="/admin/carrier" to="/admin/carrier"
class="inline-flex items-center justify-center gap-2 text-xl uppercase bg-primary-500 text-white h-[50px] px-8 rounded" class="inline-flex items-center justify-center text-xl text-white uppercase bg-primary-500 h-[50px] px-8 rounded hover:opacity-80 gap-2"
> >
<Icon name="mdi:plus" size="28" /> <Icon name="mdi:plus" size="28" />
Ajouter Ajouter
@@ -43,10 +43,6 @@ const goToCarrier = (id: number) => {
router.push(`/admin/carrier/${id}`) router.push(`/admin/carrier/${id}`)
} }
definePageMeta({
layout: 'default'
})
onMounted(async () => { onMounted(async () => {
carrierList.value = await getCarrierList(false) carrierList.value = await getCarrierList(false)
}) })

View File

@@ -6,11 +6,12 @@
</h1> </h1>
<UiButton <UiButton
class="text-xl uppercase bg-primary-500 text-white h-[50px] w-[272px]" class="inline-flex items-center justify-center text-xl text-white uppercase bg-primary-500 h-[50px] px-8 rounded hover:opacity-80 gap-2"
type="submit" type="submit"
:disabled="isLoading || !auth.isAdmin" :disabled="isLoading || !auth.isAdmin"
> >
{{ customerId ? "Sauvegarder" : "Ajouter" }} <Icon :name="customerId ? 'mdi:check' : 'mdi:plus'" size="28" />
{{ customerId ? "Valider" : "Ajouter" }}
</UiButton> </UiButton>
</div> </div>
@@ -25,10 +26,11 @@
<h2 class="text-3xl font-bold uppercase">Adresses client</h2> <h2 class="text-3xl font-bold uppercase">Adresses client</h2>
<UiButton <UiButton
type="button" type="button"
class="text-xl uppercase bg-primary-500 text-white h-[50px] w-[272px]" class="inline-flex items-center justify-center text-xl text-white uppercase bg-primary-500 h-[50px] px-8 rounded hover:opacity-80 gap-2"
:disabled="customerId === null || !auth.isAdmin" :disabled="customerId === null || !auth.isAdmin"
@click="goToAddAddress" @click="goToAddAddress"
> >
<Icon name="mdi:plus" size="28" />
Ajouter Ajouter
</UiButton> </UiButton>
</div> </div>
@@ -80,8 +82,6 @@ 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"
import {useAuthStore} from "~/stores/auth" import {useAuthStore} from "~/stores/auth"
definePageMeta({layout: "default"})
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
const auth = useAuthStore() const auth = useAuthStore()

View File

@@ -8,8 +8,6 @@ import { createAddress, getAddress, updateAddress } from "~/services/address"
import { getCustomer, updateCustomer } from "~/services/customer" import { getCustomer, updateCustomer } from "~/services/customer"
import type { CustomerData } from "~/services/dto/customer-data" import type { CustomerData } from "~/services/dto/customer-data"
definePageMeta({ layout: "default" })
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
const customerId = computed(() => Number(route.query.customerId)) const customerId = computed(() => Number(route.query.customerId))

View File

@@ -3,7 +3,7 @@
<h1 class="text-3xl font-bold uppercase text-primary-500">Liste des Clients</h1> <h1 class="text-3xl font-bold uppercase text-primary-500">Liste des Clients</h1>
<NuxtLink <NuxtLink
to="/admin/customer" to="/admin/customer"
class="inline-flex items-center justify-center gap-2 text-xl uppercase bg-primary-500 text-white h-[50px] px-8 rounded-md" class="inline-flex items-center justify-center text-xl text-white uppercase bg-primary-500 h-[50px] px-8 rounded hover:opacity-80 gap-2"
:class="auth.isAdmin ? '' : 'cursor-not-allowed opacity-60'" :class="auth.isAdmin ? '' : 'cursor-not-allowed opacity-60'"
@click="handleAddClick" @click="handleAddClick"
> >
@@ -94,8 +94,6 @@ import { getCustomerList } from "~/services/customer"
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"
definePageMeta({ layout: "default" })
const customerList = ref<CustomerData[]>([]) const customerList = ref<CustomerData[]>([])
const router = useRouter() const router = useRouter()
const auth = useAuthStore() const auth = useAuthStore()

View File

@@ -1,7 +0,0 @@
<template>
</template>
<script setup lang="ts">
definePageMeta({
layout: 'default'
})
</script>

View File

@@ -6,11 +6,12 @@
</h1> </h1>
<UiButton <UiButton
class="text-xl uppercase bg-primary-500 text-white h-[50px] w-[272px]" class="inline-flex items-center justify-center text-xl text-white uppercase bg-primary-500 h-[50px] px-8 rounded hover:opacity-80 gap-2"
type="submit" type="submit"
:disabled="isLoading || !auth.isAdmin" :disabled="isLoading || !auth.isAdmin"
> >
{{ supplierId ? "Sauvegarder" : "Ajouter" }} <Icon :name="supplierId ? 'mdi:check' : 'mdi:plus'" size="28" />
{{ supplierId ? "Valider" : "Ajouter" }}
</UiButton> </UiButton>
</div> </div>
@@ -25,10 +26,11 @@
<h2 class="text-3xl font-bold uppercase">Adresses fournisseur</h2> <h2 class="text-3xl font-bold uppercase">Adresses fournisseur</h2>
<UiButton <UiButton
type="button" type="button"
class="text-xl uppercase bg-primary-500 text-white h-[50px] w-[272px]" class="inline-flex items-center justify-center text-xl text-white uppercase bg-primary-500 h-[50px] px-8 rounded hover:opacity-80 gap-2"
:disabled="supplierId === null || !auth.isAdmin" :disabled="supplierId === null || !auth.isAdmin"
@click="goToAddAddress" @click="goToAddAddress"
> >
<Icon name="mdi:plus" size="28" />
Ajouter Ajouter
</UiButton> </UiButton>
</div> </div>
@@ -80,8 +82,6 @@ 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"
import {useAuthStore} from "~/stores/auth" import {useAuthStore} from "~/stores/auth"
definePageMeta({layout: "default"})
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
const auth = useAuthStore() const auth = useAuthStore()

View File

@@ -8,8 +8,6 @@ import {createAddress, getAddress, updateAddress} from "~/services/address";
import {getSupplier, updateSupplier} from "~/services/supplier"; import {getSupplier, updateSupplier} from "~/services/supplier";
import type {SupplierData} from "~/services/dto/supplier-data"; import type {SupplierData} from "~/services/dto/supplier-data";
definePageMeta({ layout: "default" })
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
const supplierId = computed(() => { return Number(route.query.supplierId) }) const supplierId = computed(() => { return Number(route.query.supplierId) })

View File

@@ -3,7 +3,7 @@
<h1 class="text-3xl font-bold uppercase text-primary-500">Liste des fournisseurs</h1> <h1 class="text-3xl font-bold uppercase text-primary-500">Liste des fournisseurs</h1>
<NuxtLink <NuxtLink
to="/admin/supplier" to="/admin/supplier"
class="inline-flex items-center justify-center gap-2 text-xl uppercase bg-primary-500 text-white h-[50px] px-8 rounded" class="inline-flex items-center justify-center text-xl text-white uppercase bg-primary-500 h-[50px] px-8 rounded hover:opacity-80 gap-2"
:class="auth.isAdmin ? '' : 'cursor-not-allowed opacity-60'" :class="auth.isAdmin ? '' : 'cursor-not-allowed opacity-60'"
@click="handleAddClick" @click="handleAddClick"
> >
@@ -90,8 +90,6 @@ import { getSupplierList } from "~/services/supplier"
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"
definePageMeta({ layout: "default" })
const supplierList = ref<SupplierData[]>([]) const supplierList = ref<SupplierData[]>([])
const router = useRouter() const router = useRouter()
const auth = useAuthStore() const auth = useAuthStore()

View File

@@ -6,10 +6,11 @@
{{ userId ? "Modifications de l'utilisateur" : "Ajout d'un utilisateur" }} {{ userId ? "Modifications de l'utilisateur" : "Ajout d'un utilisateur" }}
</h1> </h1>
<UiButton <UiButton
class="text-xl uppercase bg-primary-500 text-white h-[50px] w-[272px]" class="inline-flex items-center justify-center text-xl text-white uppercase bg-primary-500 h-[50px] px-8 rounded hover:opacity-80 gap-2"
type="submit" type="submit"
> >
{{ userId ? 'Sauvegarder' : 'Ajouter' }} <Icon :name="userId ? 'mdi:check' : 'mdi:plus'" size="28" />
{{ userId ? 'Valider' : 'Ajouter' }}
</UiButton> </UiButton>
</div> </div>
@@ -38,10 +39,6 @@
</form> </form>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
definePageMeta({
layout: 'default'
})
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'

View File

@@ -2,8 +2,8 @@
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<h1 class="text-3xl font-bold uppercase text-primary-500">Liste des utilisateurs</h1> <h1 class="text-3xl font-bold uppercase text-primary-500">Liste des utilisateurs</h1>
<NuxtLink <NuxtLink
class="inline-flex items-center justify-center gap-2 text-xl uppercase bg-primary-500 text-white h-[50px] px-8 rounded-md" to="/admin/user"
@click="router.push('/admin/user/')" class="inline-flex items-center justify-center text-xl text-white uppercase bg-primary-500 h-[50px] px-8 rounded hover:opacity-80 gap-2"
> >
<Icon name="mdi:plus" size="28" /> <Icon name="mdi:plus" size="28" />
Ajouter Ajouter
@@ -38,10 +38,6 @@
<script setup lang="ts"> <script setup lang="ts">
definePageMeta({
layout: 'default'
})
import type {UserData} from "~/services/dto/user-data"; import type {UserData} from "~/services/dto/user-data";
import {getAdminUsers} from "~/services/auth"; import {getAdminUsers} from "~/services/auth";
import {ROLE} from "~/utils/constants"; import {ROLE} from "~/utils/constants";

View File

@@ -118,10 +118,10 @@
<UiButton <UiButton
v-if="auth.isAdmin" v-if="auth.isAdmin"
type="submit" type="submit"
class="text-xl uppercase bg-primary-500 text-white h-[50px] w-[272px] mb-16" class="inline-flex items-center justify-center text-xl text-white uppercase bg-primary-500 h-[50px] px-8 rounded hover:opacity-80 gap-2 mb-16"
> >
Enregistrer <Icon name="mdi:check" size="28" />
Valider
</UiButton> </UiButton>
</div> </div>
<div class="flex justify-evenly gap-y-8 gap-x-40 mb-8 border-b border-slate-400"> <div class="flex justify-evenly gap-y-8 gap-x-40 mb-8 border-b border-slate-400">

View File

@@ -28,9 +28,9 @@
<div>{{ shipment.customer?.name }}</div> <div>{{ shipment.customer?.name }}</div>
<div>{{ shipment.address?.fullAddress }}</div> <div>{{ shipment.address?.fullAddress }}</div>
<div> <div>
<template v-if="formatBovinShipmentLines(shipment).length"> <template v-if="formatShipmentLines(shipment).length">
<div <div
v-for="(line, index) in formatBovinShipmentLines(shipment)" v-for="(line, index) in formatShipmentLines(shipment)"
:key="index" :key="index"
class="leading-5" class="leading-5"
> >
@@ -62,16 +62,17 @@ const formatWeighing = (shipment: ShipmentData) => {
return `${gross - tare} kg` return `${gross - tare} kg`
} }
const formatBovinShipmentLines = (shipment: ShipmentData) => {
if (!shipment.bovinShipments?.length) { const formatShipmentLines = (shipment: ShipmentData) => {
if (!shipment.shipmentType && shipment.nbBovinSend == null) {
return [] return []
} }
return shipment.bovinShipments.map((entry) => {
const label = typeof entry.shipmentType === 'string' const label = typeof shipment.shipmentType === 'string'
? entry.shipmentType ? shipment.shipmentType
: entry.shipmentType?.label : shipment.shipmentType?.label
return `${label ?? ''} : ${entry.nbBovinSend ?? ''}`
}) return [`${label ?? ''} : ${shipment.nbBovinSend ?? ''}`]
} }
const goShipment = (id: number) => { const goShipment = (id: number) => {

View File

@@ -24,12 +24,12 @@
@click="goToShipment(shipment.id)" @click="goToShipment(shipment.id)"
@keydown.enter="goToShipment(shipment.id)" @keydown.enter="goToShipment(shipment.id)"
> >
<div>{{ shipment.customer?.label }}</div> <div>{{ shipment.customer?.name }}</div>
<div>{{ shipment.address?.fullAddress }}</div> <div>{{ shipment.address?.fullAddress }}</div>
<div> <div>
<template v-if="formatBovinShipmentLines(shipment).length"> <template v-if="formatShipmentLines(shipment).length">
<div <div
v-for="(line, index) in formatBovinShipmentLines(shipment)" v-for="(line, index) in formatShipmentLines(shipment)"
:key="index" :key="index"
class="leading-5" class="leading-5"
> >
@@ -38,7 +38,7 @@
</template> </template>
</div> </div>
<div>{{ shipment.carrier?.name }}</div> <div>{{ shipment.carrier?.name }}</div>
<div>{{ shipment.licencePlate }}</div> <div>{{ shipment.licensePlate }}</div>
</div> </div>
</div> </div>
</div> </div>
@@ -55,16 +55,17 @@ const router = useRouter()
const goToShipment = (id: number) => { const goToShipment = (id: number) => {
router.push(`/shipment/${id}`) router.push(`/shipment/${id}`)
} }
const formatBovinShipmentLines = (shipment: ShipmentData) => {
if (!shipment.bovinShipments?.length) { const formatShipmentLines = (shipment: ShipmentData) => {
if (!shipment.shipmentType && shipment.nbBovinSend == null) {
return [] return []
} }
return shipment.bovinShipments.map((entry) => {
const label = typeof entry.shipmentType === 'string' const label = typeof shipment.shipmentType === 'string'
? entry.shipmentType ? shipment.shipmentType
: entry.shipmentType?.label : shipment.shipmentType?.label
return `${label ?? ''} : ${entry.nbBovinSend ?? ''}`
}) return [`${label ?? ''} : ${shipment.nbBovinSend ?? ''}`]
} }
onMounted(async () => { onMounted(async () => {

View File

@@ -1,50 +0,0 @@
import { useApi } from '~/composables/useApi'
import type { BovinShipmentData } from '~/services/dto/bovin-shipment-data'
import type { ShipmentBovinePayload, BovinShipmentListResponse } from '~/services/dto/bovin-shipment-data'
export async function getBovinShipmentList(
shipmentIri: string
): Promise<BovinShipmentData[]> {
const api = useApi()
const response = await api.get<BovinShipmentListResponse>(
'bovin_shipments',
{ shipment: shipmentIri },
{
toastErrorKey: 'errors.shipmentBovine.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 createShipmentBovine(
payload: ShipmentBovinePayload
): Promise<BovinShipmentData> {
const api = useApi()
return api.post<BovinShipmentData>('bovin_shipments', payload, {
toastErrorKey: 'errors.shipmentBovine.create'
})
}
export async function deleteShipmentBovine(id: number): Promise<void> {
const api = useApi()
await api.delete<void>(`bovin_shipments/${id}`, {}, {
toastErrorKey: 'errors.shipmentBovine.delete'
})
}
export async function updateShipmentBovine(
id: number,
payload: Partial<ShipmentBovinePayload>
): Promise<BovinShipmentData> {
const api = useApi()
return api.patch<BovinShipmentData>(`bovin_shipments/${id}`, payload, {
toastErrorKey: 'errors.shipmentBovine.update'
})
}

View File

@@ -1,18 +0,0 @@
import type {ShipmentTypeData} from "~/services/dto/shipment-type-data";
export interface BovinShipmentData {
id: number
nbBovinSend: number | null
shipment?: string | null
shipmentType?: ShipmentTypeData | null
}
export type ShipmentBovinePayload = {
nbBovinSend: number
shipment: string
shipmentType: string
}
export type BovinShipmentListResponse =
| BovinShipmentData[]
| { 'hydra:member'?: BovinShipmentData[] }

View File

@@ -9,16 +9,10 @@ export interface ShipmentTypeData {
code: string code: string
} }
export interface BovinShipmentData {
id?: number
shipmentType?: ShipmentTypeData | string | null
nbBovinSend: number | null
}
export type ShipmentData = { export type ShipmentData = {
id: number id: number
identificationNumber?: string | null identificationNumber?: string | null
licencePlate: string | null licensePlate: string | null
shipmentDate: string shipmentDate: string
currentStep: number currentStep: number
isValid: boolean isValid: boolean
@@ -26,7 +20,8 @@ export type ShipmentData = {
carrier?: CarrierData | null carrier?: CarrierData | null
truck?: TruckData | null truck?: TruckData | null
customer?: CustomerData | null customer?: CustomerData | null
bovinShipments?: BovinShipmentData[] | null shipmentType?: ShipmentTypeData | null
nbBovinSend?: number | null
weights?: WeightShipmentEntryData[] | null weights?: WeightShipmentEntryData[] | null
} }
@@ -48,20 +43,20 @@ export type ShipmentFormData = {
carrierId: string, carrierId: string,
driverId: string, driverId: string,
vehicleId: string, vehicleId: string,
licencePlate: string, licensePlate: string,
} }
export type ShipmentPayload = { export type ShipmentPayload = {
licencePlate?: string | null licensePlate?: string | null
shipmentDate?: string shipmentDate?: string
currentStep?: number currentStep?: number
isValid?: boolean isValid?: boolean
carrier?: string | null carrier?: string | null
truck?: string | null truck?: string | null
customer?: string | null customer?: string | null
bovinShipments?: string[] | null
address?: string | null address?: string | null
user?: string | null user?: string | null
driver?: string | null driver?: string | null
shipmentType?: string | null
nbBovinSend?: number | null
} }

View File

@@ -0,0 +1,52 @@
<?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 Version20260218144828 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 bovin_shipment DROP CONSTRAINT fk_7049f4502ee48a36');
$this->addSql('ALTER TABLE bovin_shipment DROP CONSTRAINT fk_7049f4507be036fc');
$this->addSql('DROP TABLE bovin_shipment');
$this->addSql('ALTER TABLE shipment ADD nb_bovin_send INT NOT NULL');
$this->addSql('ALTER TABLE shipment ADD shipment_type_id INT DEFAULT NULL');
$this->addSql('ALTER TABLE shipment ADD CONSTRAINT FK_2CB20DC2EE48A36 FOREIGN KEY (shipment_type_id) REFERENCES shipment_type (id) NOT DEFERRABLE');
$this->addSql('CREATE INDEX IDX_2CB20DC2EE48A36 ON shipment (shipment_type_id)');
$this->addSql('DROP INDEX uniq_weight_shipment_type');
$this->addSql('DROP INDEX uniq_weight_reception_type');
$this->addSql('ALTER INDEX idx_weight_shipment RENAME TO IDX_7CD55417BE036FC');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE TABLE bovin_shipment (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, nb_bovin_send INT NOT NULL, shipment_id INT DEFAULT NULL, shipment_type_id INT DEFAULT NULL, PRIMARY KEY (id))');
$this->addSql('CREATE INDEX idx_7049f4507be036fc ON bovin_shipment (shipment_id)');
$this->addSql('CREATE INDEX idx_7049f4502ee48a36 ON bovin_shipment (shipment_type_id)');
$this->addSql('CREATE UNIQUE INDEX uniq_bovin_shipment_one_type ON bovin_shipment (shipment_id)');
$this->addSql('ALTER TABLE bovin_shipment ADD CONSTRAINT fk_7049f4502ee48a36 FOREIGN KEY (shipment_type_id) REFERENCES shipment_type (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE bovin_shipment ADD CONSTRAINT fk_7049f4507be036fc FOREIGN KEY (shipment_id) REFERENCES shipment (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE shipment DROP CONSTRAINT FK_2CB20DC2EE48A36');
$this->addSql('DROP INDEX IDX_2CB20DC2EE48A36');
$this->addSql('ALTER TABLE shipment DROP nb_bovin_send');
$this->addSql('ALTER TABLE shipment DROP shipment_type_id');
$this->addSql('CREATE UNIQUE INDEX uniq_weight_shipment_type ON weight (shipment_id, type)');
$this->addSql('CREATE UNIQUE INDEX uniq_weight_reception_type ON weight (reception_id, type)');
$this->addSql('ALTER INDEX idx_7cd55417be036fc RENAME TO idx_weight_shipment');
}
}

View File

@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20260225110000 extends AbstractMigration
{
public function getDescription(): string
{
return 'Rename shipment.licence_plate to license_plate';
}
public function up(Schema $schema): void
{
$this->addSql('ALTER TABLE shipment RENAME COLUMN licence_plate TO license_plate');
}
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE shipment RENAME COLUMN license_plate TO licence_plate');
}
}

View File

@@ -1,101 +0,0 @@
<?php
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: ['shipment' => 'exact'])]
#[ORM\UniqueConstraint(name: 'uniq_bovin_shipment_one_type', columns: ['shipment_id'])]
#[ORM\Table(name: 'bovin_shipment')]
#[ApiResource(
operations: [
new Get(
requirements: ['id' => '\d+'],
normalizationContext: ['groups' => ['shipment-bovine:read']],
),
new GetCollection(
normalizationContext: ['groups' => ['shipment-bovine:read']],
),
new Post(
normalizationContext: ['groups' => ['shipment-bovine:read']],
denormalizationContext: ['groups' => ['shipment-bovine:write']],
),
new Patch(
normalizationContext: ['groups' => ['shipment-bovine:read']],
denormalizationContext: ['groups' => ['shipment-bovine:write']],
),
new Delete(),
],
security: "is_granted('ROLE_USER')",
)]
class BovinShipment
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
#[Groups(['shipment:read', 'shipment-bovine:read'])]
private ?int $id = null;
#[ORM\ManyToOne(inversedBy: 'bovinShipments')]
#[Groups(['shipment-bovine:read', 'shipment-bovine:write'])]
#[ApiProperty(readableLink: true)]
private ?Shipment $shipment = null;
#[ORM\ManyToOne]
#[Groups(['shipment:read', 'shipment-bovine:write', 'shipment-bovine:read'])]
#[ApiProperty(readableLink: true)]
private ?ShipmentType $shipmentType = null;
#[ORM\Column]
#[Groups(['shipment:read', 'shipment-bovine:write', 'shipment-bovine:read'])]
private ?int $nbBovinSend = null;
public function getId(): ?int
{
return $this->id;
}
public function getShipment(): ?Shipment
{
return $this->shipment;
}
public function setShipment(?Shipment $shipment): void
{
$this->shipment = $shipment;
}
public function getShipmentType(): ?ShipmentType
{
return $this->shipmentType;
}
public function setShipmentType(?ShipmentType $shipmentType): void
{
$this->shipmentType = $shipmentType;
}
public function getNbBovinSend(): ?int
{
return $this->nbBovinSend;
}
public function setNbBovinSend(?int $nbBovinSend): void
{
$this->nbBovinSend = $nbBovinSend;
}
}

View File

@@ -80,7 +80,7 @@ class Shipment
#[ORM\Column(length: 255)] #[ORM\Column(length: 255)]
#[Groups(['shipment:read', 'shipment:write'])] #[Groups(['shipment:read', 'shipment:write'])]
private ?string $licencePlate = null; private ?string $licensePlate = null;
#[ORM\Column(length: 20, unique: true, nullable: true)] #[ORM\Column(length: 20, unique: true, nullable: true)]
#[Groups(['shipment:read'])] #[Groups(['shipment:read'])]
@@ -123,17 +123,15 @@ class Shipment
#[ApiProperty(readableLink: true)] #[ApiProperty(readableLink: true)]
private ?Customer $customer = null; private ?Customer $customer = null;
/** #[ORM\ManyToOne(inversedBy: 'shipments')]
* @var Collection<int, BovinShipment> #[ORM\JoinColumn(nullable: true)]
*/
#[ORM\OneToMany(
targetEntity: BovinShipment::class,
mappedBy: 'shipment',
cascade: ['persist', 'remove'],
orphanRemoval: true
)]
#[Groups(['shipment:read', 'shipment:write'])] #[Groups(['shipment:read', 'shipment:write'])]
private Collection $bovinShipments; #[ApiProperty(readableLink: true)]
private ?ShipmentType $shipmentType = null;
#[ORM\Column]
#[Groups(['shipment:read', 'shipment:write'])]
private int $nbBovinSend = 0;
/** /**
* @var Collection<int, Weight> * @var Collection<int, Weight>
@@ -156,8 +154,7 @@ class Shipment
public function __construct() public function __construct()
{ {
$this->bovinShipments = new ArrayCollection(); $this->weights = new ArrayCollection();
$this->weights = new ArrayCollection();
} }
public function getId(): ?int public function getId(): ?int
@@ -165,14 +162,14 @@ class Shipment
return $this->id; return $this->id;
} }
public function getLicencePlate(): ?string public function getLicensePlate(): ?string
{ {
return $this->licencePlate; return $this->licensePlate;
} }
public function setLicencePlate(?string $licencePlate): void public function setLicensePlate(?string $licensePlate): void
{ {
$this->licencePlate = $licencePlate; $this->licensePlate = $licensePlate;
} }
public function getIdentificationNumber(): ?string public function getIdentificationNumber(): ?string
@@ -195,6 +192,26 @@ class Shipment
$this->currentStep = $currentStep; $this->currentStep = $currentStep;
} }
public function getShipmentType(): ?ShipmentType
{
return $this->shipmentType;
}
public function setShipmentType(?ShipmentType $shipmentType): void
{
$this->shipmentType = $shipmentType;
}
public function getNbBovinSend(): int
{
return $this->nbBovinSend;
}
public function setNbBovinSend(int $nbBovinSend): void
{
$this->nbBovinSend = $nbBovinSend;
}
public function getIsValid(): ?bool public function getIsValid(): ?bool
{ {
return $this->isValid; return $this->isValid;
@@ -261,37 +278,6 @@ class Shipment
$this->customer = $customer; $this->customer = $customer;
} }
public function getBovinShipments(): Collection
{
return $this->bovinShipments;
}
public function setBovinShipments(Collection $bovinShipments): void
{
$this->bovinShipments = $bovinShipments;
}
public function addBovinShipment(BovinShipment $bovinShipment): self
{
if (!$this->bovinShipments->contains($bovinShipment)) {
$this->bovinShipments->add($bovinShipment);
$bovinShipment->setShipment($this);
}
return $this;
}
public function removeBovinShipment(BovinShipment $bovinShipment): self
{
if ($this->bovinShipments->removeElement($bovinShipment)) {
if ($bovinShipment->getShipment() === $this) {
$bovinShipment->setShipment(null);
}
}
return $this;
}
/** /**
* @return Collection<int, Weight> * @return Collection<int, Weight>
*/ */

View File

@@ -7,6 +7,8 @@ namespace App\Entity;
use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Get; use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection; use ApiPlatform\Metadata\GetCollection;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Attribute\Groups; use Symfony\Component\Serializer\Attribute\Groups;
@@ -40,6 +42,14 @@ class ShipmentType
#[Groups(['shipment-type:read', 'shipment:read'])] #[Groups(['shipment-type:read', 'shipment:read'])]
private ?string $code = null; private ?string $code = null;
#[ORM\OneToMany(mappedBy: 'shipmentType', targetEntity: Shipment::class)]
private Collection $shipments;
public function __construct()
{
$this->shipments = new ArrayCollection();
}
public function getId(): ?int public function getId(): ?int
{ {
return $this->id; return $this->id;

View File

@@ -250,13 +250,11 @@
<td> <td>
<strong>Bovin</strong><br><br> <strong>Bovin</strong><br><br>
<div class="bigtable-notes"> <div class="bigtable-notes">
{% if shipment.bovinShipments is not empty %} {% if shipment.shipmentType %}
{% for entry in shipment.bovinShipments %} <p>
<p> {{ shipment.shipmentType.label ?? '-' }} :
{{ entry.shipmentType ? entry.shipmentType.label : '-' }} : {{ shipment.nbBovinSend ?? 0 }}
{{ entry.nbBovinSend ?? 0 }} </p>
</p>
{% endfor %}
{% else %} {% else %}
<p>-</p> <p>-</p>
{% endif %} {% endif %}
@@ -277,7 +275,7 @@
<div class="meta"> <div class="meta">
<p>Transporteur : {{ shipment.carrier ? shipment.carrier.name : '-' }}</p> <p>Transporteur : {{ shipment.carrier ? shipment.carrier.name : '-' }}</p>
<p>Mode d'expédition : {{ shipment.truck ? shipment.truck.name : '-' }}</p> <p>Mode d'expédition : {{ shipment.truck ? shipment.truck.name : '-' }}</p>
<p>Immatriculation : {{ shipment.licencePlate ?? '-' }}</p> <p>Immatriculation : {{ shipment.licensePlate ?? '-' }}</p>
</div> </div>
</td> </td>