Compare commits

..

1 Commits

Author SHA1 Message Date
359c4e27a5 feat : ajout loader skeleton 2026-02-13 15:20:35 +01:00
40 changed files with 206 additions and 373 deletions

View File

@@ -46,7 +46,7 @@ Ajouter dans le fichier .env du frontend
* [#324] Creation page admin listing clients * [#324] Creation page admin listing clients
* [#326] Admin modification creation client * [#326] Admin modification creation client
* [#325] Correction diverses * [#325] Correction diverses
* [#319] Réflexion sur des loaders de type skeleton
### Changed ### Changed
### Fixed ### Fixed

View File

@@ -1,2 +1,2 @@
parameters: parameters:
app.version: '0.0.47' app.version: '0.0.46'

View File

@@ -2,9 +2,9 @@
<template> <template>
<NuxtLink :to="link"> <NuxtLink :to="link">
<div class="w-[324px] h-[228px] border border-black rounded-lg p-6 flex flex-col justify-between"> <div class="w-[324px] h-[228px] border border-black rounded-md p-6 flex flex-col justify-between">
<div class="flex justify-between"> <div class="flex justify-between">
<div class="rounded-full w-[80px] h-[80px] bg-[#D9D9D9] flex justify-center items-center"> <div class="rounded-full w-[80px] h-[80px] bg-neutral-400 flex justify-center items-center">
<Icon :name="iconName" style="color: black" size="44" /> <Icon :name="iconName" style="color: black" size="44" />
</div> </div>
<div> <div>

View File

@@ -2,7 +2,7 @@
<div <div
v-if="receptionStore.current?.receptionType?.code === RECEPTION_TYPE_CODES.BOVINS" v-if="receptionStore.current?.receptionType?.code === RECEPTION_TYPE_CODES.BOVINS"
class="flex flex-col items-center gap-16"> class="flex flex-col items-center gap-16">
<h1 class="text-4xl uppercase font-bold">Sélection des races réceptionnées</h1> <h1 class="text-4xl uppercase font-bold">Sélection des marchandises réceptionnnées</h1>
<div <div
class="flex flex-row gap-8 items-center"> class="flex flex-row gap-8 items-center">
<div <div
@@ -10,7 +10,6 @@
:key="type.id" :key="type.id"
class="mt-8 flex flex-row mb-2 gap-6"> class="mt-8 flex flex-row mb-2 gap-6">
<UiNumberInput <UiNumberInput
:id="type.id"
:label="type.label" :label="type.label"
:code="type.code" :code="type.code"
v-model="bovineQuantities[String(type.id)]" v-model="bovineQuantities[String(type.id)]"
@@ -78,14 +77,14 @@ onMounted(async () => {
}) })
watch( watch(
[() => receptionId.value, () => bovineType.value], () => receptionId.value,
async ([id, types]) => { async (id) => {
if (!id || !receptionIri.value || types.length === 0) { if (!id || !receptionIri.value) {
return return
} }
const selectionMap: Record<string, number | null> = {} const selectionMap: Record<string, number | null> = {}
for (const type of types) { for (const type of bovineType.value) {
selectionMap[String(type.id)] = 0 selectionMap[String(type.id)] = 0
} }

View File

@@ -1,5 +1,6 @@
<template> <template>
<form @submit.prevent="validate"> <skeletonForm v-if="isPageLoading"/>
<form v-else @submit.prevent="validate">
<div class="grid grid-cols-2 items-start gap-y-8 gap-x-40 mb-16"> <div class="grid grid-cols-2 items-start gap-y-8 gap-x-40 mb-16">
<h1 class="font-bold text-5xl uppercase col-start-1 row-start-1">Réception</h1> <h1 class="font-bold text-5xl uppercase col-start-1 row-start-1">Réception</h1>
<!-- Nom de l'utilisateur --> <!-- Nom de l'utilisateur -->
@@ -91,7 +92,6 @@
label: driver.name label: driver.name
}))" }))"
:loading="isLoadingDrivers" :loading="isLoadingDrivers"
v-if="isLiotCarrier"
wrapper-class="col-start-2 row-start-4" wrapper-class="col-start-2 row-start-4"
/> />
<!-- Plaque d'immatriculation --> <!-- Plaque d'immatriculation -->
@@ -148,6 +148,7 @@ import {RECEPTION_TYPE_CODES, SUPPLIER_CODE} from "~/utils/constants";
import {deleteReceptionBovine, getReceptionBovineList} from "~/services/reception-bovine"; import {deleteReceptionBovine, getReceptionBovineList} from "~/services/reception-bovine";
import type {ReceptionFormData} from "~/services/dto/reception-data"; import type {ReceptionFormData} from "~/services/dto/reception-data";
const isPageLoading = ref(true)
const router = useRouter() const router = useRouter()
const receptionStore = useReceptionStore() const receptionStore = useReceptionStore()
const form = reactive<ReceptionFormData>({ const form = reactive<ReceptionFormData>({
@@ -330,15 +331,17 @@ const setDefaultUser = () => {
// On récupère toutes les données des selects au chargement du composant // On récupère toutes les données des selects au chargement du composant
onMounted(async () => { onMounted(async () => {
receptionTypes.value = await getReceptionTypeList() receptionTypes.value = await getReceptionTypeList()
await loadUsers() await loadUsers()
await loadSuppliers() await loadSuppliers()
await loadTrucks() await loadTrucks()
await loadCarriers() await loadCarriers()
await loadDrivers() await loadDrivers()
await loadVehicles() await loadVehicles()
await authStore.ensureSession() await authStore.ensureSession()
setDefaultUser() setDefaultUser()
//isPageLoading.value = false
}) })
// Ajuste driver/vehicle quand le transporteur change (logique LIOT) // Ajuste driver/vehicle quand le transporteur change (logique LIOT)

View File

@@ -26,7 +26,7 @@
<div <div
v-if="selectedMerchandiseTypeId && !isGranule" v-if="selectedMerchandiseTypeId && !isGranule"
class="flex gap-4 w-[550px] justify-between" class="flex gap-4 w-[550px] justify-evenly"
> >
<div <div
v-for="building in buildings" v-for="building in buildings"
@@ -51,13 +51,13 @@
<div <div
v-for="building in buildings" v-for="building in buildings"
:key="building.id" :key="building.id"
class="flex items-center gap-2 text-lg pl-[2px]" class="flex items-center gap-2 text-lg"
> >
<UiCheckbox <UiCheckbox
v-model="selectedPelletBuildingIds[String(type.id)]" v-model="selectedPelletBuildingIds[String(type.id)]"
:value="String(building.id)" :value="String(building.id)"
:label="building.label" :label="building.label"
label-class="text-xl" label-class="text-lg"
/> />
</div> </div>
</div> </div>

View File

@@ -83,14 +83,14 @@ onMounted(async () => {
}) })
watch( watch(
[() => receptionId, () => bovineType.value], () => receptionId,
async ([id, types]) => { async (id) => {
if (!id || !receptionIri.value || types.length === 0) { if (!id || !receptionIri.value) {
return return
} }
const selectionMap: Record<string, number | null> = {} const selectionMap: Record<string, number | null> = {}
for (const type of types) { for (const type of bovineType.value) {
selectionMap[String(type.id)] = 0 selectionMap[String(type.id)] = 0
} }
@@ -105,7 +105,7 @@ watch(
} }
Object.assign(bovineQuantities, selectionMap) Object.assign(bovineQuantities, selectionMap)
const existingOther = reception.bovineDetail const existingOther = await reception.bovineDetail
const parsedOther = const parsedOther =
typeof existingOther === 'string' && existingOther.trim() !== '' typeof existingOther === 'string' && existingOther.trim() !== ''
? Number(existingOther) ? Number(existingOther)

View File

@@ -1,10 +1,9 @@
<template> <template>
<form @submit.prevent="validate"> <form @submit.prevent="validate">
<div class="grid grid-cols-2 gap-x-40 gap-y-8 mb-8"> <div class="grid grid-cols-2 gap-x-40 gap-y-8 mb-8">
<UiNumberInput <UiTextInput
label="Dsd" label="Dsd"
class="col-start-2" class="col-start-2"
labelClass="font-bold uppercase"
v-model="sharedWeightMeta.dsd" v-model="sharedWeightMeta.dsd"
:disabled="!auth.isAdmin" :disabled="!auth.isAdmin"
/> />
@@ -20,12 +19,9 @@
:key="weight.type" :key="weight.type"
:label="getWeightLabel(weight.type)" :label="getWeightLabel(weight.type)"
labelClass="font-bold uppercase text-xl" labelClass="font-bold uppercase text-xl"
inputClass="w-24"
v-model="weight.weight" v-model="weight.weight"
:wrapper-class="weight.type === 'tare' ? 'col-start-1 row-start-1' : 'col-start-2 row-start-1'" :wrapper-class="weight.type === 'tare' ? 'col-start-1 row-start-1' : 'col-start-2 row-start-1'"
:disabled="!auth.isAdmin" :disabled="!auth.isAdmin"
:min="0"
:max="48000"
/> />
</div> </div>

View File

@@ -1,5 +1,6 @@
<template> <template>
<form @submit.prevent="validate"> <skeletonForm v-if="isPageLoading"/>
<form v-else @submit.prevent="validate">
<div class="grid grid-cols-2 items-start gap-y-8 gap-x-40 mb-16"> <div class="grid grid-cols-2 items-start gap-y-8 gap-x-40 mb-16">
<h1 class="font-bold text-5xl uppercase col-start-1 row-start-1">Expédition</h1> <h1 class="font-bold text-5xl uppercase col-start-1 row-start-1">Expédition</h1>
<!-- Nom de l'utilisateur --> <!-- Nom de l'utilisateur -->
@@ -22,27 +23,24 @@
wrapper-class="col-start-1 row-start-3" wrapper-class="col-start-1 row-start-3"
/> />
<!-- Type d'expédition --> <!-- Type d'expédition -->
<div class="col-start-1 row-start-4 h-[64px]"> <div class="col-start-1 row-start-4">
<div class="flex items-end gap-8"> <label class="font-bold uppercase text-xl mb-2">
<UiRadioGroup Type d'expédition
id="shipment-type" </label>
name="shipment-type" <div class="grid grid-cols-2 gap-x-8">
label="Type d'expédition" <div
v-model="selectedShipmentTypeId" v-for="type in bovineShipment"
:options="bovineShipment.map((type) => ({ :key="type.id"
value: String(type.id), class="mt-2 flex flex-row gap-6"
label: type.label >
}))" <UiNumberInput
/> :label="type.label"
<UiNumberInput v-model="bovineQuantities[String(type.id)]"
id="shipment-type-quantity" :placeholder="0"
label="Quantité" :min="0"
v-model="shipmentQuantity" :max="10"
:placeholder="0" />
:min="0" </div>
:max="1200"
:disabled="!selectedShipmentTypeId"
/>
</div> </div>
</div> </div>
<!-- Client --> <!-- Client -->
@@ -100,7 +98,6 @@
}))" }))"
:loading="isLoadingDrivers" :loading="isLoadingDrivers"
wrapper-class="col-start-2 row-start-4" wrapper-class="col-start-2 row-start-4"
v-if="isLiotCarrier"
/> />
<!-- Plaque d'immatriculation (hors LIOT) --> <!-- Plaque d'immatriculation (hors LIOT) -->
<div v-if="!isLiotCarrier" class="col-start-2 row-start-5"> <div v-if="!isLiotCarrier" class="col-start-2 row-start-5">
@@ -127,7 +124,7 @@
<div class="flex justify-center"> <div class="flex justify-center">
<button <button
type="submit" type="submit"
class="text-xl uppercase bg-primary-500 text-white h-[50px] w-[272px] justify-self-end" class="text-xl uppercase bg-primary-500 text-white h-[50px] w-[272px]"
>Valider >Valider
</button> </button>
</div> </div>
@@ -152,13 +149,12 @@ 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 { import {
createShipmentBovine, createShipmentBovine,
deleteShipmentBovine, deleteShipmentBovine,
getBovinShipmentList,
updateShipmentBovine updateShipmentBovine
} from "~/services/bovin-shipment"; } from "~/services/bovin-shipment";
@@ -168,7 +164,7 @@ const trucks = ref<TruckData[]>([])
const carriers = ref<CarrierData[]>([]) const carriers = ref<CarrierData[]>([])
const drivers = ref<DriverData[]>([]) const drivers = ref<DriverData[]>([])
const vehicles = ref<VehicleData[]>([]) const vehicles = ref<VehicleData[]>([])
const isPageLoading = ref(true)
const isLoadingUsers = ref(false) const isLoadingUsers = ref(false)
const isLoadingShipmentTypes = ref(false) const isLoadingShipmentTypes = ref(false)
const isLoadingCustomers = ref(false) const isLoadingCustomers = ref(false)
@@ -181,9 +177,8 @@ const isLoadingDrivers = ref(false)
const authStore = useAuthStore() const authStore = useAuthStore()
const shipmentStore = useShipmentStore() const shipmentStore = useShipmentStore()
const router = useRouter() const router = useRouter()
const bovineQuantities = ref<Record<string, number | null>>({})
const bovineShipment = ref<ShipmentTypeData[]>([]) const bovineShipment = ref<ShipmentTypeData[]>([])
const selectedShipmentTypeId = ref('')
const shipmentQuantity = ref<number | null>(0)
// Transporteur sélectionné dans le formulaire // Transporteur sélectionné dans le formulaire
const selectedCarrier = computed(() => const selectedCarrier = computed(() =>
carriers.value.find((carrier) => String(carrier.id) === form.carrierId) ?? null carriers.value.find((carrier) => String(carrier.id) === form.carrierId) ?? null
@@ -315,6 +310,7 @@ onMounted(async () => {
await loadDrivers() await loadDrivers()
await authStore.ensureSession() await authStore.ensureSession()
setDefaultUser() setDefaultUser()
isPageLoading.value = false
}) })
// Hydrate le formulaire depuis l'expédition en cours // Hydrate le formulaire depuis l'expédition en cours
watch( watch(
@@ -333,21 +329,15 @@ watch(
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) { if (!shipment || !shipment.bovinShipments) {
selectedShipmentTypeId.value = '' bovineQuantities.value = {}
shipmentQuantity.value = 0
} else { } else {
const selectedEntry = shipment.bovinShipments.find((entry) => { const next: Record<string, number | null> = {}
for (const entry of shipment.bovinShipments) {
const typeId = entry.shipmentType?.id const typeId = entry.shipmentType?.id
return Boolean(typeId) && Number(entry.nbBovinSend ?? 0) > 0 if (!typeId) continue
}) ?? shipment.bovinShipments.find((entry) => Boolean(entry.shipmentType?.id)) next[String(typeId)] = entry.nbBovinSend ?? null
if (!selectedEntry?.shipmentType?.id) {
selectedShipmentTypeId.value = ''
shipmentQuantity.value = 0
} else {
selectedShipmentTypeId.value = String(selectedEntry.shipmentType.id)
shipmentQuantity.value = selectedEntry.nbBovinSend ?? 0
} }
bovineQuantities.value = next
} }
isHydrating.value = false isHydrating.value = false
}, },
@@ -475,22 +465,16 @@ watch(
} }
) )
const buildDesiredBovinShipments = () => { const buildDesiredBovinShipments = () => {
const typeId = Number(selectedShipmentTypeId.value) return bovineShipment.value
if (!Number.isFinite(typeId)) { .map((type) => {
return [] const raw = bovineQuantities.value[String(type.id)]
} const quantity = raw === null || raw === undefined ? 0 : Number(raw)
const type = bovineShipment.value.find((entry) => entry.id === typeId) return {
if (!type) { type,
return [] quantity: Number.isFinite(quantity) ? Math.max(0, Math.trunc(quantity)) : 0
} }
const raw = shipmentQuantity.value })
const quantity = raw === null || raw === undefined ? 0 : Number(raw) .filter((entry) => entry.quantity > 0)
const normalizedQuantity = Number.isFinite(quantity) ? Math.max(0, Math.trunc(quantity)) : 0
if (normalizedQuantity <= 0) {
return []
}
return [{type, quantity: normalizedQuantity}]
} }
const syncBovinShipments = async ( const syncBovinShipments = async (
shipmentId: number, shipmentId: number,

View File

@@ -1,26 +0,0 @@
<template>
<div class="flex flex-col items-center gap-16">
<h1 class="font-bold text-5xl uppercase">Charment des bovins</h1>
<div
class="w-full flex flex-col items-center justify-center">
<UiLoadingDots />
</div>
<button
class="text-xl uppercase bg-primary-500 text-white h-[50px] w-[272px] ml-4"
@click="goNext"
>Pesée</button>
</div>
</template>
<script setup lang="ts">
import {useShipmentStore} from "~/stores/shipment";
const shipmentStore = useShipmentStore()
const goNext = async () => {
const nextStep = shipmentStore.current.currentStep + 1
await shipmentStore.updateShipment(shipmentStore.current.id, {
currentStep: nextStep
})
}
</script>

View File

@@ -0,0 +1,17 @@
<template>
<div class="grid grid-cols-2 items-start gap-y-8 gap-x-40 mb-16">
<UiSkeletonBlock height="48px"/>
<div class="flex flex-col gap-2" v-for="i in 9">
<UiSkeletonBlock width="96px"/>
<UiSkeletonBlock width="100%" height="42px"/>
</div>
</div>
<div class="flex justify-center">
<UiSkeletonBlock
width="272px"
height="50px"
/>
</div>
</template>
<script setup lang="ts">
</script>

View File

@@ -0,0 +1,13 @@
<template>
<div class="ps-20">
<UiSkeletonBlock height="48px" class="my-4"/>
<div class="grid grid-cols-3 justify-evenly bg-slate-100 py-4">
<UiSkeletonBlock v-for="i in 3"/>
</div>
<div
class="grid grid-cols-3 gap-4 px-4 py-4 text-sm hover:bg-slate-50 cursor-pointer border-t border-slate-200"
v-for="i in 3">
<UiSkeletonBlock v-for="i in 3"/>
</div>
</div>
</template>

View File

@@ -1,14 +1,14 @@
<template> <template>
<div :class="wrapperClass"> <div :class="wrapperClass">
<label <label
class="flex items-center gap-2 cursor-pointer" class="flex items-center gap-2"
:class="labelClass" :class="labelClass"
> >
<input <input
type="checkbox" type="checkbox"
:checked="checked" :checked="checked"
:disabled="disabled" :disabled="disabled"
:class="['cursor-pointer', inputClass]" :class="inputClass"
@change="onChange" @change="onChange"
> >
<span v-if="label">{{ label }}</span> <span v-if="label">{{ label }}</span>

View File

@@ -3,7 +3,7 @@
<label <label
v-if="label" v-if="label"
:for="id" :for="id"
class="font-bold uppercase text-xl" class="font-bold uppercase text-xl mb-2"
:class="labelClass" :class="labelClass"
> >
{{ label }} {{ label }}
@@ -14,7 +14,7 @@
:value="modelValue ?? ''" :value="modelValue ?? ''"
:disabled="disabled" :disabled="disabled"
v-bind="attrs" v-bind="attrs"
class="border-b border-black justify-self-start text-xl py-[6px] uppercase bg-transparent appearance-none h-[34px]" class="border-b border-black justify-self-start text-xl pb-[6px] uppercase bg-transparent appearance-none h-[34px]"
:class="[ :class="[
isEmpty ? 'text-neutral-400' : 'text-black', isEmpty ? 'text-neutral-400' : 'text-black',
disabled ? 'cursor-not-allowed' : 'cursor-pointer', disabled ? 'cursor-not-allowed' : 'cursor-pointer',

View File

@@ -3,7 +3,7 @@
<label <label
v-if="label" v-if="label"
:for="id" :for="id"
class="text-xl flex items-center gap-2" class="text-xl flex items-center"
:class="labelClass" :class="labelClass"
> >
<span <span
@@ -74,41 +74,14 @@ const emit = defineEmits<{
const attrs = useAttrs() const attrs = useAttrs()
const isEmpty = computed(() => props.modelValue === null || props.modelValue === undefined || props.modelValue === '') const isEmpty = computed(() => props.modelValue === null || props.modelValue === undefined || props.modelValue === '')
const toNumberOrNull = (value: number | string | undefined) => {
if (value === undefined || value === '') {
return null
}
const parsed = Number(value)
return Number.isFinite(parsed) ? parsed : null
}
const onInput = (event: Event) => { const onInput = (event: Event) => {
const target = event.target as HTMLInputElement const target = event.target as HTMLInputElement
if (target.value === '') { if (target.value === '') {
emit('update:modelValue', null) emit('update:modelValue', null)
return return
} }
const parsed = Number(target.value) const numeric = Math.max(0, Number(target.value))
if (!Number.isFinite(parsed)) { emit('update:modelValue', Number.isNaN(numeric) ? null : numeric)
emit('update:modelValue', null)
return
}
const min = toNumberOrNull(props.min)
const max = toNumberOrNull(props.max)
let numeric = parsed
if (min !== null) {
numeric = Math.max(min, numeric)
} else {
numeric = Math.max(0, numeric)
}
if (max !== null) {
numeric = Math.min(max, numeric)
}
target.value = String(numeric)
emit('update:modelValue', numeric)
} }
const onKeydown = (event: KeyboardEvent) => { const onKeydown = (event: KeyboardEvent) => {

View File

@@ -1,93 +0,0 @@
<template>
<div :class="['flex flex-col', wrapperClass]">
<label
v-if="label"
class="font-bold uppercase text-xl"
:class="labelClass"
>
{{ label }}
</label>
<div
role="radiogroup"
:aria-label="label || id || 'radio-group'"
:class="['flex items-center gap-6 mt-1', groupClass]"
>
<label
v-for="option in options"
:key="String(option.value)"
:for="`${id || 'radio'}-${option.value}`"
class="flex items-center gap-2"
:class="itemClass"
>
<input
:id="`${id || 'radio'}-${option.value}`"
type="radio"
:name="name || id || 'radio-group'"
:value="String(option.value)"
:checked="String(modelValue ?? '') === String(option.value)"
:disabled="disabled"
v-bind="attrs"
class="h-4 w-4 border-slate-300 text-primary-500 focus:ring-primary-500"
:class="[
disabled ? 'cursor-not-allowed' : 'cursor-pointer',
inputClass
]"
@change="onChange"
>
<span class="text-xl" :class="optionLabelClass">
{{ option.label }}
</span>
</label>
</div>
</div>
</template>
<script setup lang="ts">
import { useAttrs } from 'vue'
type RadioOption = {
value: string | number
label: string
}
defineOptions({ inheritAttrs: false })
const props = withDefaults(
defineProps<{
id?: string
name?: string
label?: string
modelValue: string | number | null | undefined
options: RadioOption[]
disabled?: boolean
wrapperClass?: string
labelClass?: string
groupClass?: string
itemClass?: string
inputClass?: string
optionLabelClass?: string
}>(),
{
name: '',
label: '',
disabled: false,
wrapperClass: '',
labelClass: '',
groupClass: '',
itemClass: '',
inputClass: '',
optionLabelClass: ''
}
)
const emit = defineEmits<{
(event: 'update:modelValue', value: string): void
}>()
const attrs = useAttrs()
const onChange = (event: Event) => {
const target = event.target as HTMLInputElement
emit('update:modelValue', target.value)
}
</script>

View File

@@ -3,7 +3,7 @@
<label <label
v-if="label" v-if="label"
:for="id" :for="id"
class="font-bold uppercase text-xl" class="font-bold uppercase text-xl mb-2"
:class="labelClass" :class="labelClass"
> >
{{ label }} {{ label }}
@@ -13,7 +13,7 @@
:value="modelValue ?? ''" :value="modelValue ?? ''"
:disabled="disabled || loading" :disabled="disabled || loading"
v-bind="attrs" v-bind="attrs"
class="border-b border-black justify-self-start text-xl py-[6px] bg-transparent" class="border-b border-black justify-self-start text-xl pb-[6px] bg-transparent"
:class="[ :class="[
isEmpty ? 'text-neutral-400' : 'text-black', isEmpty ? 'text-neutral-400' : 'text-black',
disabled || loading ? 'cursor-not-allowed' : 'cursor-pointer', disabled || loading ? 'cursor-not-allowed' : 'cursor-pointer',

View File

@@ -0,0 +1,22 @@
<template>
<div
:class="['animate-pulse', rounded, customClass]"
:style="{ width, height, background }"
/>
</template>
<script setup lang="ts">
withDefaults(defineProps<{
width?: string
height?: string
rounded?: string
background?: string
customClass?: string
}>(), {
width: '50%',
height: '1rem',
rounded: 'rounded-md',
background: '#e5e7eb',
customClass: ''
})
</script>

View File

@@ -3,7 +3,7 @@
<label <label
v-if="label" v-if="label"
:for="id" :for="id"
class="font-bold uppercase text-xl" class="font-bold uppercase text-xl mb-2"
:class="labelClass" :class="labelClass"
> >
{{ label }} {{ label }}
@@ -16,7 +16,7 @@
:maxlength="maxlength" :maxlength="maxlength"
:disabled="disabled" :disabled="disabled"
v-bind="attrs" v-bind="attrs"
class="border-b border-black text-xl py-[6px] bg-transparent" class="border-b border-black text-xl pb-[6px] bg-transparent"
:class="[ :class="[
isEmpty ? 'text-neutral-400' : 'text-black', isEmpty ? 'text-neutral-400' : 'text-black',
disabled ? 'cursor-not-allowed' : 'cursor-text', disabled ? 'cursor-not-allowed' : 'cursor-text',

View File

@@ -1,6 +1,6 @@
<template> <template>
<div class="flex flex-col"> <div class="flex flex-col">
<label :for="inputId" class="font-bold uppercase text-xl">{{ label }}</label> <label :for="inputId" class="font-bold uppercase text-xl mb-2">{{ label }}</label>
<div class="flex items-end gap-8"> <div class="flex items-end gap-8">
<input <input
:id="inputId" :id="inputId"

View File

@@ -1,10 +1,9 @@
export enum StepLabel { export enum StepLabel {
Reception = 'Réception', Reception = 'Réception',
GrossWeighing = 'Pesée à plein', GrossWeighing = 'Pesée à plein',
Selection = 'Sélection réception', Selection = 'Sélection réceptionnées',
TareWeighing = 'Pesée à vide', TareWeighing = 'Pesée à vide',
Shipment = 'Expédition', Shipment = 'Expédition',
ShipmentLoading = 'Chargement',
} }
export const RECEPTION_STEP_LABELS = [ export const RECEPTION_STEP_LABELS = [
@@ -17,6 +16,5 @@ export const RECEPTION_STEP_LABELS = [
export const SHIPMENT_STEP_LABELS = [ export const SHIPMENT_STEP_LABELS = [
StepLabel.Shipment, StepLabel.Shipment,
StepLabel.TareWeighing, StepLabel.TareWeighing,
StepLabel.ShipmentLoading,
StepLabel.GrossWeighing, StepLabel.GrossWeighing,
] ]

View File

@@ -60,14 +60,8 @@
Utilisateurs Utilisateurs
</a> </a>
</NuxtLink> </NuxtLink>
<NuxtLink to="/admin/customer/customer-list" custom v-slot="{ href, navigate }"> <NuxtLink to="/admin/customer/customer-list">
<a Client
:href="href"
@click="navigate"
:class="route.path.startsWith('/admin/customer') ? 'opacity-100' : 'opacity-50'"
>
Clients
</a>
</NuxtLink> </NuxtLink>
</div> </div>

View File

@@ -104,7 +104,7 @@
<main class="mx-auto w-full max-w-[1280px]"> <main class="mx-auto w-full max-w-[1280px]">
<slot/> <slot/>
</main> </main>
<footer class="w-full mt-8 bg-primary-500 px-6 py-3"> <footer class="w-full mt-8 bg-primary-500 p-6">
<p class="font-bold text-white text-right">v{{ version }}</p> <p class="font-bold text-white text-right">v{{ version }}</p>
</footer> </footer>
</div> </div>

View File

@@ -46,6 +46,6 @@ definePageMeta({
}) })
onMounted(async () => { onMounted(async () => {
carrierList.value = await getCarrierList(false) carrierList.value = await getCarrierList()
}) })
</script> </script>

View File

@@ -28,7 +28,7 @@
{{ user.username }} {{ user.username }}
</div> </div>
<div> <div>
{{ getRoleLabels(user.roles) }} {{ user.roles?.join(', ') || ' ---' }}
</div> </div>
</div> </div>
</div> </div>
@@ -42,27 +42,15 @@ definePageMeta({
}) })
import type {UserData} from "~/services/dto/user-data"; import type {UserData} from "~/services/dto/user-data";
import {getAdminUsers} from "~/services/auth"; import {getAdminUsers, getUsers} from "~/services/auth";
import {ROLE} from "~/utils/constants";
const userList = ref<UserData[]>([]) const userList = ref<UserData[]>([])
const router = useRouter() const router = useRouter()
const roleLabelByValue = new Map(ROLE.map((role) => [role.value, role.label]))
const goToUser = (id: number) => { const goToUser = (id: number) => {
router.push(`/admin/user/${id}`) router.push(`/admin/user/${id}`)
} }
const getRoleLabels = (roles?: string[]) => {
if (!roles || roles.length === 0) {
return ' ---'
}
return roles
.map((role) => roleLabelByValue.get(role) ?? role)
.join(', ')
}
onMounted(async () => { onMounted(async () => {
userList.value = await getAdminUsers() userList.value = await getAdminUsers()
}) })

View File

@@ -1,13 +1,13 @@
<script setup lang="ts"> <script setup lang="ts">
</script> </script>
<template> <template>
<div class="flex flex-wrap justify-center mt-8 gap-12 mb-8 md:mb-0"> <div class="flex flex-wrap justify-center mt-8 gap-8 mb-8 md:mb-0">
<card-link label="NOUVELLE RÉCEPTION" link="/reception" iconName="mdi:truck-outline" /> <card-link label="NOUVELLE RÉCEPTION" link="/reception" iconName="mdi:truck-outline" />
<card-link label="NOUVELLE EXPÉDITION" link="/shipment" iconName="mdi:truck-fast-outline" /> <card-link label="NOUVELLE EXPÉDITION" link="/shipment" iconName="mdi:truck-fast-outline" />
<card-link label="PLAN DE SITE" link="/" iconName="material-symbols:warehouse-outline-rounded" /> <card-link label="PLAN DE SITE" link="/" iconName="mdi:warehouse" />
<card-link label="RÉCEPTIONS EN ATTENTE" link="/reception/waiting-reception" iconName="mdi:truck-remove-outline" /> <card-link label="RÉCEPTIONS EN ATTENTE" link="/reception/waiting-reception" iconName="mdi:truck-remove-outline" />
<card-link label="EXPÉDITIONS EN ATTENTE" link="/shipment/waiting-shipment" iconName="mdi:truck-cargo-container" /> <card-link label="EXPÉDITIONS EN ATTENTE" link="/shipment/waiting-shipment" iconName="mdi:truck-cargo-container" />
<card-link label="CASES" link="/" iconName="material-symbols:bottom-sheets-outline" /> <card-link label="CASES" link="/" iconName="mdi:cube-outline" />
<card-link label="RÉCEPTIONS FINIES" link="/reception/finish-reception" iconName="mdi:truck-check-outline" /> <card-link label="RÉCEPTIONS FINIES" link="/reception/finish-reception" iconName="mdi:truck-check-outline" />
<card-link label="EXPÉDITIONS FINIES" link="/shipment/finish-shipment" iconName="mdi:truck-delivery-outline" /> <card-link label="EXPÉDITIONS FINIES" link="/shipment/finish-shipment" iconName="mdi:truck-delivery-outline" />
<card-link label="PASSEPORT DU BOVIN" link="/" iconName="mdi:cow" /> <card-link label="PASSEPORT DU BOVIN" link="/" iconName="mdi:cow" />

View File

@@ -1,28 +1,29 @@
<template> <template>
<div class="flex justify-between h-[52px] mt-16 mb-[80px]"> <div>
<div class="flex flex-1 mr-16"> <div class="flex justify-between h-[52px] mt-6 mb-[80px]">
<UiStepper <div class="flex flex-1 mr-16">
:labels="RECEPTION_STEP_LABELS" <UiStepper
:current-step="storeReception?.currentStep ?? 0" :labels="RECEPTION_STEP_LABELS"
@select="handleStepSelect" :current-step="storeReception?.currentStep ?? 0"
/> @select="handleStepSelect"
/>
</div>
<button
type="button"
class="flex flex-col justify-center uppercase text-xl bg-black text-white h-[50px] w-[272px] text-center"
@click="saveAndHold"
>Mettre en attente</button>
</div> </div>
<button <ReceptionForm v-if="!storeReception || storeReception.currentStep === 0"/>
type="button" <ReceptionWeight v-if="storeReception?.currentStep === 1" mode="gross"/>
class="flex flex-col justify-center uppercase text-xl bg-black text-white h-[50px] w-[272px] text-center" <ReceptionProductReceived
@click="saveAndHold" v-if="storeReception?.currentStep === 2 &&
>Mettre en attente
</button>
</div>
<ReceptionForm v-if="!storeReception || storeReception.currentStep === 0"/>
<ReceptionWeight v-if="storeReception?.currentStep === 1" mode="gross"/>
<ReceptionProductReceived
v-if="storeReception?.currentStep === 2 &&
receptionStore.current?.receptionType?.code === RECEPTION_TYPE_CODES.MERCHANDISES"/> receptionStore.current?.receptionType?.code === RECEPTION_TYPE_CODES.MERCHANDISES"/>
<ReceptionBovineReceived <ReceptionBovineReceived
v-if="storeReception?.currentStep === 2 && v-if="storeReception?.currentStep === 2 &&
receptionStore.current?.receptionType?.code === RECEPTION_TYPE_CODES.BOVINS"/> receptionStore.current?.receptionType?.code === RECEPTION_TYPE_CODES.BOVINS"/>
<ReceptionWeight v-if="storeReception?.currentStep !== null && storeReception?.currentStep >= 3" mode="tare"/> <ReceptionWeight v-if="storeReception?.currentStep !== null && storeReception?.currentStep >= 3" mode="tare"/>
</div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">

View File

@@ -1,10 +1,10 @@
<template> <template>
<div class="flex items-center justify-start gap-10 mt-16"> <skeletonTable v-if="isPageLoading"/>
<Icon @click="router.push('/')" name="gg:arrow-left-o" style="color: black" size="44" class="cursor-pointer"/> <div v-else class="ps-20">
<div class="flex items-center justify-start gap-10">
<Icon @click="router.push('/')" name="gg:arrow-left-o" style="color: black" size="44"/>
<h1 class="text-3xl font-bold uppercase">listes des réceptions finie</h1> <h1 class="text-3xl font-bold uppercase">listes des réceptions finie</h1>
</div> </div>
<div class="px-[86px]">
<div class="mt-6 border border-slate-200 mb-16 "> <div class="mt-6 border border-slate-200 mb-16 ">
<div class="grid grid-cols-6 gap-4 bg-slate-100 px-4 py-3 text-sm font-semibold uppercase tracking-wide"> <div class="grid grid-cols-6 gap-4 bg-slate-100 px-4 py-3 text-sm font-semibold uppercase tracking-wide">
<div>Numéro</div> <div>Numéro</div>
@@ -27,7 +27,7 @@
<div>{{ reception.supplier?.name }}</div> <div>{{ reception.supplier?.name }}</div>
<div>{{ reception.address?.fullAddress }}</div> <div>{{ reception.address?.fullAddress }}</div>
<div>{{ reception.receptionType?.label }}</div> <div>{{ reception.receptionType?.label }}</div>
<div>Plein : {{ formatWeighing(reception, 'gross') }} <br> Vide : {{ formatWeighing(reception, 'tare') }}</div> <div>{{ formatWeighing(reception, 'gross') }} | {{ formatWeighing(reception, 'tare') }}</div>
</div> </div>
</div> </div>
</div> </div>
@@ -39,7 +39,7 @@ import {getReceptionList} from "~/services/reception";
const receptionList = ref<ReceptionData[]>() const receptionList = ref<ReceptionData[]>()
const router = useRouter() const router = useRouter()
const isPageLoading = ref(true)
const formatWeighing = (reception: ReceptionData, type: 'gross' | 'tare') => { const formatWeighing = (reception: ReceptionData, type: 'gross' | 'tare') => {
const entry = reception.weights?.find((weight) => weight.type === type) const entry = reception.weights?.find((weight) => weight.type === type)
if (!entry || entry.weight == null || entry.dsd == null) { if (!entry || entry.weight == null || entry.dsd == null) {
@@ -54,5 +54,6 @@ const goToReception = (id: number) => {
onMounted(async () => { onMounted(async () => {
receptionList.value = await getReceptionList(true) receptionList.value = await getReceptionList(true)
isPageLoading.value = false
}) })
</script> </script>

View File

@@ -1,12 +1,12 @@
<template> <template>
<div class="flex items-center justify-between mt-16"> <div class="flex items-center justify-between ">
<div class="flex items-center gap-10"> <div class="flex items-center gap-10">
<Icon @click="router.push('/')" name="gg:arrow-left-o" style="color: black" size="44" class="cursor-pointer"/> <Icon @click="router.push('/')" name="gg:arrow-left-o" style="color: black" size="44" />
<h1 class="text-3xl font-bold uppercase">listes des réceptions en attente</h1> <h1 class="text-3xl font-bold uppercase">listes des réceptions en attente</h1>
</div> </div>
</div> </div>
<div class="px-[86px]"> <div class="ps-20 " >
<div class="mt-6 border border-slate-200 mb-16 "> <div class="mt-6 border border-slate-200 mb-16 ">
<div class="grid grid-cols-5 gap-4 bg-slate-100 px-4 py-3 text-sm font-semibold uppercase tracking-wide"> <div class="grid grid-cols-5 gap-4 bg-slate-100 px-4 py-3 text-sm font-semibold uppercase tracking-wide">
<div>Fournisseur</div> <div>Fournisseur</div>

View File

@@ -1,6 +1,6 @@
<template> <template>
<div> <div>
<div class="flex justify-between h-[52px] mt-16 mb-[80px]"> <div class="flex justify-between h-[52px] mt-6 mb-[80px]">
<div class="flex flex-1 mr-16"> <div class="flex flex-1 mr-16">
<UiStepper <UiStepper
:labels="SHIPMENT_STEP_LABELS" :labels="SHIPMENT_STEP_LABELS"
@@ -18,8 +18,7 @@
</div> </div>
<ShipmentForm v-if="!storeShipment || storeShipment.currentStep === 0" ref="shipmentFormRef"/> <ShipmentForm v-if="!storeShipment || storeShipment.currentStep === 0" ref="shipmentFormRef"/>
<ShipmentWeight v-if="storeShipment?.currentStep === 1" mode="gross"/> <ShipmentWeight v-if="storeShipment?.currentStep === 1" mode="gross"/>
<ShipmentLoading v-if="storeShipment?.currentStep === 2"/> <ShipmentWeight v-if="storeShipment?.currentStep >= 2" mode="tare"/>
<ShipmentWeight v-if="storeShipment?.currentStep === 3" mode="tare"/>
</div> </div>
</template> </template>

View File

@@ -1,10 +1,11 @@
<template> <template>
<div class="flex items-center justify-start gap-10 mt-16"> <skeletonTable v-if="isPageLoading"/>
<Icon @click="router.push('/')" name="gg:arrow-left-o" style="color: black" size="44" class="cursor-pointer"/> <div v-else class="ps-20 ">
<div class="flex items-center justify-start gap-10">
<Icon @click="router.push('/')" name="gg:arrow-left-o" style="color: black" size="44"/>
<h1 class="text-3xl font-bold uppercase">listes des expéditions finie</h1> <h1 class="text-3xl font-bold uppercase">listes des expéditions finie</h1>
</div> </div>
<div class="px-[86px]">
<div class="mt-6 border border-slate-200 mb-16 "> <div class="mt-6 border border-slate-200 mb-16 ">
<div class="grid grid-cols-6 gap-4 bg-slate-100 px-4 py-3 text-sm font-semibold uppercase tracking-wide"> <div class="grid grid-cols-6 gap-4 bg-slate-100 px-4 py-3 text-sm font-semibold uppercase tracking-wide">
<div>Numéro</div> <div>Numéro</div>
@@ -21,11 +22,11 @@
class="grid grid-cols-6 gap-4 px-4 py-3 text-sm hover:bg-slate-50 cursor-pointer border-t border-slate-200" class="grid grid-cols-6 gap-4 px-4 py-3 text-sm hover:bg-slate-50 cursor-pointer border-t border-slate-200"
role="button" role="button"
tabindex="0" tabindex="0"
@click="goShipment(shipment.id)" @click="goToshipment(shipment.id)"
> >
<div>{{ shipment.identificationNumber }}</div> <div>{{ shipment.identificationNumber }}</div>
<div>{{ shipment.shipmentDate }}</div> <div>{{ shipment.shipmentDate }}</div>
<div>{{ shipment.customer?.name }}</div> <div>{{ shipment.customer?.label }}</div>
<div>{{ shipment.address?.fullAddress }}</div> <div>{{ shipment.address?.fullAddress }}</div>
<div> <div>
<template v-if="formatBovinShipmentLines(shipment).length"> <template v-if="formatBovinShipmentLines(shipment).length">
@@ -38,7 +39,7 @@
</div> </div>
</template> </template>
</div> </div>
<div>Vide : {{ formatWeighing(shipment, 'tare') }} <br> Plein :{{ formatWeighing(shipment, 'gross') }}</div> <div>{{ formatWeighing(shipment, 'gross') }} | {{ formatWeighing(shipment, 'tare') }}</div>
</div> </div>
</div> </div>
</div> </div>
@@ -50,7 +51,7 @@ import {getShipmentList} from "~/services/shipment";
const shipmentList = ref<ShipmentData[]>() const shipmentList = ref<ShipmentData[]>()
const router = useRouter() const router = useRouter()
const isPageLoading = ref(true)
const formatWeighing = (shipment: ShipmentData, type: 'gross' | 'tare') => { const formatWeighing = (shipment: ShipmentData, type: 'gross' | 'tare') => {
const entry = shipment.weights?.find((weight) => weight.type === type) const entry = shipment.weights?.find((weight) => weight.type === type)
if (!entry || entry.weight == null || entry.dsd == null) { if (!entry || entry.weight == null || entry.dsd == null) {
@@ -71,11 +72,13 @@ const formatBovinShipmentLines = (shipment: ShipmentData) => {
}) })
} }
const goShipment = (id: number) => { const goToshipment = (id: number) => {
router.push(`/shipment/update/${id}`) //router.push(`/shipment/update/${id}`)
} }
onMounted(async () => { onMounted(async () => {
shipmentList.value = await getShipmentList(true) shipmentList.value = await getShipmentList(true)
isPageLoading.value = false
}) })
</script> </script>

View File

@@ -1,12 +1,12 @@
<template> <template>
<div class="flex items-center justify-between mt-16"> <div class="flex items-center justify-between ">
<div class="flex items-center gap-10"> <div class="flex items-center gap-10">
<Icon @click="router.push('/')" name="gg:arrow-left-o" style="color: black" size="44" class="cursor-pointer"/> <Icon @click="router.push('/')" name="gg:arrow-left-o" style="color: black" size="44"/>
<h1 class="text-3xl font-bold uppercase">listes des expéditions en attente</h1> <h1 class="text-3xl font-bold uppercase">listes des expéditions en attente</h1>
</div> </div>
</div> </div>
<div class="px-[86px]"> <div class="ps-20 ">
<div class="mt-6 border border-slate-200 mb-16 "> <div class="mt-6 border border-slate-200 mb-16 ">
<div class="grid grid-cols-5 gap-4 bg-slate-100 px-4 py-3 text-sm font-semibold uppercase tracking-wide"> <div class="grid grid-cols-5 gap-4 bg-slate-100 px-4 py-3 text-sm font-semibold uppercase tracking-wide">
<div>Client</div> <div>Client</div>

View File

@@ -6,7 +6,6 @@ export interface AddressData {
postalCode: string postalCode: string
city: string city: string
countryCode: string countryCode: string
fullAddress: string
} }
export interface AddressFormData { export interface AddressFormData {

View File

@@ -1,40 +0,0 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20260213114000 extends AbstractMigration
{
public function getDescription(): string
{
return 'Allow only one bovin_shipment row per shipment.';
}
public function up(Schema $schema): void
{
// Keep one row per shipment (latest id), required before adding unique index.
$this->addSql(<<<'SQL'
DELETE FROM bovin_shipment bs
USING (
SELECT id, ROW_NUMBER() OVER (PARTITION BY shipment_id ORDER BY id DESC) AS rn
FROM bovin_shipment
WHERE shipment_id IS NOT NULL
) d
WHERE bs.id = d.id
AND d.rn > 1
SQL);
$this->addSql('DROP INDEX IF EXISTS uniq_bovin_shipment');
$this->addSql('CREATE UNIQUE INDEX uniq_bovin_shipment_one_type ON bovin_shipment (shipment_id)');
}
public function down(Schema $schema): void
{
$this->addSql('DROP INDEX IF EXISTS uniq_bovin_shipment_one_type');
$this->addSql('CREATE UNIQUE INDEX uniq_bovin_shipment ON bovin_shipment (shipment_id, shipment_type_id)');
}
}

View File

@@ -168,7 +168,6 @@ class SeedCommand extends Command
['label' => 'Foin', 'code' => 'FOIN'], ['label' => 'Foin', 'code' => 'FOIN'],
['label' => 'Paille', 'code' => 'PAILLE'], ['label' => 'Paille', 'code' => 'PAILLE'],
['label' => 'Granule', 'code' => 'GRANULE'], ['label' => 'Granule', 'code' => 'GRANULE'],
['label' => 'Autres', 'code' => 'AUTRES'],
]; ];
foreach ($merchandiseTypes as $type) { foreach ($merchandiseTypes as $type) {
$this->upsertByCode(MerchandiseType::class, $type['code'], static function (MerchandiseType $entity) use ($type) { $this->upsertByCode(MerchandiseType::class, $type['code'], static function (MerchandiseType $entity) use ($type) {

View File

@@ -26,7 +26,6 @@ class ReferenceFixtures extends Fixture
['label' => 'Foin', 'code' => 'FOIN'], ['label' => 'Foin', 'code' => 'FOIN'],
['label' => 'Paille', 'code' => 'PAILLE'], ['label' => 'Paille', 'code' => 'PAILLE'],
['label' => 'Granule', 'code' => 'GRANULE'], ['label' => 'Granule', 'code' => 'GRANULE'],
['label' => 'Autres', 'code' => 'AUTRES'],
]; ];
foreach ($merchandiseTypes as $type) { foreach ($merchandiseTypes as $type) {
$merchandiseType = new MerchandiseType() $merchandiseType = new MerchandiseType()

View File

@@ -18,7 +18,7 @@ use Symfony\Component\Serializer\Attribute\Groups;
#[ORM\Entity] #[ORM\Entity]
#[ApiFilter(SearchFilter::class, properties: ['shipment' => 'exact'])] #[ApiFilter(SearchFilter::class, properties: ['shipment' => 'exact'])]
#[ORM\UniqueConstraint(name: 'uniq_bovin_shipment_one_type', columns: ['shipment_id'])] #[ORM\UniqueConstraint(name: 'uniq_bovin_shipment', columns: ['shipment_id', 'shipment_type_id'])]
#[ORM\Table(name: 'bovin_shipment')] #[ORM\Table(name: 'bovin_shipment')]
#[ApiResource( #[ApiResource(
operations: [ operations: [

View File

@@ -328,7 +328,7 @@ class Shipment
return; return;
} }
$number = sprintf('P-BL-%04d', $this->id); $number = sprintf('P-BR-%04d', $this->id);
$this->identificationNumber = $number; $this->identificationNumber = $number;
$args->getObjectManager() $args->getObjectManager()

View File

@@ -26,6 +26,10 @@ use Symfony\Component\Serializer\Attribute\Groups;
), ),
new GetCollection( new GetCollection(
normalizationContext: ['groups' => ['supplier:read']], normalizationContext: ['groups' => ['supplier:read']],
),
new GetCollection(
uriTemplate: '/admin/suppliers',
normalizationContext: ['groups' => ['supplier:read']],
security: "is_granted('ROLE_ADMIN')" security: "is_granted('ROLE_ADMIN')"
), ),
new Post( new Post(

View File

@@ -174,7 +174,7 @@
<td style="width:30%; text-align:right; vertical-align:top; font-size: 14px;"> <td style="width:30%; text-align:right; vertical-align:top; font-size: 14px;">
<div style="display:inline-block; width:75mm; line-height:1.3;"> <div style="display:inline-block; width:75mm; line-height:1.3;">
<strong>{{ shipment.customer ? shipment.customer.name : '-' }}</strong><br> <strong>{{ shipment.customer ? shipment.customer.label : '-' }}</strong><br>
<span>{{ shipment.address ? shipment.address.street : '' }}</span><br> <span>{{ shipment.address ? shipment.address.street : '' }}</span><br>
{% if shipment.address and shipment.address.street2 %} {% if shipment.address and shipment.address.street2 %}
<span>{{ shipment.address.street2 }}</span><br> <span>{{ shipment.address.street2 }}</span><br>
@@ -198,7 +198,7 @@
</tr> </tr>
<tr> <tr>
<td style="width:55%; text-align:center;"> <td style="width:55%; text-align:center;">
{{ shipment.customer ? shipment.customer.name : '-' }} {{ shipment.customer ? shipment.customer.code : '-' }}
</td> </td>
<td style="width:20%; text-align:center; white-space:nowrap;"> <td style="width:20%; text-align:center; white-space:nowrap;">
{{ shipment.shipmentDate|date('d/m/Y') }} {{ shipment.shipmentDate|date('d/m/Y') }}
@@ -276,7 +276,7 @@
<td style="width:60%; padding-right:8mm; vertical-align:top;"> <td style="width:60%; padding-right:8mm; vertical-align:top;">
<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 de livraison : {{ shipment.truck ? shipment.truck.name : '-' }}</p>
<p>Immatriculation : {{ shipment.licencePlate ?? '-' }}</p> <p>Immatriculation : {{ shipment.licencePlate ?? '-' }}</p>
</div> </div>
</td> </td>