Ajout de la génération du bon de reception et correction des retours #4
24
.idea/workspace.xml
generated
24
.idea/workspace.xml
generated
@@ -4,15 +4,13 @@
|
|||||||
<option name="autoReloadType" value="SELECTIVE" />
|
<option name="autoReloadType" value="SELECTIVE" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ChangeListManager">
|
<component name="ChangeListManager">
|
||||||
<list default="true" id="7c107abe-5995-4428-8429-b146aaca8386" name="Changes" comment="feat : ajout de la génération du bon de reception, correction de la base du formulaire multi-etape de reception et ajout d'une gestion d'erreur global">
|
<list default="true" id="7c107abe-5995-4428-8429-b146aaca8386" name="Changes" comment="feat : ajout d'une gestion d'erreur au global côté front avec la lib toaster et I18n pour centraliser les messages d'erreur">
|
||||||
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/AGENTS.md" beforeDir="false" afterPath="$PROJECT_DIR$/AGENTS.md" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/config/reference.php" beforeDir="false" afterPath="$PROJECT_DIR$/config/reference.php" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/config/reference.php" beforeDir="false" afterPath="$PROJECT_DIR$/config/reference.php" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/frontend/composables/useApi.ts" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/composables/useApi.ts" 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/nuxt.config.ts" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/nuxt.config.ts" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/frontend/package-lock.json" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/package-lock.json" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/frontend/package-lock.json" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/package-lock.json" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/frontend/package.json" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/package.json" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/frontend/package.json" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/package.json" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/frontend/services/reception.ts" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/services/reception.ts" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/frontend/pages/reception/[[id]].vue" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/pages/reception/[[id]].vue" afterDir="false" />
|
||||||
</list>
|
</list>
|
||||||
<option name="SHOW_DIALOG" value="false" />
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||||
@@ -47,7 +45,6 @@
|
|||||||
<commands />
|
<commands />
|
||||||
<urls />
|
<urls />
|
||||||
</component>
|
</component>
|
||||||
<component name="PhpDebugGeneral" listening_started="true" />
|
|
||||||
<component name="PhpServers">
|
<component name="PhpServers">
|
||||||
<servers>
|
<servers>
|
||||||
<server host="localhost" id="36c0c232-9151-4654-a36c-e0f5fd99da91" name="ferme-docker" port="8080" use_path_mappings="true">
|
<server host="localhost" id="36c0c232-9151-4654-a36c-e0f5fd99da91" name="ferme-docker" port="8080" use_path_mappings="true">
|
||||||
@@ -260,7 +257,7 @@
|
|||||||
<workItem from="1768287908317" duration="28058000" />
|
<workItem from="1768287908317" duration="28058000" />
|
||||||
<workItem from="1768374298711" duration="12403000" />
|
<workItem from="1768374298711" duration="12403000" />
|
||||||
<workItem from="1768460547451" duration="26946000" />
|
<workItem from="1768460547451" duration="26946000" />
|
||||||
<workItem from="1768547023783" duration="7809000" />
|
<workItem from="1768547023783" duration="11371000" />
|
||||||
</task>
|
</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)">
|
<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" />
|
<option name="closed" value="true" />
|
||||||
@@ -326,7 +323,15 @@
|
|||||||
<option name="project" value="LOCAL" />
|
<option name="project" value="LOCAL" />
|
||||||
<updated>1768498751836</updated>
|
<updated>1768498751836</updated>
|
||||||
</task>
|
</task>
|
||||||
<option name="localTasksCounter" value="9" />
|
<task id="LOCAL-00009" summary="feat : ajout d'une gestion d'erreur au global côté front avec la lib toaster et I18n pour centraliser les messages d'erreur">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1768555180530</created>
|
||||||
|
<option name="number" value="00009" />
|
||||||
|
<option name="presentableId" value="LOCAL-00009" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1768555180530</updated>
|
||||||
|
</task>
|
||||||
|
<option name="localTasksCounter" value="10" />
|
||||||
<servers />
|
<servers />
|
||||||
</component>
|
</component>
|
||||||
<component name="TypeScriptGeneratedFilesManager">
|
<component name="TypeScriptGeneratedFilesManager">
|
||||||
@@ -381,6 +386,7 @@
|
|||||||
<MESSAGE value="fix : correction du useApi pour qu'il n'y ait plus de retry lors d'une erreur 500 par exemple" />
|
<MESSAGE value="fix : correction du useApi pour qu'il n'y ait plus de retry lors d'une erreur 500 par exemple" />
|
||||||
<MESSAGE value="test : ajout de TU sur les services et providers" />
|
<MESSAGE value="test : ajout de TU sur les services et providers" />
|
||||||
<MESSAGE value="feat : ajout de la génération du bon de reception, correction de la base du formulaire multi-etape de reception et ajout d'une gestion d'erreur global" />
|
<MESSAGE value="feat : ajout de la génération du bon de reception, correction de la base du formulaire multi-etape de reception et ajout d'une gestion d'erreur global" />
|
||||||
<option name="LAST_COMMIT_MESSAGE" value="feat : ajout de la génération du bon de reception, correction de la base du formulaire multi-etape de reception et ajout d'une gestion d'erreur global" />
|
<MESSAGE value="feat : ajout d'une gestion d'erreur au global côté front avec la lib toaster et I18n pour centraliser les messages d'erreur" />
|
||||||
|
<option name="LAST_COMMIT_MESSAGE" value="feat : ajout d'une gestion d'erreur au global côté front avec la lib toaster et I18n pour centraliser les messages d'erreur" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
@@ -2,15 +2,11 @@
|
|||||||
<form @submit.prevent="validate">
|
<form @submit.prevent="validate">
|
||||||
<div class="grid grid-cols-1 items-start gap-8 mb-16">
|
<div class="grid grid-cols-1 items-start gap-8 mb-16">
|
||||||
<h1 class="font-bold text-5xl uppercase">Réception</h1>
|
<h1 class="font-bold text-5xl uppercase">Réception</h1>
|
||||||
<div class="flex flex-col">
|
<div>
|
||||||
<label for="license-plate" class="font-bold uppercase text-xl mb-4">Immatriculation</label>
|
<UiLicensePlateInput
|
||||||
<input
|
|
||||||
id="license-plate"
|
|
||||||
v-model="form.licensePlate"
|
v-model="form.licensePlate"
|
||||||
type="text"
|
v-model:allowAny="allowAnyLicensePlate"
|
||||||
class="border-b border-black justify-self-start text-xl pb-[6px] uppercase"
|
|
||||||
/>
|
/>
|
||||||
<p v-if="fieldErrors.licensePlate" class="text-red-600 text-sm">{{ fieldErrors.licensePlate }}</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<label for="reception-date" class="font-bold uppercase text-xl mb-4">Date de reception</label>
|
<label for="reception-date" class="font-bold uppercase text-xl mb-4">Date de reception</label>
|
||||||
@@ -20,7 +16,6 @@
|
|||||||
type="date"
|
type="date"
|
||||||
class="border-b border-black justify-self-start text-xl pb-[6px] uppercase"
|
class="border-b border-black justify-self-start text-xl pb-[6px] uppercase"
|
||||||
/>
|
/>
|
||||||
<p v-if="fieldErrors.receptionDate" class="text-red-600 text-sm">{{ fieldErrors.receptionDate }}</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-center">
|
<div class="flex justify-center">
|
||||||
@@ -34,8 +29,6 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { z } from 'zod'
|
|
||||||
import { mapZodErrors } from '~/utils/zod-errors'
|
|
||||||
import { useReceptionStore } from '~/stores/reception'
|
import { useReceptionStore } from '~/stores/reception'
|
||||||
|
|
||||||
type ReceptionFormData = {
|
type ReceptionFormData = {
|
||||||
@@ -49,20 +42,7 @@ const form = reactive<ReceptionFormData>({
|
|||||||
licensePlate: '',
|
licensePlate: '',
|
||||||
receptionDate: new Date().toISOString().slice(0, 10)
|
receptionDate: new Date().toISOString().slice(0, 10)
|
||||||
})
|
})
|
||||||
const fieldErrors = reactive<Partial<Record<keyof ReceptionFormData, string>>>({
|
const allowAnyLicensePlate = ref(false)
|
||||||
licensePlate: undefined,
|
|
||||||
receptionDate: undefined
|
|
||||||
})
|
|
||||||
const formSchema = z.object({
|
|
||||||
licensePlate: z
|
|
||||||
.string()
|
|
||||||
.min(1, 'Immatriculation requise.')
|
|
||||||
.max(20, 'Immatriculation trop longue (20 caracteres max).'),
|
|
||||||
receptionDate: z
|
|
||||||
.string()
|
|
||||||
.min(1, 'Date de reception requise.')
|
|
||||||
.regex(/^\d{4}-\d{2}-\d{2}$/, 'Date de reception invalide.')
|
|
||||||
})
|
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => receptionStore.current,
|
() => receptionStore.current,
|
||||||
@@ -74,26 +54,14 @@ watch(
|
|||||||
)
|
)
|
||||||
|
|
||||||
async function validate() {
|
async function validate() {
|
||||||
fieldErrors.licensePlate = undefined
|
|
||||||
fieldErrors.receptionDate = undefined
|
|
||||||
const normalizedLicensePlate = form.licensePlate.trim()
|
const normalizedLicensePlate = form.licensePlate.trim()
|
||||||
const normalizedReceptionDate = form.receptionDate.trim()
|
const normalizedReceptionDate = form.receptionDate.trim()
|
||||||
const result = formSchema.safeParse({
|
|
||||||
licensePlate: normalizedLicensePlate,
|
|
||||||
receptionDate: normalizedReceptionDate
|
|
||||||
})
|
|
||||||
if (!result.success) {
|
|
||||||
const errors = mapZodErrors<ReceptionFormData>(result.error)
|
|
||||||
fieldErrors.licensePlate = errors.licensePlate ?? 'Formulaire invalide.'
|
|
||||||
fieldErrors.receptionDate = errors.receptionDate ?? 'Formulaire invalide.'
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!receptionStore.current) {
|
if (!receptionStore.current) {
|
||||||
const created = await receptionStore.createReception({
|
const created = await receptionStore.createReception({
|
||||||
currentStep: 1,
|
currentStep: 1,
|
||||||
licensePlate: normalizedLicensePlate || null,
|
licensePlate: normalizedLicensePlate,
|
||||||
receptionDate: normalizedReceptionDate || null
|
receptionDate: normalizedReceptionDate
|
||||||
})
|
})
|
||||||
if (created) {
|
if (created) {
|
||||||
await router.push(`/reception/${created.id}`)
|
await router.push(`/reception/${created.id}`)
|
||||||
@@ -104,8 +72,8 @@ async function validate() {
|
|||||||
const nextStep = receptionStore.current.currentStep + 1
|
const nextStep = receptionStore.current.currentStep + 1
|
||||||
await receptionStore.updateReception(receptionStore.current.id, {
|
await receptionStore.updateReception(receptionStore.current.id, {
|
||||||
currentStep: nextStep,
|
currentStep: nextStep,
|
||||||
licensePlate: normalizedLicensePlate || null,
|
licensePlate: normalizedLicensePlate,
|
||||||
receptionDate: normalizedReceptionDate || null
|
receptionDate: normalizedReceptionDate
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
93
frontend/components/ui/license-plate-input.vue
Normal file
93
frontend/components/ui/license-plate-input.vue
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
<template>
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<label :for="inputId" class="font-bold uppercase text-xl mb-4">{{ label }}</label>
|
||||||
|
<input
|
||||||
|
:id="inputId"
|
||||||
|
:value="modelValue"
|
||||||
|
v-maska="maskOptions"
|
||||||
|
type="text"
|
||||||
|
:maxlength="maxLength"
|
||||||
|
:placeholder="placeholderText"
|
||||||
|
class="border-b border-black justify-self-start text-xl pb-[6px] uppercase"
|
||||||
|
@input="handleInput"
|
||||||
|
/>
|
||||||
|
<label :for="checkboxId" class="mt-3 flex items-center gap-3 text-sm">
|
||||||
|
<input
|
||||||
|
:id="checkboxId"
|
||||||
|
:checked="allowAny"
|
||||||
|
type="checkbox"
|
||||||
|
class="h-4 w-4 accent-primary-500"
|
||||||
|
@change="toggleAllowAny"
|
||||||
|
/>
|
||||||
|
Autoriser un format libre
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { vMaska } from 'maska/vue'
|
||||||
|
type Props = {
|
||||||
|
modelValue: string
|
||||||
|
allowAny?: boolean
|
||||||
|
label?: string
|
||||||
|
id?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
allowAny: false,
|
||||||
|
label: 'Immatriculation',
|
||||||
|
id: 'license-plate'
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(event: 'update:modelValue', value: string): void
|
||||||
|
(event: 'update:allowAny', value: boolean): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const inputId = computed(() => props.id)
|
||||||
|
const checkboxId = computed(() => `${props.id}-format`)
|
||||||
|
|
||||||
|
const maskOptions = computed(() =>
|
||||||
|
props.allowAny
|
||||||
|
? undefined
|
||||||
|
: {
|
||||||
|
mask: '@@-###-@@',
|
||||||
|
eager: true,
|
||||||
|
tokens: {
|
||||||
|
'@': {
|
||||||
|
pattern: /[A-Za-z]/,
|
||||||
|
transform: (char: string) => char.toUpperCase()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
const placeholderText = computed(() => (props.allowAny ? '' : 'AA-123-AA'))
|
||||||
|
const maxLength = computed(() => (props.allowAny ? 20 : 9))
|
||||||
|
|
||||||
|
const handleInput = (event: Event) => {
|
||||||
|
const target = event.target as HTMLInputElement | null
|
||||||
|
if (!target) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.allowAny) {
|
||||||
|
emit('update:modelValue', target.value)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
emit('update:modelValue', target.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const toggleAllowAny = (event: Event) => {
|
||||||
|
const target = event.target as HTMLInputElement | null
|
||||||
|
if (!target) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextValue = target.checked
|
||||||
|
emit('update:allowAny', nextValue)
|
||||||
|
if (!nextValue) {
|
||||||
|
emit('update:modelValue', props.modelValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
6
frontend/package-lock.json
generated
6
frontend/package-lock.json
generated
@@ -10,6 +10,7 @@
|
|||||||
"@nuxtjs/i18n": "^10.2.1",
|
"@nuxtjs/i18n": "^10.2.1",
|
||||||
"@pinia/nuxt": "^0.11.3",
|
"@pinia/nuxt": "^0.11.3",
|
||||||
"izitoast": "^1.4.0",
|
"izitoast": "^1.4.0",
|
||||||
|
"maska": "^3.2.0",
|
||||||
"nuxt": "^4.2.2",
|
"nuxt": "^4.2.2",
|
||||||
"nuxt-toast": "^1.4.0",
|
"nuxt-toast": "^1.4.0",
|
||||||
"pinia": "^3.0.4",
|
"pinia": "^3.0.4",
|
||||||
@@ -9195,6 +9196,11 @@
|
|||||||
"source-map-js": "^1.2.1"
|
"source-map-js": "^1.2.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/maska": {
|
||||||
|
"version": "3.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/maska/-/maska-3.2.0.tgz",
|
||||||
|
"integrity": "sha512-zSmSgs5/q9vMSmrdZT3rKOv9uLznNWR/niuuAdBZDTvB3SMKOX9vhMtDijFyExz+B4UClu2rvksylUh/ea1bLA=="
|
||||||
|
},
|
||||||
"node_modules/math-intrinsics": {
|
"node_modules/math-intrinsics": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
"@nuxtjs/i18n": "^10.2.1",
|
"@nuxtjs/i18n": "^10.2.1",
|
||||||
"@pinia/nuxt": "^0.11.3",
|
"@pinia/nuxt": "^0.11.3",
|
||||||
"izitoast": "^1.4.0",
|
"izitoast": "^1.4.0",
|
||||||
|
"maska": "^3.2.0",
|
||||||
"nuxt": "^4.2.2",
|
"nuxt": "^4.2.2",
|
||||||
"nuxt-toast": "^1.4.0",
|
"nuxt-toast": "^1.4.0",
|
||||||
"pinia": "^3.0.4",
|
"pinia": "^3.0.4",
|
||||||
|
|||||||
@@ -2,7 +2,11 @@
|
|||||||
<div>
|
<div>
|
||||||
<div class="flex justify-between h-[52px] mb-[90px]">
|
<div class="flex justify-between h-[52px] mb-[90px]">
|
||||||
<p class="self-center">Indicateur d’étapes</p>
|
<p class="self-center">Indicateur d’étapes</p>
|
||||||
<NuxtLink to="/" class="flex flex-col justify-center uppercase text-xl bg-black text-white h-[50px] w-[272px] text-center">Mettre en attente</NuxtLink>
|
<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>
|
||||||
<ReceptionForm v-if="!storeReception || storeReception.currentStep === 0"/>
|
<ReceptionForm v-if="!storeReception || storeReception.currentStep === 0"/>
|
||||||
<ReceptionWeight v-if="storeReception?.currentStep === 1" mode="gross"/>
|
<ReceptionWeight v-if="storeReception?.currentStep === 1" mode="gross"/>
|
||||||
@@ -21,13 +25,39 @@ const router = useRouter()
|
|||||||
const receptionStore = useReceptionStore()
|
const receptionStore = useReceptionStore()
|
||||||
const { current: storeReception } = storeToRefs(receptionStore)
|
const { current: storeReception } = storeToRefs(receptionStore)
|
||||||
|
|
||||||
onMounted(async () => {
|
const resolveReceptionId = (param: unknown) => {
|
||||||
const raw = route.params.id
|
const idStr = Array.isArray(param) ? param[0] : param
|
||||||
const idStr = Array.isArray(raw) ? raw[0] : raw
|
if (!idStr) {
|
||||||
const id = idStr ? Number(idStr) : null
|
return null
|
||||||
|
|
||||||
if (id !== null) {
|
|
||||||
await receptionStore.loadReception(id)
|
|
||||||
}
|
}
|
||||||
})
|
const id = Number(idStr)
|
||||||
|
return Number.isFinite(id) ? id : null
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => route.params.id,
|
||||||
|
async (param) => {
|
||||||
|
const id = resolveReceptionId(param)
|
||||||
|
if (id === null) {
|
||||||
|
receptionStore.clearCurrent()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
await receptionStore.loadReception(id)
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
const saveAndHold = async () => {
|
||||||
|
if (!receptionStore.current) {
|
||||||
|
await router.push('/')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await receptionStore.updateReception(receptionStore.current.id, {
|
||||||
|
currentStep: receptionStore.current.currentStep,
|
||||||
|
licensePlate: receptionStore.current.licensePlate,
|
||||||
|
receptionDate: receptionStore.current.receptionDate
|
||||||
|
})
|
||||||
|
await router.push('/')
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user